diff --git a/package.json b/package.json index 815066c..4013a95 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "axios": "^1.13.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dayjs": "^1.11.20", "framer-motion": "^12.38.0", "i18next": "^25.6.0", "lucide-react": "^1.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22127de..020e36e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + dayjs: + specifier: ^1.11.20 + version: 1.11.20 framer-motion: specifier: ^12.38.0 version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -1960,6 +1963,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -5657,6 +5663,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dayjs@1.11.20: {} + debug@3.2.7: dependencies: ms: 2.1.3 diff --git a/src/app/[locale]/(dashboard)/dashboard/tools/page.tsx b/src/app/[locale]/(dashboard)/dashboard/tools/page.tsx index b13a226..ff44536 100644 --- a/src/app/[locale]/(dashboard)/dashboard/tools/page.tsx +++ b/src/app/[locale]/(dashboard)/dashboard/tools/page.tsx @@ -61,19 +61,37 @@ function ToolCard({ whileHover={{ scale: 1.02 }} className="group relative h-full glass p-6 rounded-2xl border border-[var(--color-border-faint)] overflow-hidden" > - {/* Spotlight */} + {/* Spotlight & Glowing Border effect */} + + {/* Border glow specifically tracking mouse */} + {/* İçerik, Z-ekseninde hafifçe öne çıkarılır ki 3D efekti belirginleşsin */}
@@ -137,6 +155,15 @@ export default function ToolsPage() { colorClass="bg-orange-500/20 text-orange-400" spotlightColor="rgba(249, 115, 22, 0.15)" /> + +
); diff --git a/src/app/[locale]/(dashboard)/dashboard/tools/tube-strategist/[projectId]/episode/[episodeId]/page.tsx b/src/app/[locale]/(dashboard)/dashboard/tools/tube-strategist/[projectId]/episode/[episodeId]/page.tsx new file mode 100644 index 0000000..a870f20 --- /dev/null +++ b/src/app/[locale]/(dashboard)/dashboard/tools/tube-strategist/[projectId]/episode/[episodeId]/page.tsx @@ -0,0 +1,811 @@ +'use client'; + +import React, { useState, useEffect, Component, ErrorInfo, ReactNode } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { motion, AnimatePresence } from 'framer-motion'; +import { getEpisodeById, analyzeEpisode, generateMoreQuestions, generateEpisodeQuestions, generateEpisodeSeoMarketing, generateEpisodeCrisisSponsors, generateThumbnail, EpisodeResponse } from '../../../services/strategistApi'; +import { Loader2, ArrowLeft, Sparkles, AlertTriangle, Layers, Target, FileText, Camera, ShieldAlert, Users, Film, Clock, Presentation, Type as TypeIcon, HelpCircle, BarChart, Zap, Download, Maximize2, RefreshCw, Star, ShieldCheck, Mail, Copy, Check } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +class ErrorBoundary extends Component<{children: ReactNode}, {hasError: boolean, error: Error | null}> { + constructor(props: {children: ReactNode}) { + super(props); + this.state = { hasError: false, error: null }; + } + static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } + componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error("ErrorBoundary caught an error", error, errorInfo); } + render() { + if (this.state.hasError) { + return ( +
+

Uygulama Çöktü (ErrorBoundary)

+
+            {this.state.error?.stack || this.state.error?.message || "Bilinmeyen bir hata oluştu"}
+          
+ +
+ ); + } + return this.props.children; + } +} + +export default function EpisodeWorkbenchPage() { + const params = useParams(); + const router = useRouter(); + const projectId = params.projectId as string; + const episodeId = params.episodeId as string; + + const [episode, setEpisode] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isAnalyzing, setIsAnalyzing] = useState(false); + const [error, setError] = useState(null); + + const [activeTab, setActiveTab] = useState<'titles' | 'questions' | 'seo' | 'gap' | 'segments' | 'friction' | 'visual' | 'guest' | 'crisis'>('titles'); + const [isGeneratingQuestions, setIsGeneratingQuestions] = useState(false); + const [isGeneratingSeo, setIsGeneratingSeo] = useState(false); + const [isGeneratingCrisis, setIsGeneratingCrisis] = useState(false); + const [isGeneratingThumbnail, setIsGeneratingThumbnail] = useState(false); + const [isThumbnailExpanded, setIsThumbnailExpanded] = useState(false); + const [isCopied, setIsCopied] = useState(false); + + const fetchEpisode = async () => { + try { + const data = await getEpisodeById(episodeId); + setEpisode(data); + } catch (err) { + console.error(err); + setError("Bölüm yüklenemedi."); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchEpisode(); + }, [episodeId]); + + const handleAnalyze = async () => { + setIsAnalyzing(true); + setError(null); + try { + await analyzeEpisode(episodeId); + await fetchEpisode(); // Refresh after analysis + } catch (err: any) { + setError(err?.response?.data?.message || "Analiz sırasında bir hata oluştu."); + } finally { + setIsAnalyzing(false); + } + }; + + const handleGenerateMoreQuestions = async () => { + setIsGeneratingQuestions(true); + setError(null); + try { + await generateMoreQuestions(episodeId); + await fetchEpisode(); + } catch (err: any) { + setError(err?.response?.data?.message || "Yeni soru üretilirken hata oluştu."); + } finally { + setIsGeneratingQuestions(false); + } + }; + + const handleGenerateQuestions = async () => { + setIsGeneratingQuestions(true); + setError(null); + try { + await generateEpisodeQuestions(episodeId); + await fetchEpisode(); + } catch (err: any) { + setError(err?.response?.data?.message || "Sorular üretilirken hata oluştu."); + } finally { + setIsGeneratingQuestions(false); + } + }; + + const handleGenerateSeoMarketing = async () => { + setIsGeneratingSeo(true); + setError(null); + try { + await generateEpisodeSeoMarketing(episodeId); + await fetchEpisode(); + } catch (err: any) { + setError(err?.response?.data?.message || "SEO verisi üretilirken hata oluştu."); + } finally { + setIsGeneratingSeo(false); + } + }; + + const handleGenerateCrisisSponsors = async () => { + setIsGeneratingCrisis(true); + setError(null); + try { + await generateEpisodeCrisisSponsors(episodeId); + await fetchEpisode(); + } catch (err: any) { + setError(err?.response?.data?.message || "Kriz ve Sponsor verisi üretilirken hata oluştu."); + } finally { + setIsGeneratingCrisis(false); + } + }; + + const handleGenerateThumbnail = async () => { + setIsGeneratingThumbnail(true); + setError(null); + try { + await generateThumbnail(episodeId); + await fetchEpisode(); + } catch (err: any) { + setError(err?.response?.data?.message || "Thumbnail üretilirken hata oluştu."); + } finally { + setIsGeneratingThumbnail(false); + } + }; + + const handleDownloadThumbnail = () => { + if (episode?.masterAnalysis?.thumbnailUrl) { + const link = document.createElement('a'); + link.href = episode.masterAnalysis.thumbnailUrl; + link.download = `thumbnail-${episodeId}.jpg`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + }; + + const handleCopyKeywords = () => { + if (episode?.masterAnalysis?.seoAnalysis?.targetKeywords) { + const keywords = episode.masterAnalysis.seoAnalysis.targetKeywords.join(', '); + navigator.clipboard.writeText(keywords); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (!episode) { + return ( +
+

Bölüm Bulunamadı

+ +
+ ); + } + + const analysis = episode.masterAnalysis; + + return ( + +
+ + {/* Header */} +
+
+ +
+
+

+ {episode.topic === 'AI_AUTO' ? ( + + Yapay Zeka Tarafından Belirlenecek + + ) : ( + episode.topic + )} +

+
+ Pre-Production +
+
+
+ {episode.targetAudience} + {episode.duration} + {episode.format} +
+
+
+ + {episode.status !== 'COMPLETED' ? ( + + ) : ( + + )} +
+ + + {error && ( + + + {error} + + )} + + + {episode.status === 'COMPLETED' && analysis ? ( +
+ + {/* Sidebar Navigation */} +
+ + + + + + + + + +
+ + {/* Content Area */} +
+ + {activeTab === 'titles' && ( + +
+

+ Başlık Önerileri +

+ {analysis.titleSuggestions && analysis.titleSuggestions.length > 0 ? ( +
+ {analysis.titleSuggestions.map((item: any, idx: number) => { + const isObj = typeof item === 'object' && item !== null; + const title = isObj ? item.title : item; + const seoScore = isObj ? item.seoScore : null; + + return ( +
+
+
+ {idx + 1} +
+ {title} +
+ {seoScore && ( +
+ SEO: {seoScore} +
+ )} +
+ ); + })} +
+ ) : ( +

Eski JSON yapısı sebebiyle başlık önerileri bulunamadı. Lütfen analizi yeniden yapın.

+ )} +
+ +
+
+

Psikolojik Tema

+

{analysis.psychologicalTheme || "Veri yok."}

+
+ +
+
+

Thumbnail Konsepti

+

{analysis.thumbnailConcept || "Veri yok."}

+
+ +
+ {analysis.thumbnailUrl ? ( +
+
setIsThumbnailExpanded(true)}> + Thumbnail Preview +
+ + Büyütmek İçin Tıkla +
+
+
+ + +
+
+ ) : ( + + )} +
+
+
+
+ )} + + {activeTab === 'questions' && ( + +
+

+ Kışkırtıcı & Derin Sorular +

+
+ + + {analysis.interviewQuestions && analysis.interviewQuestions.length > 0 && ( + + )} +
+
+ + {analysis.interviewQuestions && analysis.interviewQuestions.length > 0 ? ( +
+ {analysis.interviewQuestions.map((q: any, idx: number) => { + const isObj = typeof q === 'object' && q !== null; + const questionText = isObj ? q.question : q; + const neuroDirection = isObj ? q.neuroMarketingAnswerDirection : null; + const neuroScore = isObj ? q.neuroMarketingScore : null; + const targetArea = isObj ? q.targetArea : null; + + return ( +
+
+ + {/* Tags */} +
+ {targetArea && ( + + {targetArea} + + )} + {neuroScore && ( + + Skoru: {neuroScore} + + )} +
+ +
+
+ {idx + 1} +
+
+

{questionText}

+ {neuroDirection && ( +
+

Neuro-Marketing Yanıt Yönlendirmesi

+

{neuroDirection}

+
+ )} +
+
+
+ ); + })} +
+ ) : ( +
+

Soru analizi bulunamadı.

+
+ )} +
+ )} + + {activeTab === 'seo' && ( + +
+

+ SEO & Pazarlama Analizi +

+ +
+ + {analysis.seoAnalysis || analysis.marketingAnalysis ? ( +
+ {/* SEO Section */} + {analysis.seoAnalysis && ( +
+

Arama Motoru Optimizasyonu (SEO)

+
+
+
+ Hedef Anahtar Kelimeler + +
+
+ {(analysis.seoAnalysis.targetKeywords || []).map((kw: string, i: number) => ( + {kw} + ))} +
+
+
+ Arama Niyeti (Search Intent) +

{analysis.seoAnalysis.searchIntent || "Belirtilmemiş"}

+
+
+
+ Açıklama Şablonu (Description Template) +

{analysis.seoAnalysis.descriptionTemplate || "Belirtilmemiş"}

+
+
+ )} + + {/* Marketing Section */} + {analysis.marketingAnalysis && ( +
+

Nöro-Pazarlama Stratejisi

+
+
+ Nöro-Pazarlama Tetikleyicileri +
    + {(analysis.marketingAnalysis.neuroMarketingTriggers || []).map((tr: string, i: number) => ( +
  • {tr}
  • + ))} +
+
+
+ İzleyici Psikolojisi +

{analysis.marketingAnalysis.audiencePsychology || "Belirtilmemiş"}

+
+
+
+ Thumbnail & Kanca Uyumu +

{analysis.marketingAnalysis.thumbnailHookAlignment || "Belirtilmemiş"}

+
+
+ )} +
+ ) : ( +
+

SEO & Pazarlama analizi bulunamadı.

+
+ )} +
+ )} + + {activeTab === 'gap' && ( + +

+ Gap Analysis +

+
+ {analysis.gapAnalysis || "Veri bulunamadı."} +
+
+ )} + + {activeTab === 'segments' && ( + +

+ Segment Archetypes +

+
+ {analysis.segmentArchetypes || "Veri bulunamadı."} +
+
+ )} + + {activeTab === 'friction' && ( + +

+ Devil's Advocate / Friction Points +

+ {analysis.frictionPoints && analysis.frictionPoints.length > 0 ? ( +
+ {analysis.frictionPoints.map((point: string, idx: number) => ( +
+
+

{point}

+
+ ))} +
+ ) : ( +

Veri bulunamadı.

+ )} +
+ )} + + {activeTab === 'visual' && ( + +

+ Visual DNA & Shot List +

+ {analysis.visualDna && analysis.visualDna.length > 0 ? ( +
+ {analysis.visualDna.map((item: any, idx: number) => ( +
+ + {item.timestamp || '00:00'} + +

{item.suggestion}

+
+ ))} +
+ ) : ( +

Veri bulunamadı.

+ )} +
+ )} + + {activeTab === 'guest' && ( + +

+ Guest Briefing Notes +

+
+ {analysis.guestBriefing || "Veri bulunamadı."} +
+
+ )} + + {activeTab === 'crisis' && ( + +
+

+ Kriz Yönetimi & Sponsorlar +

+ +
+ + {(analysis.crisisManagement || (analysis.sponsors && analysis.sponsors.length > 0)) ? ( +
+ {/* Crisis Section */} + {analysis.crisisManagement && ( +
+
+

Potansiyel Linç İhtimali

+

{analysis.crisisManagement.potentialBacklash || "Veri yok."}

+
+
+

PR Stratejisi

+

{analysis.crisisManagement.prStrategy || "Veri yok."}

+
+
+ )} + + {/* Sponsors Section */} + {analysis.sponsors && analysis.sponsors.length > 0 && ( +
+

+ Potansiyel Sponsor Markalar +

+
+ {analysis.sponsors.map((sponsor: any, idx: number) => ( +
+
+
+
+

{sponsor.brandName}

+

{sponsor.reasoning}

+
+
+
Entegrasyon Fikri
+

{sponsor.integrationIdea}

+
+
+
+
+ Türkçe E-Posta Taslağı +
+
+ {sponsor.emailDraft} +
+
+
+
+ ))} +
+
+ )} +
+ ) : ( +
+

Kriz ve Sponsor analizi bulunamadı.

+
+ )} +
+ )} + +
+
+ ) : ( +
+
+ +
+

Pre-Production Hazır Değil

+

+ Bölüm tasarımınızı başlatmak için analiz butonuna tıklayın. Yapay zeka, referans verilerinizi kullanarak 5 katmanlı bir üretim dosyası hazırlayacaktır. +

+ {episode.status !== 'ANALYZING' && ( + + )} +
+ )} + +
+ + {/* Full Screen Loading Overlay */} + + {isAnalyzing && ( + + +

Pre-Production Yapılıyor

+

+ Yeni bölümünüz için boşluk analizi, şeytanın avukatı argümanları, segment yapıları ve görsel DNA oluşturuluyor... +

+
+ )} +
+ + {/* Thumbnail Lightbox Overlay */} + + {isThumbnailExpanded && episode?.masterAnalysis?.thumbnailUrl && ( + setIsThumbnailExpanded(false)} + > + e.stopPropagation()} + /> + + )} + +
+ ); +} diff --git a/src/app/[locale]/(dashboard)/dashboard/tools/tube-strategist/[projectId]/page.tsx b/src/app/[locale]/(dashboard)/dashboard/tools/tube-strategist/[projectId]/page.tsx new file mode 100644 index 0000000..3b008d2 --- /dev/null +++ b/src/app/[locale]/(dashboard)/dashboard/tools/tube-strategist/[projectId]/page.tsx @@ -0,0 +1,725 @@ +"use client"; + +import React, { useState, useEffect } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import { + ArrowLeft, Zap, Video, Film, Loader2, PlayCircle, Eye, MessageCircle, + Settings, FileText, CheckCircle2, Sparkles, Target, AlignLeft, Users, Clock, User, FilePlus, X +} from 'lucide-react'; +import { + getProjectById, ProjectResponse, addVideoToProject, addDocumentToProject, + updateProject, createEpisode, getTopicSuggestions, TopicSuggestion, EpisodeResponse +} from '../services/strategistApi'; + +class ErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean, error: Error | null}> { + constructor(props: {children: React.ReactNode}) { super(props); this.state = { hasError: false, error: null }; } + static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error("ErrorBoundary caught an error", error, errorInfo); } + render() { + if (this.state.hasError) { + return ( +
+

Sayfa Yüklenirken Hata Oluştu (ErrorBoundary)

+
{this.state.error?.toString()}
+ +
+ ); + } + return this.props.children; + } +} + +export default function StrategistHubPage() { + const router = useRouter(); + const params = useParams(); + const projectId = params.projectId as string; + + const [project, setProject] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(""); + + // Dataset State + const [activeTab, setActiveTab] = useState<'videos' | 'documents'>('videos'); + + // Add Video State + const [videoUrl, setVideoUrl] = useState(""); + const [isAddingVideo, setIsAddingVideo] = useState(false); + const [videoError, setVideoError] = useState(""); + + // Add Document State + const [docTitle, setDocTitle] = useState(""); + const [docContent, setDocContent] = useState(""); + const [docType, setDocType] = useState<'transcript' | 'comments'>('transcript'); + const [isAddingDoc, setIsAddingDoc] = useState(false); + const [docError, setDocError] = useState(""); + + // Settings Modal State + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [settingsForm, setSettingsForm] = useState({ + name: "", tone: "", targetDuration: "", speakerName: "", targetAudience: "", formatDescription: "" + }); + const [isUpdatingSettings, setIsUpdatingSettings] = useState(false); + + // New Episode Modal State + const [isEpisodeModalOpen, setIsEpisodeModalOpen] = useState(false); + const [episodeForm, setEpisodeForm] = useState({ + topic: "", format: "", targetAudience: "", duration: "" + }); + const [isAiTopic, setIsAiTopic] = useState(false); + const [suggestions, setSuggestions] = useState([]); + const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false); + const [isCreatingEpisode, setIsCreatingEpisode] = useState(false); + const [episodeError, setEpisodeError] = useState(""); + + const fetchProject = async () => { + try { + setIsLoading(true); + const data = await getProjectById(projectId); + setProject(data); + setSettingsForm({ + name: data.name || "", + tone: data.tone || "", + targetDuration: data.targetDuration || "", + speakerName: data.speakerName || "", + targetAudience: data.targetAudience || "", + formatDescription: data.formatDescription || "" + }); + // Set initial values for episode modal if not set + setEpisodeForm(prev => ({ + ...prev, + targetAudience: data.targetAudience || "", + duration: data.targetDuration || "", + format: data.formatDescription ? data.formatDescription.substring(0, 50) + '...' : "" + })); + } catch (err: any) { + setError("Proje yüklenemedi."); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + if (projectId) { + fetchProject(); + } + }, [projectId]); + + const handleUpdateSettings = async (e: React.FormEvent) => { + e.preventDefault(); + setIsUpdatingSettings(true); + try { + await updateProject(projectId, settingsForm); + await fetchProject(); + setIsSettingsOpen(false); + } catch (err) { + console.error("Failed to update settings", err); + } finally { + setIsUpdatingSettings(false); + } + }; + + const handleAddVideo = async (e: React.FormEvent) => { + e.preventDefault(); + if (!videoUrl) return; + setIsAddingVideo(true); + setVideoError(""); + try { + await addVideoToProject(projectId, videoUrl); + setVideoUrl(""); + await fetchProject(); + } catch (err: any) { + setVideoError(err?.response?.data?.message || "Video eklenemedi."); + } finally { + setIsAddingVideo(false); + } + }; + + const handleAddDocument = async (e: React.FormEvent) => { + e.preventDefault(); + if (!docTitle || !docContent) return; + setIsAddingDoc(true); + setDocError(""); + try { + await addDocumentToProject(projectId, docTitle, docContent, docType); + setDocTitle(""); + setDocContent(""); + await fetchProject(); + } catch (err: any) { + setDocError(err?.response?.data?.message || "Doküman eklenemedi."); + } finally { + setIsAddingDoc(false); + } + }; + + const handleGetSuggestions = async () => { + setIsLoadingSuggestions(true); + try { + const res = await getTopicSuggestions(projectId); + setSuggestions(res.suggestions || []); + } catch (err) { + console.error("Failed to get suggestions", err); + } finally { + setIsLoadingSuggestions(false); + } + }; + + const handleCreateEpisode = async (e: React.FormEvent) => { + e.preventDefault(); + const finalTopic = isAiTopic ? "AI_AUTO" : episodeForm.topic; + if (!finalTopic && !isAiTopic) { + setEpisodeError("Lütfen bir konu başlığı girin veya yapay zeka önerisi seçin."); + return; + } + + setIsCreatingEpisode(true); + setEpisodeError(""); + try { + await createEpisode( + projectId, + finalTopic, + episodeForm.format, + episodeForm.targetAudience, + episodeForm.duration + ); + setIsEpisodeModalOpen(false); + setEpisodeForm(prev => ({ ...prev, topic: "" })); + setIsAiTopic(false); + setSuggestions([]); + await fetchProject(); // Refresh the list + } catch (err: any) { + setEpisodeError(err?.response?.data?.message || "Bölüm oluşturulamadı."); + } finally { + setIsCreatingEpisode(false); + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (!project) { + return ( +
+

Proje Bulunamadı

+ +
+ ); + } + + const youtubeVideos = project.videos?.filter(v => !v.videoId.startsWith('doc://')) || []; + const manualDocs = project.videos?.filter(v => v.videoId.startsWith('doc://')) || []; + + return ( + +
+ + {/* Header */} +
+
+ +
+
+

{project.name}

+
+ Hub Merkezi +
+
+

+ Formatınızı yönetin, verisetinizi genişletin ve yeni bölümler tasarlayın. +

+
+
+ + +
+ + {/* Format Info Card */} +
+
+
+

+ Format & Konsept +

+

+ {project.formatDescription || "Format açıklaması girilmemiş. Ayarlardan ekleyebilirsiniz."} +

+ +
+
+ {project.targetAudience || '-'} +
+
+ {project.tone || '-'} +
+
+ {project.targetDuration || '-'} +
+
+ {project.speakerName || '-'} +
+
+
+
+
+ +
+ + {/* Left Column: DATASET */} +
+
+
+

+

+
+ + {/* Tabs */} +
+ + +
+ + {activeTab === 'videos' && ( + <> +
+ setVideoUrl(e.target.value)} + className="flex-1 bg-transparent border-none px-3 text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-0" + required + /> + +
+ {videoError &&

{videoError}

} + + {youtubeVideos.length === 0 ? ( +
+ +

Video eklenmedi.

+
+ ) : ( +
+ {youtubeVideos.map((vid: any) => ( +
+ {vid.title} +
+

+ {vid.title} +

+
+ {Number(vid.viewCount || 0).toLocaleString('tr-TR')} + {Number(vid.totalComments || 0).toLocaleString('tr-TR')} +
+
+
+ ))} +
+ )} + + )} + + {activeTab === 'documents' && ( + <> +
+
+ setDocTitle(e.target.value)} + className="flex-1 bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg px-3 py-2 text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-1 focus:ring-red-500" + required + /> + +
+