diff --git a/src/pages/Video/VideoPage.module.css b/src/pages/Video/VideoPage.module.css
index e69de29..0fb5ad9 100644
--- a/src/pages/Video/VideoPage.module.css
+++ b/src/pages/Video/VideoPage.module.css
@@ -0,0 +1,7 @@
+.videoMark::before {
+ inset: 18px 15px 18px 22px;
+ background: var(--color-accent);
+ border: 0;
+ border-radius: 0;
+ clip-path: polygon(0 0, 100% 50%, 0 100%);
+}
diff --git a/src/pages/Video/VideoPage.tsx b/src/pages/Video/VideoPage.tsx
index 9f31087..e3c969f 100644
--- a/src/pages/Video/VideoPage.tsx
+++ b/src/pages/Video/VideoPage.tsx
@@ -1,6 +1,300 @@
+import { useEffect, useState, type ChangeEvent, type FormEvent } from "react";
+import { useNavigate } from "react-router-dom";
+import logo from "../../assets/common/HITGENERATOR-Logo.png";
+import { useAppDispatch, useAppSelector } from "../../store/hooks";
+import { clearRequestError, request, resetState } from "../../store/slices/slice";
+import pictureStyles from "../Picture/Picture.module.css";
+import styles from "./VideoPage.module.css";
+
+type AspectRatio = "1:1" | "4:5" | "16:9";
+
+const aspectRatios: AspectRatio[] = ["1:1", "4:5", "16:9"];
export default function Video() {
- return (
-
video
- )
-}
\ No newline at end of file
+ const navigate = useNavigate();
+ const dispatch = useAppDispatch();
+ const { loading, error: requestError } = useAppSelector((state) => state.request);
+
+ const [prompt, setPrompt] = useState("");
+ const [characters, setCharacters] = useState([]);
+ const [scenes, setScenes] = useState([]);
+ const [screenplays, setScreenplays] = useState([]);
+ const [aspectRatio, setAspectRatio] = useState("16:9");
+ const [formError, setFormError] = useState(null);
+ const [isSubmitted, setIsSubmitted] = useState(false);
+
+ useEffect(() => {
+ dispatch(clearRequestError());
+ }, [dispatch]);
+
+ function resetErrors() {
+ setFormError(null);
+ dispatch(clearRequestError());
+ }
+
+ function addImageFiles(
+ event: ChangeEvent,
+ setFiles: React.Dispatch>,) {
+ const nextFiles = event.currentTarget.files?.[0] ?? null;
+
+ if (nextFiles && !nextFiles.type.startsWith("image/")) {
+ setFormError("Выберите файлы изображений.");
+ return;
+ }
+
+ resetErrors();
+ setFiles((currentFiles) => nextFiles ? [...currentFiles, nextFiles] : currentFiles);
+ setIsSubmitted(false);
+ }
+
+ function addScreenplayFiles(event: ChangeEvent) {
+ const nextFiles = Array.from(event.currentTarget.files ?? []);
+ const isTextFile = (file: File) => {
+ return (file.type.startsWith("text/") || /\.(txt|md)$/i.test(file.name)) || file.type.startsWith("image/");
+
+ };
+ if (nextFiles.some((file) => !isTextFile(file))) {
+ setFormError("Выберите текстовые файлы в формате TXT или MD.");
+ return;
+ }
+
+ resetErrors();
+ setScreenplays((currentFiles) => [...currentFiles, ...nextFiles]);
+ event.currentTarget.value = "";
+ setIsSubmitted(false);
+ }
+
+ function removeFile(
+ index: number,
+ setFiles: React.Dispatch>,
+ ) {
+ setFiles((currentFiles) => currentFiles.filter((_, fileIndex) => fileIndex !== index));
+ setIsSubmitted(false);
+ }
+
+ async function handleSubmit(event: FormEvent) {
+ event.preventDefault();
+
+ const text = prompt.trim();
+
+ if (!text) {
+ setFormError("Добавьте описание видео.");
+ return;
+ }
+
+
+
+ resetErrors();
+ setIsSubmitted(false);
+
+ await dispatch(
+ request({
+ id: Date.now(),
+ text,
+ files: [...characters, ...scenes, ...screenplays],
+ ratio: aspectRatio,
+ }),
+ ).unwrap();
+ setIsSubmitted(true);
+ }
+
+ const previewClassName = [
+ pictureStyles.previewCanvas,
+ aspectRatio === "1:1"
+ ? pictureStyles.square
+ : aspectRatio === "4:5"
+ ? pictureStyles.portrait
+ : pictureStyles.wide,
+ ].join(" ");
+ const status = loading ? "Генерируем" : isSubmitted ? "Запрос принят" : "Ожидание";
+ //const attachmentsCount = characters.length + scenes.length + screenplays.length;
+
+ return (
+
+
+
+
+

+
+ VIDEO
+
+
+
+
+
+
+
+ {status}
+
+
+
+
+
+
+
+ {loading ? "Генерируем" : isSubmitted ? "Запрос принят" : "Ожидаемый ролик"}
+
+ {loading &&
}
+
+
+
+
+
+
+
+ );
+}