fix: display ocr data submissions

This commit is contained in:
2026-05-25 02:00:34 +02:00
parent 11d6fc423a
commit a769d1a0b3
5 changed files with 220 additions and 54 deletions
+157 -4
View File
@@ -30,6 +30,10 @@ const filePreviewUrl = ref<string>("");
const isModalOpen = ref(false); const isModalOpen = ref(false);
const userSubmissions = ref<any[]>([]); const userSubmissions = ref<any[]>([]);
const isLoadingSubmissions = ref(false); const isLoadingSubmissions = ref(false);
const selectedSubmissionFile = ref<any>(null);
const isSubmissionFileModalOpen = ref(false);
const imageAspectRatio = ref<"portrait" | "landscape">("landscape");
const copyFeedback = ref<string>("");
const openFilePreview = async (file: File) => { const openFilePreview = async (file: File) => {
selectedFile.value = file; selectedFile.value = file;
@@ -58,6 +62,42 @@ const closeModal = () => {
filePreviewUrl.value = ""; filePreviewUrl.value = "";
}; };
const openSubmissionFileModal = (file: any) => {
selectedSubmissionFile.value = file;
isSubmissionFileModalOpen.value = true;
// Check image dimensions for aspect ratio
if (file?.content_type?.startsWith("image/")) {
const img = new Image();
img.onload = () => {
imageAspectRatio.value = img.height > img.width ? "portrait" : "landscape";
};
img.src = file.file_url;
}
};
const closeSubmissionFileModal = () => {
isSubmissionFileModalOpen.value = false;
selectedSubmissionFile.value = null;
};
const copyOCRText = async () => {
if (selectedSubmissionFile.value?.ocr_raw_data) {
try {
await navigator.clipboard.writeText(selectedSubmissionFile.value.ocr_raw_data);
copyFeedback.value = "Copied!";
setTimeout(() => {
copyFeedback.value = "";
}, 2000);
} catch (err) {
copyFeedback.value = "Failed to copy";
setTimeout(() => {
copyFeedback.value = "";
}, 2000);
}
}
};
const getGameHeaderImage = () => { const getGameHeaderImage = () => {
if (props.gameImage) { if (props.gameImage) {
return props.gameImage; return props.gameImage;
@@ -218,7 +258,8 @@ onMounted(() => {
<img :src="getGameHeaderImage()!" :alt="gameName" class="w-full h-full object-cover" /> <img :src="getGameHeaderImage()!" :alt="gameName" class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-black/30"></div> <div class="absolute inset-0 bg-black/30"></div>
</figure> </figure>
<figure v-else class="relative h-48 bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center"> <figure v-else
class="relative h-48 bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center">
<i class="mdi mdi-gamepad-variant text-6xl text-white opacity-30"></i> <i class="mdi mdi-gamepad-variant text-6xl text-white opacity-30"></i>
</figure> </figure>
<div class="card-body"> <div class="card-body">
@@ -265,7 +306,8 @@ onMounted(() => {
<p class="font-medium truncate group-hover:text-primary">{{ file.name }}</p> <p class="font-medium truncate group-hover:text-primary">{{ file.name }}</p>
<p class="text-xs text-base-content/70">{{ formatFileSize(file.size) }}</p> <p class="text-xs text-base-content/70">{{ formatFileSize(file.size) }}</p>
</div> </div>
<button @click.stop="removeFile(index)" class="btn btn-ghost btn-xs opacity-0 group-hover:opacity-100"> <button @click.stop="removeFile(index)"
class="btn btn-ghost btn-xs opacity-0 group-hover:opacity-100">
<i class="mdi mdi-close"></i> <i class="mdi mdi-close"></i>
</button> </button>
</div> </div>
@@ -407,9 +449,10 @@ onMounted(() => {
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div v-for="file in submission.files" :key="file.id" class="flex items-center gap-2"> <div v-for="file in submission.files" :key="file.id" class="flex items-center gap-2">
<i :class="['mdi text-primary text-sm', getFileIcon({ type: file.content_type })]"></i> <i :class="['mdi text-primary text-sm', getFileIcon({ type: file.content_type })]"></i>
<a :href="file.file_url" target="_blank" class="link link-primary truncate max-w-xs"> <button @click="openSubmissionFileModal(file)"
class="link link-primary truncate max-w-xs text-left">
{{ file.original_filename }} {{ file.original_filename }}
</a> </button>
<span class="text-xs text-base-content/50">({{ formatFileSize(file.file_size) }})</span> <span class="text-xs text-base-content/50">({{ formatFileSize(file.file_size) }})</span>
</div> </div>
</div> </div>
@@ -487,5 +530,115 @@ onMounted(() => {
<div class="modal-backdrop" @click="closeModal"></div> <div class="modal-backdrop" @click="closeModal"></div>
</div> </div>
<!-- Submission File Modal -->
<div :class="['modal', { 'modal-open': isSubmissionFileModalOpen }]">
<div class="modal-box w-11/12 max-w-6xl max-h-[90vh] overflow-hidden">
<h3 class="font-bold text-lg mb-4">
<i :class="['mdi mr-2', getFileIcon({ type: selectedSubmissionFile?.content_type })]"></i>
{{ selectedSubmissionFile?.original_filename }}
</h3>
<!-- Responsive Layout: Portrait images side-by-side (left/right), landscape images and other files stacked (top/bottom) -->
<div :class="['gap-4 h-[70vh]', imageAspectRatio === 'portrait' ? 'grid grid-cols-1 lg:grid-cols-2' : 'flex flex-col']">
<!-- File Display -->
<div class="flex flex-col overflow-hidden" :class="imageAspectRatio === 'portrait' ? 'h-full' : 'flex-1'">
<p class="text-sm text-base-content/70 mb-2">File Preview</p>
<div class="flex-1 overflow-auto border border-base-300 rounded-lg">
<!-- Image Preview -->
<div v-if="selectedSubmissionFile?.content_type.startsWith('image/')"
class="flex items-center justify-center h-full p-4">
<img :src="selectedSubmissionFile.file_url" :alt="selectedSubmissionFile.original_filename"
class="max-w-full max-h-full object-contain rounded" />
</div>
<!-- PDF Preview -->
<div v-else-if="selectedSubmissionFile?.content_type === 'application/pdf'" class="h-full">
<iframe :src="selectedSubmissionFile.file_url" class="w-full h-full border-0 rounded"></iframe>
</div>
<!-- Text Preview -->
<div v-else-if="selectedSubmissionFile?.content_type.startsWith('text/')" class="p-4">
<div class="bg-base-200 p-4 rounded font-mono text-sm whitespace-pre-wrap break-words">
<p class="text-base-content/70">Text content preview not available. Download file to view.</p>
</div>
</div>
<!-- Unsupported Preview -->
<div v-else class="flex items-center justify-center h-full">
<div class="text-center">
<i class="mdi mdi-file-question text-6xl text-base-content/30 mb-4"></i>
<p class="text-base-content/70">Preview not available</p>
<p class="text-sm text-base-content/50">{{ selectedSubmissionFile?.content_type }}</p>
</div>
</div>
</div>
<div class="mt-2">
<a v-if="selectedSubmissionFile?.file_url" :href="selectedSubmissionFile.file_url" download
class="btn btn-sm btn-outline w-full">
<i class="mdi mdi-download mr-2"></i>
Download File
</a>
</div>
</div>
<!-- OCR Data -->
<div class="flex flex-col overflow-hidden" :class="imageAspectRatio === 'portrait' ? 'h-full' : 'flex-1'">
<p class="text-sm text-base-content/70 mb-2">OCR Data</p>
<div class="flex-1 overflow-auto border border-base-300 rounded-lg p-4 bg-base-200">
<div v-if="selectedSubmissionFile?.ocr_status === 'processed' && selectedSubmissionFile.ocr_raw_data">
<div class="space-y-3">
<div>
<p class="text-xs font-semibold text-base-content/70 mb-1">Status:</p>
<span :class="getOCRStatusColor(selectedSubmissionFile.ocr_status)">{{
selectedSubmissionFile.ocr_status }}</span>
</div>
<div class="divider my-2"></div>
<div>
<div class="flex items-center justify-between mb-2">
<p class="text-xs font-semibold text-base-content/70">Extracted Text:</p>
<button @click="copyOCRText" class="btn btn-xs btn-ghost gap-1">
<i class="mdi mdi-content-copy"></i>
{{ copyFeedback || "Copy" }}
</button>
</div>
<div
class="bg-base-100 p-3 rounded text-xs whitespace-pre-wrap break-words font-mono overflow-y-auto h-full">
{{ selectedSubmissionFile.ocr_raw_data }}
</div>
</div>
</div>
</div>
<div v-else-if="selectedSubmissionFile?.ocr_status === 'processing'"
class="flex items-center justify-center h-full">
<div class="text-center">
<span class="loading loading-spinner loading-lg"></span>
<p class="text-base-content/70 mt-4">OCR is being processed...</p>
</div>
</div>
<div v-else-if="selectedSubmissionFile?.ocr_status === 'error'"
class="flex items-center justify-center h-full">
<div class="text-center">
<i class="mdi mdi-alert-circle text-4xl text-error mb-2"></i>
<p class="text-base-content/70">OCR processing failed</p>
</div>
</div>
<div v-else class="flex items-center justify-center h-full">
<div class="text-center">
<i class="mdi mdi-clock-outline text-4xl text-base-content/30 mb-2"></i>
<p class="text-base-content/70">Waiting for OCR processing...</p>
</div>
</div>
</div>
</div>
</div>
<div class="modal-action mt-4">
<button @click="closeSubmissionFileModal" class="btn">Close</button>
</div>
</div>
<div class="modal-backdrop" @click="closeSubmissionFileModal"></div>
</div>
</div> </div>
</template> </template>
+1 -1
View File
@@ -1,4 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListGames, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateSubmission, submissionsApiGetSubmission, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions } from './sdk.gen'; export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListGames, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateSubmission, submissionsApiGetSubmission, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions } from './sdk.gen';
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, Input, LeaderboardEntryOut, LeaderboardOut, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResultsOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen'; export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, Input, LeaderboardEntryOut, LeaderboardOut, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResultsOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
+6 -6
View File
@@ -204,11 +204,6 @@ export const submissionsApiCreateSubmission = <ThrowOnError extends boolean = fa
} }
}); });
/**
* Get Submission
*/
export const submissionsApiGetSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiGetSubmissionData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiGetSubmissionResponses, unknown, ThrowOnError>({ url: '/api/submissions/{submission_id}', ...options });
/** /**
* List User Submissions * List User Submissions
*/ */
@@ -217,4 +212,9 @@ export const submissionsApiListUserSubmissions = <ThrowOnError extends boolean =
/** /**
* List Submissions By Game * List Submissions By Game
*/ */
export const submissionsApiListSubmissionsByGame = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListSubmissionsByGameData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListSubmissionsByGameResponses, unknown, ThrowOnError>({ url: '/api/submissions/game/{game_path}', ...options }); export const submissionsApiListSubmissionsByGame = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListSubmissionsByGameData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListSubmissionsByGameResponses, unknown, ThrowOnError>({ url: '/api/submissions/game', ...options });
/**
* Get Submission
*/
export const submissionsApiGetSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiGetSubmissionData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiGetSubmissionResponses, unknown, ThrowOnError>({ url: '/api/submissions/{submission_id}', ...options });
+54 -42
View File
@@ -351,14 +351,14 @@ export type SubmissionFileOut = {
* Original Filename * Original Filename
*/ */
original_filename: string; original_filename: string;
/**
* File Size
*/
file_size: number;
/** /**
* Content Type * Content Type
*/ */
content_type: string; content_type: string;
/**
* File Size
*/
file_size: number;
/** /**
* File Url * File Url
*/ */
@@ -367,6 +367,10 @@ export type SubmissionFileOut = {
* Ocr Status * Ocr Status
*/ */
ocr_status: string; ocr_status: string;
/**
* Ocr Raw Data
*/
ocr_raw_data: string;
}; };
/** /**
@@ -1349,6 +1353,52 @@ export type SubmissionsApiCreateSubmissionResponses = {
export type SubmissionsApiCreateSubmissionResponse = SubmissionsApiCreateSubmissionResponses[keyof SubmissionsApiCreateSubmissionResponses]; export type SubmissionsApiCreateSubmissionResponse = SubmissionsApiCreateSubmissionResponses[keyof SubmissionsApiCreateSubmissionResponses];
export type SubmissionsApiListUserSubmissionsData = {
body?: never;
path?: never;
query: {
/**
* Game Path
*/
game_path: string;
};
url: '/api/submissions/user';
};
export type SubmissionsApiListUserSubmissionsResponses = {
/**
* Response
*
* OK
*/
200: Array<SubmissionOut>;
};
export type SubmissionsApiListUserSubmissionsResponse = SubmissionsApiListUserSubmissionsResponses[keyof SubmissionsApiListUserSubmissionsResponses];
export type SubmissionsApiListSubmissionsByGameData = {
body?: never;
path?: never;
query: {
/**
* Game Path
*/
game_path: string;
};
url: '/api/submissions/game';
};
export type SubmissionsApiListSubmissionsByGameResponses = {
/**
* Response
*
* OK
*/
200: Array<SubmissionOut>;
};
export type SubmissionsApiListSubmissionsByGameResponse = SubmissionsApiListSubmissionsByGameResponses[keyof SubmissionsApiListSubmissionsByGameResponses];
export type SubmissionsApiGetSubmissionData = { export type SubmissionsApiGetSubmissionData = {
body?: never; body?: never;
path: { path: {
@@ -1369,41 +1419,3 @@ export type SubmissionsApiGetSubmissionResponses = {
}; };
export type SubmissionsApiGetSubmissionResponse = SubmissionsApiGetSubmissionResponses[keyof SubmissionsApiGetSubmissionResponses]; export type SubmissionsApiGetSubmissionResponse = SubmissionsApiGetSubmissionResponses[keyof SubmissionsApiGetSubmissionResponses];
export type SubmissionsApiListUserSubmissionsData = {
body?: never;
path?: never;
query: {
/**
* Game Path
*/
game_path: string;
};
url: '/api/submissions/user';
};
export type SubmissionsApiListUserSubmissionsResponses = {
/**
* OK
*/
200: unknown;
};
export type SubmissionsApiListSubmissionsByGameData = {
body?: never;
path: {
/**
* Game Path
*/
game_path: string;
};
query?: never;
url: '/api/submissions/game/{game_path}';
};
export type SubmissionsApiListSubmissionsByGameResponses = {
/**
* OK
*/
200: unknown;
};
+2 -1
View File
@@ -7,10 +7,11 @@ from ninja import Schema
class SubmissionFileOut(Schema): class SubmissionFileOut(Schema):
id: UUID id: UUID
original_filename: str original_filename: str
file_size: int
content_type: str content_type: str
file_size: int
file_url: Optional[str] = None file_url: Optional[str] = None
ocr_status: str ocr_status: str
ocr_raw_data: str
class SubmissionOut(Schema): class SubmissionOut(Schema):