| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- import React, { useState, useEffect } from 'react';
- import { GenerationTemplate } from '@/api/entities';
- import { Button } from '@/components/ui/button';
- import { Input } from '@/components/ui/input';
- import { Textarea } from '@/components/ui/textarea';
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
- import { Label } from '@/components/ui/label';
- import { Alert, AlertDescription } from '@/components/ui/alert';
- import {
- HelpCircle,
- Save,
- PlusCircle,
- Loader2,
- Image
- } from 'lucide-react';
- import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
- } from "@/components/ui/tooltip";
- export default function ImagePromptManager() {
- const [templates, setTemplates] = useState([]);
- const [currentTemplate, setCurrentTemplate] = useState({
- id: null,
- name: '',
- image_prompt: '',
- style: 'fashion'
- });
- const [isLoading, setIsLoading] = useState(false);
- const [message, setMessage] = useState(null);
- useEffect(() => {
- loadTemplates();
- }, []);
- const loadTemplates = async () => {
- const data = await GenerationTemplate.list('-created_date');
- setTemplates(data);
- };
- const handleSelectTemplate = (templateId) => {
- const selected = templates.find(t => t.id === templateId);
- if (selected) {
- setCurrentTemplate(selected);
- }
- };
- const handleInputChange = (field, value) => {
- setCurrentTemplate(prev => ({ ...prev, [field]: value }));
- };
- const handleSaveTemplate = async () => {
- if (!currentTemplate.name || !currentTemplate.image_prompt) {
- setMessage({ type: 'error', text: '模板名称和生图提示词不能为空' });
- return;
- }
-
- setIsLoading(true);
- setMessage(null);
-
- try {
- if (currentTemplate.id) {
- await GenerationTemplate.update(currentTemplate.id, {
- name: currentTemplate.name,
- image_prompt: currentTemplate.image_prompt,
- style: currentTemplate.style
- });
- } else {
- await GenerationTemplate.create({
- name: currentTemplate.name,
- image_prompt: currentTemplate.image_prompt,
- style: currentTemplate.style,
- text_template: "", // 保持兼容性
- target_platform: "xiaohongshu"
- });
- }
-
- await loadTemplates();
- setMessage({ type: 'success', text: '生图模板保存成功!' });
- handleNewTemplate();
- } catch (error) {
- setMessage({ type: 'error', text: '保存失败,请重试' });
- }
-
- setIsLoading(false);
- };
- const handleNewTemplate = () => {
- setCurrentTemplate({ id: null, name: '', image_prompt: '', style: 'fashion' });
- };
- const styleOptions = [
- { value: 'fashion', label: '时尚穿搭' },
- { value: 'lifestyle', label: '生活方式' },
- { value: 'product', label: '产品展示' },
- { value: 'story', label: '故事场景' }
- ];
- return (
- <div className="space-y-6">
- {message && (
- <Alert className={message.type === 'error' ? 'border-red-200 bg-red-50 text-red-700' : 'border-green-200 bg-green-50 text-green-700'}>
- <AlertDescription>{message.text}</AlertDescription>
- </Alert>
- )}
-
- <div className="flex items-center justify-between">
- <h3 className="text-lg font-semibold text-slate-900 flex items-center gap-2">
- <Image className="w-5 h-5" />
- 生图提示词模板
- </h3>
- <div className="flex gap-2">
- <Button variant="outline" size="sm" onClick={handleNewTemplate} className="gap-1">
- <PlusCircle className="w-4 h-4" />
- 新建模板
- </Button>
- <Button
- size="sm"
- onClick={handleSaveTemplate}
- disabled={isLoading}
- className="gap-1 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
- >
- {isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
- 保存模板
- </Button>
- </div>
- </div>
- <div className="grid md:grid-cols-2 gap-6">
- <div>
- <Label className="flex items-center mb-2 font-medium text-slate-700">
- 选择模板
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <HelpCircle className="w-3.5 h-3.5 ml-1.5 text-slate-400" />
- </TooltipTrigger>
- <TooltipContent><p>选择一个现有模板进行编辑,或新建一个模板</p></TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </Label>
- <Select onValueChange={handleSelectTemplate} value={currentTemplate.id || ''}>
- <SelectTrigger>
- <SelectValue placeholder="选择或新建一个模板..." />
- </SelectTrigger>
- <SelectContent>
- {templates.map(t => (
- <SelectItem key={t.id} value={t.id}>{t.name}</SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- <div>
- <Label htmlFor="template-name" className="font-medium text-slate-700">模板名称</Label>
- <Input
- id="template-name"
- value={currentTemplate.name}
- onChange={e => handleInputChange('name', e.target.value)}
- placeholder="为你的模板起个名字"
- className="mt-2"
- />
- </div>
- </div>
- <div>
- <Label htmlFor="style" className="font-medium text-slate-700">风格类型</Label>
- <Select value={currentTemplate.style} onValueChange={value => handleInputChange('style', value)}>
- <SelectTrigger className="mt-2">
- <SelectValue placeholder="选择风格类型" />
- </SelectTrigger>
- <SelectContent>
- {styleOptions.map(option => (
- <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div>
- <Label htmlFor="image-prompt" className="flex items-center mb-2 font-medium text-slate-700">
- 生图提示词
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <HelpCircle className="w-3.5 h-3.5 ml-1.5 text-slate-400" />
- </TooltipTrigger>
- <TooltipContent><p>用于生成图片的详细描述,包含场景、人物、服装、风格等要素</p></TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </Label>
- <Textarea
- id="image-prompt"
- value={currentTemplate.image_prompt}
- onChange={e => handleInputChange('image_prompt', e.target.value)}
- placeholder="例如:一个亚洲女孩,身穿飘逸的白色连衣裙,赤脚走在黄昏时分的沙滩上,海浪轻轻拍打着她的脚踝。背景是橘色和紫色的晚霞,光线柔和,氛围浪漫..."
- className="h-32"
- />
- </div>
- </div>
- );
- }
|