Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- frontend/index.html +124 -186
- frontend/script.js +660 -731
- frontend/style.css +455 -275
frontend/index.html
CHANGED
|
@@ -1,25 +1,25 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" dir="ltr">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>AI
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<link rel="stylesheet" href="
|
| 9 |
</head>
|
| 10 |
-
<body class="bg-
|
| 11 |
-
<div class="
|
| 12 |
-
<div class="
|
| 13 |
-
<
|
| 14 |
-
|
| 15 |
-
<
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
<button id="theme-toggle" class="p-2 rounded-full bg-white/20 hover:bg-white/30 transition-colors"
|
| 20 |
<!-- Sun icon (for dark mode) -->
|
| 21 |
<svg id="sun-icon" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-yellow-300 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 22 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
| 23 |
</svg>
|
| 24 |
<!-- Moon icon (for light mode) -->
|
| 25 |
<svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -27,191 +27,129 @@
|
|
| 27 |
</svg>
|
| 28 |
</button>
|
| 29 |
</div>
|
| 30 |
-
</
|
| 31 |
-
|
| 32 |
-
<!--
|
| 33 |
-
<
|
| 34 |
-
<
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
Q&A
|
| 42 |
-
</button>
|
| 43 |
-
<button id="tab-code" class="tab-button px-6 py-4 font-medium text-gray-500 dark:text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 whitespace-nowrap transition-colors duration-200">
|
| 44 |
-
Code Generation
|
| 45 |
-
</button>
|
| 46 |
-
</div>
|
| 47 |
-
|
| 48 |
-
<!-- Tab Content -->
|
| 49 |
-
<div class="p-6">
|
| 50 |
-
<!-- Upload Document Section -->
|
| 51 |
-
<div id="content-upload" class="tab-content">
|
| 52 |
-
<div class="bg-purple-50 dark:bg-purple-900/20 p-6 rounded-xl transition-colors duration-200">
|
| 53 |
-
<h2 class="text-2xl font-semibold text-purple-800 dark:text-purple-300 mb-4 transition-colors duration-200">Document & Image Analysis</h2>
|
| 54 |
-
<p class="text-gray-600 dark:text-gray-300 mb-4 transition-colors duration-200">Upload a text file or image for analysis</p>
|
| 55 |
-
|
| 56 |
-
<div class="flex flex-col items-center justify-center border-2 border-dashed border-purple-300 dark:border-purple-700 rounded-lg p-8 mb-4 bg-white dark:bg-gray-700 transition-colors duration-200">
|
| 57 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-purple-400 dark:text-purple-300 mb-3 transition-colors duration-200" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 58 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
| 59 |
</svg>
|
| 60 |
-
<label for="fileInput" class="cursor-pointer bg-purple-600 text-white px-4 py-2 rounded-lg hover:bg-purple-700 transition">
|
| 61 |
-
Choose File
|
| 62 |
-
</label>
|
| 63 |
-
<input type="file" id="fileInput" class="hidden">
|
| 64 |
-
<p id="fileName" class="mt-2 text-sm text-gray-500 dark:text-gray-300 transition-colors duration-200">No file chosen</p>
|
| 65 |
</div>
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
<
|
| 69 |
-
<
|
| 70 |
-
|
| 71 |
-
</
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
<
|
| 75 |
-
|
| 76 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
| 77 |
-
</svg>
|
| 78 |
-
Analyze Image
|
| 79 |
-
</button>
|
| 80 |
</div>
|
| 81 |
</div>
|
| 82 |
</div>
|
| 83 |
-
|
| 84 |
-
<!--
|
| 85 |
-
<div
|
| 86 |
-
<div class="
|
| 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 |
-
|
| 129 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 130 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
|
| 131 |
-
</svg>
|
| 132 |
-
Translate Text
|
| 133 |
-
</button>
|
| 134 |
-
</div>
|
| 135 |
-
</div>
|
| 136 |
-
</div>
|
| 137 |
-
|
| 138 |
-
<!-- Q&A Section -->
|
| 139 |
-
<div id="content-qa" class="tab-content hidden">
|
| 140 |
-
<div class="bg-amber-50 dark:bg-amber-900/20 p-6 rounded-xl transition-colors duration-200">
|
| 141 |
-
<h2 class="text-2xl font-semibold text-amber-800 dark:text-amber-300 mb-4 transition-colors duration-200">Questions & Answers</h2>
|
| 142 |
-
<p class="text-gray-600 dark:text-gray-300 mb-4 transition-colors duration-200">Enter reference text and your question to get an answer</p>
|
| 143 |
-
|
| 144 |
-
<div class="mb-4">
|
| 145 |
-
<label for="contextInput" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 transition-colors duration-200">Reference Text:</label>
|
| 146 |
-
<textarea id="contextInput" class="w-full p-4 border border-amber-300 dark:border-amber-700 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 transition-colors duration-200" rows="4" placeholder="Enter reference text here..."></textarea>
|
| 147 |
-
</div>
|
| 148 |
-
|
| 149 |
-
<div class="mb-4">
|
| 150 |
-
<label for="questionInput" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 transition-colors duration-200">Question:</label>
|
| 151 |
-
<input type="text" id="questionInput" class="w-full p-4 border border-amber-300 dark:border-amber-700 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 transition-colors duration-200" placeholder="Enter your question here...">
|
| 152 |
-
</div>
|
| 153 |
-
|
| 154 |
-
<div class="flex justify-center">
|
| 155 |
-
<button onclick="askQuestion()" class="px-5 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition flex items-center">
|
| 156 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 157 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 158 |
-
</svg>
|
| 159 |
-
Get Answer
|
| 160 |
-
</button>
|
| 161 |
-
</div>
|
| 162 |
</div>
|
| 163 |
</div>
|
| 164 |
-
|
| 165 |
-
<!--
|
| 166 |
-
<div id="
|
| 167 |
-
<
|
| 168 |
-
<h2 class="text-2xl font-semibold text-teal-800 dark:text-teal-300 mb-4 transition-colors duration-200">Code Generation</h2>
|
| 169 |
-
<p class="text-gray-600 dark:text-gray-300 mb-4 transition-colors duration-200">Enter a description of the code you want to generate</p>
|
| 170 |
-
|
| 171 |
-
<div class="mb-4">
|
| 172 |
-
<textarea id="codeInput" class="w-full p-4 border border-teal-300 dark:border-teal-700 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 transition-colors duration-200" rows="6" placeholder="Example: Write code to create a chart for sales data..."></textarea>
|
| 173 |
-
</div>
|
| 174 |
-
|
| 175 |
-
<div class="flex justify-center">
|
| 176 |
-
<button onclick="generateCode()" class="px-5 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition flex items-center">
|
| 177 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 178 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 179 |
-
</svg>
|
| 180 |
-
Generate Code
|
| 181 |
-
</button>
|
| 182 |
-
</div>
|
| 183 |
-
</div>
|
| 184 |
</div>
|
| 185 |
-
|
| 186 |
-
<!--
|
| 187 |
-
<div class="
|
| 188 |
-
<
|
| 189 |
-
<div class="flex
|
| 190 |
-
<
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
| 194 |
</svg>
|
| 195 |
-
|
| 196 |
-
<
|
| 197 |
-
</div>
|
| 198 |
-
<div class="relative">
|
| 199 |
-
<div id="output" class="p-4 bg-white dark:bg-gray-800 border dark:border-gray-600 rounded-lg h-60 overflow-auto text-gray-800 dark:text-gray-200 transition-colors duration-200"></div>
|
| 200 |
-
<div id="loader" class="hidden absolute inset-0 flex items-center justify-center bg-white dark:bg-gray-800 bg-opacity-80 dark:bg-opacity-80 rounded-lg transition-colors duration-200">
|
| 201 |
-
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-700 dark:border-purple-400 transition-colors duration-200"></div>
|
| 202 |
-
</div>
|
| 203 |
</div>
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
-
|
| 209 |
-
<footer class="mt-6 text-center text-gray-500 dark:text-gray-400 text-sm transition-colors duration-200">
|
| 210 |
-
<p>Developed by Rezaiguia Soltane © 2025</p>
|
| 211 |
-
</footer>
|
| 212 |
</div>
|
| 213 |
-
|
| 214 |
-
<
|
| 215 |
-
<
|
|
|
|
|
|
|
| 216 |
</body>
|
| 217 |
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" dir="ltr" class="">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AI Chat Assistant</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="style.css">
|
| 9 |
</head>
|
| 10 |
+
<body class="bg-gray-100 font-sans min-h-screen">
|
| 11 |
+
<div class="app-container">
|
| 12 |
+
<div class="app-content">
|
| 13 |
+
<!-- Header -->
|
| 14 |
+
<header class="bg-gradient-to-r from-purple-600 to-indigo-600 p-4 shadow-md rounded-t-lg">
|
| 15 |
+
<div class="container mx-auto flex justify-between items-center">
|
| 16 |
+
<h1 class="text-2xl font-bold text-white">AI Assistant</h1>
|
| 17 |
+
|
| 18 |
+
<!-- Theme Toggle Button -->
|
| 19 |
+
<button id="theme-toggle" class="p-2 rounded-full bg-white/20 hover:bg-white/30 transition-colors">
|
| 20 |
<!-- Sun icon (for dark mode) -->
|
| 21 |
<svg id="sun-icon" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-yellow-300 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 22 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707-.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
| 23 |
</svg>
|
| 24 |
<!-- Moon icon (for light mode) -->
|
| 25 |
<svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
| 27 |
</svg>
|
| 28 |
</button>
|
| 29 |
</div>
|
| 30 |
+
</header>
|
| 31 |
+
|
| 32 |
+
<!-- Main Chat Area -->
|
| 33 |
+
<main class="flex-1 overflow-hidden flex flex-col bg-white">
|
| 34 |
+
<!-- Chat Messages Container -->
|
| 35 |
+
<div id="chat-messages" class="flex-1 overflow-y-auto p-4 space-y-4 bg-white">
|
| 36 |
+
<!-- Welcome Message -->
|
| 37 |
+
<div class="flex items-start">
|
| 38 |
+
<div class="flex-shrink-0 bg-purple-600 rounded-full p-2">
|
| 39 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 40 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
</svg>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
</div>
|
| 43 |
+
<div class="ml-3 bg-white rounded-lg p-4 shadow-sm max-w-3xl border border-gray-100">
|
| 44 |
+
<p class="text-gray-800">Welcome! I'm your AI assistant. I can help you with:</p>
|
| 45 |
+
<ul class="mt-2 list-disc list-inside text-gray-700">
|
| 46 |
+
<li>Summarizing text</li>
|
| 47 |
+
<li>Translating between languages</li>
|
| 48 |
+
<li>Answering questions</li>
|
| 49 |
+
<li>Generating code</li>
|
| 50 |
+
<li>Analyzing documents and images</li>
|
| 51 |
+
</ul>
|
| 52 |
+
<p class="mt-2 text-gray-800">How can I assist you today?</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
</div>
|
| 54 |
</div>
|
| 55 |
</div>
|
| 56 |
+
|
| 57 |
+
<!-- Feature Selection Bar -->
|
| 58 |
+
<div class="bg-white border-t border-gray-100 p-3">
|
| 59 |
+
<div class="flex overflow-x-auto space-x-2 pb-2 feature-buttons">
|
| 60 |
+
<button class="feature-btn active" data-feature="chat">
|
| 61 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 62 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
|
| 63 |
+
</svg>
|
| 64 |
+
Chat
|
| 65 |
+
</button>
|
| 66 |
+
<button class="feature-btn" data-feature="summarize">
|
| 67 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 68 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" />
|
| 69 |
+
</svg>
|
| 70 |
+
Summarize
|
| 71 |
+
</button>
|
| 72 |
+
<button class="feature-btn" data-feature="translate">
|
| 73 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 74 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
|
| 75 |
+
</svg>
|
| 76 |
+
Translate
|
| 77 |
+
</button>
|
| 78 |
+
<button class="feature-btn" data-feature="qa">
|
| 79 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 80 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 81 |
+
</svg>
|
| 82 |
+
Q&A
|
| 83 |
+
</button>
|
| 84 |
+
<button class="feature-btn" data-feature="code">
|
| 85 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 86 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 87 |
+
</svg>
|
| 88 |
+
Code
|
| 89 |
+
</button>
|
| 90 |
+
<button class="feature-btn" data-feature="document">
|
| 91 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 92 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
| 93 |
+
</svg>
|
| 94 |
+
Document
|
| 95 |
+
</button>
|
| 96 |
+
<button class="feature-btn" data-feature="image">
|
| 97 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 98 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
| 99 |
+
</svg>
|
| 100 |
+
Image
|
| 101 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
</div>
|
| 103 |
</div>
|
| 104 |
+
|
| 105 |
+
<!-- Feature-specific Options (initially hidden) -->
|
| 106 |
+
<div id="feature-options" class="bg-white border-t border-gray-100 p-3 hidden">
|
| 107 |
+
<!-- Content will be dynamically inserted based on selected feature -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
</div>
|
| 109 |
+
|
| 110 |
+
<!-- Input Area -->
|
| 111 |
+
<div class="bg-white border-t border-gray-100 p-4 rounded-b-lg">
|
| 112 |
+
<form id="chat-form" class="flex items-end gap-2">
|
| 113 |
+
<div class="flex-1 relative">
|
| 114 |
+
<textarea id="user-input" rows="1" class="w-full p-3 pr-10 border border-gray-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 bg-white text-gray-900 resize-none" placeholder="Type your message..."></textarea>
|
| 115 |
+
|
| 116 |
+
<!-- File Upload Button (initially hidden) -->
|
| 117 |
+
<label id="file-upload-label" for="file-upload" class="absolute right-3 bottom-3 cursor-pointer hidden">
|
| 118 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 hover:text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 119 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
|
| 120 |
</svg>
|
| 121 |
+
</label>
|
| 122 |
+
<input id="file-upload" type="file" class="hidden">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
</div>
|
| 124 |
+
<button type="submit" class="p-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition flex-shrink-0">
|
| 125 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 126 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
| 127 |
+
</svg>
|
| 128 |
+
</button>
|
| 129 |
+
</form>
|
| 130 |
</div>
|
| 131 |
+
</main>
|
| 132 |
+
</div>
|
| 133 |
+
</div>
|
| 134 |
+
|
| 135 |
+
<!-- History Panel (hidden by default) -->
|
| 136 |
+
<div id="historyPanel" class="fixed right-0 top-0 h-full w-80 bg-white shadow-lg transform translate-x-full transition-transform duration-300 ease-in-out z-50 overflow-y-auto">
|
| 137 |
+
<div class="p-4 bg-purple-600 text-white">
|
| 138 |
+
<div class="flex justify-between items-center">
|
| 139 |
+
<h3 class="text-lg font-semibold">History</h3>
|
| 140 |
+
<button id="closeHistory" class="text-white hover:text-purple-200">
|
| 141 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 142 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
| 143 |
+
</svg>
|
| 144 |
+
</button>
|
| 145 |
</div>
|
| 146 |
</div>
|
| 147 |
+
<div id="historyItems" class="p-4 space-y-3"></div>
|
|
|
|
|
|
|
|
|
|
| 148 |
</div>
|
| 149 |
+
|
| 150 |
+
<!-- Notification Container -->
|
| 151 |
+
<div id="notification-container" class="fixed top-4 right-4 z-50"></div>
|
| 152 |
+
|
| 153 |
+
<script src="script.js"></script>
|
| 154 |
</body>
|
| 155 |
</html>
|
frontend/script.js
CHANGED
|
@@ -1,762 +1,691 @@
|
|
| 1 |
-
// Tab switching functionality
|
| 2 |
document.addEventListener("DOMContentLoaded", () => {
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
})
|
| 25 |
-
|
| 26 |
-
// Restore active tab from localStorage
|
| 27 |
-
const activeTab = localStorage.getItem("activeTab")
|
| 28 |
-
if (activeTab) {
|
| 29 |
-
document.getElementById(activeTab)?.click()
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
// File input handling
|
| 33 |
-
const fileInput = document.getElementById("fileInput")
|
| 34 |
-
const fileName = document.getElementById("fileName")
|
| 35 |
-
|
| 36 |
-
fileInput.addEventListener("change", () => {
|
| 37 |
-
if (fileInput.files.length > 0) {
|
| 38 |
-
fileName.textContent = fileInput.files[0].name
|
| 39 |
} else {
|
| 40 |
-
|
| 41 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
})
|
| 43 |
-
|
| 44 |
-
// Copy output button
|
| 45 |
-
const copyButton = document.getElementById("copyOutput")
|
| 46 |
-
copyButton.addEventListener("click", () => {
|
| 47 |
-
const output = document.getElementById("output")
|
| 48 |
-
navigator.clipboard
|
| 49 |
-
.writeText(output.textContent)
|
| 50 |
-
.then(() => {
|
| 51 |
-
showNotification("Results copied successfully!")
|
| 52 |
-
})
|
| 53 |
-
.catch((err) => {
|
| 54 |
-
console.error("Failed to copy text: ", err)
|
| 55 |
-
showNotification("Failed to copy text: " + err.message, "error")
|
| 56 |
-
})
|
| 57 |
-
})
|
| 58 |
-
|
| 59 |
-
// Translation options toggle
|
| 60 |
-
const translateBtn = document.getElementById("translateBtn")
|
| 61 |
-
const translationOptions = document.getElementById("translationOptions")
|
| 62 |
-
|
| 63 |
-
translateBtn.addEventListener("click", () => {
|
| 64 |
-
// Toggle translation options visibility
|
| 65 |
-
if (translationOptions.classList.contains("hidden")) {
|
| 66 |
-
translationOptions.classList.remove("hidden")
|
| 67 |
-
} else {
|
| 68 |
-
// If options are already visible, perform translation
|
| 69 |
-
translateText()
|
| 70 |
-
}
|
| 71 |
-
})
|
| 72 |
-
|
| 73 |
-
// Theme toggle
|
| 74 |
-
const themeToggle = document.getElementById("theme-toggle")
|
| 75 |
-
themeToggle.addEventListener("click", toggleTheme)
|
| 76 |
-
|
| 77 |
-
// Add download results button
|
| 78 |
-
const outputContainer = document.querySelector(".bg-gray-100.dark\\:bg-gray-700.p-4.rounded-xl")
|
| 79 |
-
const downloadButton = document.createElement("button")
|
| 80 |
-
downloadButton.id = "downloadOutput"
|
| 81 |
-
downloadButton.className =
|
| 82 |
-
"text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 flex items-center ml-4 transition-colors duration-200"
|
| 83 |
-
downloadButton.innerHTML = `
|
| 84 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 85 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
| 86 |
-
</svg>
|
| 87 |
-
Download
|
| 88 |
-
`
|
| 89 |
-
downloadButton.addEventListener("click", downloadResults)
|
| 90 |
-
|
| 91 |
-
// Add the download button next to the copy button
|
| 92 |
-
const resultHeader = outputContainer.querySelector(".flex.justify-between.items-center.mb-2")
|
| 93 |
-
resultHeader.appendChild(downloadButton)
|
| 94 |
-
|
| 95 |
-
// Initialize history panel
|
| 96 |
-
initHistoryPanel()
|
| 97 |
-
|
| 98 |
-
// Make sure theme toggle is properly initialized
|
| 99 |
-
const themeToggleBtn = document.getElementById("theme-toggle")
|
| 100 |
-
if (themeToggleBtn) {
|
| 101 |
-
themeToggleBtn.addEventListener("click", toggleTheme)
|
| 102 |
-
console.log("Theme toggle button initialized")
|
| 103 |
-
} else {
|
| 104 |
-
console.error("Theme toggle button not found")
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
// Initialize theme immediately
|
| 108 |
-
initTheme()
|
| 109 |
-
console.log("Theme initialized:", document.documentElement.classList.contains("dark") ? "dark" : "light")
|
| 110 |
})
|
| 111 |
-
|
| 112 |
-
//
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
}
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
document.documentElement.classList.toggle("dark")
|
| 133 |
-
|
| 134 |
-
//
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
if (document.documentElement.classList.contains("dark")) {
|
| 140 |
-
// Dark mode is active
|
| 141 |
-
sunIcon.classList.remove("hidden")
|
| 142 |
-
moonIcon.classList.add("hidden")
|
| 143 |
-
localStorage.setItem("theme", "dark")
|
| 144 |
-
showNotification("Dark mode activated")
|
| 145 |
-
} else {
|
| 146 |
-
// Light mode is active
|
| 147 |
-
sunIcon.classList.add("hidden")
|
| 148 |
-
moonIcon.classList.remove("hidden")
|
| 149 |
-
localStorage.setItem("theme", "light")
|
| 150 |
-
showNotification("Light mode activated")
|
| 151 |
-
}
|
| 152 |
-
}
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
// Show notification function
|
| 156 |
-
function showNotification(message, type = "success") {
|
| 157 |
-
// Create notification element
|
| 158 |
-
const notification = document.createElement("div")
|
| 159 |
-
|
| 160 |
-
// Set class based on notification type
|
| 161 |
-
if (type === "success") {
|
| 162 |
-
notification.className =
|
| 163 |
-
"notification fixed top-4 right-4 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded shadow-md dark:bg-green-900 dark:text-green-100 dark:border-green-400"
|
| 164 |
-
} else if (type === "error") {
|
| 165 |
-
notification.className =
|
| 166 |
-
"notification fixed top-4 right-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-md dark:bg-red-900 dark:text-red-100 dark:border-red-400"
|
| 167 |
-
} else if (type === "warning") {
|
| 168 |
-
notification.className =
|
| 169 |
-
"notification fixed top-4 right-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 rounded shadow-md dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-400"
|
| 170 |
-
} else if (type === "info") {
|
| 171 |
-
notification.className =
|
| 172 |
-
"notification fixed top-4 right-4 bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 rounded shadow-md dark:bg-blue-900 dark:text-blue-100 dark:border-blue-400"
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
notification.innerHTML = message
|
| 176 |
-
|
| 177 |
-
// Add to document
|
| 178 |
-
document.body.appendChild(notification)
|
| 179 |
-
|
| 180 |
-
// Remove after 3 seconds
|
| 181 |
-
setTimeout(() => {
|
| 182 |
-
notification.style.opacity = "0"
|
| 183 |
-
notification.style.transform = "translateY(-20px)"
|
| 184 |
-
notification.style.transition = "opacity 0.5s, transform 0.5s"
|
| 185 |
-
|
| 186 |
-
setTimeout(() => {
|
| 187 |
-
notification.remove()
|
| 188 |
-
}, 500)
|
| 189 |
-
}, 3000)
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
// Show/hide loader
|
| 193 |
-
function toggleLoader(show) {
|
| 194 |
-
const loader = document.getElementById("loader")
|
| 195 |
-
if (show) {
|
| 196 |
-
loader.classList.remove("hidden")
|
| 197 |
} else {
|
| 198 |
-
|
|
|
|
|
|
|
| 199 |
}
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
</button>
|
| 253 |
</div>
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
</svg>
|
|
|
|
|
|
|
| 269 |
`
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
loadHistory()
|
| 279 |
-
}
|
| 280 |
-
|
| 281 |
-
function toggleHistoryPanel() {
|
| 282 |
-
const historyPanel = document.getElementById("historyPanel")
|
| 283 |
-
if (historyPanel.classList.contains("translate-x-full")) {
|
| 284 |
-
historyPanel.classList.remove("translate-x-full")
|
| 285 |
-
} else {
|
| 286 |
-
historyPanel.classList.add("translate-x-full")
|
| 287 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
}
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
history.pop()
|
| 304 |
-
}
|
| 305 |
-
|
| 306 |
-
// Save back to localStorage
|
| 307 |
-
localStorage.setItem("aiAppHistory", JSON.stringify(history))
|
| 308 |
-
|
| 309 |
-
// Update UI if history panel exists
|
| 310 |
-
loadHistory()
|
| 311 |
}
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
const formattedDate = date.toLocaleDateString() + " " + date.toLocaleTimeString()
|
| 335 |
-
|
| 336 |
-
historyItem.innerHTML = `
|
| 337 |
-
<div class="flex justify-between items-start">
|
| 338 |
-
<h4 class="font-medium text-gray-800 dark:text-gray-200">${item.action}</h4>
|
| 339 |
-
<span class="text-xs text-gray-500 dark:text-gray-400">${formattedDate}</span>
|
| 340 |
-
</div>
|
| 341 |
-
<p class="text-sm text-gray-600 dark:text-gray-300 mt-1">${item.details}</p>
|
| 342 |
-
`
|
| 343 |
-
|
| 344 |
-
historyItemsContainer.appendChild(historyItem)
|
| 345 |
-
})
|
| 346 |
}
|
| 347 |
-
|
| 348 |
-
//
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
outputElement.innerHTML = "" // Clear previous content
|
| 352 |
-
|
| 353 |
-
// Create a container for formatted output
|
| 354 |
-
const container = document.createElement("div")
|
| 355 |
-
container.className = "formatted-output"
|
| 356 |
-
|
| 357 |
-
switch (type) {
|
| 358 |
-
case "translation":
|
| 359 |
-
// Get language names
|
| 360 |
-
const sourceLang = getLanguageName(document.getElementById("sourceLanguage").value)
|
| 361 |
-
const targetLang = getLanguageName(document.getElementById("targetLanguage").value)
|
| 362 |
-
|
| 363 |
-
// Create translation result elements
|
| 364 |
-
const header = document.createElement("div")
|
| 365 |
-
header.className = "result-header"
|
| 366 |
-
header.innerHTML = `<h3>Translation Result</h3>`
|
| 367 |
-
|
| 368 |
-
const originalBox = document.createElement("div")
|
| 369 |
-
originalBox.className = "text-box original"
|
| 370 |
-
originalBox.innerHTML = `
|
| 371 |
-
<div class="text-box-header">${sourceLang}</div>
|
| 372 |
-
<div class="text-content">${document.getElementById("textInput").value}</div>
|
| 373 |
-
`
|
| 374 |
-
|
| 375 |
-
const translatedBox = document.createElement("div")
|
| 376 |
-
translatedBox.className = "text-box translated"
|
| 377 |
-
|
| 378 |
-
// Add text-to-speech button for translated text
|
| 379 |
-
const translatedText = data.translation || data.result || data.text || JSON.stringify(data)
|
| 380 |
-
translatedBox.innerHTML = `
|
| 381 |
-
<div class="text-box-header flex justify-between items-center">
|
| 382 |
-
<span>${targetLang}</span>
|
| 383 |
-
<button class="text-to-speech-btn p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600" title="Listen to text">
|
| 384 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 385 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
|
| 386 |
-
</svg>
|
| 387 |
-
</button>
|
| 388 |
-
</div>
|
| 389 |
-
<div class="text-content">${translatedText}</div>
|
| 390 |
-
`
|
| 391 |
-
|
| 392 |
-
container.appendChild(header)
|
| 393 |
-
container.appendChild(originalBox)
|
| 394 |
-
container.appendChild(translatedBox)
|
| 395 |
-
|
| 396 |
-
// Add event listener for text-to-speech
|
| 397 |
-
setTimeout(() => {
|
| 398 |
-
const ttsBtn = container.querySelector(".text-to-speech-btn")
|
| 399 |
-
if (ttsBtn) {
|
| 400 |
-
ttsBtn.addEventListener("click", () => {
|
| 401 |
-
speakText(translatedText, document.getElementById("targetLanguage").value)
|
| 402 |
-
})
|
| 403 |
-
}
|
| 404 |
-
}, 0)
|
| 405 |
-
|
| 406 |
-
// Add to history
|
| 407 |
-
addToHistory("Translation", `${sourceLang} to ${targetLang}`)
|
| 408 |
-
break
|
| 409 |
-
|
| 410 |
-
case "summarize":
|
| 411 |
-
const summaryHeader = document.createElement("div")
|
| 412 |
-
summaryHeader.className = "result-header"
|
| 413 |
-
summaryHeader.innerHTML = `<h3>Summary</h3>`
|
| 414 |
-
|
| 415 |
-
const summaryContent = document.createElement("div")
|
| 416 |
-
summaryContent.className = "text-box summary"
|
| 417 |
-
summaryContent.innerHTML = `
|
| 418 |
-
<div class="text-content">${data.summary || data.result || data.text || JSON.stringify(data)}</div>
|
| 419 |
-
`
|
| 420 |
-
|
| 421 |
-
container.appendChild(summaryHeader)
|
| 422 |
-
container.appendChild(summaryContent)
|
| 423 |
-
|
| 424 |
-
// Add to history
|
| 425 |
-
addToHistory("Summarization", "Text summarized")
|
| 426 |
-
break
|
| 427 |
-
|
| 428 |
-
case "qa":
|
| 429 |
-
const qaHeader = document.createElement("div")
|
| 430 |
-
qaHeader.className = "result-header"
|
| 431 |
-
qaHeader.innerHTML = `<h3>Answer</h3>`
|
| 432 |
-
|
| 433 |
-
const questionBox = document.createElement("div")
|
| 434 |
-
questionBox.className = "text-box question"
|
| 435 |
-
questionBox.innerHTML = `
|
| 436 |
-
<div class="text-box-header">Question</div>
|
| 437 |
-
<div class="text-content">${document.getElementById("questionInput").value}</div>
|
| 438 |
-
`
|
| 439 |
-
|
| 440 |
-
const answerBox = document.createElement("div")
|
| 441 |
-
answerBox.className = "text-box answer"
|
| 442 |
-
answerBox.innerHTML = `
|
| 443 |
-
<div class="text-box-header">Answer</div>
|
| 444 |
-
<div class="text-content">${data.answer || data.result || JSON.stringify(data)}</div>
|
| 445 |
-
`
|
| 446 |
-
|
| 447 |
-
container.appendChild(qaHeader)
|
| 448 |
-
container.appendChild(questionBox)
|
| 449 |
-
container.appendChild(answerBox)
|
| 450 |
-
|
| 451 |
-
// Add to history
|
| 452 |
-
addToHistory("Question Answered", document.getElementById("questionInput").value)
|
| 453 |
-
break
|
| 454 |
-
|
| 455 |
-
case "code":
|
| 456 |
-
const codeHeader = document.createElement("div")
|
| 457 |
-
codeHeader.className = "result-header"
|
| 458 |
-
codeHeader.innerHTML = `<h3>Generated Code</h3>`
|
| 459 |
-
|
| 460 |
-
const codeBox = document.createElement("div")
|
| 461 |
-
codeBox.className = "text-box code"
|
| 462 |
-
|
| 463 |
-
// Format code with syntax highlighting if possible
|
| 464 |
-
const codeContent = document.createElement("pre")
|
| 465 |
-
codeContent.className = "code-content"
|
| 466 |
-
codeContent.textContent = data.code || data.result || JSON.stringify(data, null, 2)
|
| 467 |
-
|
| 468 |
-
codeBox.appendChild(codeContent)
|
| 469 |
-
container.appendChild(codeHeader)
|
| 470 |
-
container.appendChild(codeBox)
|
| 471 |
-
|
| 472 |
-
// Add to history
|
| 473 |
-
addToHistory("Code Generation", document.getElementById("codeInput").value.substring(0, 50) + "...")
|
| 474 |
-
break
|
| 475 |
-
|
| 476 |
-
case "extract_text":
|
| 477 |
-
const extractHeader = document.createElement("div")
|
| 478 |
-
extractHeader.className = "result-header"
|
| 479 |
-
extractHeader.innerHTML = `<h3>Extracted Text</h3>`
|
| 480 |
-
|
| 481 |
-
const extractBox = document.createElement("div")
|
| 482 |
-
extractBox.className = "text-box extracted"
|
| 483 |
-
extractBox.innerHTML = `
|
| 484 |
-
<div class="text-content">${data.text || data.result || JSON.stringify(data)}</div>
|
| 485 |
-
`
|
| 486 |
-
|
| 487 |
-
container.appendChild(extractHeader)
|
| 488 |
-
container.appendChild(extractBox)
|
| 489 |
-
|
| 490 |
-
// Add to history
|
| 491 |
-
addToHistory("Text Extraction", "Extracted from file")
|
| 492 |
-
break
|
| 493 |
-
|
| 494 |
-
case "image_caption":
|
| 495 |
-
const captionHeader = document.createElement("div")
|
| 496 |
-
captionHeader.className = "result-header"
|
| 497 |
-
captionHeader.innerHTML = `<h3>Image Analysis</h3>`
|
| 498 |
-
|
| 499 |
-
const captionBox = document.createElement("div")
|
| 500 |
-
captionBox.className = "text-box caption"
|
| 501 |
-
captionBox.innerHTML = `
|
| 502 |
-
<div class="text-content">${data.caption || data.result || JSON.stringify(data)}</div>
|
| 503 |
-
`
|
| 504 |
-
|
| 505 |
-
container.appendChild(captionHeader)
|
| 506 |
-
container.appendChild(captionBox)
|
| 507 |
-
|
| 508 |
-
// Add to history
|
| 509 |
-
addToHistory("Image Analysis", "Image analyzed")
|
| 510 |
-
break
|
| 511 |
-
|
| 512 |
-
default:
|
| 513 |
-
// Fallback to JSON display for unknown types
|
| 514 |
-
container.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`
|
| 515 |
-
}
|
| 516 |
-
|
| 517 |
-
outputElement.appendChild(container)
|
| 518 |
}
|
| 519 |
-
|
| 520 |
-
//
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
// Map language codes to BCP 47 language tags
|
| 531 |
-
const langMap = {
|
| 532 |
-
ar: "ar-SA",
|
| 533 |
-
en: "en-US",
|
| 534 |
-
fr: "fr-FR",
|
| 535 |
-
es: "es-ES",
|
| 536 |
-
zh: "zh-CN",
|
| 537 |
-
}
|
| 538 |
-
|
| 539 |
-
// Set language
|
| 540 |
-
utterance.lang = langMap[langCode] || "en-US"
|
| 541 |
-
|
| 542 |
-
// Get available voices
|
| 543 |
-
const voices = window.speechSynthesis.getVoices()
|
| 544 |
-
|
| 545 |
-
// Try to find a voice for the selected language
|
| 546 |
-
const voice = voices.find((v) => v.lang.startsWith(langMap[langCode]?.split("-")[0] || "en"))
|
| 547 |
-
if (voice) {
|
| 548 |
-
utterance.voice = voice
|
| 549 |
-
}
|
| 550 |
-
|
| 551 |
-
// Speak
|
| 552 |
-
window.speechSynthesis.speak(utterance)
|
| 553 |
-
|
| 554 |
-
// Show notification
|
| 555 |
-
showNotification("Playing audio...", "info")
|
| 556 |
-
}
|
| 557 |
-
|
| 558 |
-
// Helper function to get language name from code
|
| 559 |
-
function getLanguageName(code) {
|
| 560 |
-
const languages = {
|
| 561 |
-
ar: "Arabic",
|
| 562 |
-
en: "English",
|
| 563 |
-
fr: "French",
|
| 564 |
-
es: "Spanish",
|
| 565 |
-
zh: "Chinese",
|
| 566 |
-
}
|
| 567 |
-
return languages[code] || code
|
| 568 |
}
|
| 569 |
-
|
| 570 |
-
//
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 592 |
}
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
async function processText(endpoint) {
|
| 607 |
-
// If endpoint is translate, we now handle it separately
|
| 608 |
-
if (endpoint === "translate") {
|
| 609 |
-
// Show translation options if they're hidden
|
| 610 |
-
if (document.getElementById("translationOptions").classList.contains("hidden")) {
|
| 611 |
-
document.getElementById("translationOptions").classList.remove("hidden")
|
| 612 |
return
|
| 613 |
}
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
toggleLoader(true)
|
| 625 |
-
const formData = new FormData()
|
| 626 |
-
formData.append("text", text)
|
| 627 |
-
|
| 628 |
-
try {
|
| 629 |
-
const response = await fetch(`https://soltane777-textgeneration.hf.space/${endpoint}`, {
|
| 630 |
-
method: "POST",
|
| 631 |
-
body: formData,
|
| 632 |
-
})
|
| 633 |
-
|
| 634 |
-
if (!response.ok) {
|
| 635 |
-
throw new Error(`Server responded with status: ${response.status}`)
|
| 636 |
}
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
|
|
|
|
|
|
| 657 |
return
|
| 658 |
-
}
|
| 659 |
-
|
| 660 |
-
toggleLoader(true)
|
| 661 |
-
const formData = new FormData()
|
| 662 |
-
formData.append("text", text)
|
| 663 |
-
formData.append("source_lang", sourceLanguage)
|
| 664 |
-
formData.append("target_lang", targetLanguage)
|
| 665 |
-
|
| 666 |
-
try {
|
| 667 |
-
const response = await fetch("https://soltane777-textgeneration.hf.space/translate", {
|
| 668 |
-
method: "POST",
|
| 669 |
-
body: formData,
|
| 670 |
-
})
|
| 671 |
-
|
| 672 |
-
if (!response.ok) {
|
| 673 |
-
throw new Error(`Server responded with status: ${response.status}`)
|
| 674 |
-
}
|
| 675 |
-
|
| 676 |
-
const data = await response.json()
|
| 677 |
-
formatOutput(data, "translation")
|
| 678 |
-
} catch (error) {
|
| 679 |
-
console.error("Error:", error)
|
| 680 |
-
document.getElementById("output").innerHTML =
|
| 681 |
-
`<div class="error-message">Error: ${error.message || "Failed to fetch translation data"}</div>`
|
| 682 |
-
showNotification("Error translating text: " + (error.message || "Unknown error"), "error")
|
| 683 |
-
} finally {
|
| 684 |
-
toggleLoader(false)
|
| 685 |
-
}
|
| 686 |
}
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
const
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
})
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
const data = await response.json()
|
| 710 |
-
formatOutput(data, "qa")
|
| 711 |
-
} catch (error) {
|
| 712 |
-
console.error("Error:", error)
|
| 713 |
-
document.getElementById("output").innerHTML =
|
| 714 |
-
`<div class="error-message">Error: ${error.message || "Failed to fetch data"}</div>`
|
| 715 |
-
showNotification("Error getting answer: " + (error.message || "Unknown error"), "error")
|
| 716 |
-
} finally {
|
| 717 |
-
toggleLoader(false)
|
| 718 |
-
}
|
| 719 |
-
}
|
| 720 |
-
|
| 721 |
-
async function generateCode() {
|
| 722 |
-
const prompt = document.getElementById("codeInput").value
|
| 723 |
-
|
| 724 |
-
if (!prompt) {
|
| 725 |
-
showNotification("Please enter a description for the code.", "warning")
|
| 726 |
-
return
|
| 727 |
-
}
|
| 728 |
-
|
| 729 |
-
toggleLoader(true)
|
| 730 |
-
const formData = new FormData()
|
| 731 |
-
formData.append("prompt", prompt)
|
| 732 |
-
|
| 733 |
-
try {
|
| 734 |
-
const response = await fetch("https://soltane777-textgeneration.hf.space/generate_code/", {
|
| 735 |
-
method: "POST",
|
| 736 |
-
body: formData,
|
| 737 |
})
|
| 738 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
if (!response.ok) {
|
| 740 |
throw new Error(`Server responded with status: ${response.status}`)
|
| 741 |
}
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
console.error("Error:", error)
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
}
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
}
|
| 762 |
-
|
|
|
|
|
|
|
|
|
| 1 |
document.addEventListener("DOMContentLoaded", () => {
|
| 2 |
+
// Auto-resize textarea
|
| 3 |
+
const textarea = document.getElementById("user-input")
|
| 4 |
+
textarea.addEventListener("input", autoResizeTextarea)
|
| 5 |
+
|
| 6 |
+
// Feature button selection
|
| 7 |
+
const featureButtons = document.querySelectorAll(".feature-btn")
|
| 8 |
+
const featureOptions = document.getElementById("feature-options")
|
| 9 |
+
const fileUploadLabel = document.getElementById("file-upload-label")
|
| 10 |
+
|
| 11 |
+
featureButtons.forEach((button) => {
|
| 12 |
+
button.addEventListener("click", () => {
|
| 13 |
+
// Remove active class from all buttons
|
| 14 |
+
featureButtons.forEach((btn) => btn.classList.remove("active"))
|
| 15 |
+
|
| 16 |
+
// Add active class to clicked button
|
| 17 |
+
button.classList.add("active")
|
| 18 |
+
|
| 19 |
+
// Show/hide file upload button based on feature
|
| 20 |
+
const feature = button.dataset.feature
|
| 21 |
+
if (feature === "document" || feature === "image") {
|
| 22 |
+
fileUploadLabel.classList.remove("hidden")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
} else {
|
| 24 |
+
fileUploadLabel.classList.add("hidden")
|
| 25 |
}
|
| 26 |
+
|
| 27 |
+
// Update feature options
|
| 28 |
+
updateFeatureOptions(feature)
|
| 29 |
+
|
| 30 |
+
// Save active feature to localStorage
|
| 31 |
+
localStorage.setItem("activeFeature", feature)
|
| 32 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
})
|
| 34 |
+
|
| 35 |
+
// Restore active feature from localStorage
|
| 36 |
+
const activeFeature = localStorage.getItem("activeFeature") || "chat"
|
| 37 |
+
document.querySelector(`.feature-btn[data-feature="${activeFeature}"]`)?.click()
|
| 38 |
+
|
| 39 |
+
// File input handling
|
| 40 |
+
const fileInput = document.getElementById("file-upload")
|
| 41 |
+
fileInput.addEventListener("change", handleFileSelection)
|
| 42 |
+
|
| 43 |
+
// Chat form submission
|
| 44 |
+
const chatForm = document.getElementById("chat-form")
|
| 45 |
+
chatForm.addEventListener("submit", handleChatSubmit)
|
| 46 |
+
|
| 47 |
+
// History panel toggle
|
| 48 |
+
const historyButton = document.createElement("button")
|
| 49 |
+
historyButton.id = "historyButton"
|
| 50 |
+
historyButton.className = "absolute left-4 top-4 p-2 rounded-full bg-white/20 hover:bg-white/30 transition-colors"
|
| 51 |
+
historyButton.innerHTML = `
|
| 52 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 53 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
| 54 |
+
</svg>
|
| 55 |
+
`
|
| 56 |
+
document.querySelector("header .container").appendChild(historyButton)
|
| 57 |
+
|
| 58 |
+
historyButton.addEventListener("click", toggleHistoryPanel)
|
| 59 |
+
document.getElementById("closeHistory").addEventListener("click", toggleHistoryPanel)
|
| 60 |
+
|
| 61 |
+
// Theme toggle functionality
|
| 62 |
+
const themeToggle = document.getElementById("theme-toggle")
|
| 63 |
+
const sunIcon = document.getElementById("sun-icon")
|
| 64 |
+
const moonIcon = document.getElementById("moon-icon")
|
| 65 |
+
|
| 66 |
+
// Check for saved theme preference or use system preference
|
| 67 |
+
const savedTheme = localStorage.getItem("theme")
|
| 68 |
+
const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
|
| 69 |
+
|
| 70 |
+
if (savedTheme === "dark" || (!savedTheme && systemPrefersDark)) {
|
| 71 |
+
document.documentElement.classList.add("dark")
|
| 72 |
+
sunIcon.classList.remove("hidden")
|
| 73 |
+
moonIcon.classList.add("hidden")
|
| 74 |
}
|
| 75 |
+
|
| 76 |
+
// Toggle theme when button is clicked
|
| 77 |
+
themeToggle.addEventListener("click", () => {
|
| 78 |
+
const isDark = document.documentElement.classList.toggle("dark")
|
| 79 |
+
|
| 80 |
+
// Toggle icons
|
| 81 |
+
if (isDark) {
|
| 82 |
+
sunIcon.classList.remove("hidden")
|
| 83 |
+
moonIcon.classList.add("hidden")
|
| 84 |
+
localStorage.setItem("theme", "dark")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
} else {
|
| 86 |
+
sunIcon.classList.add("hidden")
|
| 87 |
+
moonIcon.classList.remove("hidden")
|
| 88 |
+
localStorage.setItem("theme", "light")
|
| 89 |
}
|
| 90 |
+
|
| 91 |
+
// Show notification
|
| 92 |
+
showNotification(`Switched to ${isDark ? "dark" : "light"} mode`, "info")
|
| 93 |
+
})
|
| 94 |
+
|
| 95 |
+
// Load history from localStorage
|
| 96 |
+
loadHistory()
|
| 97 |
+
})
|
| 98 |
+
|
| 99 |
+
// Auto-resize textarea
|
| 100 |
+
function autoResizeTextarea() {
|
| 101 |
+
this.style.height = "auto"
|
| 102 |
+
this.style.height = this.scrollHeight + "px"
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// Update feature options based on selected feature
|
| 106 |
+
function updateFeatureOptions(feature) {
|
| 107 |
+
const optionsContainer = document.getElementById("feature-options")
|
| 108 |
+
|
| 109 |
+
// Clear previous options
|
| 110 |
+
optionsContainer.innerHTML = ""
|
| 111 |
+
|
| 112 |
+
switch (feature) {
|
| 113 |
+
case "chat":
|
| 114 |
+
// No special options for chat
|
| 115 |
+
optionsContainer.classList.add("hidden")
|
| 116 |
+
break
|
| 117 |
+
|
| 118 |
+
case "translate":
|
| 119 |
+
optionsContainer.classList.remove("hidden")
|
| 120 |
+
optionsContainer.innerHTML = `
|
| 121 |
+
<div class="language-selector">
|
| 122 |
+
<div>
|
| 123 |
+
<label for="sourceLanguage" class="block text-sm font-medium text-gray-700 mb-1">From:</label>
|
| 124 |
+
<select id="sourceLanguage" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 bg-white text-gray-900">
|
| 125 |
+
<option value="ar">Arabic</option>
|
| 126 |
+
<option value="en" selected>English</option>
|
| 127 |
+
<option value="fr">French</option>
|
| 128 |
+
<option value="es">Spanish</option>
|
| 129 |
+
<option value="zh">Chinese</option>
|
| 130 |
+
</select>
|
| 131 |
+
</div>
|
| 132 |
+
<div>
|
| 133 |
+
<label for="targetLanguage" class="block text-sm font-medium text-gray-700 mb-1">To:</label>
|
| 134 |
+
<select id="targetLanguage" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 bg-white text-gray-900">
|
| 135 |
+
<option value="ar" selected>Arabic</option>
|
| 136 |
+
<option value="en">English</option>
|
| 137 |
+
<option value="fr">French</option>
|
| 138 |
+
<option value="es">Spanish</option>
|
| 139 |
+
<option value="zh">Chinese</option>
|
| 140 |
+
</select>
|
| 141 |
+
</div>
|
|
|
|
| 142 |
</div>
|
| 143 |
+
`
|
| 144 |
+
break
|
| 145 |
+
|
| 146 |
+
case "qa":
|
| 147 |
+
optionsContainer.classList.remove("hidden")
|
| 148 |
+
optionsContainer.innerHTML = `
|
| 149 |
+
<div class="mb-2">
|
| 150 |
+
<label for="contextInput" class="block text-sm font-medium text-gray-700 mb-1">Reference Text:</label>
|
| 151 |
+
<textarea id="contextInput" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 bg-white text-gray-900 resize-none" rows="2" placeholder="Enter reference text here..."></textarea>
|
| 152 |
+
</div>
|
| 153 |
+
`
|
| 154 |
+
// Auto-resize for the new textarea
|
| 155 |
+
document.getElementById("contextInput").addEventListener("input", autoResizeTextarea)
|
| 156 |
+
break
|
| 157 |
+
|
| 158 |
+
default:
|
| 159 |
+
optionsContainer.classList.add("hidden")
|
| 160 |
+
break
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
// Handle file selection
|
| 165 |
+
function handleFileSelection() {
|
| 166 |
+
const fileInput = document.getElementById("file-upload")
|
| 167 |
+
const userInput = document.getElementById("user-input")
|
| 168 |
+
|
| 169 |
+
if (fileInput.files.length > 0) {
|
| 170 |
+
const file = fileInput.files[0]
|
| 171 |
+
|
| 172 |
+
// Create file preview element
|
| 173 |
+
const filePreview = document.createElement("div")
|
| 174 |
+
filePreview.className = "file-preview"
|
| 175 |
+
filePreview.innerHTML = `
|
| 176 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 177 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
|
| 178 |
</svg>
|
| 179 |
+
<span class="file-preview-name">${file.name}</span>
|
| 180 |
+
<span class="file-preview-remove" onclick="removeFile()">×</span>
|
| 181 |
`
|
| 182 |
+
|
| 183 |
+
// Add file preview after textarea
|
| 184 |
+
const inputContainer = userInput.parentElement
|
| 185 |
+
|
| 186 |
+
// Remove any existing file preview
|
| 187 |
+
const existingPreview = inputContainer.querySelector(".file-preview")
|
| 188 |
+
if (existingPreview) {
|
| 189 |
+
existingPreview.remove()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
| 191 |
+
|
| 192 |
+
inputContainer.appendChild(filePreview)
|
| 193 |
+
|
| 194 |
+
// Update placeholder text
|
| 195 |
+
userInput.placeholder = "Add a message about this file..."
|
| 196 |
}
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// Remove selected file
|
| 200 |
+
const removeFile = () => {
|
| 201 |
+
const fileInput = document.getElementById("file-upload")
|
| 202 |
+
const userInput = document.getElementById("user-input")
|
| 203 |
+
const filePreview = document.querySelector(".file-preview")
|
| 204 |
+
|
| 205 |
+
// Reset file input
|
| 206 |
+
fileInput.value = ""
|
| 207 |
+
|
| 208 |
+
// Remove file preview
|
| 209 |
+
if (filePreview) {
|
| 210 |
+
filePreview.remove()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
}
|
| 212 |
+
|
| 213 |
+
// Reset placeholder
|
| 214 |
+
userInput.placeholder = "Type your message..."
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
window.removeFile = removeFile
|
| 218 |
+
|
| 219 |
+
// Handle chat form submission
|
| 220 |
+
function handleChatSubmit(e) {
|
| 221 |
+
e.preventDefault()
|
| 222 |
+
|
| 223 |
+
const userInput = document.getElementById("user-input")
|
| 224 |
+
const fileInput = document.getElementById("file-upload")
|
| 225 |
+
const activeFeature = document.querySelector(".feature-btn.active").dataset.feature
|
| 226 |
+
|
| 227 |
+
// Get user message
|
| 228 |
+
const userMessage = userInput.value.trim()
|
| 229 |
+
|
| 230 |
+
// Check if there's a message or file
|
| 231 |
+
if (!userMessage && fileInput.files.length === 0) {
|
| 232 |
+
showNotification("Please enter a message or select a file", "warning")
|
| 233 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
}
|
| 235 |
+
|
| 236 |
+
// Add user message to chat
|
| 237 |
+
if (userMessage) {
|
| 238 |
+
addMessageToChat("user", userMessage)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
}
|
| 240 |
+
|
| 241 |
+
// Process based on active feature
|
| 242 |
+
processRequest(activeFeature, userMessage, fileInput.files[0])
|
| 243 |
+
|
| 244 |
+
// Clear input and file
|
| 245 |
+
userInput.value = ""
|
| 246 |
+
userInput.style.height = "auto"
|
| 247 |
+
|
| 248 |
+
if (fileInput.files.length > 0) {
|
| 249 |
+
removeFile()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
}
|
| 251 |
+
|
| 252 |
+
// Scroll to bottom
|
| 253 |
+
scrollToBottom()
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
// Process request based on feature
|
| 257 |
+
function processRequest(feature, message, file) {
|
| 258 |
+
// Show loading indicator
|
| 259 |
+
showLoadingIndicator()
|
| 260 |
+
|
| 261 |
+
// Determine which API endpoint to use
|
| 262 |
+
let endpoint
|
| 263 |
+
const formData = new FormData()
|
| 264 |
+
|
| 265 |
+
switch (feature) {
|
| 266 |
+
case "summarize":
|
| 267 |
+
endpoint = "summarize"
|
| 268 |
+
formData.append("text", message)
|
| 269 |
+
break
|
| 270 |
+
|
| 271 |
+
case "translate":
|
| 272 |
+
endpoint = "translate"
|
| 273 |
+
formData.append("text", message)
|
| 274 |
+
formData.append("source_lang", document.getElementById("sourceLanguage").value)
|
| 275 |
+
formData.append("target_lang", document.getElementById("targetLanguage").value)
|
| 276 |
+
break
|
| 277 |
+
|
| 278 |
+
case "qa":
|
| 279 |
+
endpoint = "qa/"
|
| 280 |
+
// Use fetch with JSON instead of FormData for QA
|
| 281 |
+
const context = document.getElementById("contextInput").value.trim()
|
| 282 |
+
if (!context) {
|
| 283 |
+
hideLoadingIndicator()
|
| 284 |
+
showNotification("Please enter reference text", "warning")
|
| 285 |
+
return
|
| 286 |
}
|
| 287 |
+
// We'll handle this differently below
|
| 288 |
+
break
|
| 289 |
+
|
| 290 |
+
case "code":
|
| 291 |
+
endpoint = "generate_code/"
|
| 292 |
+
formData.append("prompt", message)
|
| 293 |
+
break
|
| 294 |
+
|
| 295 |
+
case "document":
|
| 296 |
+
if (!file) {
|
| 297 |
+
hideLoadingIndicator()
|
| 298 |
+
showNotification("Please select a document file", "warning")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
return
|
| 300 |
}
|
| 301 |
+
endpoint = "extract_text"
|
| 302 |
+
formData.append("file", file)
|
| 303 |
+
break
|
| 304 |
+
|
| 305 |
+
case "image":
|
| 306 |
+
if (!file) {
|
| 307 |
+
hideLoadingIndicator()
|
| 308 |
+
showNotification("Please select an image file", "warning")
|
| 309 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
}
|
| 311 |
+
endpoint = "image_caption"
|
| 312 |
+
formData.append("file", file)
|
| 313 |
+
break
|
| 314 |
+
|
| 315 |
+
default:
|
| 316 |
+
// For regular chat, we'll simulate a response
|
| 317 |
+
setTimeout(() => {
|
| 318 |
+
const responses = [
|
| 319 |
+
"I'm here to help! What would you like to know?",
|
| 320 |
+
"That's an interesting question. Let me think about it...",
|
| 321 |
+
"I can assist with summarizing text, translating languages, answering questions, generating code, and analyzing documents and images. What would you like me to help you with?",
|
| 322 |
+
"I understand. Is there anything specific you'd like me to explain further?",
|
| 323 |
+
"I'm your AI assistant. I'm designed to be helpful, harmless, and honest.",
|
| 324 |
+
]
|
| 325 |
+
const randomResponse = responses[Math.floor(Math.random() * responses.length)]
|
| 326 |
+
addMessageToChat("assistant", randomResponse)
|
| 327 |
+
hideLoadingIndicator()
|
| 328 |
+
scrollToBottom()
|
| 329 |
+
|
| 330 |
+
// Add to history
|
| 331 |
+
addToHistory("Chat", message.substring(0, 30) + (message.length > 30 ? "..." : ""))
|
| 332 |
+
}, 1000)
|
| 333 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
}
|
| 335 |
+
|
| 336 |
+
// Special handling for QA
|
| 337 |
+
if (feature === "qa") {
|
| 338 |
+
const context = document.getElementById("contextInput").value.trim()
|
| 339 |
+
const question = message
|
| 340 |
+
|
| 341 |
+
fetch("https://soltane777-textgeneration.hf.space/qa/", {
|
| 342 |
+
method: "POST",
|
| 343 |
+
headers: { "Content-Type": "application/json" },
|
| 344 |
+
body: JSON.stringify({ context: context, question: question }),
|
| 345 |
+
})
|
| 346 |
+
.then((response) => {
|
| 347 |
+
if (!response.ok) {
|
| 348 |
+
throw new Error(`Server responded with status: ${response.status}`)
|
| 349 |
+
}
|
| 350 |
+
return response.json()
|
| 351 |
})
|
| 352 |
+
.then((data) => {
|
| 353 |
+
const answer = data.answer || "Sorry, I couldn't find an answer to that question."
|
| 354 |
+
addMessageToChat("assistant", answer)
|
| 355 |
+
addToHistory("Question Answered", question.substring(0, 30) + (question.length > 30 ? "..." : ""))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
})
|
| 357 |
+
.catch((error) => {
|
| 358 |
+
console.error("Error:", error)
|
| 359 |
+
addMessageToChat("assistant", `Error: ${error.message || "Failed to get an answer"}`)
|
| 360 |
+
showNotification("Error getting answer: " + (error.message || "Unknown error"), "error")
|
| 361 |
+
})
|
| 362 |
+
.finally(() => {
|
| 363 |
+
hideLoadingIndicator()
|
| 364 |
+
scrollToBottom()
|
| 365 |
+
})
|
| 366 |
+
|
| 367 |
+
return
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
// Process other requests
|
| 371 |
+
fetch(`https://soltane777-textgeneration.hf.space/${endpoint}`, {
|
| 372 |
+
method: "POST",
|
| 373 |
+
body: formData,
|
| 374 |
+
})
|
| 375 |
+
.then((response) => {
|
| 376 |
if (!response.ok) {
|
| 377 |
throw new Error(`Server responded with status: ${response.status}`)
|
| 378 |
}
|
| 379 |
+
return response.json()
|
| 380 |
+
})
|
| 381 |
+
.then((data) => {
|
| 382 |
+
let result
|
| 383 |
+
|
| 384 |
+
switch (feature) {
|
| 385 |
+
case "summarize":
|
| 386 |
+
result = data.summary || "Sorry, I couldn't summarize that text."
|
| 387 |
+
addToHistory("Summarization", "Text summarized")
|
| 388 |
+
break
|
| 389 |
+
|
| 390 |
+
case "translate":
|
| 391 |
+
const sourceLang = getLanguageName(document.getElementById("sourceLanguage").value)
|
| 392 |
+
const targetLang = getLanguageName(document.getElementById("targetLanguage").value)
|
| 393 |
+
result = data.translation || "Sorry, I couldn't translate that text."
|
| 394 |
+
addToHistory("Translation", `${sourceLang} to ${targetLang}`)
|
| 395 |
+
break
|
| 396 |
+
|
| 397 |
+
case "code":
|
| 398 |
+
result = formatCodeResponse(data)
|
| 399 |
+
addToHistory("Code Generation", message.substring(0, 30) + (message.length > 30 ? "..." : ""))
|
| 400 |
+
break
|
| 401 |
+
|
| 402 |
+
case "document":
|
| 403 |
+
result = data.text || "Sorry, I couldn't extract text from that document."
|
| 404 |
+
addToHistory("Text Extraction", "Extracted from file")
|
| 405 |
+
break
|
| 406 |
+
|
| 407 |
+
case "image":
|
| 408 |
+
result = data.caption || "Sorry, I couldn't analyze that image."
|
| 409 |
+
addToHistory("Image Analysis", "Image analyzed")
|
| 410 |
+
break
|
| 411 |
+
|
| 412 |
+
default:
|
| 413 |
+
result = JSON.stringify(data)
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
addMessageToChat("assistant", result)
|
| 417 |
+
})
|
| 418 |
+
.catch((error) => {
|
| 419 |
console.error("Error:", error)
|
| 420 |
+
addMessageToChat("assistant", `Error: ${error.message || "Failed to process request"}`)
|
| 421 |
+
showNotification("Error: " + (error.message || "Unknown error"), "error")
|
| 422 |
+
})
|
| 423 |
+
.finally(() => {
|
| 424 |
+
hideLoadingIndicator()
|
| 425 |
+
scrollToBottom()
|
| 426 |
+
})
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
// Format code response
|
| 430 |
+
function formatCodeResponse(data) {
|
| 431 |
+
if (!data.code && !data[0]) {
|
| 432 |
+
return "Sorry, I couldn't generate code for that prompt."
|
| 433 |
}
|
| 434 |
+
|
| 435 |
+
const code = data.code || data[0].generated_text || JSON.stringify(data)
|
| 436 |
+
|
| 437 |
+
// Format as code block
|
| 438 |
+
return "\`\`\`\n" + code + "\n\`\`\`"
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
// Add message to chat
|
| 442 |
+
function addMessageToChat(sender, message) {
|
| 443 |
+
const chatMessages = document.getElementById("chat-messages")
|
| 444 |
+
const messageDiv = document.createElement("div")
|
| 445 |
+
|
| 446 |
+
if (sender === "user") {
|
| 447 |
+
messageDiv.className = "flex justify-end mb-4"
|
| 448 |
+
messageDiv.innerHTML = `
|
| 449 |
+
<div class="max-w-3xl user-message p-4 shadow-sm">
|
| 450 |
+
<p class="text-gray-800 whitespace-pre-wrap">${formatMessage(message)}</p>
|
| 451 |
+
</div>
|
| 452 |
+
`
|
| 453 |
+
} else {
|
| 454 |
+
messageDiv.className = "flex items-start mb-4"
|
| 455 |
+
messageDiv.innerHTML = `
|
| 456 |
+
<div class="flex-shrink-0 bg-purple-600 rounded-full p-2 mr-3">
|
| 457 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 458 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
| 459 |
+
</svg>
|
| 460 |
+
</div>
|
| 461 |
+
<div class="max-w-3xl assistant-message p-4 shadow-sm">
|
| 462 |
+
<div class="text-gray-800 whitespace-pre-wrap">${formatMessage(message)}</div>
|
| 463 |
+
<div class="message-actions">
|
| 464 |
+
<button class="message-action-btn copy-btn" onclick="copyToClipboard(this)">
|
| 465 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 mr-1 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 466 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
| 467 |
+
</svg>
|
| 468 |
+
Copy
|
| 469 |
+
</button>
|
| 470 |
+
</div>
|
| 471 |
+
</div>
|
| 472 |
+
`
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
chatMessages.appendChild(messageDiv)
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
// Format message with code blocks and links
|
| 479 |
+
function formatMessage(message) {
|
| 480 |
+
// Replace code blocks
|
| 481 |
+
message = message.replace(/\`\`\`([\s\S]*?)\`\`\`/g, (match, code) => {
|
| 482 |
+
return `<pre class="code-content">${escapeHtml(code)}</pre>`
|
| 483 |
+
})
|
| 484 |
+
|
| 485 |
+
// Replace inline code
|
| 486 |
+
message = message.replace(/`([^`]+)`/g, (match, code) => {
|
| 487 |
+
return `<code class="bg-gray-100 px-1 py-0.5 rounded text-sm font-mono">${escapeHtml(code)}</code>`
|
| 488 |
+
})
|
| 489 |
+
|
| 490 |
+
// Replace URLs with links
|
| 491 |
+
message = message.replace(/(https?:\/\/[^\s]+)/g, (url) => {
|
| 492 |
+
return `<a href="${url}" target="_blank" class="text-purple-600 hover:underline">${url}</a>`
|
| 493 |
+
})
|
| 494 |
+
|
| 495 |
+
return message
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
// Escape HTML
|
| 499 |
+
function escapeHtml(unsafe) {
|
| 500 |
+
return unsafe
|
| 501 |
+
.replace(/&/g, "&")
|
| 502 |
+
.replace(/</g, "<")
|
| 503 |
+
.replace(/>/g, ">")
|
| 504 |
+
.replace(/"/g, """)
|
| 505 |
+
.replace(/'/g, "'")
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
// Copy to clipboard
|
| 509 |
+
window.copyToClipboard = (button) => {
|
| 510 |
+
const messageDiv = button.closest(".assistant-message")
|
| 511 |
+
const textContent = messageDiv.querySelector(".whitespace-pre-wrap").innerText
|
| 512 |
+
|
| 513 |
+
navigator.clipboard
|
| 514 |
+
.writeText(textContent)
|
| 515 |
+
.then(() => {
|
| 516 |
+
// Change button text temporarily
|
| 517 |
+
const originalText = button.innerHTML
|
| 518 |
+
button.innerHTML = `
|
| 519 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 mr-1 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 520 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
| 521 |
+
</svg>
|
| 522 |
+
Copied!
|
| 523 |
+
`
|
| 524 |
+
|
| 525 |
+
setTimeout(() => {
|
| 526 |
+
button.innerHTML = originalText
|
| 527 |
+
}, 2000)
|
| 528 |
+
|
| 529 |
+
showNotification("Text copied to clipboard!")
|
| 530 |
+
})
|
| 531 |
+
.catch((err) => {
|
| 532 |
+
console.error("Failed to copy text: ", err)
|
| 533 |
+
showNotification("Failed to copy text: " + err.message, "error")
|
| 534 |
+
})
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
// Show loading indicator
|
| 538 |
+
function showLoadingIndicator() {
|
| 539 |
+
const chatMessages = document.getElementById("chat-messages")
|
| 540 |
+
|
| 541 |
+
// Create loading message
|
| 542 |
+
const loadingDiv = document.createElement("div")
|
| 543 |
+
loadingDiv.id = "loading-indicator"
|
| 544 |
+
loadingDiv.className = "flex items-start mb-4"
|
| 545 |
+
loadingDiv.innerHTML = `
|
| 546 |
+
<div class="flex-shrink-0 bg-purple-600 rounded-full p-2 mr-3">
|
| 547 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 548 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
| 549 |
+
</svg>
|
| 550 |
+
</div>
|
| 551 |
+
<div class="max-w-3xl assistant-message p-4 shadow-sm flex items-center">
|
| 552 |
+
<div class="spinner mr-3"></div>
|
| 553 |
+
<p class="text-gray-600">Thinking...</p>
|
| 554 |
+
</div>
|
| 555 |
+
`
|
| 556 |
+
|
| 557 |
+
chatMessages.appendChild(loadingDiv)
|
| 558 |
+
scrollToBottom()
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
// Hide loading indicator
|
| 562 |
+
function hideLoadingIndicator() {
|
| 563 |
+
const loadingIndicator = document.getElementById("loading-indicator")
|
| 564 |
+
if (loadingIndicator) {
|
| 565 |
+
loadingIndicator.remove()
|
| 566 |
+
}
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
// Scroll to bottom of chat
|
| 570 |
+
function scrollToBottom() {
|
| 571 |
+
const chatMessages = document.getElementById("chat-messages")
|
| 572 |
+
chatMessages.scrollTop = chatMessages.scrollHeight
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
// Show notification
|
| 576 |
+
function showNotification(message, type = "success") {
|
| 577 |
+
const container = document.getElementById("notification-container")
|
| 578 |
+
|
| 579 |
+
// Create notification element
|
| 580 |
+
const notification = document.createElement("div")
|
| 581 |
+
|
| 582 |
+
// Set class based on notification type
|
| 583 |
+
if (type === "success") {
|
| 584 |
+
notification.className =
|
| 585 |
+
"notification mb-2 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded shadow-md"
|
| 586 |
+
} else if (type === "error") {
|
| 587 |
+
notification.className = "notification mb-2 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-md"
|
| 588 |
+
} else if (type === "warning") {
|
| 589 |
+
notification.className =
|
| 590 |
+
"notification mb-2 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 rounded shadow-md"
|
| 591 |
+
} else if (type === "info") {
|
| 592 |
+
notification.className =
|
| 593 |
+
"notification mb-2 bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 rounded shadow-md"
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
notification.innerHTML = message
|
| 597 |
+
|
| 598 |
+
// Add to container
|
| 599 |
+
container.appendChild(notification)
|
| 600 |
+
|
| 601 |
+
// Remove after 3 seconds
|
| 602 |
+
setTimeout(() => {
|
| 603 |
+
notification.style.opacity = "0"
|
| 604 |
+
notification.style.transform = "translateY(-20px)"
|
| 605 |
+
notification.style.transition = "opacity 0.5s, transform 0.5s"
|
| 606 |
+
|
| 607 |
+
setTimeout(() => {
|
| 608 |
+
notification.remove()
|
| 609 |
+
}, 500)
|
| 610 |
+
}, 3000)
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
// History panel functions
|
| 614 |
+
function toggleHistoryPanel() {
|
| 615 |
+
const historyPanel = document.getElementById("historyPanel")
|
| 616 |
+
if (historyPanel.classList.contains("translate-x-full")) {
|
| 617 |
+
historyPanel.classList.remove("translate-x-full")
|
| 618 |
+
} else {
|
| 619 |
+
historyPanel.classList.add("translate-x-full")
|
| 620 |
+
}
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
function addToHistory(action, details) {
|
| 624 |
+
// Get existing history or initialize empty array
|
| 625 |
+
const history = JSON.parse(localStorage.getItem("aiAppHistory") || "[]")
|
| 626 |
+
|
| 627 |
+
// Add new item
|
| 628 |
+
history.unshift({
|
| 629 |
+
action,
|
| 630 |
+
details,
|
| 631 |
+
timestamp: new Date().toISOString(),
|
| 632 |
+
})
|
| 633 |
+
|
| 634 |
+
// Keep only the last 20 items
|
| 635 |
+
if (history.length > 20) {
|
| 636 |
+
history.pop()
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
// Save back to localStorage
|
| 640 |
+
localStorage.setItem("aiAppHistory", JSON.stringify(history))
|
| 641 |
+
|
| 642 |
+
// Update UI if history panel exists
|
| 643 |
+
loadHistory()
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
function loadHistory() {
|
| 647 |
+
const historyItemsContainer = document.getElementById("historyItems")
|
| 648 |
+
if (!historyItemsContainer) return
|
| 649 |
+
|
| 650 |
+
// Clear existing items
|
| 651 |
+
historyItemsContainer.innerHTML = ""
|
| 652 |
+
|
| 653 |
+
// Get history from localStorage
|
| 654 |
+
const history = JSON.parse(localStorage.getItem("aiAppHistory") || "[]")
|
| 655 |
+
|
| 656 |
+
if (history.length === 0) {
|
| 657 |
+
historyItemsContainer.innerHTML = `<p class="text-gray-500 text-center">No history yet</p>`
|
| 658 |
+
return
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
// Add each history item
|
| 662 |
+
history.forEach((item) => {
|
| 663 |
+
const historyItem = document.createElement("div")
|
| 664 |
+
historyItem.className = "bg-gray-100 p-3 rounded-lg"
|
| 665 |
+
|
| 666 |
+
const date = new Date(item.timestamp)
|
| 667 |
+
const formattedDate = date.toLocaleDateString() + " " + date.toLocaleTimeString()
|
| 668 |
+
|
| 669 |
+
historyItem.innerHTML = `
|
| 670 |
+
<div class="flex justify-between items-start">
|
| 671 |
+
<h4 class="font-medium text-gray-800">${item.action}</h4>
|
| 672 |
+
<span class="text-xs text-gray-500">${formattedDate}</span>
|
| 673 |
+
</div>
|
| 674 |
+
<p class="text-sm text-gray-600 mt-1">${item.details}</p>
|
| 675 |
+
`
|
| 676 |
+
|
| 677 |
+
historyItemsContainer.appendChild(historyItem)
|
| 678 |
+
})
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
// Helper function to get language name from code
|
| 682 |
+
function getLanguageName(code) {
|
| 683 |
+
const languages = {
|
| 684 |
+
ar: "Arabic",
|
| 685 |
+
en: "English",
|
| 686 |
+
fr: "French",
|
| 687 |
+
es: "Spanish",
|
| 688 |
+
zh: "Chinese",
|
| 689 |
}
|
| 690 |
+
return languages[code] || code
|
| 691 |
+
}
|
frontend/style.css
CHANGED
|
@@ -1,286 +1,466 @@
|
|
| 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 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
.text-box-header {
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
font-weight: 500;
|
| 148 |
-
color: #4b5563;
|
| 149 |
-
border-bottom: 1px solid #e5e7eb;
|
| 150 |
-
font-size: 14px;
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
html.dark .text-box-header {
|
| 154 |
-
background-color: #374151;
|
| 155 |
-
color: #e5e7eb;
|
| 156 |
-
border-bottom: 1px solid #4b5563;
|
| 157 |
}
|
| 158 |
-
|
| 159 |
.text-content {
|
| 160 |
-
padding:
|
| 161 |
-
line-height: 1.6;
|
| 162 |
-
color: #1f2937;
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
html.dark .text-content {
|
| 166 |
-
color: #f3f4f6;
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
.original .text-box-header {
|
| 170 |
-
background-color: #eff6ff;
|
| 171 |
-
color: #1e40af;
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
html.dark .original .text-box-header {
|
| 175 |
-
background-color: #1e3a8a;
|
| 176 |
-
color: #93c5fd;
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
.translated .text-box-header {
|
| 180 |
-
background-color: #f0fdf4;
|
| 181 |
-
color: #166534;
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
html.dark .translated .text-box-header {
|
| 185 |
-
background-color: #14532d;
|
| 186 |
-
color: #86efac;
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
.question .text-box-header {
|
| 190 |
-
background-color: #fff7ed;
|
| 191 |
-
color: #9a3412;
|
| 192 |
-
}
|
| 193 |
-
|
| 194 |
-
html.dark .question .text-box-header {
|
| 195 |
-
background-color: #7c2d12;
|
| 196 |
-
color: #fdba74;
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
.answer .text-box-header {
|
| 200 |
-
background-color: #ecfdf5;
|
| 201 |
-
color: #065f46;
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
html.dark .answer .text-box-header {
|
| 205 |
-
background-color: #064e3b;
|
| 206 |
-
color: #6ee7b7;
|
| 207 |
-
}
|
| 208 |
-
|
| 209 |
-
.code-content {
|
| 210 |
-
padding: 12px 16px;
|
| 211 |
-
font-family: monospace;
|
| 212 |
-
white-space: pre-wrap;
|
| 213 |
-
background-color: #f8fafc;
|
| 214 |
-
color: #334155;
|
| 215 |
-
line-height: 1.5;
|
| 216 |
font-size: 14px;
|
| 217 |
-
overflow-x: auto;
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
html.dark .code-content {
|
| 221 |
-
background-color: #0f172a;
|
| 222 |
-
color: #e2e8f0;
|
| 223 |
-
}
|
| 224 |
-
|
| 225 |
-
.error-message {
|
| 226 |
-
color: #b91c1c;
|
| 227 |
-
padding: 12px;
|
| 228 |
-
background-color: #fee2e2;
|
| 229 |
-
border-radius: 6px;
|
| 230 |
-
font-weight: 500;
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
html.dark .error-message {
|
| 234 |
-
color: #fca5a5;
|
| 235 |
-
background-color: #7f1d1d;
|
| 236 |
}
|
| 237 |
-
|
| 238 |
-
/*
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
font-size: 13px;
|
| 243 |
-
}
|
| 244 |
-
|
| 245 |
-
.text-content {
|
| 246 |
-
padding: 10px 12px;
|
| 247 |
-
font-size: 14px;
|
| 248 |
-
}
|
| 249 |
-
}
|
| 250 |
-
|
| 251 |
-
/* Theme toggle button animation */
|
| 252 |
-
#theme-toggle {
|
| 253 |
-
cursor: pointer;
|
| 254 |
-
transition: transform 0.2s ease, background-color 0.2s ease;
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
#theme-toggle:hover {
|
| 258 |
-
background-color: rgba(255, 255, 255, 0.3);
|
| 259 |
-
}
|
| 260 |
-
|
| 261 |
-
#theme-toggle:active {
|
| 262 |
-
transform: scale(0.9);
|
| 263 |
}
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
|
| 268 |
}
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
|
|
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
transition: all 0.2s ease;
|
| 277 |
}
|
| 278 |
-
|
| 279 |
-
.
|
| 280 |
-
|
|
|
|
| 281 |
}
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
|
|
|
| 285 |
}
|
| 286 |
-
|
|
|
|
| 1 |
+
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap");
|
| 2 |
+
|
| 3 |
+
body {
|
| 4 |
+
font-family: "Poppins", Arial, sans-serif;
|
| 5 |
+
margin: 0;
|
| 6 |
+
padding: 0;
|
| 7 |
+
background-color: #f5f7fa;
|
| 8 |
+
transition: background-color 0.3s ease;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
/* Dark mode styles */
|
| 12 |
+
html.dark body {
|
| 13 |
+
background-color: #111827;
|
| 14 |
+
color: #f3f4f6;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
html.dark .app-content {
|
| 18 |
+
border-left: 1px solid #374151;
|
| 19 |
+
border-right: 1px solid #374151;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
html.dark main,
|
| 23 |
+
html.dark #chat-messages,
|
| 24 |
+
html.dark .feature-btn:not(.active),
|
| 25 |
+
html.dark .user-message,
|
| 26 |
+
html.dark textarea,
|
| 27 |
+
html.dark select {
|
| 28 |
+
background-color: #1f2937;
|
| 29 |
+
color: #f3f4f6;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
html.dark .feature-btn:not(.active) {
|
| 33 |
+
border-color: #374151;
|
| 34 |
+
color: #d1d5db;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
html.dark .feature-btn:hover:not(.active) {
|
| 38 |
+
background-color: #374151;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
html.dark .assistant-message {
|
| 42 |
+
background-color: #1f2937;
|
| 43 |
+
border-color: #374151;
|
| 44 |
+
border-left: 3px solid #7c3aed;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
html.dark .user-message {
|
| 48 |
+
border-color: #374151;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
html.dark .border-gray-100,
|
| 52 |
+
html.dark .border-gray-200,
|
| 53 |
+
html.dark .border-gray-300 {
|
| 54 |
+
border-color: #374151;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
html.dark .text-gray-700,
|
| 58 |
+
html.dark .text-gray-800,
|
| 59 |
+
html.dark .text-gray-900 {
|
| 60 |
+
color: #e5e7eb;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
html.dark .text-gray-500,
|
| 64 |
+
html.dark .text-gray-600 {
|
| 65 |
+
color: #9ca3af;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
html.dark .bg-white {
|
| 69 |
+
background-color: #1f2937;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
html.dark .bg-gray-100 {
|
| 73 |
+
background-color: #374151;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
html.dark .code-content {
|
| 77 |
+
background-color: #111827;
|
| 78 |
+
border-color: #374151;
|
| 79 |
+
color: #e5e7eb;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
html.dark .message-action-btn {
|
| 83 |
+
background-color: #374151;
|
| 84 |
+
color: #9ca3af;
|
| 85 |
+
border-color: #4b5563;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
html.dark .message-action-btn:hover {
|
| 89 |
+
background-color: #4b5563;
|
| 90 |
+
color: #e5e7eb;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
html.dark #historyPanel {
|
| 94 |
+
background-color: #1f2937;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
html.dark #historyItems .bg-gray-100 {
|
| 98 |
+
background-color: #374151;
|
| 99 |
+
border-color: #4b5563;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
html.dark .file-preview {
|
| 103 |
+
background-color: #374151;
|
| 104 |
+
border-color: #4b5563;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
/* App container with borders */
|
| 108 |
+
.app-container {
|
| 109 |
+
display: flex;
|
| 110 |
+
flex-direction: column;
|
| 111 |
+
height: 100vh;
|
| 112 |
+
padding: 20px;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.app-content {
|
| 116 |
+
max-width: 1200px;
|
| 117 |
+
width: 100%;
|
| 118 |
+
margin: 0 auto;
|
| 119 |
+
display: flex;
|
| 120 |
+
flex-direction: column;
|
| 121 |
+
height: 100%;
|
| 122 |
+
border-radius: 12px;
|
| 123 |
+
overflow: hidden;
|
| 124 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
| 125 |
+
border-left: 1px solid #e5e7eb;
|
| 126 |
+
border-right: 1px solid #e5e7eb;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/* Feature buttons styling */
|
| 130 |
+
.feature-buttons {
|
| 131 |
+
-ms-overflow-style: none; /* Hide scrollbar in IE and Edge */
|
| 132 |
+
scrollbar-width: none; /* Hide scrollbar in Firefox */
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.feature-buttons::-webkit-scrollbar {
|
| 136 |
+
display: none; /* Hide scrollbar in Chrome, Safari and Opera */
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.feature-btn {
|
| 140 |
+
display: flex;
|
| 141 |
+
align-items: center;
|
| 142 |
+
gap: 0.5rem;
|
| 143 |
+
padding: 0.5rem 1rem;
|
| 144 |
+
border-radius: 0.5rem;
|
| 145 |
+
font-size: 0.875rem;
|
| 146 |
+
font-weight: 500;
|
| 147 |
+
color: #4b5563;
|
| 148 |
+
background-color: #f9fafb;
|
| 149 |
+
transition: all 0.2s ease;
|
| 150 |
+
white-space: nowrap;
|
| 151 |
+
border: 1px solid #f0f0f0;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.feature-btn:hover {
|
| 155 |
+
background-color: #f3f4f6;
|
| 156 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.feature-btn.active {
|
| 160 |
+
color: #ffffff;
|
| 161 |
+
background-color: #7c3aed;
|
| 162 |
+
border: 1px solid #7c3aed;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
/* Chat message styling */
|
| 166 |
+
.user-message {
|
| 167 |
+
background-color: #f9fafb;
|
| 168 |
+
border-radius: 0.75rem 0.75rem 0 0.75rem;
|
| 169 |
+
border: 1px solid #f0f0f0;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.assistant-message {
|
| 173 |
+
background-color: #ffffff;
|
| 174 |
+
border-radius: 0.75rem 0.75rem 0.75rem 0;
|
| 175 |
+
border-left: 3px solid #7c3aed;
|
| 176 |
+
border-top: 1px solid #f0f0f0;
|
| 177 |
+
border-right: 1px solid #f0f0f0;
|
| 178 |
+
border-bottom: 1px solid #f0f0f0;
|
| 179 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
/* Auto-resize textarea */
|
| 183 |
+
textarea {
|
| 184 |
+
overflow-y: hidden;
|
| 185 |
+
min-height: 44px;
|
| 186 |
+
max-height: 200px;
|
| 187 |
+
background-color: #ffffff;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/* Custom scrollbar */
|
| 191 |
+
::-webkit-scrollbar {
|
| 192 |
+
width: 8px;
|
| 193 |
+
height: 8px;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
::-webkit-scrollbar-track {
|
| 197 |
+
background: #ffffff;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
::-webkit-scrollbar-thumb {
|
| 201 |
+
background: #c4b5fd;
|
| 202 |
+
border-radius: 10px;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
::-webkit-scrollbar-thumb:hover {
|
| 206 |
+
background: #a78bfa;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
/* Animation for notifications */
|
| 210 |
+
@keyframes fadeIn {
|
| 211 |
+
from {
|
| 212 |
+
opacity: 0;
|
| 213 |
+
transform: translateY(-10px);
|
| 214 |
+
}
|
| 215 |
+
to {
|
| 216 |
+
opacity: 1;
|
| 217 |
+
transform: translateY(0);
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
.notification {
|
| 222 |
+
animation: fadeIn 0.3s ease-out forwards;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
/* Language selection styles */
|
| 226 |
+
.language-selector {
|
| 227 |
+
display: grid;
|
| 228 |
+
grid-template-columns: 1fr 1fr;
|
| 229 |
+
gap: 0.75rem;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
select {
|
| 233 |
+
appearance: none;
|
| 234 |
+
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
| 235 |
+
background-repeat: no-repeat;
|
| 236 |
+
background-position: right 0.7rem center;
|
| 237 |
+
background-size: 1em;
|
| 238 |
+
padding-right: 2.5rem;
|
| 239 |
+
background-color: #ffffff;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/* Formatted output styles */
|
| 243 |
+
.formatted-output {
|
| 244 |
+
display: flex;
|
| 245 |
+
flex-direction: column;
|
| 246 |
+
gap: 16px;
|
| 247 |
+
width: 100%;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.result-header {
|
| 251 |
+
margin-bottom: 8px;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.result-header h3 {
|
| 255 |
+
font-size: 18px;
|
| 256 |
+
font-weight: 600;
|
| 257 |
+
color: #4b5563;
|
| 258 |
+
margin: 0;
|
| 259 |
+
padding-bottom: 8px;
|
| 260 |
+
border-bottom: 1px solid #f0f0f0;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.text-box {
|
| 264 |
+
background-color: white;
|
| 265 |
+
border-radius: 8px;
|
| 266 |
+
border: 1px solid #f0f0f0;
|
| 267 |
+
overflow: hidden;
|
| 268 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.text-box-header {
|
| 272 |
+
background-color: #f9fafb;
|
| 273 |
+
padding: 8px 12px;
|
| 274 |
+
font-weight: 500;
|
| 275 |
+
color: #4b5563;
|
| 276 |
+
border-bottom: 1px solid #f0f0f0;
|
| 277 |
+
font-size: 14px;
|
| 278 |
+
display: flex;
|
| 279 |
+
justify-content: space-between;
|
| 280 |
+
align-items: center;
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.text-content {
|
| 284 |
+
padding: 12px 16px;
|
| 285 |
+
line-height: 1.6;
|
| 286 |
+
color: #1f2937;
|
| 287 |
+
background-color: #ffffff;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.code-content {
|
| 291 |
+
padding: 12px 16px;
|
| 292 |
+
font-family: monospace;
|
| 293 |
+
white-space: pre-wrap;
|
| 294 |
+
background-color: #f9fafb;
|
| 295 |
+
color: #334155;
|
| 296 |
+
line-height: 1.5;
|
| 297 |
+
font-size: 14px;
|
| 298 |
+
overflow-x: auto;
|
| 299 |
+
border: 1px solid #f0f0f0;
|
| 300 |
+
border-radius: 6px;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
.error-message {
|
| 304 |
+
color: #b91c1c;
|
| 305 |
+
padding: 12px;
|
| 306 |
+
background-color: #fee2e2;
|
| 307 |
+
border-radius: 6px;
|
| 308 |
+
font-weight: 500;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
/* File upload preview */
|
| 312 |
+
.file-preview {
|
| 313 |
+
display: flex;
|
| 314 |
+
align-items: center;
|
| 315 |
+
gap: 0.5rem;
|
| 316 |
+
padding: 0.5rem;
|
| 317 |
+
background-color: #f9fafb;
|
| 318 |
+
border-radius: 0.5rem;
|
| 319 |
+
margin-top: 0.5rem;
|
| 320 |
+
border: 1px solid #f0f0f0;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.file-preview-name {
|
| 324 |
+
font-size: 0.875rem;
|
| 325 |
+
color: #4b5563;
|
| 326 |
+
flex: 1;
|
| 327 |
+
white-space: nowrap;
|
| 328 |
+
overflow: hidden;
|
| 329 |
+
text-overflow: ellipsis;
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
.file-preview-remove {
|
| 333 |
+
color: #ef4444;
|
| 334 |
+
cursor: pointer;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
/* Loading spinner */
|
| 338 |
+
.spinner {
|
| 339 |
+
border: 3px solid rgba(124, 58, 237, 0.3);
|
| 340 |
+
border-radius: 50%;
|
| 341 |
+
border-top: 3px solid #7c3aed;
|
| 342 |
+
width: 24px;
|
| 343 |
+
height: 24px;
|
| 344 |
+
animation: spin 1s linear infinite;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
@keyframes spin {
|
| 348 |
+
0% {
|
| 349 |
+
transform: rotate(0deg);
|
| 350 |
+
}
|
| 351 |
+
100% {
|
| 352 |
+
transform: rotate(360deg);
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
/* Message actions */
|
| 357 |
+
.message-actions {
|
| 358 |
+
display: flex;
|
| 359 |
+
gap: 0.5rem;
|
| 360 |
+
margin-top: 0.5rem;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.message-action-btn {
|
| 364 |
+
padding: 0.25rem 0.5rem;
|
| 365 |
+
font-size: 0.75rem;
|
| 366 |
+
color: #6b7280;
|
| 367 |
+
background-color: #f9fafb;
|
| 368 |
+
border-radius: 0.25rem;
|
| 369 |
+
transition: all 0.2s ease;
|
| 370 |
+
border: 1px solid #f0f0f0;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
.message-action-btn:hover {
|
| 374 |
+
background-color: #f3f4f6;
|
| 375 |
+
color: #4b5563;
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
/* History panel */
|
| 379 |
+
#historyPanel {
|
| 380 |
+
background-color: #ffffff;
|
| 381 |
+
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.05);
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
#historyItems .bg-gray-100 {
|
| 385 |
+
background-color: #f9fafb;
|
| 386 |
+
border: 1px solid #f0f0f0;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
/* Theme toggle animation */
|
| 390 |
+
#theme-toggle {
|
| 391 |
+
transition: transform 0.3s ease;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
#theme-toggle:hover {
|
| 395 |
+
transform: rotate(15deg);
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
/* Responsive adjustments */
|
| 399 |
+
@media (max-width: 1024px) {
|
| 400 |
+
.app-container {
|
| 401 |
+
padding: 15px;
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
@media (max-width: 768px) {
|
| 406 |
+
.app-container {
|
| 407 |
+
padding: 10px;
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
.feature-btn {
|
| 411 |
+
padding: 0.4rem 0.8rem;
|
| 412 |
+
font-size: 0.8rem;
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
@media (max-width: 640px) {
|
| 417 |
+
.app-container {
|
| 418 |
+
padding: 5px;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
.feature-btn {
|
| 422 |
+
padding: 0.375rem 0.75rem;
|
| 423 |
+
font-size: 0.75rem;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
.text-box-header {
|
| 427 |
+
padding: 6px 10px;
|
| 428 |
+
font-size: 13px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
}
|
| 430 |
+
|
| 431 |
.text-content {
|
| 432 |
+
padding: 10px 12px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
font-size: 14px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
}
|
| 435 |
+
|
| 436 |
+
/* Make sure the chat form is properly sized on mobile */
|
| 437 |
+
#chat-form {
|
| 438 |
+
flex-direction: column;
|
| 439 |
+
gap: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
}
|
| 441 |
+
|
| 442 |
+
#chat-form button {
|
| 443 |
+
align-self: flex-end;
|
|
|
|
| 444 |
}
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
/* Fix for very small screens */
|
| 448 |
+
@media (max-width: 380px) {
|
| 449 |
+
.app-container {
|
| 450 |
+
padding: 0;
|
| 451 |
}
|
| 452 |
+
|
| 453 |
+
.app-content {
|
| 454 |
+
border-radius: 0;
|
|
|
|
| 455 |
}
|
| 456 |
+
|
| 457 |
+
.feature-btn {
|
| 458 |
+
padding: 0.25rem 0.5rem;
|
| 459 |
+
font-size: 0.7rem;
|
| 460 |
}
|
| 461 |
+
|
| 462 |
+
.feature-btn svg {
|
| 463 |
+
width: 16px;
|
| 464 |
+
height: 16px;
|
| 465 |
}
|
| 466 |
+
}
|