Files
ai-app-skg/components/Tools.tsx
2025-12-23 16:23:56 +08:00

147 lines
5.6 KiB
TypeScript

import React, { useState } from 'react';
import { TRANSLATIONS } from '../constants';
import { AppLanguage } from '../types';
import { generateImage, generateVideo } from '../services/geminiService';
import { Loader2, Image as ImageIcon, Video, Download } from 'lucide-react';
interface ToolsProps {
language: AppLanguage;
}
const Tools: React.FC<ToolsProps> = ({ language }) => {
const t = TRANSLATIONS[language];
const [activeTab, setActiveTab] = useState<'image' | 'video'>('image');
const [prompt, setPrompt] = useState('');
const [loading, setLoading] = useState(false);
const [resultUrl, setResultUrl] = useState<string | null>(null);
const [imageSize, setImageSize] = useState<"1K" | "2K" | "4K">("1K");
const [videoRatio, setVideoRatio] = useState<"16:9" | "9:16">("16:9");
const [error, setError] = useState<string | null>(null);
const handleGenerate = async () => {
if (!prompt.trim()) return;
setLoading(true);
setError(null);
setResultUrl(null);
try {
if (activeTab === 'image') {
const images = await generateImage(prompt, imageSize);
if (images.length > 0) setResultUrl(images[0]);
} else {
const video = await generateVideo(prompt, videoRatio);
setResultUrl(video);
}
} catch (e: any) {
setError(e.message || "Generation failed");
} finally {
setLoading(false);
}
};
return (
<div className="max-w-4xl mx-auto p-4 space-y-6">
<div className="flex space-x-2 bg-slate-200 p-1 rounded-lg w-fit">
<button
onClick={() => setActiveTab('image')}
className={`flex items-center space-x-2 px-4 py-2 rounded-md transition ${activeTab === 'image' ? 'bg-white shadow text-blue-600' : 'text-slate-600'}`}
>
<ImageIcon size={18} />
<span>{t.imageGen}</span>
</button>
<button
onClick={() => setActiveTab('video')}
className={`flex items-center space-x-2 px-4 py-2 rounded-md transition ${activeTab === 'video' ? 'bg-white shadow text-purple-600' : 'text-slate-600'}`}
>
<Video size={18} />
<span>{t.videoGen}</span>
</button>
</div>
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 p-6">
<textarea
className="w-full p-4 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:outline-none resize-none"
rows={4}
placeholder={activeTab === 'image' ? t.imagePromptPlaceholder : t.videoPromptPlaceholder}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
<div className="mt-4 flex flex-wrap gap-4 items-center justify-between">
<div className="flex gap-4">
{activeTab === 'image' ? (
<div className="flex items-center space-x-2">
<span className="text-sm text-slate-500">{t.imageSize}:</span>
<select
value={imageSize}
onChange={(e) => setImageSize(e.target.value as any)}
className="p-2 bg-slate-50 border border-slate-200 rounded-lg text-sm"
>
<option value="1K">1K</option>
<option value="2K">2K</option>
<option value="4K">4K</option>
</select>
</div>
) : (
<div className="flex items-center space-x-2">
<span className="text-sm text-slate-500">{t.aspectRatio}:</span>
<select
value={videoRatio}
onChange={(e) => setVideoRatio(e.target.value as any)}
className="p-2 bg-slate-50 border border-slate-200 rounded-lg text-sm"
>
<option value="16:9">{t.landscape}</option>
<option value="9:16">{t.portrait}</option>
</select>
</div>
)}
</div>
<button
onClick={handleGenerate}
disabled={loading || !prompt.trim()}
className="px-6 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700 disabled:opacity-50 flex items-center space-x-2"
>
{loading ? <Loader2 className="animate-spin" size={18} /> : null}
<span>{loading ? t.generating : t.generate}</span>
</button>
</div>
{error && (
<div className="mt-4 p-3 bg-red-50 text-red-600 text-sm rounded-lg">
{error}
</div>
)}
{loading && activeTab === 'video' && (
<div className="mt-4 text-center text-sm text-slate-500 animate-pulse">
{t.videoDuration}
</div>
)}
{resultUrl && (
<div className="mt-8 border-t pt-6">
<div className="relative rounded-xl overflow-hidden bg-black flex justify-center items-center">
{activeTab === 'image' ? (
<img src={resultUrl} alt="Generated" className="max-h-[500px] w-auto object-contain" />
) : (
<video src={resultUrl} controls autoPlay loop className="max-h-[500px] w-auto" />
)}
<a
href={resultUrl}
download={`generated-${activeTab}-${Date.now()}.${activeTab === 'image' ? 'png' : 'mp4'}`}
className="absolute top-4 right-4 bg-white/90 p-2 rounded-full shadow hover:bg-white text-slate-800"
title={t.download}
>
<Download size={20} />
</a>
</div>
</div>
)}
</div>
</div>
);
};
export default Tools;