File size: 5,610 Bytes
b06d05b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import React from 'react';
import type { MediaFile } from '../types';
import { GenerationStatus } from '../types';
import { SparklesIcon, LoaderIcon, WandIcon } from './Icons';

interface MediaItemProps {
  item: MediaFile;
  autofit: boolean;
  isApiKeySet: boolean;
  onGenerate: (id: string, customInstructions?: string) => void;
  onCaptionChange: (id:string, caption: string) => void;
  onCustomInstructionsChange: (id: string, instructions: string) => void;
  onSelectionChange: (id: string, isSelected: boolean) => void;
}

const getScoreColor = (score?: number) => {
    if (score === undefined) return 'text-gray-500';
    if (score >= 4) return 'text-green-400';
    if (score >= 3) return 'text-yellow-400';
    return 'text-red-400';
};


const MediaItem: React.FC<MediaItemProps> = ({ 
    item, 
    autofit,
    isApiKeySet,
    onGenerate, 
    onCaptionChange,
    onCustomInstructionsChange,
    onSelectionChange 
}) => {
  const isVideo = item.file.type.startsWith('video/');
  const textareaRef = React.useRef<HTMLTextAreaElement>(null);

  React.useEffect(() => {
    if (textareaRef.current && autofit) {
        textareaRef.current.style.height = 'auto'; // Reset height
        textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
    } else if (textareaRef.current) {
        textareaRef.current.style.height = ''; // Revert to CSS-defined height
    }
  }, [item.caption, autofit]);

  const getStatusColor = () => {
    switch(item.status) {
      case GenerationStatus.SUCCESS: return 'border-green-500';
      case GenerationStatus.ERROR: return 'border-red-500';
      case GenerationStatus.GENERATING: return 'border-indigo-500';
      case GenerationStatus.CHECKING: return 'border-yellow-500';
      default: return 'border-gray-700';
    }
  };
  
  const isProcessing = item.status === GenerationStatus.GENERATING || item.status === GenerationStatus.CHECKING;


  return (
    <div className={`bg-gray-800 rounded-lg overflow-hidden border-2 transition-colors ${getStatusColor()}`}>
      <div className="relative p-2">
         <input
          type="checkbox"
          checked={item.isSelected}
          onChange={(e) => onSelectionChange(item.id, e.target.checked)}
          className="absolute top-4 left-4 h-6 w-6 bg-gray-900 border-gray-600 text-indigo-500 rounded focus:ring-indigo-600 z-10"
        />
        {item.qualityScore !== undefined && (
            <div className="absolute top-4 right-4 bg-gray-900/70 backdrop-blur-sm px-3 py-1 rounded-full text-sm font-semibold flex items-center gap-1.5 z-10">
                <span className={`tracking-widest ${getScoreColor(item.qualityScore)}`}>
                    {'★'.repeat(item.qualityScore)}{'☆'.repeat(5 - item.qualityScore)}
                </span>
                <span className="text-gray-300">{item.qualityScore}/5</span>
            </div>
        )}
        {isVideo ? (
          <video src={item.previewUrl} controls className="w-full h-64 object-contain rounded-md bg-gray-900"></video>
        ) : (
          <img src={item.previewUrl} alt={item.file.name} className="w-full h-64 object-contain rounded-md" />
        )}
      </div>
      <div className="p-4 space-y-4">
        <p className="text-sm text-gray-400 truncate" title={item.file.name}>{item.file.name}</p>
        
        <textarea
            ref={textareaRef}
            value={item.caption}
            onChange={(e) => onCaptionChange(item.id, e.target.value)}
            placeholder="Generated caption will appear here..."
            rows={!autofit ? 6 : 1}
            className={`w-full p-2 bg-gray-900 border border-gray-700 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors resize-none overflow-hidden ${!autofit ? 'h-32' : ''}`}
        />

        <div className="flex flex-col sm:flex-row gap-2">
            <input 
                type="text"
                placeholder="Custom instructions for refinement..."
                value={item.customInstructions}
                onChange={(e) => onCustomInstructionsChange(item.id, e.target.value)}
                className="flex-grow p-2 bg-gray-700 border border-gray-600 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
            />
             <button
                onClick={() => onGenerate(item.id, item.customInstructions)}
                disabled={isProcessing || !isApiKeySet}
                className="flex items-center justify-center px-3 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:bg-gray-500 disabled:cursor-not-allowed transition-colors"
                title={!isApiKeySet ? "Please select an API key in Global Settings" : (item.customInstructions ? "Refine with instructions" : "Generate caption")}
            >
                {isProcessing ? (
                    <LoaderIcon className="w-5 h-5 animate-spin" />
                ) : (
                    item.customInstructions ? <WandIcon className="w-5 h-5 mr-2" /> : <SparklesIcon className="w-5 h-5 mr-2" />
                )}
                <span>
                    {item.status === GenerationStatus.GENERATING ? 'Generating...' :
                     item.status === GenerationStatus.CHECKING ? 'Checking...' :
                     item.customInstructions ? 'Refine' : 'Generate'}
                </span>
            </button>
        </div>
        
        {item.status === GenerationStatus.ERROR && (
          <p className="text-sm text-red-400 mt-2">{item.errorMessage}</p>
        )}
      </div>
    </div>
  );
};

export default MediaItem;