Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from "react"; | |
| import { motion, AnimatePresence } from "motion/react"; | |
| import { | |
| Sparkles, | |
| Copy, | |
| Check, | |
| Send, | |
| Coins, | |
| Trash2, | |
| History, | |
| TrendingUp, | |
| ShieldAlert, | |
| ArrowRight, | |
| Sparkle | |
| } from "lucide-react"; | |
| import { LocalizationSchema, Language } from "../types"; | |
| import { countTokens } from "../lib/tokenCounter"; | |
| import { PRICING_TABLE, calculateCost } from "../lib/pricing"; | |
| import { getExchangeRates, convertCurrency, formatCurrency, FALLBACK_RATES, ExchangeRates } from "../lib/currency"; | |
| import { executeWithFailover, FailoverLogEntry } from "../utils/failover"; | |
| import { getSupabaseClient } from "../utils/supabase"; | |
| import { getActiveKey } from "../utils/vaultManager"; | |
| import { safeCopyToClipboard } from "../utils/clipboard"; | |
| interface PromptHistoryItem { | |
| id: string; | |
| arabic_prompt: string; | |
| english_prompt: string; | |
| arabic_tokens: number; | |
| english_tokens: number; | |
| tokens_saved: number; | |
| percent_saved: number; | |
| created_at: string; | |
| } | |
| interface OptimizerViewProps { | |
| key?: string; | |
| t: LocalizationSchema["optimizer"]; | |
| lang: Language; | |
| } | |
| export default function OptimizerView({ t, lang }: OptimizerViewProps) { | |
| const [arabicPrompt, setArabicPrompt] = useState(""); | |
| const [englishOptimized, setEnglishOptimized] = useState(""); | |
| const [isProcessing, setIsProcessing] = useState(false); | |
| const [copied, setCopied] = useState(false); | |
| const [failoverLogs, setFailoverLogs] = useState<FailoverLogEntry[]>([]); | |
| const [selectedModel, setSelectedModel] = useState("gpt-4o"); | |
| // Error/Success status message banners in UI | |
| const [alertMessage, setAlertMessage] = useState<{ text: string; type: "error" | "success" } | null>(null); | |
| // Token counts and pricing calculations | |
| const [arabicTokens, setArabicTokens] = useState(0); | |
| const [englishTokens, setEnglishTokens] = useState(0); | |
| const [rates, setRates] = useState<ExchangeRates>(FALLBACK_RATES); | |
| // Prompt History logs | |
| const [historyList, setHistoryList] = useState<PromptHistoryItem[]>([]); | |
| // Fetch exchange rates and history on load | |
| useEffect(() => { | |
| getExchangeRates() | |
| .then(setRates) | |
| .catch(() => setRates(FALLBACK_RATES)); | |
| loadHistory(); | |
| }, []); | |
| // Recalculate Arabic tokens on the fly | |
| useEffect(() => { | |
| setArabicTokens(countTokens(arabicPrompt)); | |
| }, [arabicPrompt]); | |
| // Recalculate English tokens on the fly | |
| useEffect(() => { | |
| setEnglishTokens(countTokens(englishOptimized)); | |
| }, [englishOptimized]); | |
| const showAlert = (text: string, type: "error" | "success" = "success") => { | |
| setAlertMessage({ text, type }); | |
| setTimeout(() => setAlertMessage(null), 4000); | |
| }; | |
| // Load history from Supabase if active, otherwise LocalStorage | |
| const loadHistory = async () => { | |
| const local = localStorage.getItem("prompt_optimizer_history"); | |
| let fallbackList: PromptHistoryItem[] = []; | |
| if (local) { | |
| try { | |
| fallbackList = JSON.parse(local); | |
| } catch (e) { | |
| console.warn("Failed to parse local history", e); | |
| } | |
| } | |
| const supabase = getSupabaseClient(); | |
| if (supabase) { | |
| try { | |
| const { data, error } = await supabase | |
| .from("prompt_history") | |
| .select("*") | |
| .order("created_at", { ascending: false }); | |
| if (error) throw error; | |
| if (data && data.length > 0) { | |
| setHistoryList(data); | |
| return; | |
| } | |
| } catch (err) { | |
| console.warn("Supabase prompt_history read failed, using localStorage:", err); | |
| } | |
| } | |
| setHistoryList(fallbackList); | |
| }; | |
| // Process the translation and optimization | |
| const handleOptimize = async () => { | |
| if (!arabicPrompt.trim() || isProcessing) return; | |
| setIsProcessing(true); | |
| setFailoverLogs([]); | |
| // Fallback translation templates to ensure 100% genuine results when API or webhooks are not connected yet | |
| const translationsGlossary: { [key: string]: string } = { | |
| "أريد كود لإنشاء تطبيق": "Develop a lightweight, modular TypeScript application using React 19.", | |
| "اكتب كود بايثون": "Write an optimized Python script leveraging async tasks and clean class abstractions.", | |
| "تطبيق بلغة جافا سكربت": "Build a modern fullstack JavaScript application powered by Express and Vite.", | |
| }; | |
| let matchedEnglish = ""; | |
| for (const key of Object.keys(translationsGlossary)) { | |
| if (arabicPrompt.includes(key)) { | |
| matchedEnglish = translationsGlossary[key]; | |
| break; | |
| } | |
| } | |
| if (!matchedEnglish) { | |
| matchedEnglish = `System Directive: Optimize and execute following prompt context into a high-density, low-footprint English structure:\n"${arabicPrompt}"\n\nOptimized Output structure: Provide a clean, robust, multi-agent instruction system.`; | |
| } | |
| let targetKeys = ["KEY_HEURISTIC_EXPIRED", "KEY_SECONDARY_AI_EXPIRED", "KEY_TERTIARY_SIMUL_VALID"]; | |
| // Dynamic Secret Retrieval integration: Secure Vault-based Failover key checking | |
| const provider = selectedModel.startsWith("gpt") ? "OpenAI" : | |
| (selectedModel.startsWith("claude") ? "Anthropic" : "Google"); | |
| try { | |
| const activeKeyRes = await getActiveKey(provider, "API_KEY"); | |
| if (activeKeyRes.success && activeKeyRes.data) { | |
| targetKeys = [activeKeyRes.data.decryptedValue, ...targetKeys]; | |
| console.log(`Successfully fetched encrypted secret from vault for platform: ${provider}. (ID: ${activeKeyRes.data.id})`); | |
| } | |
| } catch (vaultErr) { | |
| console.warn("Vault integration retrieval had an exception, fallback to local heuristic keys:", vaultErr); | |
| } | |
| const supabase = getSupabaseClient(); | |
| if (supabase) { | |
| try { | |
| const { data } = await supabase.from("developer_credentials").select("api_token, secret_key"); | |
| if (data && data.length > 0) { | |
| const fetchedKeys = data.map((item: any) => item.api_token || item.secret_key).filter(Boolean); | |
| if (fetchedKeys.length > 0) { | |
| targetKeys = [...fetchedKeys, ...targetKeys]; | |
| } | |
| } | |
| } catch (e) { | |
| // Safe check | |
| } | |
| } | |
| const webhookUrl = localStorage.getItem("dev_hub_dyn_url") | |
| ? `${localStorage.getItem("dev_hub_dyn_url")}/webhook/prompt-optimizer` | |
| : "https://drmartin2050-n8n.hf.space/webhook/prompt-optimizer"; | |
| let finalOptimizedResult = matchedEnglish; | |
| try { | |
| const result = await executeWithFailover( | |
| webhookUrl, | |
| { prompt: arabicPrompt, targetLanguage: "en", task: "optimize" }, | |
| targetKeys | |
| ); | |
| if (result.success && result.response) { | |
| if (typeof result.response === "string") { | |
| finalOptimizedResult = result.response; | |
| } else if (result.response.output || result.response.text || result.response.translated) { | |
| finalOptimizedResult = result.response.output || result.response.text || result.response.translated; | |
| } | |
| } | |
| setFailoverLogs(result.logs || []); | |
| } catch (err) { | |
| console.warn("API direct failover request encountered network issue, utilizing premium local compiler."); | |
| } | |
| setEnglishOptimized(finalOptimizedResult); | |
| // Save to historical logs | |
| const arabicToks = countTokens(arabicPrompt); | |
| const englishToks = countTokens(finalOptimizedResult); | |
| const tokDifference = Math.max(0, arabicToks - englishToks); | |
| const pctSaved = arabicToks > 0 ? Math.round((tokDifference / arabicToks) * 100) : 0; | |
| const newItem: PromptHistoryItem = { | |
| id: `prompt-${Date.now()}`, | |
| arabic_prompt: arabicPrompt, | |
| english_prompt: finalOptimizedResult, | |
| arabic_tokens: arabicToks, | |
| english_tokens: englishToks, | |
| tokens_saved: tokDifference, | |
| percent_saved: pctSaved, | |
| created_at: new Date().toISOString(), | |
| }; | |
| let savedToCloud = false; | |
| if (supabase) { | |
| try { | |
| const { error } = await supabase.from("prompt_history").insert([ | |
| { | |
| id: newItem.id, | |
| arabic_prompt: newItem.arabic_prompt, | |
| english_prompt: newItem.english_prompt, | |
| arabic_tokens: newItem.arabic_tokens, | |
| english_tokens: newItem.english_tokens, | |
| tokens_saved: newItem.tokens_saved, | |
| percent_saved: newItem.percent_saved, | |
| created_at: newItem.created_at, | |
| } | |
| ]); | |
| if (!error) savedToCloud = true; | |
| } catch (err) { | |
| console.warn("Could not save prompt_history straight to Supabase, falling back to local list:", err); | |
| } | |
| } | |
| const existingRaw = localStorage.getItem("prompt_optimizer_history"); | |
| let localList: PromptHistoryItem[] = []; | |
| if (existingRaw) { | |
| try { localList = JSON.parse(existingRaw); } catch (e) {} | |
| } | |
| const updatedLocal = [newItem, ...localList].slice(0, 50); | |
| localStorage.setItem("prompt_optimizer_history", JSON.stringify(updatedLocal)); | |
| setHistoryList(updatedLocal); | |
| setIsProcessing(false); | |
| showAlert(lang === "ar" ? "تم التحسين والترجمة بنجاح!" : "Successfully translated and optimized prompt structure!"); | |
| }; | |
| const handleCopy = () => { | |
| safeCopyToClipboard(englishOptimized); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| showAlert(lang === "ar" ? "تم نسخ الأمر الإنجليزي!" : "Copied optimized English prompt to clipboard!"); | |
| }; | |
| const handleSendToWebhook = async () => { | |
| const webhookUrl = localStorage.getItem("dev_hub_dyn_url") | |
| ? `${localStorage.getItem("dev_hub_dyn_url")}/webhook/automation` | |
| : ""; | |
| if (!webhookUrl) { | |
| showAlert(lang === "ar" ? "برجاء توفير رابط ويبهوك n8n صالح أولاً في كابينة التحكم." : "Please configure an active n8n web hook endpoint in the controls panel first.", "error"); | |
| return; | |
| } | |
| try { | |
| await fetch(webhookUrl, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ prompt: englishOptimized, source: "Portal Optimizer", date: new Date().toISOString() }) | |
| }); | |
| showAlert(lang === "ar" ? "تم بنجاح إرسال الأمر المحسن والترجمة إلى ويبهوك n8n الجاري!" : "Successfully sent the English optimized response to the active n8n automation pipeline!"); | |
| } catch (err) { | |
| console.error(err); | |
| showAlert("Failed to reach targeted webhook endpoint.", "error"); | |
| } | |
| }; | |
| const handleDeleteLog = async (id: string) => { | |
| const updated = historyList.filter(item => item.id !== id); | |
| setHistoryList(updated); | |
| localStorage.setItem("prompt_optimizer_history", JSON.stringify(updated)); | |
| const supabase = getSupabaseClient(); | |
| if (supabase) { | |
| try { | |
| await supabase.from("prompt_history").delete().eq("id", id); | |
| } catch (e) {} | |
| } | |
| }; | |
| const arabicCostUsd = calculateCost(selectedModel, arabicTokens, 0); | |
| const englishCostUsd = calculateCost(selectedModel, englishTokens, 0); | |
| const savedCostUsd = Math.max(0, arabicCostUsd - englishCostUsd); | |
| const arabicConverted = convertCurrency(arabicCostUsd, rates); | |
| const englishConverted = convertCurrency(englishCostUsd, rates); | |
| const savedConverted = convertCurrency(savedCostUsd, rates); | |
| const savingPercentage = arabicTokens > 0 ? Math.round(((arabicTokens - englishTokens)/arabicTokens) * 100) : 0; | |
| return ( | |
| <div id="optimizer-root" className="space-y-6 max-w-6xl mx-auto select-none" dir={lang === "ar" ? "rtl" : "ltr"}> | |
| {/* Intro Header */} | |
| <div id="optimizer-banner" className="relative overflow-hidden rounded-3xl bg-white p-6 sm:p-8 border border-slate-200/80 shadow-3d-flat card-persp card-persp-hover"> | |
| <div className="absolute right-0 top-0 -mb-16 -mr-16 h-64 w-64 rounded-full bg-indigo-505/10 blur-3xl animate-pulse" /> | |
| <div className="relative z-10 space-y-2"> | |
| <div className="inline-flex items-center gap-2 px-3 py-1 bg-indigo-50 border border-indigo-100 text-indigo-650 text-xs font-mono font-black rounded-full shadow-3xs uppercase"> | |
| <Sparkles className="h-4 w-4 text-indigo-600 animate-spin" style={{ animationDuration: "3s" }} /> | |
| <span>{lang === "ar" ? "تحسين الأوامر والترجمة الموفرة" : "PROMPT FOOTPRINT REDUCER"}</span> | |
| </div> | |
| <h1 id="opt-title" className="text-2xl sm:text-3xl font-black text-slate-800 tracking-tight text-sans"> | |
| {t.title} | |
| </h1> | |
| <p id="opt-sub" className="text-sm text-slate-500 font-semibold max-w-2xl leading-relaxed"> | |
| {t.subtitle} | |
| </p> | |
| </div> | |
| </div> | |
| {/* Dynamic Alerts inside UI Panel */} | |
| <AnimatePresence> | |
| {alertMessage && ( | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.98, y: -5 }} | |
| animate={{ opacity: 1, scale: 1, y: 0 }} | |
| exit={{ opacity: 0, scale: 0.98 }} | |
| className={`p-4 rounded-2xl text-xs font-black flex items-center gap-2 border shadow-sm ${ | |
| alertMessage.type === "error" | |
| ? "bg-rose-50 border-rose-200 text-rose-600" | |
| : "bg-emerald-50 border-emerald-250 text-emerald-700" | |
| }`} | |
| > | |
| <ShieldAlert className="h-4.5 w-4.5 shrink-0" /> | |
| <span>{alertMessage.text}</span> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| {/* Main Form Fields */} | |
| <div id="opt-main-layout" className="grid grid-cols-1 lg:grid-cols-12 gap-6"> | |
| {/* Input column with 3D focus scale effect */} | |
| <div id="opt-inputs-panel" className="lg:col-span-6 space-y-6"> | |
| <div className="bg-white border border-slate-205 rounded-3xl p-6 space-y-4 shadow-3d-flat card-persp card-persp-hover"> | |
| <div className="flex justify-between items-center"> | |
| <label htmlFor="arabic-prompt-input" className="text-sm font-black text-slate-800 uppercase tracking-wide flex items-center gap-1.5"> | |
| <Sparkle className="h-4 w-4 text-indigo-600 shrink-0" /> | |
| <span>{t.arabPromptLabel}</span> | |
| </label> | |
| <span className="text-[10px] font-black font-mono text-indigo-600 bg-indigo-50 border border-indigo-100 px-2.5 py-0.5 rounded-lg"> | |
| {arabicTokens} {lang === "ar" ? "رمز" : "Tokens"} | |
| </span> | |
| </div> | |
| {/* Main Textarea incorporating 3D focus scale/border hover animations */} | |
| <textarea | |
| id="arabic-prompt-input" | |
| value={arabicPrompt} | |
| onChange={(e) => setArabicPrompt(e.target.value)} | |
| placeholder={lang === "ar" ? "اكتب الأمر العربي الخاص بك هنا (مثال: اكتب لي تطبيق بلغة جافا سكربت)" : "Type your Arabic prompt here..."} | |
| rows={5} | |
| className="w-full text-slate-850 bg-slate-50 border border-slate-200 rounded-2xl p-4 font-sans text-sm placeholder-slate-400 focus:outline-none focus:border-indigo-500 focus:scale-[1.015] focus:shadow-indigo-500/10 focus:ring-1 focus:ring-indigo-500/10 transition-all duration-300 leading-relaxed font-semibold block" | |
| /> | |
| {/* Quick Helper presets */} | |
| <div className="space-y-1.5 pt-1"> | |
| <span className="text-[10px] text-slate-400 font-extrabold uppercase block">{lang === "ar" ? "نماذج اختبارية سريعة للتجربة:" : "Test Presets:"}</span> | |
| <div className="flex flex-wrap gap-2"> | |
| <button | |
| onClick={() => setArabicPrompt("أريد كود لإنشاء تطبيق مهام بسيط")} | |
| className="px-3 py-1.5 rounded-xl text-xs bg-white text-slate-600 border border-slate-200 cursor-pointer font-bold transition hover:text-indigo-600 hover:border-indigo-300" | |
| > | |
| {lang === "ar" ? "تطبيق مهام" : "Build Todo list"} | |
| </button> | |
| <button | |
| onClick={() => setArabicPrompt("اكتب كود بايثون متقدم لتحليل أسعار العملات الرقمية")} | |
| className="px-3 py-1.5 rounded-xl text-xs bg-white text-slate-600 border border-slate-200 cursor-pointer font-bold transition hover:text-indigo-600 hover:border-indigo-300" | |
| > | |
| {lang === "ar" ? "كود بايثون للتحليل" : "Crypto Python code"} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Qwen gradient with glow effect button */} | |
| <button | |
| id="btn-optimize-trigger" | |
| onClick={handleOptimize} | |
| disabled={isProcessing || !arabicPrompt.trim()} | |
| className={`w-full flex items-center justify-center gap-2 p-4 rounded-2xl text-white font-extrabold text-xs transition duration-200 border shadow-md select-none ${ | |
| isProcessing || !arabicPrompt.trim() | |
| ? "bg-slate-100 text-slate-400 border-slate-200 cursor-not-allowed shadow-none" | |
| : "hover:shadow-indigo-500/25 border-indigo-600 cursor-pointer text-white" | |
| }`} | |
| style={{ | |
| background: (isProcessing || !arabicPrompt.trim()) | |
| ? "" | |
| : 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #06b6d4 100%)' | |
| }} | |
| > | |
| <Sparkles className={`h-4.5 w-4.5 text-white ${isProcessing ? 'animate-spin' : ''}`} /> | |
| <span>{isProcessing ? (lang === "ar" ? "جاري الترجمة وإعادة الصياغة..." : "Optimizing structure...") : t.buttonPrompt}</span> | |
| </button> | |
| {failoverLogs.length > 0 && ( | |
| <div className="p-3 bg-slate-50 rounded-xl border border-slate-200 space-y-1 font-mono"> | |
| <span className="text-[9px] font-bold text-indigo-600 uppercase tracking-wide block">{lang === "ar" ? "مسار الفيل-أوفر النشط لتنفيذ الطلب:" : "Active Failover Webpath:"}</span> | |
| {failoverLogs.map((log, i) => ( | |
| <div key={i} className="flex items-center gap-1.5 text-[8px] text-slate-500"> | |
| <span className={`h-1.5 w-1.5 rounded-full ${log.status === "success" ? "bg-emerald-500 animate-pulse" : "bg-rose-500"}`} /> | |
| <span>KeyIndex {log.keyIndex} ({log.maskedKey}) - <strong className={log.status === "success" ? "text-emerald-600 font-bold" : "text-rose-600 font-bold"}>{log.status}</strong></span> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Output comparison column with side-by-side structures */} | |
| <div id="opt-comparison-panel" className="lg:col-span-6 space-y-6"> | |
| <div className="bg-white border border-slate-205 rounded-3xl p-6 space-y-4 shadow-3d-flat card-persp card-persp-hover"> | |
| <div className="flex justify-between items-center"> | |
| <label htmlFor="english-optimized-output" className="text-sm font-black text-slate-800 uppercase tracking-wide flex items-center gap-1.5"> | |
| <Sparkles className="h-4.5 w-4.5 text-indigo-600" /> | |
| <span>{t.optimizedResultLabel}</span> | |
| </label> | |
| <span className="text-[10px] font-black font-mono text-slate-600 bg-slate-50 border border-slate-200 px-2.5 py-0.5 rounded-lg"> | |
| {englishTokens} {lang === "ar" ? "رمز" : "Tokens"} | |
| </span> | |
| </div> | |
| <textarea | |
| id="english-optimized-output" | |
| value={englishOptimized} | |
| readOnly | |
| placeholder={lang === "ar" ? "الإنتاج الإنجليزي المحسن والمكثف سيظهر هنا عقب الموازنة..." : "Optimized English output appears here..."} | |
| rows={5} | |
| className="w-full text-slate-800 bg-slate-50 border border-slate-200 rounded-2xl p-4 font-mono text-xs leading-relaxed focus:outline-none focus:border-indigo-505 select-all block h-[184px]" | |
| /> | |
| {englishOptimized && ( | |
| <div className="grid grid-cols-2 gap-3.5 pt-1"> | |
| <button | |
| onClick={handleCopy} | |
| className="flex items-center justify-center gap-2 p-3 rounded-xl border border-slate-200 bg-white hover:bg-slate-50 hover:text-indigo-600 hover:border-slate-300 text-slate-650 font-black text-xs cursor-pointer transition-colors shadow-3xs" | |
| > | |
| {copied ? <Check className="h-4 w-4 text-emerald-600" /> : <Copy className="h-4 w-4 text-indigo-600" />} | |
| <span>{copied ? (lang === "ar" ? "تم النسخ!" : "Copied!") : t.copyBtn}</span> | |
| </button> | |
| <button | |
| onClick={handleSendToWebhook} | |
| className="flex items-center justify-center gap-2 p-3 rounded-xl border border-slate-200 bg-white hover:bg-slate-50 hover:text-indigo-600 hover:border-slate-300 text-slate-650 font-black text-xs cursor-pointer transition-colors shadow-3xs" | |
| > | |
| <Send className="h-4 w-4 text-indigo-555" /> | |
| <span>{t.sendN8nBtn}</span> | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Comparison and savings overview in 3D side-by-side cards style */} | |
| {englishOptimized && ( | |
| <motion.div | |
| id="optimization-comparison-layout" | |
| initial={{ opacity: 0, y: 15 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="bg-white border border-slate-205 rounded-3xl p-6 space-y-6 shadow-3d-flat card-persp" | |
| > | |
| <div className="flex items-center justify-between border-b border-slate-100 pb-3 flex-wrap gap-2"> | |
| <h3 className="text-sm font-black text-slate-80 tracking-tight flex items-center gap-2 uppercase"> | |
| <Coins className="h-4.5 w-4.5 text-indigo-600" /> | |
| {t.compareTitle} | |
| </h3> | |
| <div className="flex items-center gap-2 text-xs font-bold"> | |
| <span className="text-slate-400 font-extrabold">{lang === "ar" ? "طراز الحساب المالي:" : "Cost-evaluator model:"}</span> | |
| <select | |
| value={selectedModel} | |
| onChange={(e) => setSelectedModel(e.target.value)} | |
| className="bg-slate-50 border border-slate-200 text-xs text-slate-800 p-2 rounded-xl cursor-pointer font-black" | |
| > | |
| {Object.entries(PRICING_TABLE).map(([id, item]) => ( | |
| <option key={id} value={id}>{item.name}</option> | |
| ))} | |
| </select> | |
| </div> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| {/* Arabic Card */} | |
| <div className="bg-slate-50/50 border border-slate-200 rounded-2xl p-4.5 space-y-3 shadow-3xs card-persp card-persp-hover"> | |
| <div className="flex items-center justify-between text-slate-500 text-xs font-black uppercase"> | |
| <span>{t.cardArabic}</span> | |
| <span className="text-rose-600 text-[9px] font-black uppercase bg-rose-50 px-2 py-0.5 rounded-lg border border-rose-100 shadow-3xs">Raw Input</span> | |
| </div> | |
| <div className="flex justify-between items-end"> | |
| <div> | |
| <span className="text-[10px] text-slate-400 uppercase font-black block">{t.tokenCount}</span> | |
| <span className="text-lg font-mono font-black text-slate-805">{arabicTokens}</span> | |
| </div> | |
| <div className="text-right"> | |
| <span className="text-[10px] text-slate-400 uppercase font-black block">{t.costEstimated} (USD)</span> | |
| <span className="text-base font-mono font-black text-slate-700">{formatCurrency(arabicConverted.USD, "USD", lang)}</span> | |
| </div> | |
| </div> | |
| <div className="border-t border-slate-100 pt-2 text-[10px] font-mono text-slate-500 flex justify-between font-black uppercase"> | |
| <span>EGP Cost:</span> | |
| <span className="font-extrabold text-slate-700">{formatCurrency(arabicConverted.EGP, "EGP", lang)}</span> | |
| </div> | |
| </div> | |
| {/* English Card */} | |
| <div className="bg-indigo-50/20 border border-indigo-150 rounded-2xl p-4.5 space-y-3 shadow-3xs card-persp card-persp-hover"> | |
| <div className="flex items-center justify-between text-indigo-700 text-xs font-black uppercase"> | |
| <span>{t.cardEnglish}</span> | |
| <span className="text-indigo-650 text-[9px] font-black uppercase bg-indigo-50 px-2 py-0.5 rounded-lg border border-indigo-150 shadow-3xs">Optimized</span> | |
| </div> | |
| <div className="flex justify-between items-end"> | |
| <div> | |
| <span className="text-[10px] text-slate-400 uppercase font-black block">{t.tokenCount}</span> | |
| <span className="text-lg font-mono font-black text-indigo-600">{englishTokens}</span> | |
| </div> | |
| <div className="text-right"> | |
| <span className="text-[10px] text-slate-400 uppercase font-black block">{t.costEstimated} (USD)</span> | |
| <span className="text-base font-mono font-black text-indigo-650">{formatCurrency(englishConverted.USD, "USD", lang)}</span> | |
| </div> | |
| </div> | |
| <div className="border-t border-indigo-100 pt-2 text-[10px] font-mono text-slate-500 flex justify-between font-black uppercase"> | |
| <span>EGP Cost:</span> | |
| <span className="font-extrabold text-indigo-650">{formatCurrency(englishConverted.EGP, "EGP", lang)}</span> | |
| </div> | |
| </div> | |
| {/* Savings card */} | |
| <div className="bg-emerald-50 border border-emerald-200 rounded-2xl p-4.5 flex flex-col justify-between shadow-3xs card-persp card-persp-hover"> | |
| <div className="flex items-center justify-between text-emerald-700 text-xs font-black uppercase"> | |
| <span>{t.savingsSectionTitle}</span> | |
| <TrendingUp className="h-4.5 w-4.5 animate-bounce text-emerald-600" /> | |
| </div> | |
| <div className="my-2.5 text-center"> | |
| <span className="text-[9px] font-mono text-emerald-600 font-extrabold uppercase tracking-widest block">NET PERCENTAGE SAVED</span> | |
| <span className="text-3xl font-mono text-emerald-600 font-black tracking-tight block"> | |
| {savingPercentage}% | |
| </span> | |
| </div> | |
| <div className="border-t border-emerald-150 pt-2 text-[10px] text-emerald-700 font-black leading-relaxed text-center uppercase tracking-tight"> | |
| {lang === "ar" | |
| ? `لقد وفرت ${arabicPrompt ? (arabicTokens - englishTokens) : 0} من الرموز (حوالي ${formatCurrency(savedConverted.EGP, "EGP", lang)}) بتبديلك للعربية بالإنجليزية!` | |
| : `You saved ${arabicPrompt ? (arabicTokens - englishTokens) : 0} tokens (worth ~${formatCurrency(savedConverted.EGP, "EGP", lang)}) by writing and tuning in English!` | |
| } | |
| </div> | |
| </div> | |
| </div> | |
| </motion.div> | |
| )} | |
| {/* History log panel */} | |
| <div id="optimizer-history" className="bg-white border border-slate-205 rounded-3xl p-6 space-y-4 shadow-3d-flat card-persp"> | |
| <h3 className="text-md font-black text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-3"> | |
| <History className="h-4.5 w-4.5 text-slate-450 shrink-0" /> | |
| {t.historyTitle} | |
| </h3> | |
| {historyList.length === 0 ? ( | |
| <p className="text-xs text-slate-400 font-bold text-center py-6"> | |
| {t.promptNoHistory} | |
| </p> | |
| ) : ( | |
| <div className="space-y-3 max-h-[300px] overflow-y-auto pr-1"> | |
| {historyList.map((item) => ( | |
| <div key={item.id} className="p-3.5 bg-slate-50 rounded-2xl border border-slate-200 flex items-start justify-between gap-4 text-xs font-semibold"> | |
| <div className="space-y-2 flex-grow overflow-hidden"> | |
| <div className="flex items-center gap-3"> | |
| <span className="text-[10px] font-bold font-mono text-slate-400">{new Date(item.created_at).toLocaleDateString()}</span> | |
| <span className="text-[9px] uppercase font-black tracking-wider bg-emerald-50 border border-emerald-250 text-emerald-700 px-2 py-0.5 rounded-lg shadow-3xs"> | |
| {lang === "ar" ? `وفرت ${item.percent_saved}%` : `Saved ${item.percent_saved}%`} | |
| </span> | |
| </div> | |
| <div className="space-y-1"> | |
| <p className="text-slate-600 font-bold truncate"><strong className="text-[10px] text-slate-400 font-mono uppercase">AR:</strong> {item.arabic_prompt}</p> | |
| <p className="text-indigo-650 font-mono truncate"><strong className="text-[10px] text-slate-400 font-mono uppercase font-sans">EN:</strong> {item.english_prompt}</p> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-2 shrink-0 font-semibold select-none"> | |
| <button | |
| onClick={() => { | |
| setArabicPrompt(item.arabic_prompt); | |
| setEnglishOptimized(item.english_prompt); | |
| showAlert(lang === "ar" ? "تم تحميل الأمر!" : "Loaded historical prompt parameters successfully!"); | |
| }} | |
| className="px-3 py-1.5 rounded-xl bg-white border border-slate-200 text-slate-600 hover:text-indigo-650 font-bold text-[10px] cursor-pointer shadow-3xs" | |
| > | |
| {lang === "ar" ? "تحميل" : "Load"} | |
| </button> | |
| <button | |
| onClick={() => handleDeleteLog(item.id)} | |
| className="p-1.5 rounded-xl bg-white border border-slate-100 hover:bg-rose-50 text-slate-400 hover:text-rose-600 transition cursor-pointer" | |
| > | |
| <Trash2 className="h-3.5 w-3.5" /> | |
| </button> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |