dachbord / src /components /OptimizerView.tsx
drmartin2050's picture
feat: add prompt translator and clipboard utilities
16df1b7
Raw
History Blame Contribute Delete
30.7 kB
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>
);
}