Spaces:
Running
Running
| tsx | |
| import React, { useCallback } from 'react' | |
| import { useAppStore } from '@/store/useAppStore' | |
| import { CHARACTER_LIMITS } from '@/utils/constants' | |
| import { getCharacterWarnings } from '@/utils/validation' | |
| import { AlertTriangle, Info } from 'lucide-react' | |
| /** | |
| * Component for prompt input with character counting and validation | |
| */ | |
| export function PromptInput() { | |
| const { | |
| prompt, | |
| characterCount, | |
| setPrompt, | |
| validation | |
| } = useAppStore() | |
| const validationResult = validation() | |
| const characterWarnings = getCharacterWarnings(characterCount) | |
| /** | |
| * Handle input change with debouncing | |
| */ | |
| const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => { | |
| setPrompt(e.target.value) | |
| }, [setPrompt]) | |
| /** | |
| * Get character count color based on limits | |
| */ | |
| const getCharacterCountColor = () => { | |
| if (characterCount > CHARACTER_LIMITS.WARNING) { | |
| return 'text-yellow-600 dark:text-yellow-400' | |
| } | |
| if (characterCount > CHARACTER_LIMITS.INPUT * 0.9) { | |
| return 'text-red-600 dark:text-red-400' | |
| } | |
| return 'text-gray-600 dark:text-gray-400' | |
| } | |
| /** | |
| * Get character count background color | |
| */ | |
| const getCharacterCountBg = () => { | |
| if (characterCount > CHARACTER_LIMITS.WARNING) { | |
| return 'bg-yellow-100 dark:bg-yellow-900' | |
| } | |
| if (characterCount > CHARACTER_LIMITS.INPUT * 0.9) { | |
| return 'bg-red-100 dark:bg-red-900' | |
| } | |
| return 'bg-gray-100 dark:bg-gray-700' | |
| } | |
| return ( | |
| <div className="space-y-4"> | |
| {/* Character Counter */} | |
| <div className="flex items-center justify-between"> | |
| <div className={`px-3 py-1 rounded-full text-sm font-medium ${getCharacterCountBg()} ${getCharacterCountColor()}`}> | |
| {characterCount.toLocaleString()} / {CHARACTER_LIMITS.INPUT.toLocaleString()} Zeichen | |
| </div> | |
| {characterCount > CHARACTER_LIMITS.WARNING && ( | |
| <div className="flex items-center text-yellow-600 dark:text-yellow-400 text-sm"> | |
| <AlertTriangle className="w-4 h-4 mr-1" /> | |
| Langer Prompt | |
| </div> | |
| )} | |
| </div> | |
| {/* Main Textarea */} | |
| <div className="relative"> | |
| <textarea | |
| value={prompt} | |
| onChange={handleChange} | |
| placeholder="Gib hier deinen Prompt ein, den du optimieren möchtest..." | |
| className="input-field min-h-[200px] resize-none text-base leading-relaxed" | |
| maxLength={CHARACTER_LIMITS.INPUT} | |
| aria-label="Prompt Eingabefeld" | |
| aria-describedby="prompt-help prompt-warnings" | |
| /> | |
| {/* Character progress bar */} | |
| <div className="absolute bottom-0 left-0 right-0 h-1 bg-gray-200 dark:bg-gray-700 rounded-b-lg overflow-hidden"> | |
| <div | |
| className={`h-full transition-all duration-300 ${ | |
| characterCount > CHARACTER_LIMITS.INPUT ? 'bg-red-500' : | |
| characterCount > CHARACTER_LIMITS.WARNING ? 'bg-yellow-500' : | |
| 'bg-green-500' | |
| }`} | |
| style={{ width: `${Math.min((characterCount / CHARACTER_LIMITS.INPUT) * 100, 100)}%` }} | |
| /> | |
| </div> | |
| </div> | |
| {/* Helper Text */} | |
| <div id="prompt-help" className="text-sm text-gray-600 dark:text-gray-400"> | |
| <p className="mb-2"> | |
| <strong>Tipps für bessere Prompts:</strong> | |
| </p> | |
| <ul className="list-disc list-inside space-y-1 ml-2"> | |
| <li>Sei spezifisch über das gewünschte Ergebnis</li> | |
| <li>Füge Kontext und Hintergrundinformationen hinzu</li> | |
| <li>Definiere das gewünschte Ausgabeformat</li> | |
| <li>Verwende Beispiele, wo es hilfreich ist</li> | |
| </ul> | |
| </div> | |
| {/* Warnings */} | |
| {characterWarnings.length > 0 && ( | |
| <div id="prompt-warnings" className="space-y-2"> | |
| {characterWarnings.map((warning, index) => ( | |
| <div key={index} className="flex items-start space-x-2 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg"> | |
| <AlertTriangle className="w-5 h-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" /> | |
| <div> | |
| <p className="text-sm font-medium text-yellow-800 dark:text-yellow-200"> | |
| {warning.type === 'error' ? 'Fehler:' : 'Warnung:'} | |
| </p> | |
| <p className="text-sm text-yellow-700 dark:text-yellow-300"> | |
| {warning.message} | |
| </p> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {/* Validation Errors */} | |
| {validationResult.errors.length > 0 && ( | |
| <div className="space-y-2"> | |
| {validationResult.errors.map((error, index) => ( | |
| <div key={index} className="flex items-start space-x-2 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg"> | |
| <AlertTriangle className="w-5 h-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" /> | |
| <p className="text-sm text-red-700 dark:text-red-300">{error}</p> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {/* Suggestions */} | |
| {validationResult.suggestions.length > 0 && ( | |
| <div className="p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg"> | |
| <div className="flex items-start space-x-2"> | |
| <Info className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" /> | |
| <div> | |
| <p className="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2"> | |
| Verbesserungsvorschläge: | |
| </p> | |
| <ul className="text-sm text-blue-700 dark:text-blue-300 space-y-1"> | |
| {validationResult.suggestions.map((suggestion, index) => ( | |
| <li key={index} className="flex items-start"> | |
| <span className="text-blue-500 mr-2">•</span> | |
| <span>{suggestion}</span> | |
| </li> | |
| ))} | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Quick Actions */} | |
| <div className="flex flex-wrap gap-2"> | |
| <button | |
| type="button" | |
| onClick={() => setPrompt('Erstelle einen detaillierten Plan für ein umfassendes Marketing-Konzept für ein nachhaltiges E-Commerce-Unternehmen.')} | |
| className="text-xs px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors" | |
| > | |
| Marketing Beispiel | |
| </button> | |
| <button | |
| type="button" | |
| onClick={() => setPrompt('Entwickle eine React-Komponente für einen Dark-Mode Toggle mit Tailwind CSS.')} | |
| className="text-xs px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors" | |
| > | |
| Code Beispiel | |
| </button> | |
| <button | |
| type="button" | |
| onClick={() => setPrompt('Erstelle ein professionelles Porträt einer Frau im Alter von 30 Jahren in einem modernen Büro.')} | |
| className="text-xs px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors" | |
| > | |
| Bild Beispiel | |
| </button> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| </html> |