diff --git a/src/features/project/TranscriptionModal/TranscriptionModal.tsx b/src/features/project/TranscriptionModal/TranscriptionModal.tsx index 8745019..eac05c7 100644 --- a/src/features/project/TranscriptionModal/TranscriptionModal.tsx +++ b/src/features/project/TranscriptionModal/TranscriptionModal.tsx @@ -14,7 +14,7 @@ import styles from "./TranscriptionModal.module.scss" interface ITranscriptionFormData { file_key: string - engine: "whisper" | "google" + engine: "whisper" | "google" | "salutespeech" language: string model: string } @@ -22,25 +22,32 @@ interface ITranscriptionFormData { const ENGINE_OPTIONS = [ { value: "whisper", label: "Whisper (локальный)" }, { value: "google", label: "Google Speech" }, + { value: "salutespeech", label: "SaluteSpeech" }, ] const LANGUAGE_OPTIONS = [ { value: "auto", label: "Авто" }, { value: "ru", label: "Русский" }, - { value: "en", label: "English" }, + { value: "en", label: "Английский" }, ] -const MODEL_OPTIONS = [ - { value: "base", label: "Base" }, - { value: "small", label: "Small" }, - { value: "medium", label: "Medium" }, - { value: "large", label: "Large" }, +const WHISPER_MODEL_OPTIONS = [ + { value: "base", label: "Базовая" }, + { value: "small", label: "Малая" }, + { value: "medium", label: "Средняя" }, + { value: "large", label: "Большая" }, +] + +const SALUTE_MODEL_OPTIONS = [ + { value: "general", label: "Общая" }, + { value: "finance", label: "Финансы" }, + { value: "medicine", label: "Медицина" }, ] export const TranscriptionModal: FunctionComponent< ITranscriptionModalProps > = ({ projectId, open, onOpenChange }): JSX.Element => { - const { control, handleSubmit, reset, watch } = + const { control, handleSubmit, reset, watch, setValue } = useForm({ defaultValues: { file_key: "", @@ -52,6 +59,14 @@ export const TranscriptionModal: FunctionComponent< const engine = watch("engine") + useEffect(() => { + if (engine === "salutespeech") { + setValue("model", "general") + } else if (engine === "whisper") { + setValue("model", "base") + } + }, [engine, setValue]) + const { data: files } = api.useQuery("get", "/api/files/files/", { queryKey: ["files", projectId], }) @@ -159,7 +174,7 @@ export const TranscriptionModal: FunctionComponent< /> - {engine === "whisper" && ( + {(engine === "whisper" || engine === "salutespeech") && (
Модель
- {MODEL_OPTIONS.map((opt) => ( + {(engine === "whisper" + ? WHISPER_MODEL_OPTIONS + : SALUTE_MODEL_OPTIONS + ).map((opt) => ( {opt.label} diff --git a/src/shared/api/__generated__/openapi.types.ts b/src/shared/api/__generated__/openapi.types.ts index 4b9ea98..a1f96f8 100644 --- a/src/shared/api/__generated__/openapi.types.ts +++ b/src/shared/api/__generated__/openapi.types.ts @@ -518,6 +518,58 @@ export interface paths { patch?: never; trace?: never; }; + "/api/captions/presets/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Presets + * @description List system presets + user's own presets. + */ + get: operations["list_presets_api_captions_presets__get"]; + put?: never; + /** + * Create Preset + * @description Create a user preset. + */ + post: operations["create_preset_api_captions_presets__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/captions/presets/{preset_id}/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Preset + * @description Get a single preset. + */ + get: operations["get_preset_api_captions_presets__preset_id___get"]; + put?: never; + post?: never; + /** + * Delete Preset + * @description Delete a user preset (cannot delete system or others' presets). + */ + delete: operations["delete_preset_api_captions_presets__preset_id___delete"]; + options?: never; + head?: never; + /** + * Update Preset + * @description Update a user preset (cannot edit system or others' presets). + */ + patch: operations["update_preset_api_captions_presets__preset_id___patch"]; + trace?: never; + }; "/api/jobs/jobs/": { parameters: { query?: never; @@ -632,6 +684,46 @@ export interface paths { patch?: never; trace?: never; }; + "/api/tasks/silence-detect/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit Silence Detect + * @description Submit a background task to detect silent segments in media file. + */ + post: operations["submit_silence_detect_api_tasks_silence_detect__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tasks/silence-apply/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit Silence Apply + * @description Submit a background task to apply silence cuts to media file. + */ + post: operations["submit_silence_apply_api_tasks_silence_apply__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/tasks/media-convert/": { parameters: { query?: never; @@ -853,7 +945,7 @@ export interface components { * Artifact Type * @enum {string} */ - artifact_type: "TRANSCRIPTION_JSON" | "SILENCE_REMOVED_VIDEO" | "THUMBNAIL" | "AUDIO_PROXY" | "RENDERED_VIDEO" | "FRAME_SPRITES"; + artifact_type: "TRANSCRIPTION_JSON" | "SILENCE_REMOVED_VIDEO" | "CONVERTED_VIDEO" | "THUMBNAIL" | "AUDIO_PROXY" | "RENDERED_VIDEO" | "FRAME_SPRITES"; }; /** ArtifactMediaFileRead */ ArtifactMediaFileRead: { @@ -872,7 +964,7 @@ export interface components { * Artifact Type * @enum {string} */ - artifact_type: "TRANSCRIPTION_JSON" | "SILENCE_REMOVED_VIDEO" | "THUMBNAIL" | "AUDIO_PROXY" | "RENDERED_VIDEO" | "FRAME_SPRITES"; + artifact_type: "TRANSCRIPTION_JSON" | "SILENCE_REMOVED_VIDEO" | "CONVERTED_VIDEO" | "THUMBNAIL" | "AUDIO_PROXY" | "RENDERED_VIDEO" | "FRAME_SPRITES"; /** Is Deleted */ is_deleted: boolean; /** Is Active */ @@ -906,6 +998,186 @@ export interface components { */ folder: string; }; + /** CaptionAnimationStyle */ + CaptionAnimationStyle: { + /** + * Highlight Style + * @default color + * @enum {string} + */ + highlight_style: "color" | "scale" | "underline" | "color_scale"; + /** + * Highlight Scale + * @default 1.1 + */ + highlight_scale: number; + /** + * Segment Transition + * @default fade + * @enum {string} + */ + segment_transition: "fade" | "slide" | "none"; + /** + * Fade Duration Frames + * @default 3 + */ + fade_duration_frames: number; + /** + * Animation Speed + * @default 1 + */ + animation_speed: number; + }; + /** CaptionBackgroundStyle */ + CaptionBackgroundStyle: { + /** + * Bg Color + * @default rgba(0,0,0,0.6) + */ + bg_color: string; + /** + * Bg Blur Px + * @default 0 + */ + bg_blur_px: number; + /** Bg Glow Color */ + bg_glow_color?: string | null; + /** + * Bg Border Radius Px + * @default 15 + */ + bg_border_radius_px: number; + /** + * Bg Padding Px + * @default 20 + */ + bg_padding_px: number; + }; + /** CaptionLayoutStyle */ + CaptionLayoutStyle: { + /** + * Vertical Position + * @default bottom + * @enum {string} + */ + vertical_position: "top" | "center" | "bottom"; + /** + * Horizontal Alignment + * @default center + * @enum {string} + */ + horizontal_alignment: "left" | "center" | "right"; + /** + * Padding Px + * @default 20 + */ + padding_px: number; + /** + * Max Width Pct + * @default 90 + */ + max_width_pct: number; + /** + * Lines Per Screen + * @default 2 + */ + lines_per_screen: number; + }; + /** CaptionPresetCreate */ + CaptionPresetCreate: { + /** Name */ + name: string; + /** Description */ + description?: string | null; + style_config: components["schemas"]["CaptionStyleConfig"]; + }; + /** CaptionPresetRead */ + CaptionPresetRead: { + /** + * Id + * Format: uuid + */ + id: string; + /** User Id */ + user_id: string | null; + /** Name */ + name: string; + /** Description */ + description: string | null; + /** Is System */ + is_system: boolean; + style_config: components["schemas"]["CaptionStyleConfig"]; + /** Preview Url */ + preview_url: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + }; + /** CaptionPresetUpdate */ + CaptionPresetUpdate: { + /** Name */ + name?: string | null; + /** Description */ + description?: string | null; + style_config?: components["schemas"]["CaptionStyleConfig"] | null; + }; + /** CaptionStyleConfig */ + CaptionStyleConfig: { + text?: components["schemas"]["CaptionTextStyle"]; + layout?: components["schemas"]["CaptionLayoutStyle"]; + animation?: components["schemas"]["CaptionAnimationStyle"]; + background?: components["schemas"]["CaptionBackgroundStyle"]; + }; + /** CaptionTextStyle */ + CaptionTextStyle: { + /** + * Font Family + * @default Lobster + */ + font_family: string; + /** + * Font Size + * @default 40 + */ + font_size: number; + /** + * Font Weight + * @default 400 + */ + font_weight: number; + /** + * Text Color + * @default #FFFFFF + */ + text_color: string; + /** + * Highlight Color + * @default #FFCC00 + */ + highlight_color: string; + /** + * Text Shadow + * @default 2px 2px 4px rgba(0,0,0,0.5) + */ + text_shadow: string | null; + /** + * Text Stroke Width + * @default 0 + */ + text_stroke_width: number; + /** + * Text Stroke Color + * @default #000000 + */ + text_stroke_color: string; + }; /** * CaptionsGenerateRequest * @description Request to generate captions/subtitles video. @@ -932,6 +1204,18 @@ export interface components { * @description Associated project ID */ project_id?: string | null; + /** + * Preset Id + * @description Caption style preset ID (mutually exclusive with style_config) + */ + preset_id?: string | null; + /** + * Style Config + * @description Inline caption style config (overrides preset_id) + */ + style_config?: { + [key: string]: unknown; + } | null; }; /** CaptionsRequest */ CaptionsRequest: { @@ -1157,7 +1441,7 @@ export interface components { * Job Type * @enum {string} */ - job_type: "MEDIA_PROBE" | "SILENCE_REMOVE" | "MEDIA_CONVERT" | "TRANSCRIPTION_GENERATE" | "CAPTIONS_GENERATE" | "FRAME_EXTRACT"; + job_type: "MEDIA_PROBE" | "SILENCE_REMOVE" | "SILENCE_DETECT" | "SILENCE_APPLY" | "MEDIA_CONVERT" | "TRANSCRIPTION_GENERATE" | "CAPTIONS_GENERATE" | "FRAME_EXTRACT"; }; /** JobEventCreate */ JobEventCreate: { @@ -1241,7 +1525,7 @@ export interface components { * Job Type * @enum {string} */ - job_type: "MEDIA_PROBE" | "SILENCE_REMOVE" | "MEDIA_CONVERT" | "TRANSCRIPTION_GENERATE" | "CAPTIONS_GENERATE" | "FRAME_EXTRACT"; + job_type: "MEDIA_PROBE" | "SILENCE_REMOVE" | "SILENCE_DETECT" | "SILENCE_APPLY" | "MEDIA_CONVERT" | "TRANSCRIPTION_GENERATE" | "CAPTIONS_GENERATE" | "FRAME_EXTRACT"; /** Project Pct */ project_pct: number | null; /** Error Message */ @@ -1565,7 +1849,9 @@ export interface components { */ status: "DRAFT" | "PROCESSING" | "DONE" | "FAILED"; /** Workspace State */ - workspace_state: Record | null; + workspace_state: { + [key: string]: unknown; + } | null; /** Is Active */ is_active: boolean; /** @@ -1592,7 +1878,9 @@ export interface components { /** Status */ status?: ("DRAFT" | "PROCESSING" | "DONE" | "FAILED") | null; /** Workspace State */ - workspace_state?: Record | null; + workspace_state?: { + [key: string]: unknown; + } | null; }; /** SegmentNode */ "SegmentNode-Input": { @@ -1618,6 +1906,73 @@ export interface components { /** Lines */ lines: components["schemas"]["LineNode-Output"][]; }; + /** + * SilenceApplyRequest + * @description Request to apply silence cuts to media file. + */ + SilenceApplyRequest: { + /** + * File Key + * @description Storage key of the input file + */ + file_key: string; + /** + * Out Folder + * @description Output folder for processed file + */ + out_folder: string; + /** + * Project Id + * @description Associated project ID + */ + project_id?: string | null; + /** + * Output Name + * @description Display name for the output file + */ + output_name?: string | null; + /** + * Cuts + * @description Cut regions: [{'start_ms': int, 'end_ms': int}, ...] + */ + cuts: { + [key: string]: unknown; + }[]; + }; + /** + * SilenceDetectRequest + * @description Request to detect silent segments in media file. + */ + SilenceDetectRequest: { + /** + * File Key + * @description Storage key of the input file + */ + file_key: string; + /** + * Project Id + * @description Associated project ID + */ + project_id?: string | null; + /** + * Min Silence Duration Ms + * @description Minimum silence duration in milliseconds + * @default 200 + */ + min_silence_duration_ms: number; + /** + * Silence Threshold Db + * @description Silence threshold in decibels + * @default 16 + */ + silence_threshold_db: number; + /** + * Padding Ms + * @description Padding around non-silent segments in milliseconds + * @default 100 + */ + padding_ms: number; + }; /** * SilenceRemoveRequest * @description Request to remove silence from media file. @@ -1731,7 +2086,7 @@ export interface components { * Job Type * @enum {string} */ - job_type: "MEDIA_PROBE" | "SILENCE_REMOVE" | "MEDIA_CONVERT" | "TRANSCRIPTION_GENERATE" | "CAPTIONS_GENERATE" | "FRAME_EXTRACT"; + job_type: "MEDIA_PROBE" | "SILENCE_REMOVE" | "SILENCE_DETECT" | "SILENCE_APPLY" | "MEDIA_CONVERT" | "TRANSCRIPTION_GENERATE" | "CAPTIONS_GENERATE" | "FRAME_EXTRACT"; /** Progress Pct */ progress_pct?: number | null; /** Current Message */ @@ -1839,7 +2194,7 @@ export interface components { * @default whisper * @enum {string} */ - engine: "whisper" | "google"; + engine: "whisper" | "google" | "salutespeech"; /** * Language * @description Language code (e.g., 'en') @@ -3555,6 +3910,154 @@ export interface operations { }; }; }; + list_presets_api_captions_presets__get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaptionPresetRead"][]; + }; + }; + }; + }; + create_preset_api_captions_presets__post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionPresetCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaptionPresetRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_preset_api_captions_presets__preset_id___get: { + parameters: { + query?: never; + header?: never; + path: { + preset_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaptionPresetRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_preset_api_captions_presets__preset_id___delete: { + parameters: { + query?: never; + header?: never; + path: { + preset_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_preset_api_captions_presets__preset_id___patch: { + parameters: { + query?: never; + header?: never; + path: { + preset_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionPresetUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaptionPresetRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; list_jobs_endpoint_api_jobs_jobs__get: { parameters: { query?: never; @@ -3917,6 +4420,72 @@ export interface operations { }; }; }; + submit_silence_detect_api_tasks_silence_detect__post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SilenceDetectRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskSubmitResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + submit_silence_apply_api_tasks_silence_apply__post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SilenceApplyRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskSubmitResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; submit_media_convert_api_tasks_media_convert__post: { parameters: { query?: never;