Spaces:
Sleeping
Sleeping
| /* | |
| * AIToolsDialog.tsx | |
| * Purpose: Shows the focused text-editing dialog for refine/change/expand/regenerate AI actions. | |
| * Used by: GoogleSlidesEditor when a text element is edited with AI. | |
| * Depends on: AI edit route and editor apply callbacks. | |
| */ | |
| import React, { useState } from 'react'; | |
| import { X, Sparkles, RefreshCw, FileText, Maximize2 } from 'lucide-react'; | |
| interface AIToolsDialogProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| selectedText: string; | |
| onApply: (newText: string) => void; | |
| onAIEdit: (action: 'refine' | 'change' | 'expand') => Promise<string>; | |
| } | |
| export default function AIToolsDialog({ | |
| isOpen, | |
| onClose, | |
| selectedText, | |
| onApply, | |
| onAIEdit | |
| }: AIToolsDialogProps) { | |
| const [currentText, setCurrentText] = useState(selectedText); | |
| const [isProcessing, setIsProcessing] = useState(false); | |
| const [selectedAction, setSelectedAction] = useState<'refine' | 'change' | 'expand' | null>(null); | |
| if (!isOpen) return null; | |
| const handleAction = async (action: 'refine' | 'change' | 'expand') => { | |
| setIsProcessing(true); | |
| setSelectedAction(action); | |
| try { | |
| const newText = await onAIEdit(action); | |
| setCurrentText(newText); | |
| } catch (error) { | |
| console.error('AI edit error:', error); | |
| } finally { | |
| setIsProcessing(false); | |
| setSelectedAction(null); | |
| } | |
| }; | |
| const handleApply = () => { | |
| onApply(currentText); | |
| onClose(); | |
| }; | |
| const handleCancel = () => { | |
| setCurrentText(selectedText); | |
| onClose(); | |
| }; | |
| return ( | |
| <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[1000]"> | |
| <div className="bg-white rounded-lg shadow-xl w-[600px] max-h-[80vh] flex flex-col"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between p-4 border-b"> | |
| <div className="flex items-center gap-2"> | |
| <Sparkles className="w-5 h-5 text-purple-600" /> | |
| <h2 className="text-lg font-semibold">AI Text Tools</h2> | |
| </div> | |
| <button | |
| onClick={handleCancel} | |
| className="p-1 hover:bg-gray-100 rounded transition-colors" | |
| > | |
| <X className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| {/* Content */} | |
| <div className="flex-1 overflow-auto p-4"> | |
| <div className="space-y-4"> | |
| {/* Action buttons */} | |
| <div className="grid grid-cols-3 gap-3"> | |
| <button | |
| onClick={() => handleAction('refine')} | |
| disabled={isProcessing} | |
| className={` | |
| p-4 rounded-lg border-2 transition-all | |
| ${isProcessing && selectedAction === 'refine' | |
| ? 'border-purple-500 bg-purple-50' | |
| : 'border-gray-200 hover:border-purple-400 hover:bg-purple-50'} | |
| ${isProcessing ? 'cursor-not-allowed opacity-75' : 'cursor-pointer'} | |
| `} | |
| > | |
| <div className="flex flex-col items-center gap-2"> | |
| {isProcessing && selectedAction === 'refine' ? ( | |
| <div className="animate-spin h-6 w-6 border-2 border-purple-600 border-t-transparent rounded-full" /> | |
| ) : ( | |
| <Sparkles className="w-6 h-6 text-purple-600" /> | |
| )} | |
| <div className="text-sm font-medium">Refine Text</div> | |
| <div className="text-xs text-gray-500 text-center">Make it more professional</div> | |
| </div> | |
| </button> | |
| <button | |
| onClick={() => handleAction('change')} | |
| disabled={isProcessing} | |
| className={` | |
| p-4 rounded-lg border-2 transition-all | |
| ${isProcessing && selectedAction === 'change' | |
| ? 'border-purple-500 bg-purple-50' | |
| : 'border-gray-200 hover:border-purple-400 hover:bg-purple-50'} | |
| ${isProcessing ? 'cursor-not-allowed opacity-75' : 'cursor-pointer'} | |
| `} | |
| > | |
| <div className="flex flex-col items-center gap-2"> | |
| {isProcessing && selectedAction === 'change' ? ( | |
| <div className="animate-spin h-6 w-6 border-2 border-purple-600 border-t-transparent rounded-full" /> | |
| ) : ( | |
| <RefreshCw className="w-6 h-6 text-purple-600" /> | |
| )} | |
| <div className="text-sm font-medium">Rewrite</div> | |
| <div className="text-xs text-gray-500 text-center">Different wording, same meaning</div> | |
| </div> | |
| </button> | |
| <button | |
| onClick={() => handleAction('expand')} | |
| disabled={isProcessing} | |
| className={` | |
| p-4 rounded-lg border-2 transition-all | |
| ${isProcessing && selectedAction === 'expand' | |
| ? 'border-purple-500 bg-purple-50' | |
| : 'border-gray-200 hover:border-purple-400 hover:bg-purple-50'} | |
| ${isProcessing ? 'cursor-not-allowed opacity-75' : 'cursor-pointer'} | |
| `} | |
| > | |
| <div className="flex flex-col items-center gap-2"> | |
| {isProcessing && selectedAction === 'expand' ? ( | |
| <div className="animate-spin h-6 w-6 border-2 border-purple-600 border-t-transparent rounded-full" /> | |
| ) : ( | |
| <Maximize2 className="w-6 h-6 text-purple-600" /> | |
| )} | |
| <div className="text-sm font-medium">Expand</div> | |
| <div className="text-xs text-gray-500 text-center">Add more detail</div> | |
| </div> | |
| </button> | |
| </div> | |
| {/* Text preview */} | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Preview | |
| </label> | |
| <div className="relative"> | |
| <textarea | |
| value={currentText} | |
| onChange={(e) => setCurrentText(e.target.value)} | |
| className="w-full h-40 p-3 border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-purple-500" | |
| placeholder="Your text will appear here..." | |
| /> | |
| {isProcessing && ( | |
| <div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center rounded-lg"> | |
| <div className="flex items-center gap-2"> | |
| <div className="animate-spin h-5 w-5 border-2 border-purple-600 border-t-transparent rounded-full" /> | |
| <span className="text-sm text-gray-600">AI is working...</span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| <div className="mt-2 flex items-center justify-between text-xs text-gray-500"> | |
| <span>{currentText.split(' ').length} words</span> | |
| <span>{currentText.length} characters</span> | |
| </div> | |
| </div> | |
| {/* Comparison */} | |
| {currentText !== selectedText && ( | |
| <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3"> | |
| <div className="flex items-start gap-2"> | |
| <FileText className="w-4 h-4 text-yellow-600 mt-0.5" /> | |
| <div className="flex-1"> | |
| <div className="text-sm font-medium text-yellow-800">Changes made</div> | |
| <div className="text-xs text-yellow-700 mt-1"> | |
| The text has been modified. Click "Apply Changes" to save or "Cancel" to revert. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Footer */} | |
| <div className="flex items-center justify-end gap-3 p-4 border-t"> | |
| <button | |
| onClick={handleCancel} | |
| className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors" | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| onClick={handleApply} | |
| disabled={isProcessing} | |
| className="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| Apply Changes | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } |