feat(frontend): add SaluteSpeech engine option to TranscriptionModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniil
2026-04-04 00:11:00 +03:00
parent 305e72725c
commit cf8ded79d7
2 changed files with 605 additions and 18 deletions
@@ -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<ITranscriptionFormData>({
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<
/>
</div>
{engine === "whisper" && (
{(engine === "whisper" || engine === "salutespeech") && (
<div className={styles.selectField}>
<div className={styles.selectLabel}>Модель</div>
<Controller
@@ -171,7 +186,10 @@ export const TranscriptionModal: FunctionComponent<
onValueChange={field.onChange}
placeholder="Выберите модель"
>
{MODEL_OPTIONS.map((opt) => (
{(engine === "whisper"
? WHISPER_MODEL_OPTIONS
: SALUTE_MODEL_OPTIONS
).map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
+577 -8
View File
@@ -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<string, unknown> | 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<string, unknown> | 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;