generated from fahricansecer/boilerplate-fe
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useCreateFromDocument } from "@/hooks/use-api";
|
import { useExtractDocumentTopics, useCreateFromExtractedText } from "@/hooks/use-api";
|
||||||
import { useToast } from "@/components/ui/toast";
|
import { useToast } from "@/components/ui/toast";
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
@@ -42,9 +42,13 @@ export default function DocumentToVideoPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const createFromDocument = useCreateFromDocument();
|
const extractDocumentTopics = useExtractDocumentTopics();
|
||||||
|
const createFromExtractedText = useCreateFromExtractedText();
|
||||||
|
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [extractedData, setExtractedData] = useState<{text: string; topics: string[]; originalFilename: string} | null>(null);
|
||||||
|
const [selectedTopic, setSelectedTopic] = useState<string | null>(null);
|
||||||
|
|
||||||
const [style, setStyle] = useState("CINEMATIC");
|
const [style, setStyle] = useState("CINEMATIC");
|
||||||
const [cinematicReference, setCinematicReference] = useState("");
|
const [cinematicReference, setCinematicReference] = useState("");
|
||||||
const [duration, setDuration] = useState(60);
|
const [duration, setDuration] = useState(60);
|
||||||
@@ -54,18 +58,40 @@ export default function DocumentToVideoPage() {
|
|||||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.files && e.target.files.length > 0) {
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
setFile(e.target.files[0]);
|
setFile(e.target.files[0]);
|
||||||
|
setExtractedData(null);
|
||||||
|
setSelectedTopic(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenerate = async () => {
|
const handleExtractTopics = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast("error", "Lütfen bir belge seçin.");
|
toast("error", "Lütfen bir belge seçin.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await createFromDocument.mutateAsync({
|
const result = await extractDocumentTopics.mutateAsync({ file });
|
||||||
file,
|
setExtractedData(result);
|
||||||
|
if (result.topics.length > 0) {
|
||||||
|
setSelectedTopic(result.topics[0]);
|
||||||
|
}
|
||||||
|
toast("success", "Belge incelendi ve konular çıkarıldı!");
|
||||||
|
} catch (error) {
|
||||||
|
toast("error", "Konu çıkarılırken hata oluştu. Belki belge okunamıyor veya çok büyük.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
if (!extractedData || !selectedTopic) {
|
||||||
|
toast("error", "Lütfen bir belge yükleyip konu seçin.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result: any = await createFromExtractedText.mutateAsync({
|
||||||
|
text: extractedData.text,
|
||||||
|
topic: selectedTopic,
|
||||||
|
originalFilename: extractedData.originalFilename,
|
||||||
language,
|
language,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
videoStyle: style,
|
videoStyle: style,
|
||||||
@@ -73,7 +99,7 @@ export default function DocumentToVideoPage() {
|
|||||||
targetDuration: duration,
|
targetDuration: duration,
|
||||||
});
|
});
|
||||||
|
|
||||||
toast("success", "Belge → Video projesi oluşturuldu!");
|
toast("success", "Video projesi oluşturuldu!");
|
||||||
router.push(`/dashboard/projects/${result.id}`);
|
router.push(`/dashboard/projects/${result.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast("error", "Proje oluşturulurken hata oluştu.");
|
toast("error", "Proje oluşturulurken hata oluştu.");
|
||||||
@@ -115,6 +141,51 @@ export default function DocumentToVideoPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!extractedData && file && (
|
||||||
|
<button
|
||||||
|
onClick={handleExtractTopics}
|
||||||
|
disabled={extractDocumentTopics.isPending}
|
||||||
|
className="w-full flex items-center justify-center gap-2 py-3.5 rounded-xl font-medium text-white shadow-lg bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{extractDocumentTopics.isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 size={18} className="animate-spin" />
|
||||||
|
Belge İnceleniyor...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FileText size={18} />
|
||||||
|
Konu Çıkar
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{extractedData && (
|
||||||
|
<div className="mt-6 p-4 rounded-xl bg-blue-500/5 border border-blue-500/20">
|
||||||
|
<h3 className="font-medium text-blue-400 mb-3 text-sm flex items-center gap-2">
|
||||||
|
<Wand2 size={16} />
|
||||||
|
Şu Konulardan Birini Seçin:
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{extractedData.topics.map((topic, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => setSelectedTopic(topic)}
|
||||||
|
className={cn(
|
||||||
|
"p-3 rounded-lg border text-sm cursor-pointer transition-all",
|
||||||
|
selectedTopic === topic
|
||||||
|
? "bg-blue-500/20 border-blue-500 text-blue-400"
|
||||||
|
: "bg-[var(--color-bg-surface)] border-[var(--color-border-faint)] text-[var(--color-text-secondary)] hover:border-blue-500/50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{topic}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Video Settings */}
|
{/* Video Settings */}
|
||||||
@@ -235,16 +306,16 @@ export default function DocumentToVideoPage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={createFromDocument.isPending || !file}
|
disabled={createFromExtractedText.isPending || !selectedTopic}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full py-4 rounded-xl font-semibold text-base flex items-center justify-center gap-2 transition-all",
|
"w-full py-4 rounded-xl font-semibold text-base flex items-center justify-center gap-2 transition-all",
|
||||||
createFromDocument.isPending
|
createFromExtractedText.isPending
|
||||||
? "bg-blue-500/20 text-blue-400 cursor-wait"
|
? "bg-blue-500/20 text-blue-400 cursor-wait"
|
||||||
: "bg-blue-500 hover:bg-blue-600 text-white shadow-lg shadow-blue-500/20",
|
: "bg-blue-500 hover:bg-blue-600 text-white shadow-lg shadow-blue-500/20",
|
||||||
!file && "opacity-50 cursor-not-allowed"
|
!selectedTopic && "opacity-50 cursor-not-allowed"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{createFromDocument.isPending ? (
|
{createFromExtractedText.isPending ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 size={20} className="animate-spin" />
|
<Loader2 size={20} className="animate-spin" />
|
||||||
<span>Video Projesi Oluşturuluyor... (Bu işlem uzun sürebilir)</span>
|
<span>Video Projesi Oluşturuluyor... (Bu işlem uzun sürebilir)</span>
|
||||||
@@ -252,7 +323,7 @@ export default function DocumentToVideoPage() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Wand2 size={20} />
|
<Wand2 size={20} />
|
||||||
<span>Belge → Video Oluştur</span>
|
<span>Konudan Video Oluştur</span>
|
||||||
<ArrowRight size={16} />
|
<ArrowRight size={16} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import {
|
|||||||
type CreateFromTweetPayload,
|
type CreateFromTweetPayload,
|
||||||
type CreateFromYoutubePayload,
|
type CreateFromYoutubePayload,
|
||||||
type CreateFromDocumentPayload,
|
type CreateFromDocumentPayload,
|
||||||
|
type ExtractDocumentTopicsPayload,
|
||||||
|
type CreateFromExtractedTextPayload,
|
||||||
|
type ExtractDocumentTopicsResponse,
|
||||||
type Template,
|
type Template,
|
||||||
type PaginatedResponse,
|
type PaginatedResponse,
|
||||||
} from '@/lib/api/api-service';
|
} from '@/lib/api/api-service';
|
||||||
@@ -181,6 +184,11 @@ export function useGenerateSceneImage() {
|
|||||||
projectsApi.generateSceneImage(projectId, sceneId, customPrompt),
|
projectsApi.generateSceneImage(projectId, sceneId, customPrompt),
|
||||||
onSuccess: (updatedScene, variables) => {
|
onSuccess: (updatedScene, variables) => {
|
||||||
qc.invalidateQueries({ queryKey: queryKeys.projects.detail(variables.projectId) });
|
qc.invalidateQueries({ queryKey: queryKeys.projects.detail(variables.projectId) });
|
||||||
|
toast.success('Görsel başarıyla üretildi');
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
console.error('Görsel üretme hatası:', error);
|
||||||
|
toast.error(error.response?.data?.message || 'Görsel üretilemedi');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -378,6 +386,25 @@ export function useCreateFromDocument() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Dokümandan konu önerileri çıkar */
|
||||||
|
export function useExtractDocumentTopics() {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (data: ExtractDocumentTopicsPayload) => projectsApi.extractDocumentTopics(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Çıkarılan metin ve konudan proje üret */
|
||||||
|
export function useCreateFromExtractedText() {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (data: CreateFromExtractedTextPayload) => projectsApi.createFromExtractedText(data),
|
||||||
|
onSuccess: () => {
|
||||||
|
qc.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||||
|
qc.invalidateQueries({ queryKey: queryKeys.dashboard.stats });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════
|
||||||
// NOTIFICATIONS — Bildirim hook'ları
|
// NOTIFICATIONS — Bildirim hook'ları
|
||||||
// ═══════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ export interface CreateFromYoutubePayload {
|
|||||||
language?: string;
|
language?: string;
|
||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
videoStyle?: string;
|
videoStyle?: string;
|
||||||
|
cinematicReference?: string;
|
||||||
targetDuration?: number;
|
targetDuration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +266,28 @@ export interface CreateFromDocumentPayload {
|
|||||||
language?: string;
|
language?: string;
|
||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
videoStyle?: string;
|
videoStyle?: string;
|
||||||
|
cinematicReference?: string;
|
||||||
|
targetDuration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtractDocumentTopicsPayload {
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtractDocumentTopicsResponse {
|
||||||
|
text: string;
|
||||||
|
topics: string[];
|
||||||
|
originalFilename: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateFromExtractedTextPayload {
|
||||||
|
text: string;
|
||||||
|
topic: string;
|
||||||
|
originalFilename?: string;
|
||||||
|
language?: string;
|
||||||
|
aspectRatio?: string;
|
||||||
|
videoStyle?: string;
|
||||||
|
cinematicReference?: string;
|
||||||
targetDuration?: number;
|
targetDuration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +369,19 @@ export const projectsApi = {
|
|||||||
}).then((r) => r.data);
|
}).then((r) => r.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
extractDocumentTopics: (data: ExtractDocumentTopicsPayload) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', data.file);
|
||||||
|
return apiClient.post<ExtractDocumentTopicsResponse>('/projects/extract-document-topics', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}).then((r) => r.data);
|
||||||
|
},
|
||||||
|
|
||||||
|
createFromExtractedText: (data: CreateFromExtractedTextPayload) =>
|
||||||
|
apiClient.post<Project>('/projects/document-from-topic', data).then((r) => r.data),
|
||||||
|
|
||||||
updateScene: (projectId: string, sceneId: string, data: Partial<Scene>) =>
|
updateScene: (projectId: string, sceneId: string, data: Partial<Scene>) =>
|
||||||
apiClient.patch<Scene>(`/projects/${projectId}/scenes/${sceneId}`, data).then((r) => r.data),
|
apiClient.patch<Scene>(`/projects/${projectId}/scenes/${sceneId}`, data).then((r) => r.data),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user