feat: view info submitted

for transparency, see the exact details that is sent automatically to the owner of the instance:
- job id
- convert from
- convert to
- ffmpeg stderr
- actual video file (if submitted)
This commit is contained in:
Maya 2025-10-18 20:57:52 +03:00
parent 91a061a2ef
commit e5ff309d83
No known key found for this signature in database
7 changed files with 170 additions and 38 deletions

View File

@ -85,6 +85,13 @@
"vertd_generic_yes": "Submit video", "vertd_generic_yes": "Submit video",
"vertd_generic_no": "Don't submit", "vertd_generic_no": "Don't submit",
"vertd_failed_to_keep": "Failed to keep the video on the server: {error}", "vertd_failed_to_keep": "Failed to keep the video on the server: {error}",
"vertd_details": "View error details",
"vertd_details_body": "If you submit your file, <b>your video will also be attached</b> alongside the error log being sent to us for review. The following information is the log that we automatically receive:",
"vertd_details_job_id": "<b>Job ID:</b> {jobId}",
"vertd_details_from": "<b>From format:</b> {from}",
"vertd_details_to": "<b>To format:</b> {to}",
"vertd_details_error_message": "<b>Error message:</b> [view_link]View error logs[/view_link]",
"vertd_details_close": "Close",
"unsupported_format": "Only image, video, audio, and document files are supported", "unsupported_format": "Only image, video, audio, and document files are supported",
"vertd_not_found": "Could not find the vertd instance to start video conversion. Are you sure the instance URL is set correctly?", "vertd_not_found": "Could not find the vertd instance to start video conversion. Are you sure the instance URL is set correctly?",
"worker_downloading": "The {type} converter is currently being initialized, please wait a few moments.", "worker_downloading": "The {type} converter is currently being initialized, please wait a few moments.",

View File

@ -3,19 +3,13 @@
import { removeDialog } from "$lib/store/DialogProvider"; import { removeDialog } from "$lib/store/DialogProvider";
import { BanIcon, CheckIcon, InfoIcon, TriangleAlert } from "lucide-svelte"; import { BanIcon, CheckIcon, InfoIcon, TriangleAlert } from "lucide-svelte";
import { quintOut } from "svelte/easing"; import { quintOut } from "svelte/easing";
import type { Dialog as DialogType } from "$lib/store/DialogProvider";
type Props = { type Props = DialogType;
id: number;
title: string;
message: string;
buttons: {
text: string;
action: () => void;
}[];
type: "success" | "error" | "info" | "warning";
};
let { id, title, message, buttons, type }: Props = $props(); let props: Props = $props();
const { id, title, message, buttons, type } = props;
const additional = "additional" in props ? props.additional : undefined;
const colors = { const colors = {
success: "purple", success: "purple",
@ -59,7 +53,14 @@
</div> </div>
</div> </div>
<div class="flex flex-col gap-1 w-full"> <div class="flex flex-col gap-1 w-full">
<p class="text-sm font-normal text-muted">{message}</p> {#if typeof message === "string"}
<p class="text-sm font-normal text-muted whitespace-pre-wrap">{message}</p>
{:else}
{@const MessageComponent = message}
<div class="text-sm font-normal text-muted">
<MessageComponent {id} {title} {type} {buttons} {additional} />
</div>
{/if}
</div> </div>
<div class="flex flex-row items-center gap-4 w-full"> <div class="flex flex-row items-center gap-4 w-full">
{#each buttons as { text, action }, i} {#each buttons as { text, action }, i}

View File

@ -2,6 +2,10 @@
export interface VertdErrorProps { export interface VertdErrorProps {
jobId: string; jobId: string;
auth: string; auth: string;
from?: string;
to?: string;
errorMessage?: string;
fileName?: string;
} }
</script> </script>
@ -10,6 +14,8 @@
import { m } from "$lib/paraglide/messages"; import { m } from "$lib/paraglide/messages";
import { ToastManager, type ToastProps } from "$lib/toast/index.svelte"; import { ToastManager, type ToastProps } from "$lib/toast/index.svelte";
import { addDialog } from "$lib/store/DialogProvider";
import VertdErrorDetails from "./VertdErrorDetails.svelte";
const toast: ToastProps<VertdErrorProps> = $props(); const toast: ToastProps<VertdErrorProps> = $props();
@ -52,22 +58,50 @@
ToastManager.remove(toast.id); ToastManager.remove(toast.id);
}; };
const showDetails = () => {
addDialog(
m["convert.errors.vertd_details"](),
VertdErrorDetails as any,
[
{
text: "Close",
action: () => {},
},
],
"info",
{
jobId: toast.additional.jobId || "Unknown",
from: toast.additional.from || "Unknown",
to: toast.additional.to || "Unknown",
errorMessage: toast.additional.errorMessage || "Unknown error",
},
);
};
</script> </script>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<p class="text-black">{m["convert.errors.vertd_generic_body"]()}</p> <p class="text-black">{m["convert.errors.vertd_generic_body"]()}</p>
<div class="flex gap-4"> <div class="flex flex-col gap-2">
<button <button
onclick={submit} onclick={showDetails}
class="btn rounded-lg h-fit py-2 w-full bg-accent-red-alt text-white" class="btn rounded-lg h-fit py-2 w-full bg-accent-blue text-black"
disabled={submitting} disabled={submitting}
>{m["convert.errors.vertd_generic_yes"]()}</button >View Details Submitted</button
>
<button
onclick={remove}
class="btn rounded-lg h-fit py-2 w-full"
disabled={submitting}
>{m["convert.errors.vertd_generic_no"]()}</button
> >
<div class="flex gap-4">
<button
onclick={submit}
class="btn rounded-lg h-fit py-2 w-full bg-accent-red-alt text-white"
disabled={submitting}
>{m["convert.errors.vertd_generic_yes"]()}</button
>
<button
onclick={remove}
class="btn rounded-lg h-fit py-2 w-full"
disabled={submitting}
>{m["convert.errors.vertd_generic_no"]()}</button
>
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,52 @@
<script lang="ts">
import { m } from "$lib/paraglide/messages";
import type { DialogProps } from "$lib/store/DialogProvider";
import { link } from "$lib/store/index.svelte";
interface VertdErrorDetailsProps {
jobId: string;
from: string;
to: string;
errorMessage: string;
}
type Props = DialogProps<VertdErrorDetailsProps>;
let { additional }: Props = $props();
</script>
<div class="flex flex-col gap-2">
<p>{@html m["convert.errors.vertd_details_body"]()}</p>
<p>
<span class="text-black dynadark:text-white">
{@html m["convert.errors.vertd_details_job_id"]({
jobId: additional.jobId,
})}
</span>
</p>
<p>
<span class="text-black dynadark:text-white">
{@html m["convert.errors.vertd_details_from"]({ from: additional.from })}
</span>
</p>
<p>
<span class="text-black dynadark:text-white">
{@html m["convert.errors.vertd_details_to"]({ to: additional.to })}
</span>
</p>
<p>
<span class="text-black dynadark:text-white">
{@html link(
["view_link"],
m["convert.errors.vertd_details_error_message"](),
[
URL.createObjectURL(
new Blob([additional.errorMessage], { type: "text/plain" })
)
],
[true],
["text-blue-500 font-normal hover:underline"]
)}
</span>
</p>
</div>

View File

@ -26,9 +26,9 @@
easing: quintOut, easing: quintOut,
}} }}
> >
{#each dialogList as { id, title, message, buttons, type }, i} {#each dialogList as dialog, i}
{#if i === 0} {#if i === 0}
<Dialog {id} {title} {message} {buttons} {type} /> <Dialog {...dialog} />
{/if} {/if}
{/each} {/each}
</div> </div>

View File

@ -377,6 +377,9 @@ export class VertdConverter extends Converter {
additional: { additional: {
jobId: uploadRes.id, jobId: uploadRes.id,
auth: uploadRes.auth, auth: uploadRes.auth,
from: input.from,
to: to,
errorMessage: msg.data.message,
}, },
}); });
} }

View File

@ -1,17 +1,39 @@
import type { Component } from "svelte";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
type DialogType = "success" | "error" | "info" | "warning"; type DialogType = "success" | "error" | "info" | "warning";
export interface Dialog { type BaseDialog = {
id: number; id: number;
title: string; title: string;
message: string;
buttons: { buttons: {
text: string; text: string;
action: () => void; action: () => void;
}[]; }[];
type: DialogType; type: DialogType;
} };
export type StringDialog = BaseDialog & {
message: string;
};
export type ComponentDialog<T = unknown> = BaseDialog & {
message: Component<DialogProps<T>>;
additional: T;
};
export type Dialog<T = unknown> = StringDialog | ComponentDialog<T>;
export type DialogProps<T = unknown> = {
id: number;
title: string;
type: DialogType;
buttons: {
text: string;
action: () => void;
}[];
additional: T;
};
const dialogs = writable<Dialog[]>([]); const dialogs = writable<Dialog[]>([]);
@ -19,20 +41,33 @@ let dialogId = 0;
function addDialog( function addDialog(
title: string, title: string,
message: string, message: string | Component<DialogProps>,
buttons: Dialog["buttons"], buttons: BaseDialog["buttons"],
type: DialogType, type: DialogType,
) { additional?: unknown,
): number {
const id = dialogId++; const id = dialogId++;
const newDialog: Dialog = { if (typeof message === "string") {
id, const newDialog: StringDialog = {
title, id,
message, title,
buttons, message,
type, buttons,
}; type,
dialogs.update((currentDialogs) => [...currentDialogs, newDialog]); };
dialogs.update((currentDialogs) => [...currentDialogs, newDialog]);
} else {
const newDialog: ComponentDialog = {
id,
title,
message,
buttons,
type,
additional,
};
dialogs.update((currentDialogs) => [...currentDialogs, newDialog]);
}
return id; return id;
} }