优化界面

This commit is contained in:
yuantao
2025-09-16 18:38:02 +08:00
parent e0600f5d50
commit 2345ed80f1
15 changed files with 725 additions and 266 deletions

View File

@@ -3,7 +3,7 @@ import { Textarea } from './ui/Textarea';
import { Button } from './ui/Button';
import { useAppStore } from '../store/useAppStore';
import { useImageGeneration, useImageEditing } from '../hooks/useImageGeneration';
import { Upload, Wand2, Edit3, MousePointer, HelpCircle, Menu, ChevronDown, ChevronRight, RotateCcw } from 'lucide-react';
import { Upload, Wand2, Edit3, MousePointer, HelpCircle, ChevronDown, ChevronRight, RotateCcw } from 'lucide-react';
import { blobToBase64 } from '../utils/imageUtils';
import { PromptHints } from './PromptHints';
import { cn } from '../utils/cn';
@@ -54,7 +54,7 @@ export const PromptComposer: React.FC = () => {
prompt: currentPrompt,
referenceImages: referenceImages.length > 0 ? referenceImages : undefined,
temperature,
seed: seed || undefined
seed: seed !== null ? seed : undefined
});
} else if (selectedTool === 'edit' || selectedTool === 'mask') {
edit(currentPrompt);
@@ -141,7 +141,7 @@ export const PromptComposer: React.FC = () => {
<div className="w-8 bg-white flex flex-col items-center justify-center rounded-l-xl">
<button
onClick={() => setShowPromptPanel(true)}
className="w-6 h-16 bg-gray-100 hover:bg-gray-200 rounded-r-lg flex items-center justify-center transition-colors group"
className="w-6 h-16 bg-gray-100 hover:bg-gray-200 rounded-r-lg flex items-center justify-center transition-all duration-300 ease-in-out group"
title="显示提示面板"
>
<div className="flex flex-col space-y-1">
@@ -156,24 +156,24 @@ export const PromptComposer: React.FC = () => {
return (
<>
<div className="w-72 h-full bg-white p-4 flex flex-col space-y-5 overflow-y-auto">
<div className="w-72 h-full bg-white p-5 flex flex-col overflow-y-auto space-y-5">
<div>
<div className="flex items-center justify-between mb-2">
<h3 className="text-xs font-medium text-gray-500 uppercase tracking-wide"></h3>
<div className="flex items-center space-x-0.5">
<div className="flex items-center justify-between mb-3">
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide"></h3>
<div className="flex items-center space-x-1">
<Button
variant="ghost"
size="icon"
onClick={() => setShowHintsModal(true)}
className="h-6 w-6 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full card"
className="h-7 w-7 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full card"
>
<HelpCircle className="h-3.5 w-3.5" />
<HelpCircle className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => setShowPromptPanel(false)}
className="h-6 w-6 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full card"
className="h-7 w-7 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full card"
title="隐藏面板"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -183,19 +183,19 @@ export const PromptComposer: React.FC = () => {
</Button>
</div>
</div>
<div className="grid grid-cols-3 gap-1.5">
<div className="grid grid-cols-3 gap-2.5">
{tools.map((tool) => (
<button
key={tool.id}
onClick={() => setSelectedTool(tool.id)}
className={cn(
'flex flex-col items-center p-2 rounded-lg border transition-all duration-200',
'flex flex-col items-center p-3 rounded-xl border transition-all duration-200 hover:scale-105',
selectedTool === tool.id
? 'bg-yellow-50 border-yellow-300 text-yellow-700 shadow-sm'
: 'bg-gray-50 border-gray-200 text-gray-500 hover:bg-yellow-50 hover:border-yellow-300 hover:text-yellow-700'
)}
>
<tool.icon className="h-4 w-4 mb-1" />
<tool.icon className="h-5 w-5 mb-1.5" />
<span className="text-xs font-medium">{tool.label}</span>
</button>
))}
@@ -203,29 +203,29 @@ export const PromptComposer: React.FC = () => {
</div>
{/* 文件上传 */}
<div>
<div className="space-y-3">
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={cn(
"border border-dashed rounded-md p-4 text-center transition-colors",
"border-2 border-dashed rounded-xl p-5 text-center transition-colors",
isDragOver
? "border-yellow-400 bg-yellow-400/10"
: "border-gray-300 hover:border-yellow-400"
)}
>
<label className="text-sm font-medium text-gray-700 mb-1 block">
<label className="text-sm font-semibold text-gray-700 mb-2 block">
{selectedTool === 'generate' ? '参考图像' : selectedTool === 'edit' ? '样式参考' : '上传图像'}
</label>
{selectedTool === 'mask' && (
<p className="text-xs text-gray-500 mb-2">使</p>
<p className="text-xs text-gray-500 mb-3">使</p>
)}
{selectedTool === 'generate' && (
<p className="text-xs text-gray-500 mb-2">2</p>
<p className="text-xs text-gray-500 mb-3">2</p>
)}
{selectedTool === 'edit' && (
<p className="text-xs text-gray-500 mb-2">
<p className="text-xs text-gray-500 mb-3">
{canvasImage ? '可选样式参考最多2张图像' : '上传要编辑的图像最多2张图像'}
</p>
)}
@@ -238,8 +238,8 @@ export const PromptComposer: React.FC = () => {
className="hidden"
/>
<div className="flex flex-col items-center justify-center space-y-2">
<Upload className={cn("h-6 w-6", isDragOver ? "text-yellow-500" : "text-gray-400")} />
<div className="flex flex-col items-center justify-center space-y-3">
<Upload className={cn("h-7 w-7", isDragOver ? "text-yellow-500" : "text-gray-400")} />
<div>
<p className={cn(
"text-sm font-medium",
@@ -261,7 +261,7 @@ export const PromptComposer: React.FC = () => {
(selectedTool === 'edit' && editReferenceImages.length >= 2)
}
>
<Upload className="h-3 w-3 mr-1" />
<Upload className="h-3.5 w-3.5 mr-1.5" />
</Button>
</div>
@@ -270,21 +270,24 @@ export const PromptComposer: React.FC = () => {
{/* Show uploaded images preview */}
{((selectedTool === 'generate' && uploadedImages.length > 0) ||
(selectedTool === 'edit' && editReferenceImages.length > 0)) && (
<div className="mt-2 space-y-2">
<div className="space-y-2.5">
{(selectedTool === 'generate' ? uploadedImages : editReferenceImages).map((image, index) => (
<div key={index} className="relative">
<img
src={image}
alt={`参考图像 ${index + 1}`}
className="w-full h-16 object-cover rounded border border-gray-300"
className="w-full h-20 object-cover rounded-lg border-2 border-gray-200"
/>
<button
onClick={() => selectedTool === 'generate' ? removeUploadedImage(index) : removeEditReferenceImage(index)}
className="absolute top-1 right-1 bg-gray-100/80 text-gray-600 hover:text-gray-800 rounded-full p-1 transition-colors"
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1.5 transition-colors shadow-md hover:bg-red-600"
>
×
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div className="absolute bottom-1 left-1 bg-gray-100/80 text-xs px-1 py-0.5 rounded text-gray-700">
<div className="absolute bottom-2 left-2 bg-black/60 text-white text-xs px-2 py-1 rounded-lg">
{index + 1}
</div>
</div>
@@ -294,8 +297,8 @@ export const PromptComposer: React.FC = () => {
</div>
{/* 提示输入 */}
<div>
<label className="text-xs font-medium text-gray-500 mb-2 block uppercase tracking-wide">
<div className="flex-grow space-y-3">
<label className="text-xs font-semibold text-gray-500 block uppercase tracking-wide">
{selectedTool === 'generate' ? '提示词' : '编辑指令'}
</label>
<Textarea
@@ -306,19 +309,19 @@ export const PromptComposer: React.FC = () => {
? '描述您想要创建的内容...'
: '描述您想要的修改...'
}
className="min-h-[100px] resize-none text-sm"
className="min-h-[120px] resize-none text-sm rounded-xl"
/>
{/* 提示质量指示器 */}
<button
onClick={() => setShowHintsModal(true)}
className="mt-2 flex items-center text-xs hover:text-gray-700 transition-colors group"
className="flex items-center text-xs hover:text-gray-700 transition-colors group"
>
{currentPrompt.length < 20 ? (
<HelpCircle className="h-3 w-3 mr-2 text-red-400 group-hover:text-red-500" />
<HelpCircle className="h-4 w-4 mr-2 text-red-400 group-hover:text-red-500" />
) : (
<div className={cn(
'h-2 w-2 rounded-full mr-2',
'h-2.5 w-2.5 rounded-full mr-2',
currentPrompt.length < 50 ? 'bg-yellow-400' : 'bg-green-400'
)} />
)}
@@ -331,44 +334,46 @@ export const PromptComposer: React.FC = () => {
{/* 生成按钮 */}
{isGenerating ? (
<div className="flex gap-2">
<div className="flex-shrink-0">
{isGenerating ? (
<div className="flex gap-3">
<Button
onClick={() => selectedTool === 'generate' ? cancelGeneration() : cancelEdit()}
className="flex-1 h-14 text-base font-semibold bg-red-500 hover:bg-red-600 rounded-xl card"
>
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2" />
</Button>
</div>
) : (
<Button
onClick={() => selectedTool === 'generate' ? cancelGeneration() : cancelEdit()}
className="flex-1 h-12 text-sm font-medium bg-red-500 hover:bg-red-600 rounded-lg card"
onClick={handleGenerate}
disabled={!currentPrompt.trim()}
className="w-full h-14 text-base font-semibold rounded-xl shadow-md hover:shadow-lg transition-all card"
>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
<Wand2 className="h-5 w-5 mr-2" />
{selectedTool === 'generate' ? '生成图像' : '应用编辑'}
</Button>
</div>
) : (
<Button
onClick={handleGenerate}
disabled={!currentPrompt.trim()}
className="w-full h-12 text-sm font-medium rounded-lg shadow-sm hover:shadow-md transition-shadow card"
>
<Wand2 className="h-4 w-4 mr-2" />
{selectedTool === 'generate' ? '生成图像' : '应用编辑'}
</Button>
)}
)}
</div>
{/* 高级控制 */}
<div className="pt-2 border-t border-gray-100">
<div className="pt-3 border-t border-gray-100 flex-shrink-0">
<button
onClick={() => setShowAdvanced(!showAdvanced)}
className="flex items-center text-xs text-gray-500 hover:text-gray-700 transition-colors duration-200"
className="flex items-center text-sm text-gray-500 hover:text-gray-700 transition-colors duration-200"
>
{showAdvanced ? <ChevronDown className="h-3 w-3 mr-1" /> : <ChevronRight className="h-3 w-3 mr-1" />}
{showAdvanced ? <ChevronDown className="h-4 w-4 mr-1.5" /> : <ChevronRight className="h-4 w-4 mr-1.5" />}
</button>
{showAdvanced && (
<div className="mt-3 space-y-3">
<div className="mt-4 space-y-4 animate-in slide-down duration-300">
{/* 创造力 */}
<div>
<label className="text-xs text-gray-500 mb-1.5 block flex justify-between">
<span></span>
<span className="font-mono">{temperature}</span>
<div className="space-y-2">
<label className="text-sm text-gray-500 block flex justify-between">
<span className="font-medium"></span>
<span className="font-mono bg-gray-100 px-2 py-0.5 rounded">{temperature}</span>
</label>
<input
type="range"
@@ -377,13 +382,13 @@ export const PromptComposer: React.FC = () => {
step="0.1"
value={temperature}
onChange={(e) => setTemperature(parseFloat(e.target.value))}
className="w-full h-1.5 bg-gray-200 rounded-full appearance-none cursor-pointer slider"
className="w-full h-2 bg-gray-200 rounded-full appearance-none cursor-pointer slider"
/>
</div>
{/* 种子 */}
<div>
<label className="text-xs text-gray-500 mb-1.5 block">
<div className="space-y-2">
<label className="text-sm text-gray-500 block font-medium">
()
</label>
<input
@@ -391,7 +396,7 @@ export const PromptComposer: React.FC = () => {
value={seed || ''}
onChange={(e) => setSeed(e.target.value ? parseInt(e.target.value) : null)}
placeholder="随机"
className="w-full h-8 px-2.5 bg-gray-50 border border-gray-200 rounded-md text-xs text-gray-700 focus:outline-none focus:ring-1 focus:ring-yellow-400"
className="w-full h-10 px-3 bg-gray-50 border border-gray-200 rounded-lg text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-yellow-400 focus:border-transparent"
/>
</div>
</div>
@@ -399,31 +404,29 @@ export const PromptComposer: React.FC = () => {
<button
onClick={() => setShowClearConfirm(!showClearConfirm)}
className="flex items-center text-xs text-gray-500 hover:text-red-500 transition-colors duration-200 mt-3"
className="flex items-center text-sm text-gray-500 hover:text-red-500 transition-colors duration-200 mt-4"
>
<RotateCcw className="h-3 w-3 mr-1.5" />
<RotateCcw className="h-4 w-4 mr-2" />
</button>
{showClearConfirm && (
<div className="mt-2 p-3 bg-red-50 rounded-lg border border-red-100">
<p className="text-xs text-red-700 mb-3">
<div className="mt-3 p-4 bg-red-50 rounded-xl border border-red-100 animate-in slide-down duration-300">
<p className="text-sm text-red-700 mb-4">
</p>
<div className="flex space-x-2">
<div className="flex space-x-3">
<Button
variant="destructive"
size="sm"
onClick={handleClearSession}
className="flex-1 h-8 text-xs card"
className="flex-1 h-10 text-sm font-semibold card text-gray-700"
>
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setShowClearConfirm(false)}
className="flex-1 h-8 text-xs border-gray-200 card"
className="flex-1 h-10 text-sm font-semibold border-gray-300 text-gray-700 hover:bg-gray-100 card"
>
</Button>
@@ -432,35 +435,11 @@ export const PromptComposer: React.FC = () => {
)}
</div>
{/* 键盘快捷键 */}
<div className="pt-3 border-t border-gray-100">
<h4 className="text-xs font-medium text-gray-500 mb-2 uppercase tracking-wide"></h4>
<div className="space-y-1.5 text-xs text-gray-600">
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-mono bg-gray-100 px-1.5 py-0.5 rounded text-gray-700"> + Enter</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-mono bg-gray-100 px-1.5 py-0.5 rounded text-gray-700"> + R</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-mono bg-gray-100 px-1.5 py-0.5 rounded text-gray-700">E</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-mono bg-gray-100 px-1.5 py-0.5 rounded text-gray-700">H</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-mono bg-gray-100 px-1.5 py-0.5 rounded text-gray-700">P</span>
</div>
</div>
</div>
</div>
{/* 提示提示模态框 */}
<PromptHints open={showHintsModal} onOpenChange={setShowHintsModal} />
</>
);
};
};
export default PromptComposer;