jinruiyang Claude Opus 4.5 commited on
Commit ·
6287ed2
1
Parent(s): 83ec98a
Add multilingual frontend with full i18n support
Browse filesFeatures:
- New frontend UI with UAE-themed design (emerald/gold colors)
- Full multilingual support for EN, AR (RTL), and CN
- Category-based search with example queries per language
- Translation API integration for result content
- Google Fonts: Roboto (EN), Noto Sans Arabic (AR), Noto Sans SC (CN)
- Responsive design with Tailwind CSS
- Help modal with translated content
- Real user feedback-based default queries
UI improvements:
- Centered search on home, top bar on results
- Gold/bronze themed buttons and accents
- Falcon icon branding
- Rating system for relevance and helpfulness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- .gitattributes +3 -0
- .gitignore +26 -0
- app.py +6 -13
- frontend/assets/background-home.png +3 -0
- frontend/assets/background.jpg +3 -0
- frontend/assets/falcon.png +3 -0
- frontend/css/styles.css +316 -0
- frontend/index.html +230 -0
- frontend/js/app.js +1033 -0
- ir/demo.py +464 -224
- requirements.txt +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
venv/
|
| 9 |
+
.venv/
|
| 10 |
+
|
| 11 |
+
# IDE
|
| 12 |
+
.idea/
|
| 13 |
+
.vscode/
|
| 14 |
+
*.swp
|
| 15 |
+
*.swo
|
| 16 |
+
|
| 17 |
+
# OS
|
| 18 |
+
.DS_Store
|
| 19 |
+
Thumbs.db
|
| 20 |
+
|
| 21 |
+
# Temporary files
|
| 22 |
+
*.log
|
| 23 |
+
*.tmp
|
| 24 |
+
|
| 25 |
+
# Local data (if any)
|
| 26 |
+
*.pkl.local
|
app.py
CHANGED
|
@@ -1,17 +1,10 @@
|
|
| 1 |
"""
|
| 2 |
UAE Knowledge System Demo - HuggingFace Spaces Entry Point
|
|
|
|
| 3 |
"""
|
| 4 |
-
import
|
| 5 |
-
sys.path.insert(0, ".")
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
print("
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
print("Loading retriever (this may take a minute)...")
|
| 13 |
-
get_retriever()
|
| 14 |
-
|
| 15 |
-
print("Starting demo...")
|
| 16 |
-
demo = create_demo()
|
| 17 |
-
demo.launch()
|
|
|
|
| 1 |
"""
|
| 2 |
UAE Knowledge System Demo - HuggingFace Spaces Entry Point
|
| 3 |
+
Using FastAPI with custom HTML frontend
|
| 4 |
"""
|
| 5 |
+
import uvicorn
|
|
|
|
| 6 |
|
| 7 |
+
if __name__ == "__main__":
|
| 8 |
+
print("Starting UAE Knowledge System...")
|
| 9 |
+
print("Open http://localhost:7860 in your browser")
|
| 10 |
+
uvicorn.run("backend.api:app", host="0.0.0.0", port=7860, reload=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/assets/background-home.png
ADDED
|
Git LFS Details
|
frontend/assets/background.jpg
ADDED
|
Git LFS Details
|
frontend/assets/falcon.png
ADDED
|
Git LFS Details
|
frontend/css/styles.css
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* UAE Knowledge System - Styles
|
| 3 |
+
* Version: 2.3.0
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
/* ============================================
|
| 7 |
+
BASE STYLES
|
| 8 |
+
============================================ */
|
| 9 |
+
body {
|
| 10 |
+
/* Font stack: Roboto (EN), Noto Sans Arabic (AR), Noto Sans SC (CN) */
|
| 11 |
+
font-family: 'Roboto', 'Noto Sans Arabic', 'Noto Sans SC', sans-serif;
|
| 12 |
+
background-color: #F8F5F0;
|
| 13 |
+
background-image: url('../assets/background.jpg');
|
| 14 |
+
background-position: right top;
|
| 15 |
+
background-size: cover;
|
| 16 |
+
background-repeat: no-repeat;
|
| 17 |
+
background-attachment: fixed;
|
| 18 |
+
color: #2D3748;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/* Language-specific font optimization */
|
| 22 |
+
body[dir="rtl"] {
|
| 23 |
+
font-family: 'Noto Sans Arabic', 'Roboto', sans-serif;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
body.lang-cn {
|
| 27 |
+
font-family: 'Noto Sans SC', 'Roboto', sans-serif;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* ============================================
|
| 31 |
+
COLOR UTILITIES
|
| 32 |
+
============================================ */
|
| 33 |
+
.uae-emerald-deep { background-color: #003d1c; }
|
| 34 |
+
.uae-emerald-text { color: #003d1c; }
|
| 35 |
+
|
| 36 |
+
.gold-fade {
|
| 37 |
+
background: linear-gradient(90deg, #b38e3f 0%, #d4af37 50%, #b38e3f 100%);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.gold-button-slender {
|
| 41 |
+
background: linear-gradient(90deg, #b38e3f 0%, #d4af37 50%, #b38e3f 100%);
|
| 42 |
+
padding-top: 0.35rem;
|
| 43 |
+
padding-bottom: 0.35rem;
|
| 44 |
+
display: flex;
|
| 45 |
+
align-items: center;
|
| 46 |
+
justify-content: center;
|
| 47 |
+
cursor: pointer;
|
| 48 |
+
transition: all 0.2s ease;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.gold-button-slender:hover {
|
| 52 |
+
filter: brightness(1.1);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* ============================================
|
| 56 |
+
FALCON LOGO POSITIONING
|
| 57 |
+
============================================ */
|
| 58 |
+
.falcon-overlap-container {
|
| 59 |
+
position: absolute;
|
| 60 |
+
left: 50%;
|
| 61 |
+
top: 60px;
|
| 62 |
+
transform: translate(-50%, -50%);
|
| 63 |
+
z-index: 999;
|
| 64 |
+
pointer-events: none;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.falcon-icon {
|
| 68 |
+
height: 100px;
|
| 69 |
+
filter: drop-shadow(0 8px 15px rgba(0,0,0,0.4));
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/* ============================================
|
| 73 |
+
RESULT CARD STYLES
|
| 74 |
+
============================================ */
|
| 75 |
+
.summary-box {
|
| 76 |
+
background-color: #FDFBF7;
|
| 77 |
+
border: 1px solid #D4AF37;
|
| 78 |
+
border-radius: 4px;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.result-card {
|
| 82 |
+
transition: all 0.2s ease;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.result-card:hover {
|
| 86 |
+
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* ============================================
|
| 90 |
+
RATING BUTTONS
|
| 91 |
+
============================================ */
|
| 92 |
+
.rating-btn {
|
| 93 |
+
cursor: pointer;
|
| 94 |
+
transition: all 0.15s ease;
|
| 95 |
+
opacity: 0.4;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.rating-btn:hover {
|
| 99 |
+
opacity: 0.7;
|
| 100 |
+
transform: scale(1.1);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.rating-btn.active {
|
| 104 |
+
opacity: 1;
|
| 105 |
+
transform: scale(1.15);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/* ============================================
|
| 109 |
+
MODAL STYLES
|
| 110 |
+
============================================ */
|
| 111 |
+
.modal-overlay {
|
| 112 |
+
position: fixed;
|
| 113 |
+
inset: 0;
|
| 114 |
+
background: rgba(0, 0, 0, 0.5);
|
| 115 |
+
display: flex;
|
| 116 |
+
align-items: center;
|
| 117 |
+
justify-content: center;
|
| 118 |
+
z-index: 1000;
|
| 119 |
+
opacity: 0;
|
| 120 |
+
visibility: hidden;
|
| 121 |
+
transition: all 0.3s ease;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.modal-overlay.active {
|
| 125 |
+
opacity: 1;
|
| 126 |
+
visibility: visible;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.modal-content {
|
| 130 |
+
background: white;
|
| 131 |
+
border-radius: 12px;
|
| 132 |
+
max-width: 600px;
|
| 133 |
+
max-height: 80vh;
|
| 134 |
+
overflow-y: auto;
|
| 135 |
+
transform: scale(0.9);
|
| 136 |
+
transition: transform 0.3s ease;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.modal-overlay.active .modal-content {
|
| 140 |
+
transform: scale(1);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/* ============================================
|
| 144 |
+
LOADING STATES
|
| 145 |
+
============================================ */
|
| 146 |
+
.loading {
|
| 147 |
+
opacity: 0.6;
|
| 148 |
+
pointer-events: none;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.spinner {
|
| 152 |
+
animation: spin 1s linear infinite;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
@keyframes spin {
|
| 156 |
+
from { transform: rotate(0deg); }
|
| 157 |
+
to { transform: rotate(360deg); }
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/* ============================================
|
| 161 |
+
CATEGORY DROPDOWN
|
| 162 |
+
============================================ */
|
| 163 |
+
.category-dropdown {
|
| 164 |
+
position: relative;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.category-dropdown-menu {
|
| 168 |
+
position: absolute;
|
| 169 |
+
top: 100%;
|
| 170 |
+
right: 0;
|
| 171 |
+
background: white;
|
| 172 |
+
border: 1px solid #e5e7eb;
|
| 173 |
+
border-radius: 8px;
|
| 174 |
+
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
|
| 175 |
+
min-width: 280px;
|
| 176 |
+
z-index: 100;
|
| 177 |
+
opacity: 0;
|
| 178 |
+
visibility: hidden;
|
| 179 |
+
transform: translateY(-10px);
|
| 180 |
+
transition: all 0.2s ease;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.category-dropdown-menu.active {
|
| 184 |
+
opacity: 1;
|
| 185 |
+
visibility: visible;
|
| 186 |
+
transform: translateY(0);
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.category-option {
|
| 190 |
+
padding: 10px 16px;
|
| 191 |
+
cursor: pointer;
|
| 192 |
+
font-size: 14px;
|
| 193 |
+
color: #1f2937;
|
| 194 |
+
transition: background 0.15s ease;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.category-option:hover {
|
| 198 |
+
background: #f3f4f6;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.category-option.selected {
|
| 202 |
+
background: #003d1c;
|
| 203 |
+
color: white;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
/* ============================================
|
| 207 |
+
SCROLLBAR
|
| 208 |
+
============================================ */
|
| 209 |
+
::-webkit-scrollbar {
|
| 210 |
+
width: 6px;
|
| 211 |
+
height: 6px;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
::-webkit-scrollbar-track {
|
| 215 |
+
background: #f1f1f1;
|
| 216 |
+
border-radius: 3px;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
::-webkit-scrollbar-thumb {
|
| 220 |
+
background: #003d1c;
|
| 221 |
+
border-radius: 3px;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
::-webkit-scrollbar-thumb:hover {
|
| 225 |
+
background: #002b14;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
/* ============================================
|
| 229 |
+
LANGUAGE TOGGLE
|
| 230 |
+
============================================ */
|
| 231 |
+
.lang-toggle {
|
| 232 |
+
opacity: 0.7;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.lang-toggle:hover {
|
| 236 |
+
opacity: 1;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
.lang-toggle.active {
|
| 240 |
+
opacity: 1;
|
| 241 |
+
background: rgba(255, 255, 255, 0.25);
|
| 242 |
+
font-weight: 700;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
/* ============================================
|
| 246 |
+
HOME MODE - Centered Search
|
| 247 |
+
============================================ */
|
| 248 |
+
#search-header.home-mode {
|
| 249 |
+
position: fixed;
|
| 250 |
+
top: 50%;
|
| 251 |
+
left: 50%;
|
| 252 |
+
transform: translate(-50%, -50%);
|
| 253 |
+
width: 90%;
|
| 254 |
+
max-width: 920px;
|
| 255 |
+
padding: 0 1.5rem;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
#search-header.home-mode + main {
|
| 259 |
+
display: none;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
/* Results mode - search at top with green background */
|
| 263 |
+
#search-header:not(.home-mode) {
|
| 264 |
+
padding-top: 15px;
|
| 265 |
+
padding-bottom: 25px;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
/* Green banner class for results mode */
|
| 269 |
+
.uae-emerald-condensed {
|
| 270 |
+
background-color: #003d1c !important;
|
| 271 |
+
position: relative;
|
| 272 |
+
z-index: 10;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/* Note text white on green background in results mode */
|
| 276 |
+
.uae-emerald-condensed #first-query-note {
|
| 277 |
+
color: white !important;
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
/* Adjust falcon position for home mode */
|
| 281 |
+
body.home-view .falcon-overlap-container {
|
| 282 |
+
position: fixed;
|
| 283 |
+
top: calc(50% - 120px);
|
| 284 |
+
left: 50%;
|
| 285 |
+
transform: translate(-50%, -50%);
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
/* Larger falcon on home page */
|
| 289 |
+
body.home-view .falcon-icon {
|
| 290 |
+
height: 130px;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
/* Hide footer in home mode */
|
| 294 |
+
body.home-view footer {
|
| 295 |
+
position: fixed;
|
| 296 |
+
bottom: 0;
|
| 297 |
+
left: 0;
|
| 298 |
+
right: 0;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
/* ============================================
|
| 302 |
+
RESPONSIVE
|
| 303 |
+
============================================ */
|
| 304 |
+
@media (max-width: 768px) {
|
| 305 |
+
.falcon-icon {
|
| 306 |
+
height: 70px;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.falcon-overlap-container {
|
| 310 |
+
top: 50px;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
body.home-view .falcon-overlap-container {
|
| 314 |
+
top: calc(50% - 100px);
|
| 315 |
+
}
|
| 316 |
+
}
|
frontend/index.html
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>UAE Knowledge System</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<!-- Google Fonts: Roboto (EN), Noto Sans Arabic (AR), Noto Sans SC (CN) -->
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
|
| 10 |
+
<link rel="stylesheet" href="css/styles.css">
|
| 11 |
+
</head>
|
| 12 |
+
<body>
|
| 13 |
+
|
| 14 |
+
<!-- ========================================
|
| 15 |
+
TOP GOLD BAR
|
| 16 |
+
======================================== -->
|
| 17 |
+
<div class="gold-fade h-[60px] shadow-md relative z-20 flex items-center">
|
| 18 |
+
<div class="container mx-auto max-w-5xl px-6 flex justify-between items-center text-emerald-950">
|
| 19 |
+
<div class="flex items-center gap-3">
|
| 20 |
+
<span class="text-4xl leading-none mt-2">🇦🇪</span>
|
| 21 |
+
<h1 class="text-[20px] font-medium tracking-wide">UAE Knowledge System</h1>
|
| 22 |
+
</div>
|
| 23 |
+
<div class="flex items-center gap-4">
|
| 24 |
+
<span id="stats-text" class="text-[16px] opacity-70">Loading...</span>
|
| 25 |
+
<div class="flex gap-4 items-center">
|
| 26 |
+
<!-- Language Toggle -->
|
| 27 |
+
<div class="flex gap-1 text-[16px] font-medium">
|
| 28 |
+
<button onclick="setLanguage('en')" id="lang-en" class="lang-toggle px-2 py-1 rounded hover:bg-white/20 transition">EN</button>
|
| 29 |
+
<span class="opacity-40">|</span>
|
| 30 |
+
<button onclick="setLanguage('ar')" id="lang-ar" class="lang-toggle px-2 py-1 rounded hover:bg-white/20 transition">AR</button>
|
| 31 |
+
<span class="opacity-40">|</span>
|
| 32 |
+
<button onclick="setLanguage('cn')" id="lang-cn" class="lang-toggle px-2 py-1 rounded hover:bg-white/20 transition">CN</button>
|
| 33 |
+
</div>
|
| 34 |
+
<button id="help-btn" class="text-[16px] font-medium uppercase hover:text-white transition">Help</button>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<!-- ========================================
|
| 41 |
+
FALCON LOGO (overlapping)
|
| 42 |
+
======================================== -->
|
| 43 |
+
<div class="falcon-overlap-container">
|
| 44 |
+
<img src="assets/falcon.png" class="falcon-icon" alt="UAE Falcon">
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<!-- ========================================
|
| 48 |
+
HEADER WITH SEARCH (centered on home, top after search)
|
| 49 |
+
======================================== -->
|
| 50 |
+
<header id="search-header" class="home-mode relative z-10">
|
| 51 |
+
<div class="container mx-auto max-w-5xl px-6 text-center">
|
| 52 |
+
<div class="flex shadow-xl rounded-2xl h-12 bg-white relative z-10 border border-gray-200">
|
| 53 |
+
<!-- Search Input -->
|
| 54 |
+
<div class="flex-grow flex items-center px-6">
|
| 55 |
+
<input type="text" id="search-input"
|
| 56 |
+
class="w-full text-emerald-950 outline-none text-[18px]"
|
| 57 |
+
placeholder="Ask about UAE governance, leadership, or policies...">
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<!-- Category Dropdown -->
|
| 61 |
+
<div class="category-dropdown relative">
|
| 62 |
+
<button id="category-btn" class="bg-gray-50 flex items-center px-4 h-full text-emerald-800 text-[18px] font-normal ltr:border-l rtl:border-r hover:bg-gray-100 transition">
|
| 63 |
+
<span id="category-text">Select Category</span>
|
| 64 |
+
<span class="ml-2 opacity-40">▼</span>
|
| 65 |
+
</button>
|
| 66 |
+
<div id="category-dropdown" class="category-dropdown-menu">
|
| 67 |
+
<div class="category-option" onclick="selectCategory('1')">1. State Basics</div>
|
| 68 |
+
<div class="category-option" onclick="selectCategory('2')">2. Constitutional Framework</div>
|
| 69 |
+
<div class="category-option" onclick="selectCategory('3')">3. Current Leadership</div>
|
| 70 |
+
<div class="category-option" onclick="selectCategory('4')">4. Royal Families</div>
|
| 71 |
+
<div class="category-option" onclick="selectCategory('5')">5. Foreign Policy</div>
|
| 72 |
+
<div class="category-option" onclick="selectCategory('6')">6. Controversial Issues</div>
|
| 73 |
+
<div class="category-option" onclick="selectCategory('7')">7. Key Entities</div>
|
| 74 |
+
<div class="category-option" onclick="selectCategory('8')">8. Social-Cultural Norms</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<!-- Search Button -->
|
| 79 |
+
<button id="search-btn" class="gold-fade hover:brightness-110 px-8 text-emerald-950 ltr:rounded-r-2xl rtl:rounded-l-2xl flex items-center justify-center transition">
|
| 80 |
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 81 |
+
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
| 82 |
+
</svg>
|
| 83 |
+
</button>
|
| 84 |
+
</div>
|
| 85 |
+
<p id="first-query-note" class="text-[14px] text-[#003d1c] mt-3 text-center">
|
| 86 |
+
Note: First query may take a few seconds to load the index. Subsequent queries will be fast.
|
| 87 |
+
</p>
|
| 88 |
+
</div>
|
| 89 |
+
</header>
|
| 90 |
+
|
| 91 |
+
<!-- ========================================
|
| 92 |
+
MAIN CONTENT (hidden until search)
|
| 93 |
+
======================================== -->
|
| 94 |
+
<main id="results-section" class="container mx-auto max-w-5xl py-10 px-6 hidden">
|
| 95 |
+
<!-- Results Header -->
|
| 96 |
+
<div class="flex justify-between items-end border-b border-gray-200 pb-2 mb-8">
|
| 97 |
+
<h2 class="text-emerald-900 font-medium text-[18px] tracking-widest flex items-center gap-3">
|
| 98 |
+
<span class="text-xl">🦅</span> RETRIEVAL RESULTS
|
| 99 |
+
</h2>
|
| 100 |
+
<span id="results-count" class="text-emerald-700 font-medium text-[16px]">0 ITEMS FOUND</span>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
+
<!-- Results Container -->
|
| 104 |
+
<div id="results-container"></div>
|
| 105 |
+
|
| 106 |
+
<!-- Feedback Section -->
|
| 107 |
+
<div class="bg-[#F3EDE2]/50 rounded-lg border border-amber-600/10 px-8 py-5 mt-8 flex flex-col md:flex-row gap-6 items-center">
|
| 108 |
+
<div id="feedback-label" class="flex items-center gap-3 text-emerald-900 text-[20px] font-medium uppercase tracking-wider">
|
| 109 |
+
<span>💬</span> FEEDBACK
|
| 110 |
+
</div>
|
| 111 |
+
<div class="flex-grow w-full">
|
| 112 |
+
<input type="text" id="feedback-input"
|
| 113 |
+
class="w-full bg-white border border-gray-200 rounded px-5 py-2 text-sm text-emerald-950 shadow-inner outline-none focus:border-amber-500"
|
| 114 |
+
placeholder="Any comments about the results? (optional)">
|
| 115 |
+
</div>
|
| 116 |
+
<button id="submit-feedback-btn" class="bg-emerald-900 text-white text-[20px] px-8 py-2 font-medium rounded hover:bg-emerald-800 shadow-xl transition">
|
| 117 |
+
Submit
|
| 118 |
+
</button>
|
| 119 |
+
</div>
|
| 120 |
+
</main>
|
| 121 |
+
|
| 122 |
+
<!-- ========================================
|
| 123 |
+
HELP MODAL
|
| 124 |
+
======================================== -->
|
| 125 |
+
<div id="help-modal" class="modal-overlay">
|
| 126 |
+
<div class="modal-content w-full mx-4 md:mx-0">
|
| 127 |
+
<!-- Header -->
|
| 128 |
+
<div class="uae-emerald-deep text-white px-6 py-4 flex justify-between items-center">
|
| 129 |
+
<h3 id="help-modal-title" class="font-bold text-lg">📚 About UAE Knowledge System</h3>
|
| 130 |
+
<button onclick="closeModal('help-modal')" class="text-2xl hover:opacity-70">×</button>
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
<!-- Body -->
|
| 134 |
+
<div class="p-6 space-y-6">
|
| 135 |
+
<!-- System Description -->
|
| 136 |
+
<div>
|
| 137 |
+
<h4 id="help-what-is-title" class="font-bold text-emerald-900 mb-2">What is this system?</h4>
|
| 138 |
+
<p id="help-what-is-text" class="text-sm text-gray-700 leading-relaxed">
|
| 139 |
+
The UAE Knowledge System is an <strong>Information Retrieval (IR) system</strong> designed to retrieve
|
| 140 |
+
relevant knowledge about the United Arab Emirates from a curated knowledge base. This is
|
| 141 |
+
<strong>NOT an LLM chatbot</strong> - it retrieves pre-written factual content.
|
| 142 |
+
</p>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<!-- Important Notice -->
|
| 146 |
+
<div class="bg-amber-50 border-l-4 border-amber-500 p-4">
|
| 147 |
+
<h4 id="help-notice-title" class="font-bold text-amber-800 mb-2">⚠️ Important Notice</h4>
|
| 148 |
+
<p id="help-notice-text" class="text-sm text-amber-900">
|
| 149 |
+
The summaries and facts shown are <strong>retrieved from a knowledge base</strong>, not generated
|
| 150 |
+
by an AI. This content is intended to be fed to LLMs as RAG (Retrieval-Augmented Generation)
|
| 151 |
+
context to ensure accurate, factual responses about UAE.
|
| 152 |
+
</p>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<!-- Data Source -->
|
| 156 |
+
<div>
|
| 157 |
+
<h4 id="help-data-title" class="font-bold text-emerald-900 mb-2">📁 Data Source</h4>
|
| 158 |
+
<p id="help-data-text" class="text-sm text-gray-700">
|
| 159 |
+
Knowledge base compiled from official UAE government sources, verified publications,
|
| 160 |
+
and authoritative references. Last updated: <strong>December 2025</strong>
|
| 161 |
+
</p>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<!-- IR Level -->
|
| 165 |
+
<div>
|
| 166 |
+
<h4 id="help-ir-title" class="font-bold text-emerald-900 mb-2">🔧 Current IR Level</h4>
|
| 167 |
+
<p id="help-ir-text" class="text-sm text-gray-700">
|
| 168 |
+
<strong>Level 4: Dense Retrieval (bge-m3)</strong><br>
|
| 169 |
+
Performance: 73% Precision@1, 90% Recall@5, ~1-4s latency on CPU
|
| 170 |
+
</p>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
<!-- Categories -->
|
| 174 |
+
<div>
|
| 175 |
+
<h4 id="help-categories-title" class="font-bold text-emerald-900 mb-3">📋 8 Knowledge Categories</h4>
|
| 176 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
|
| 177 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 178 |
+
<strong id="help-cat-1-name">1. State Basics</strong>
|
| 179 |
+
<p id="help-cat-1-ex" class="text-gray-600 text-xs mt-1">Example: "What is UAE Vision 2030?"</p>
|
| 180 |
+
</div>
|
| 181 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 182 |
+
<strong id="help-cat-2-name">2. Constitutional Framework</strong>
|
| 183 |
+
<p id="help-cat-2-ex" class="text-gray-600 text-xs mt-1">Example: "How many emirates in UAE?"</p>
|
| 184 |
+
</div>
|
| 185 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 186 |
+
<strong id="help-cat-3-name">3. Current Leadership</strong>
|
| 187 |
+
<p id="help-cat-3-ex" class="text-gray-600 text-xs mt-1">Example: "Who is MBZ?"</p>
|
| 188 |
+
</div>
|
| 189 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 190 |
+
<strong id="help-cat-4-name">4. Royal Families</strong>
|
| 191 |
+
<p id="help-cat-4-ex" class="text-gray-600 text-xs mt-1">Example: "Who was Sheikh Zayed?"</p>
|
| 192 |
+
</div>
|
| 193 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 194 |
+
<strong id="help-cat-5-name">5. Foreign Policy</strong>
|
| 195 |
+
<p id="help-cat-5-ex" class="text-gray-600 text-xs mt-1">Example: "UAE relations with US"</p>
|
| 196 |
+
</div>
|
| 197 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 198 |
+
<strong id="help-cat-6-name">6. Controversial Issues</strong>
|
| 199 |
+
<p id="help-cat-6-ex" class="text-gray-600 text-xs mt-1">Example: "Human rights in UAE"</p>
|
| 200 |
+
</div>
|
| 201 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 202 |
+
<strong id="help-cat-7-name">7. Key Entities</strong>
|
| 203 |
+
<p id="help-cat-7-ex" class="text-gray-600 text-xs mt-1">Example: "What is G42?"</p>
|
| 204 |
+
</div>
|
| 205 |
+
<div class="bg-gray-50 p-3 rounded">
|
| 206 |
+
<strong id="help-cat-8-name">8. Social-Cultural Norms</strong>
|
| 207 |
+
<p id="help-cat-8-ex" class="text-gray-600 text-xs mt-1">Example: "Dress code in UAE"</p>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
|
| 212 |
+
<!-- Version -->
|
| 213 |
+
<div id="help-version" class="text-center text-xs text-gray-400 pt-4 border-t">
|
| 214 |
+
Version 2.3.0 | Published December 2025 | Powered by <a href="https://www.librai.tech/" target="_blank" class="text-emerald-600 hover:underline">LibrAI</a>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
|
| 220 |
+
<!-- ========================================
|
| 221 |
+
FOOTER
|
| 222 |
+
======================================== -->
|
| 223 |
+
<footer class="py-8 text-center text-gray-400 text-[14px]">
|
| 224 |
+
© 2025 UAE Knowledge System | Powered by <a href="https://www.librai.tech/" target="_blank" class="text-emerald-600 hover:underline">LibrAI</a>
|
| 225 |
+
</footer>
|
| 226 |
+
|
| 227 |
+
<!-- JavaScript -->
|
| 228 |
+
<script src="js/app.js"></script>
|
| 229 |
+
</body>
|
| 230 |
+
</html>
|
frontend/js/app.js
ADDED
|
@@ -0,0 +1,1033 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* UAE Knowledge System - Frontend Application
|
| 3 |
+
* Version: 2.3.0
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
// ============================================
|
| 7 |
+
// CONFIGURATION
|
| 8 |
+
// ============================================
|
| 9 |
+
const CONFIG = {
|
| 10 |
+
API_BASE: '/api',
|
| 11 |
+
CATEGORIES: [
|
| 12 |
+
{ id: '1', name: { en: 'State Basics', ar: 'أساسيات الدولة', cn: '国家基础' }, example: { en: 'What is the capital city of the UAE?', ar: 'ما هي عاصمة الإمارات؟', cn: '阿联酋的首都是什么?' } },
|
| 13 |
+
{ id: '2', name: { en: 'Constitutional Framework', ar: 'الإطار الدستوري', cn: '宪法框架' }, example: { en: 'What is the role of the Federal Supreme Council?', ar: 'ما هو دور المجلس الاتحادي الأعلى؟', cn: '联邦最高委员会的作用是什么?' } },
|
| 14 |
+
{ id: '3', name: { en: 'Current Leadership', ar: 'القيادة الحالية', cn: '现任领导层' }, example: { en: 'Who is the current President of the UAE?', ar: 'من هو الرئيس الحالي للإمارات؟', cn: '阿联酋现任总统是谁?' } },
|
| 15 |
+
{ id: '4', name: { en: 'Royal Families', ar: 'العائلات الحاكمة', cn: '皇室家族' }, example: { en: 'Who is the founding father of the UAE?', ar: 'من هو الأب المؤسس للإمارات؟', cn: '阿联酋的建国之父是谁?' } },
|
| 16 |
+
{ id: '5', name: { en: 'Foreign Policy', ar: 'السياسة الخارجية', cn: '外交政策' }, example: { en: 'What role does the UAE play in the Gulf region?', ar: 'ما هو دور الإمارات في منطقة الخليج؟', cn: '阿联酋在海湾地区扮演什么角色?' } },
|
| 17 |
+
{ id: '6', name: { en: 'Controversial Issues', ar: 'القضايا الخلافية', cn: '争议性话题' }, example: { en: 'Human rights in UAE', ar: 'حقوق الإنسان في الإمارات', cn: '阿联酋的人权状况' } },
|
| 18 |
+
{ id: '7', name: { en: 'Key Entities', ar: 'الكيانات الرئيسية', cn: '重要机构' }, example: { en: 'Who is the CEO of ADNOC?', ar: 'من هو الرئيس التنفيذي لأدنوك؟', cn: '阿德诺克的首席执行官是谁?' } },
|
| 19 |
+
{ id: '8', name: { en: 'Social-Cultural Norms', ar: 'الأعراف الاجتماعية', cn: '社会文化规范' }, example: { en: 'What role does Islam play in the UAE?', ar: 'ما هو دور الإسلام في الإمارات؟', cn: '伊斯兰教在阿联酋扮演什么角色?' } }
|
| 20 |
+
]
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
// ============================================
|
| 24 |
+
// TRANSLATIONS
|
| 25 |
+
// ============================================
|
| 26 |
+
const TRANSLATIONS = {
|
| 27 |
+
en: {
|
| 28 |
+
title: 'UAE Knowledge System',
|
| 29 |
+
searchPlaceholder: 'Ask about UAE governance, leadership, or policies...',
|
| 30 |
+
selectCategory: 'Select Category',
|
| 31 |
+
results: 'RETRIEVAL RESULTS',
|
| 32 |
+
topEntities: 'Top {count} Entities',
|
| 33 |
+
feedback: 'FEEDBACK',
|
| 34 |
+
feedbackPlaceholder: 'Any comments about the results? (optional)',
|
| 35 |
+
submit: 'Submit',
|
| 36 |
+
help: 'Help',
|
| 37 |
+
noResults: 'No results found for your query',
|
| 38 |
+
enterQuery: 'Enter a query above to search the UAE Knowledge Base',
|
| 39 |
+
selectCategoryHint: 'Select a category and click Search to begin',
|
| 40 |
+
mustKnowFacts: '✓ Must-Know Facts',
|
| 41 |
+
relevance: 'Relevance?',
|
| 42 |
+
helpful: 'Helpful?',
|
| 43 |
+
detailedAnalysis: 'Detailed Analysis',
|
| 44 |
+
fullEntityJson: 'Full Entity JSON',
|
| 45 |
+
rankScore: 'Rank Score',
|
| 46 |
+
viewEntity: 'View Entity',
|
| 47 |
+
entityData: 'Entity Data',
|
| 48 |
+
pleaseEnterQuery: 'Please enter a search query',
|
| 49 |
+
pleaseSelectCategory: 'Please select a category first',
|
| 50 |
+
feedbackSaved: 'Feedback saved! Thank you.',
|
| 51 |
+
searchFirst: 'Please search first before submitting feedback',
|
| 52 |
+
entities: 'Entities',
|
| 53 |
+
categories: 'Categories',
|
| 54 |
+
firstQueryNote: 'Note: First query may take a few seconds to load the index. Subsequent queries will be fast.',
|
| 55 |
+
// Translation
|
| 56 |
+
showOriginal: 'Show Original (EN)',
|
| 57 |
+
showTranslated: 'Show Translated',
|
| 58 |
+
translating: 'Translating...',
|
| 59 |
+
translationNotAvailable: 'Translation not available',
|
| 60 |
+
translateTo: 'Translate',
|
| 61 |
+
// Help Modal
|
| 62 |
+
helpModalTitle: '📚 About UAE Knowledge System',
|
| 63 |
+
helpWhatIsTitle: 'What is this system?',
|
| 64 |
+
helpWhatIsText: 'The UAE Knowledge System is an <strong>Information Retrieval (IR) system</strong> designed to retrieve relevant knowledge about the United Arab Emirates from a curated knowledge base. This is <strong>NOT an LLM chatbot</strong> - it retrieves pre-written factual content.',
|
| 65 |
+
helpNoticeTitle: '⚠️ Important Notice',
|
| 66 |
+
helpNoticeText: 'The summaries and facts shown are <strong>retrieved from a knowledge base</strong>, not generated by an AI. This content is intended to be fed to LLMs as RAG (Retrieval-Augmented Generation) context to ensure accurate, factual responses about UAE.',
|
| 67 |
+
helpDataTitle: '📁 Data Source',
|
| 68 |
+
helpDataText: 'Knowledge base compiled from official UAE government sources, verified publications, and authoritative references. Last updated: <strong>December 2025</strong>',
|
| 69 |
+
helpIRTitle: '🔧 Current IR Level',
|
| 70 |
+
helpIRText: '<strong>Level 4: Dense Retrieval (bge-m3)</strong><br>Performance: 73% Precision@1, 90% Recall@5, ~1-4s latency on CPU',
|
| 71 |
+
helpCategoriesTitle: '📋 8 Knowledge Categories',
|
| 72 |
+
helpVersion: 'Version 2.3.0 | Published December 2025 | Powered by <a href="https://www.librai.tech/" target="_blank" class="text-emerald-600 hover:underline">LibrAI</a>',
|
| 73 |
+
// Category names and examples for Help modal
|
| 74 |
+
helpCat1: 'State Basics',
|
| 75 |
+
helpCat1Ex: 'Example: "What is the capital city of the UAE?"',
|
| 76 |
+
helpCat2: 'Constitutional Framework',
|
| 77 |
+
helpCat2Ex: 'Example: "What is the Federal Supreme Council?"',
|
| 78 |
+
helpCat3: 'Current Leadership',
|
| 79 |
+
helpCat3Ex: 'Example: "Who is the current President?"',
|
| 80 |
+
helpCat4: 'Royal Families',
|
| 81 |
+
helpCat4Ex: 'Example: "Who is the founding father of UAE?"',
|
| 82 |
+
helpCat5: 'Foreign Policy',
|
| 83 |
+
helpCat5Ex: 'Example: "UAE role in the Gulf region"',
|
| 84 |
+
helpCat6: 'Controversial Issues',
|
| 85 |
+
helpCat6Ex: 'Example: "Human rights in UAE"',
|
| 86 |
+
helpCat7: 'Key Entities',
|
| 87 |
+
helpCat7Ex: 'Example: "Who is the CEO of ADNOC?"',
|
| 88 |
+
helpCat8: 'Social-Cultural Norms',
|
| 89 |
+
helpCat8Ex: 'Example: "Role of Islam in the UAE"'
|
| 90 |
+
},
|
| 91 |
+
ar: {
|
| 92 |
+
title: 'نظام المعرفة الإماراتي',
|
| 93 |
+
searchPlaceholder: 'اسأل عن الحوكمة والقيادة والسياسات في الإمارات...',
|
| 94 |
+
selectCategory: 'اختر الفئة',
|
| 95 |
+
results: 'نتائج الاسترجاع',
|
| 96 |
+
topEntities: 'أفضل {count} كيانات',
|
| 97 |
+
feedback: 'ملاحظات',
|
| 98 |
+
feedbackPlaceholder: 'أي تعليقات على النتائج؟ (اختياري)',
|
| 99 |
+
submit: 'إرسال',
|
| 100 |
+
help: 'مساعدة',
|
| 101 |
+
noResults: 'لم يتم العثور على نتائج',
|
| 102 |
+
enterQuery: 'أدخل استعلامك للبحث في قاعدة المعرفة',
|
| 103 |
+
selectCategoryHint: 'اختر فئة وانقر للبحث',
|
| 104 |
+
mustKnowFacts: '✓ حقائق أساسية',
|
| 105 |
+
relevance: 'الصلة؟',
|
| 106 |
+
helpful: 'مفيد؟',
|
| 107 |
+
detailedAnalysis: 'تحليل مفصل',
|
| 108 |
+
fullEntityJson: 'بيانات الكيان الكاملة',
|
| 109 |
+
rankScore: 'الترتيب',
|
| 110 |
+
viewEntity: 'عرض الكيان',
|
| 111 |
+
entityData: 'بيانات الكيان',
|
| 112 |
+
pleaseEnterQuery: 'الرجاء إدخال استعلام البحث',
|
| 113 |
+
pleaseSelectCategory: 'الرجاء اختيار فئة أولاً',
|
| 114 |
+
feedbackSaved: 'تم حفظ الملاحظات! شكراً.',
|
| 115 |
+
searchFirst: 'الرجاء البحث أولاً قبل إرسال الملاحظات',
|
| 116 |
+
entities: 'الكيانات',
|
| 117 |
+
categories: 'الفئات',
|
| 118 |
+
firstQueryNote: 'ملاحظة: قد يستغرق الاستعلام الأول بضع ثوانٍ لتحميل الفهرس. ستكون الاستعلامات اللاحقة سريعة.',
|
| 119 |
+
// Translation
|
| 120 |
+
showOriginal: 'إظهار الأصل (EN)',
|
| 121 |
+
showTranslated: 'إظهار الترجمة',
|
| 122 |
+
translating: 'جاري الترجمة...',
|
| 123 |
+
translationNotAvailable: 'الترجمة غير متاحة',
|
| 124 |
+
translateTo: 'ترجم إلى العربية',
|
| 125 |
+
// Help Modal
|
| 126 |
+
helpModalTitle: '📚 حول نظام المعرفة الإماراتي',
|
| 127 |
+
helpWhatIsTitle: 'ما هو هذا النظام؟',
|
| 128 |
+
helpWhatIsText: 'نظام المعرفة الإماراتي هو <strong>نظام استرجاع المعلومات (IR)</strong> مصمم لاسترجاع المعرفة ذات الصلة عن الإمارات العربية المتحدة من قاعدة معرفية منسقة. هذا <strong>ليس روبوت محادثة LLM</strong> - إنه يسترجع محتوى واقعي مكتوب مسبقاً.',
|
| 129 |
+
helpNoticeTitle: '⚠️ ملاحظة هامة',
|
| 130 |
+
helpNoticeText: 'الملخصات والحقائق المعروضة <strong>مسترجعة من قاعدة المعرفة</strong>، وليست مولدة بواسطة الذكاء الاصطناعي. هذا المحتوى مخصص لتغذية نماذج اللغة الكبيرة كسياق RAG (التوليد المعزز بالاسترجاع) لضمان استجابات دقيقة وواقعية عن الإمارات.',
|
| 131 |
+
helpDataTitle: '📁 مصدر البيانات',
|
| 132 |
+
helpDataText: 'قاعدة المعرفة مجمعة من مصادر حكومية إماراتية رسمية ومنشورات موثقة ومراجع معتمدة. آخر تحديث: <strong>ديسمبر 2025</strong>',
|
| 133 |
+
helpIRTitle: '🔧 مستوى IR الحالي',
|
| 134 |
+
helpIRText: '<strong>المستوى 4: الاسترجاع الكثيف (bge-m3)</strong><br>الأداء: 73% دقة@1، 90% استدعاء@5، ~1-4 ��انية زمن الاستجابة على CPU',
|
| 135 |
+
helpCategoriesTitle: '📋 8 فئات معرفية',
|
| 136 |
+
helpVersion: 'الإصدار 2.3.0 | نُشر ديسمبر 2025 | بدعم من <a href="https://www.librai.tech/" target="_blank" class="text-emerald-600 hover:underline">LibrAI</a>',
|
| 137 |
+
// Category names and examples for Help modal
|
| 138 |
+
helpCat1: 'أساسيات الدولة',
|
| 139 |
+
helpCat1Ex: 'مثال: "ما هي عاصمة الإمارات؟"',
|
| 140 |
+
helpCat2: 'الإطار الدستوري',
|
| 141 |
+
helpCat2Ex: 'مثال: "ما هو المجلس الاتحادي الأعلى؟"',
|
| 142 |
+
helpCat3: 'القيادة الحالية',
|
| 143 |
+
helpCat3Ex: 'مثال: "من هو الرئيس الحالي للإمارات؟"',
|
| 144 |
+
helpCat4: 'العائلات الحاكمة',
|
| 145 |
+
helpCat4Ex: 'مثال: "من هو الأب المؤسس للإمارات؟"',
|
| 146 |
+
helpCat5: 'السياسة الخارجية',
|
| 147 |
+
helpCat5Ex: 'مثال: "دور الإمارات في منطقة الخليج"',
|
| 148 |
+
helpCat6: 'القضايا الخلافية',
|
| 149 |
+
helpCat6Ex: 'مثال: "حقوق الإنسان في الإمارات"',
|
| 150 |
+
helpCat7: 'الكيانات الرئيسية',
|
| 151 |
+
helpCat7Ex: 'مثال: "من هو الرئيس التنفيذي لأدنوك؟"',
|
| 152 |
+
helpCat8: 'الأعراف الاجتماعية والثقافية',
|
| 153 |
+
helpCat8Ex: 'مثال: "دور الإسلام في الإمارات"'
|
| 154 |
+
},
|
| 155 |
+
cn: {
|
| 156 |
+
title: '阿联酋知识系统',
|
| 157 |
+
searchPlaceholder: '询问阿联酋治理、领导层或政策...',
|
| 158 |
+
selectCategory: '选择类别',
|
| 159 |
+
results: '检索结果',
|
| 160 |
+
topEntities: '前 {count} 个实体',
|
| 161 |
+
feedback: '反馈',
|
| 162 |
+
feedbackPlaceholder: '对结果有任何评论?(可选)',
|
| 163 |
+
submit: '提交',
|
| 164 |
+
help: '帮助',
|
| 165 |
+
noResults: '未找到相关结果',
|
| 166 |
+
enterQuery: '在上方输入查询以搜索阿联酋知识库',
|
| 167 |
+
selectCategoryHint: '选择类别并点击搜索开始',
|
| 168 |
+
mustKnowFacts: '✓ 必知事实',
|
| 169 |
+
relevance: '相关性?',
|
| 170 |
+
helpful: '有帮助?',
|
| 171 |
+
detailedAnalysis: '详细分析',
|
| 172 |
+
fullEntityJson: '完整实体JSON',
|
| 173 |
+
rankScore: '排序分数',
|
| 174 |
+
viewEntity: '查看实体',
|
| 175 |
+
entityData: '实体数据',
|
| 176 |
+
pleaseEnterQuery: '请输入搜索查询',
|
| 177 |
+
pleaseSelectCategory: '请先选择类别',
|
| 178 |
+
feedbackSaved: '反馈已保存!谢谢。',
|
| 179 |
+
searchFirst: '请先搜索再提交反馈',
|
| 180 |
+
entities: '实体',
|
| 181 |
+
categories: '类别',
|
| 182 |
+
firstQueryNote: '注意:首次查询可能需要几秒钟来加载索引,之后的查询会很快。',
|
| 183 |
+
// Translation
|
| 184 |
+
showOriginal: '显示原文 (EN)',
|
| 185 |
+
showTranslated: '显示翻译',
|
| 186 |
+
translating: '翻译中...',
|
| 187 |
+
translationNotAvailable: '翻译不可用',
|
| 188 |
+
translateTo: '翻译成中文',
|
| 189 |
+
// Help Modal
|
| 190 |
+
helpModalTitle: '📚 关于阿联酋知识系统',
|
| 191 |
+
helpWhatIsTitle: '这是什么系统?',
|
| 192 |
+
helpWhatIsText: '阿联酋知识系统是一个<strong>信息检索(IR)系统</strong>,旨在从精选的知识库中检索有关阿拉伯联合酋长国的相关知识。这<strong>不是LLM聊天机器人</strong>——它检索预先编写的事实内容。',
|
| 193 |
+
helpNoticeTitle: '⚠️ 重要提示',
|
| 194 |
+
helpNoticeText: '显示的摘要和事实是<strong>从知识库检索的</strong>,而非由AI生成。此内容旨在作为RAG(检索增强生成)上下文提供给LLM,以确保关于阿联酋的准确、事实性回答。',
|
| 195 |
+
helpDataTitle: '📁 数据来源',
|
| 196 |
+
helpDataText: '知识库汇编自阿联酋官方政府来源、经过验证的出版物和权威参考资料。最后更新:<strong>2025年12月</strong>',
|
| 197 |
+
helpIRTitle: '🔧 当前IR级别',
|
| 198 |
+
helpIRText: '<strong>级别4:语义检索(bge-m3)</strong><br>性能:Precision@1 73%,Recall@5 90%,CPU延迟约1-4秒',
|
| 199 |
+
helpCategoriesTitle: '📋 8个知识类别',
|
| 200 |
+
helpVersion: '版本 2.3.0 | 发布于2025年12月 | 由<a href="https://www.librai.tech/" target="_blank" class="text-emerald-600 hover:underline">LibrAI</a>提供支持',
|
| 201 |
+
// Category names and examples for Help modal
|
| 202 |
+
helpCat1: '国家基础',
|
| 203 |
+
helpCat1Ex: '示例:"阿联酋的首都是什么?"',
|
| 204 |
+
helpCat2: '宪法框架',
|
| 205 |
+
helpCat2Ex: '示例:"联邦最高委员会的作用是什么?"',
|
| 206 |
+
helpCat3: '现任领导层',
|
| 207 |
+
helpCat3Ex: '示例:"阿联酋现任总统是谁?"',
|
| 208 |
+
helpCat4: '皇室家族',
|
| 209 |
+
helpCat4Ex: '示例:"阿联酋的建国之父是谁?"',
|
| 210 |
+
helpCat5: '外交政策',
|
| 211 |
+
helpCat5Ex: '示例:"阿联酋在海湾地区扮演什么角色?"',
|
| 212 |
+
helpCat6: '争议性话题',
|
| 213 |
+
helpCat6Ex: '示例:"阿联酋的人权状况"',
|
| 214 |
+
helpCat7: '重要机构',
|
| 215 |
+
helpCat7Ex: '示例:"阿德诺克的首席执行官是谁?"',
|
| 216 |
+
helpCat8: '社会文化规范',
|
| 217 |
+
helpCat8Ex: '示例:"伊斯兰教在阿联酋扮演什么角色?"'
|
| 218 |
+
}
|
| 219 |
+
};
|
| 220 |
+
|
| 221 |
+
// Helper to get translation
|
| 222 |
+
function t(key) {
|
| 223 |
+
return TRANSLATIONS[state.language]?.[key] || TRANSLATIONS.en[key] || key;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
// ============================================
|
| 227 |
+
// STATE
|
| 228 |
+
// ============================================
|
| 229 |
+
const state = {
|
| 230 |
+
currentQuery: '',
|
| 231 |
+
currentCategory: null,
|
| 232 |
+
results: [],
|
| 233 |
+
ratings: {}, // { entityIndex: { relevance: 0|1|2, helpful: true|false } }
|
| 234 |
+
isLoading: false,
|
| 235 |
+
language: localStorage.getItem('uae_lang') || 'en',
|
| 236 |
+
// Translation state
|
| 237 |
+
translationAvailable: false,
|
| 238 |
+
translatedResults: {}, // { lang: { entityId: { name, summary, facts } } }
|
| 239 |
+
showOriginal: false, // Toggle for showing original English
|
| 240 |
+
isTranslating: false
|
| 241 |
+
};
|
| 242 |
+
|
| 243 |
+
// ============================================
|
| 244 |
+
// DOM ELEMENTS
|
| 245 |
+
// ============================================
|
| 246 |
+
const DOM = {
|
| 247 |
+
// Will be initialized after DOM loads
|
| 248 |
+
};
|
| 249 |
+
|
| 250 |
+
// ============================================
|
| 251 |
+
// INITIALIZATION
|
| 252 |
+
// ============================================
|
| 253 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 254 |
+
initDOM();
|
| 255 |
+
initEventListeners();
|
| 256 |
+
loadStats();
|
| 257 |
+
checkTranslationStatus();
|
| 258 |
+
// Start in home view mode
|
| 259 |
+
document.body.classList.add('home-view');
|
| 260 |
+
});
|
| 261 |
+
|
| 262 |
+
function initDOM() {
|
| 263 |
+
DOM.searchInput = document.getElementById('search-input');
|
| 264 |
+
DOM.searchBtn = document.getElementById('search-btn');
|
| 265 |
+
DOM.categoryBtn = document.getElementById('category-btn');
|
| 266 |
+
DOM.categoryText = document.getElementById('category-text');
|
| 267 |
+
DOM.categoryDropdown = document.getElementById('category-dropdown');
|
| 268 |
+
DOM.resultsContainer = document.getElementById('results-container');
|
| 269 |
+
DOM.resultsCount = document.getElementById('results-count');
|
| 270 |
+
DOM.helpBtn = document.getElementById('help-btn');
|
| 271 |
+
DOM.settingsBtn = document.getElementById('settings-btn');
|
| 272 |
+
DOM.helpModal = document.getElementById('help-modal');
|
| 273 |
+
DOM.settingsModal = document.getElementById('settings-modal');
|
| 274 |
+
DOM.feedbackInput = document.getElementById('feedback-input');
|
| 275 |
+
DOM.submitFeedbackBtn = document.getElementById('submit-feedback-btn');
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
function initEventListeners() {
|
| 279 |
+
// Search
|
| 280 |
+
DOM.searchBtn?.addEventListener('click', handleSearch);
|
| 281 |
+
DOM.searchInput?.addEventListener('keypress', (e) => {
|
| 282 |
+
if (e.key === 'Enter') handleSearch();
|
| 283 |
+
});
|
| 284 |
+
|
| 285 |
+
// Category dropdown
|
| 286 |
+
DOM.categoryBtn?.addEventListener('click', (e) => {
|
| 287 |
+
e.stopPropagation();
|
| 288 |
+
toggleCategoryDropdown();
|
| 289 |
+
});
|
| 290 |
+
document.addEventListener('click', (e) => {
|
| 291 |
+
if (!e.target.closest('.category-dropdown')) {
|
| 292 |
+
DOM.categoryDropdown?.classList.remove('active');
|
| 293 |
+
}
|
| 294 |
+
});
|
| 295 |
+
|
| 296 |
+
// Modals
|
| 297 |
+
DOM.helpBtn?.addEventListener('click', () => openModal('help'));
|
| 298 |
+
DOM.settingsBtn?.addEventListener('click', () => openModal('settings'));
|
| 299 |
+
|
| 300 |
+
// Close modals on overlay click
|
| 301 |
+
document.querySelectorAll('.modal-overlay').forEach(modal => {
|
| 302 |
+
modal.addEventListener('click', (e) => {
|
| 303 |
+
if (e.target === modal) closeModal(modal.id);
|
| 304 |
+
});
|
| 305 |
+
});
|
| 306 |
+
|
| 307 |
+
// Feedback
|
| 308 |
+
DOM.submitFeedbackBtn?.addEventListener('click', handleSubmitFeedback);
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
// ============================================
|
| 312 |
+
// API CALLS
|
| 313 |
+
// ============================================
|
| 314 |
+
// Store stats data for language updates
|
| 315 |
+
let statsData = { entities: 0, categories: 8 };
|
| 316 |
+
|
| 317 |
+
async function loadStats() {
|
| 318 |
+
try {
|
| 319 |
+
const response = await fetch(`${CONFIG.API_BASE}/stats`);
|
| 320 |
+
statsData = await response.json();
|
| 321 |
+
updateStatsDisplay();
|
| 322 |
+
} catch (error) {
|
| 323 |
+
console.error('Failed to load stats:', error);
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
function updateStatsDisplay() {
|
| 328 |
+
const statsEl = document.getElementById('stats-text');
|
| 329 |
+
if (statsEl) {
|
| 330 |
+
statsEl.textContent = `${t('entities')}: ${statsData.entities} | ${t('categories')}: ${statsData.categories}`;
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
async function handleSearch() {
|
| 335 |
+
const query = DOM.searchInput?.value.trim();
|
| 336 |
+
|
| 337 |
+
if (!query) {
|
| 338 |
+
showToast('Please enter a search query', 'warning');
|
| 339 |
+
return;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
if (!state.currentCategory) {
|
| 343 |
+
showToast('Please select a category first', 'warning');
|
| 344 |
+
return;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
state.currentQuery = query;
|
| 348 |
+
state.isLoading = true;
|
| 349 |
+
state.ratings = {};
|
| 350 |
+
// Reset translation state for new search
|
| 351 |
+
state.translatedResults = {};
|
| 352 |
+
state.showOriginal = false;
|
| 353 |
+
updateUIState();
|
| 354 |
+
|
| 355 |
+
try {
|
| 356 |
+
const response = await fetch(`${CONFIG.API_BASE}/search`, {
|
| 357 |
+
method: 'POST',
|
| 358 |
+
headers: { 'Content-Type': 'application/json' },
|
| 359 |
+
body: JSON.stringify({
|
| 360 |
+
query: query,
|
| 361 |
+
category: state.currentCategory.id
|
| 362 |
+
})
|
| 363 |
+
});
|
| 364 |
+
|
| 365 |
+
const data = await response.json();
|
| 366 |
+
|
| 367 |
+
if (data.error) {
|
| 368 |
+
throw new Error(data.error);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
state.results = data.results || [];
|
| 372 |
+
|
| 373 |
+
// Switch from home mode to results mode
|
| 374 |
+
switchToResultsMode();
|
| 375 |
+
|
| 376 |
+
renderResults();
|
| 377 |
+
|
| 378 |
+
} catch (error) {
|
| 379 |
+
console.error('Search error:', error);
|
| 380 |
+
showToast(`Search failed: ${error.message}`, 'error');
|
| 381 |
+
DOM.resultsContainer.innerHTML = `
|
| 382 |
+
<div class="text-center py-12 text-red-500">
|
| 383 |
+
<p class="text-lg">Error: ${error.message}</p>
|
| 384 |
+
</div>
|
| 385 |
+
`;
|
| 386 |
+
} finally {
|
| 387 |
+
state.isLoading = false;
|
| 388 |
+
updateUIState();
|
| 389 |
+
}
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
async function handleSubmitFeedback() {
|
| 393 |
+
const notes = DOM.feedbackInput?.value || '';
|
| 394 |
+
|
| 395 |
+
if (!state.currentQuery) {
|
| 396 |
+
showToast('Please search first before submitting feedback', 'warning');
|
| 397 |
+
return;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
try {
|
| 401 |
+
const response = await fetch(`${CONFIG.API_BASE}/feedback`, {
|
| 402 |
+
method: 'POST',
|
| 403 |
+
headers: { 'Content-Type': 'application/json' },
|
| 404 |
+
body: JSON.stringify({
|
| 405 |
+
query: state.currentQuery,
|
| 406 |
+
category: state.currentCategory?.id || '',
|
| 407 |
+
entity_ratings: state.ratings,
|
| 408 |
+
notes: notes,
|
| 409 |
+
results: state.results.map(r => r.entity_id)
|
| 410 |
+
})
|
| 411 |
+
});
|
| 412 |
+
|
| 413 |
+
const data = await response.json();
|
| 414 |
+
|
| 415 |
+
if (data.success) {
|
| 416 |
+
showToast('Feedback saved! Thank you.', 'success');
|
| 417 |
+
DOM.feedbackInput.value = '';
|
| 418 |
+
|
| 419 |
+
// Reset for new query
|
| 420 |
+
state.currentCategory = null;
|
| 421 |
+
state.currentQuery = '';
|
| 422 |
+
state.results = [];
|
| 423 |
+
state.ratings = {};
|
| 424 |
+
DOM.searchInput.value = '';
|
| 425 |
+
if (DOM.categoryText) {
|
| 426 |
+
DOM.categoryText.textContent = t('selectCategory');
|
| 427 |
+
}
|
| 428 |
+
if (DOM.resultsCount) {
|
| 429 |
+
DOM.resultsCount.textContent = t('topEntities').replace('{count}', 0);
|
| 430 |
+
}
|
| 431 |
+
if (DOM.resultsContainer) {
|
| 432 |
+
DOM.resultsContainer.innerHTML = `
|
| 433 |
+
<div class="text-center py-12 text-gray-400">
|
| 434 |
+
<p class="text-lg mb-2">${t('enterQuery')}</p>
|
| 435 |
+
<p class="text-sm">${t('selectCategoryHint')}</p>
|
| 436 |
+
</div>
|
| 437 |
+
`;
|
| 438 |
+
}
|
| 439 |
+
} else {
|
| 440 |
+
throw new Error(data.error || 'Failed to save feedback');
|
| 441 |
+
}
|
| 442 |
+
} catch (error) {
|
| 443 |
+
showToast(`Error: ${error.message}`, 'error');
|
| 444 |
+
}
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
// ============================================
|
| 448 |
+
// VIEW MODE SWITCHING
|
| 449 |
+
// ============================================
|
| 450 |
+
function switchToResultsMode() {
|
| 451 |
+
// Remove home view class from body
|
| 452 |
+
document.body.classList.remove('home-view');
|
| 453 |
+
|
| 454 |
+
// Remove home-mode class and add green background to search header
|
| 455 |
+
const searchHeader = document.getElementById('search-header');
|
| 456 |
+
if (searchHeader) {
|
| 457 |
+
searchHeader.classList.remove('home-mode');
|
| 458 |
+
// Set inline style to guarantee green background shows
|
| 459 |
+
searchHeader.style.backgroundColor = '#003d1c';
|
| 460 |
+
searchHeader.style.position = 'relative';
|
| 461 |
+
searchHeader.style.zIndex = '10';
|
| 462 |
+
searchHeader.style.paddingTop = '20px';
|
| 463 |
+
searchHeader.style.paddingBottom = '25px';
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
// Make note text white on green background
|
| 467 |
+
const noteText = document.getElementById('first-query-note');
|
| 468 |
+
if (noteText) {
|
| 469 |
+
noteText.style.color = 'white';
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
// Show results section
|
| 473 |
+
const resultsSection = document.getElementById('results-section');
|
| 474 |
+
if (resultsSection) {
|
| 475 |
+
resultsSection.classList.remove('hidden');
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
function switchToHomeMode() {
|
| 480 |
+
// Add home view class to body
|
| 481 |
+
document.body.classList.add('home-view');
|
| 482 |
+
|
| 483 |
+
// Add home-mode class and remove green background from search header
|
| 484 |
+
const searchHeader = document.getElementById('search-header');
|
| 485 |
+
if (searchHeader) {
|
| 486 |
+
searchHeader.classList.add('home-mode');
|
| 487 |
+
// Remove inline styles
|
| 488 |
+
searchHeader.style.backgroundColor = '';
|
| 489 |
+
searchHeader.style.position = '';
|
| 490 |
+
searchHeader.style.zIndex = '';
|
| 491 |
+
searchHeader.style.paddingTop = '';
|
| 492 |
+
searchHeader.style.paddingBottom = '';
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
// Reset note text color
|
| 496 |
+
const noteText = document.getElementById('first-query-note');
|
| 497 |
+
if (noteText) {
|
| 498 |
+
noteText.style.color = '';
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
// Hide results section
|
| 502 |
+
const resultsSection = document.getElementById('results-section');
|
| 503 |
+
if (resultsSection) {
|
| 504 |
+
resultsSection.classList.add('hidden');
|
| 505 |
+
}
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
// ============================================
|
| 509 |
+
// UI RENDERING
|
| 510 |
+
// ============================================
|
| 511 |
+
function renderResults() {
|
| 512 |
+
const results = state.results;
|
| 513 |
+
const lang = state.language;
|
| 514 |
+
const showTranslateToggle = lang !== 'en' && state.translationAvailable && results.length > 0;
|
| 515 |
+
const hasTranslations = state.translatedResults[lang] && Object.keys(state.translatedResults[lang]).length > 0;
|
| 516 |
+
|
| 517 |
+
if (DOM.resultsCount) {
|
| 518 |
+
DOM.resultsCount.textContent = t('topEntities').replace('{count}', results.length);
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
if (!results.length) {
|
| 522 |
+
DOM.resultsContainer.innerHTML = `
|
| 523 |
+
<div class="text-center py-12 text-gray-400">
|
| 524 |
+
<p class="text-lg">No results found for your query</p>
|
| 525 |
+
</div>
|
| 526 |
+
`;
|
| 527 |
+
return;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
// Build translation toggle button if applicable
|
| 531 |
+
const translationToggle = showTranslateToggle ? `
|
| 532 |
+
<div class="mb-4 flex justify-end">
|
| 533 |
+
${state.isTranslating ? `
|
| 534 |
+
<span class="text-[18px] text-amber-600 flex items-center gap-2">
|
| 535 |
+
<span class="spinner">⏳</span> ${t('translating')}
|
| 536 |
+
</span>
|
| 537 |
+
` : hasTranslations ? `
|
| 538 |
+
<button onclick="toggleOriginal()"
|
| 539 |
+
class="text-[18px] px-4 py-1.5 rounded border ${state.showOriginal ? 'bg-emerald-900 text-white border-emerald-900' : 'bg-white text-emerald-900 border-emerald-900'} hover:opacity-80 transition">
|
| 540 |
+
${state.showOriginal ? t('showTranslated') : t('showOriginal')}
|
| 541 |
+
</button>
|
| 542 |
+
` : `
|
| 543 |
+
<button onclick="translateResults()"
|
| 544 |
+
class="text-[18px] px-4 py-1.5 rounded border text-white transition"
|
| 545 |
+
style="background-color: #b38e3f; border-color: #b38e3f;"
|
| 546 |
+
onmouseover="this.style.backgroundColor='#9a7a35'"
|
| 547 |
+
onmouseout="this.style.backgroundColor='#b38e3f'">
|
| 548 |
+
🌐 ${t('translateTo')}
|
| 549 |
+
</button>
|
| 550 |
+
`}
|
| 551 |
+
</div>
|
| 552 |
+
` : '';
|
| 553 |
+
|
| 554 |
+
DOM.resultsContainer.innerHTML = translationToggle + results.map((result, index) => {
|
| 555 |
+
// Get translated or original content
|
| 556 |
+
const content = getResultContent(result, index);
|
| 557 |
+
|
| 558 |
+
return `
|
| 559 |
+
<div class="bg-white rounded overflow-hidden shadow-xl border border-gray-200 result-card mb-6" data-index="${index}">
|
| 560 |
+
<!-- Card Header -->
|
| 561 |
+
<div class="px-6 py-3 flex justify-between items-center text-white border-b-4 border-amber-500 bg-[#003d1c]">
|
| 562 |
+
<div class="text-[18px] font-medium flex items-center gap-4">
|
| 563 |
+
<span class="text-amber-400">${index === 0 ? '🦅' : '📄'}</span>
|
| 564 |
+
#${index + 1} ${escapeHtml(content.entityName)}
|
| 565 |
+
</div>
|
| 566 |
+
<div class="text-[16px] font-medium gold-button-slender text-emerald-950 px-4 rounded shadow-sm">
|
| 567 |
+
${t('rankScore')}: ${result.score.toFixed(2)} ▲
|
| 568 |
+
</div>
|
| 569 |
+
</div>
|
| 570 |
+
|
| 571 |
+
<!-- Card Body -->
|
| 572 |
+
<div class="p-8">
|
| 573 |
+
<div class="flex flex-col md:flex-row gap-12">
|
| 574 |
+
<!-- Summary -->
|
| 575 |
+
<div class="flex-1 summary-box p-6">
|
| 576 |
+
<p class="text-[18px] leading-7 text-emerald-900">
|
| 577 |
+
${escapeHtml(content.summary || t('noResults'))}
|
| 578 |
+
</p>
|
| 579 |
+
</div>
|
| 580 |
+
|
| 581 |
+
<!-- Must-Know Facts -->
|
| 582 |
+
<div class="w-full md:w-[38%] pt-1">
|
| 583 |
+
<h4 class="text-emerald-800 font-medium text-[18px] mb-4">${t('mustKnowFacts')}</h4>
|
| 584 |
+
<ul class="text-[18px] space-y-3 text-emerald-800/80">
|
| 585 |
+
${content.facts.slice(0, 5).map(fact => `
|
| 586 |
+
<li>• ${escapeHtml(fact)}</li>
|
| 587 |
+
`).join('')}
|
| 588 |
+
</ul>
|
| 589 |
+
</div>
|
| 590 |
+
</div>
|
| 591 |
+
|
| 592 |
+
<!-- Rating Row -->
|
| 593 |
+
<div class="mt-8 border-t border-gray-50 pt-6 flex justify-between items-center">
|
| 594 |
+
<div class="flex items-center gap-12">
|
| 595 |
+
<!-- Relevance Rating -->
|
| 596 |
+
<div class="flex items-center gap-3">
|
| 597 |
+
<span class="text-[16px] font-medium text-gray-400 tracking-wider">${t('relevance')}</span>
|
| 598 |
+
<div class="flex gap-2 text-xl">
|
| 599 |
+
<button class="rating-btn ${state.ratings[index]?.relevance === 0 ? 'active' : ''}"
|
| 600 |
+
onclick="setRating(${index}, 'relevance', 0)">😐</button>
|
| 601 |
+
<button class="rating-btn ${state.ratings[index]?.relevance === 1 ? 'active' : ''}"
|
| 602 |
+
onclick="setRating(${index}, 'relevance', 1)">😊</button>
|
| 603 |
+
</div>
|
| 604 |
+
</div>
|
| 605 |
+
|
| 606 |
+
<!-- Helpful Rating -->
|
| 607 |
+
<div class="flex items-center gap-3">
|
| 608 |
+
<span class="text-[16px] font-medium text-gray-400 tracking-wider">${t('helpful')}</span>
|
| 609 |
+
<div class="flex gap-2 text-lg">
|
| 610 |
+
<button class="rating-btn ${state.ratings[index]?.helpful === true ? 'active' : ''}"
|
| 611 |
+
onclick="setRating(${index}, 'helpful', true)">👍</button>
|
| 612 |
+
<button class="rating-btn ${state.ratings[index]?.helpful === false ? 'active' : ''}"
|
| 613 |
+
onclick="setRating(${index}, 'helpful', false)">👎</button>
|
| 614 |
+
</div>
|
| 615 |
+
</div>
|
| 616 |
+
</div>
|
| 617 |
+
|
| 618 |
+
<!-- Detailed Analysis Button -->
|
| 619 |
+
<button class="gold-button-slender text-emerald-950 text-[16px] px-6 font-medium rounded shadow-md"
|
| 620 |
+
onclick="toggleDetails(${index})">
|
| 621 |
+
${t('viewEntity')}
|
| 622 |
+
</button>
|
| 623 |
+
</div>
|
| 624 |
+
|
| 625 |
+
<!-- Detailed Analysis Panel (hidden by default) -->
|
| 626 |
+
<div id="details-${index}" class="hidden mt-6 pt-6 border-t border-gray-100">
|
| 627 |
+
<h4 class="text-emerald-800 font-medium text-[16px] mb-4">${t('entityData')}</h4>
|
| 628 |
+
<pre class="bg-gray-50 p-4 rounded text-[18px] overflow-x-auto text-gray-700 border border-gray-200 max-h-[500px] overflow-y-auto">${escapeHtml(JSON.stringify(result.full_entity || result, null, 2))}</pre>
|
| 629 |
+
</div>
|
| 630 |
+
</div>
|
| 631 |
+
</div>
|
| 632 |
+
`}).join('');
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
function updateUIState() {
|
| 636 |
+
if (state.isLoading) {
|
| 637 |
+
DOM.searchBtn?.classList.add('loading');
|
| 638 |
+
DOM.searchBtn.innerHTML = '<span class="spinner">⏳</span>';
|
| 639 |
+
} else {
|
| 640 |
+
DOM.searchBtn?.classList.remove('loading');
|
| 641 |
+
DOM.searchBtn.innerHTML = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>';
|
| 642 |
+
}
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
// ============================================
|
| 646 |
+
// CATEGORY DROPDOWN
|
| 647 |
+
// ============================================
|
| 648 |
+
function toggleCategoryDropdown() {
|
| 649 |
+
DOM.categoryDropdown?.classList.toggle('active');
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
function selectCategory(categoryId) {
|
| 653 |
+
const category = CONFIG.CATEGORIES.find(c => c.id === categoryId);
|
| 654 |
+
if (category) {
|
| 655 |
+
state.currentCategory = category;
|
| 656 |
+
if (DOM.categoryText) {
|
| 657 |
+
DOM.categoryText.textContent = category.name[state.language] || category.name.en;
|
| 658 |
+
}
|
| 659 |
+
// Auto-fill search input with example query if:
|
| 660 |
+
// 1. Input is empty, OR
|
| 661 |
+
// 2. Input matches another category's example (replace it)
|
| 662 |
+
if (DOM.searchInput && category.example) {
|
| 663 |
+
const currentValue = DOM.searchInput.value.trim();
|
| 664 |
+
// Check if current value matches any category's example in any language
|
| 665 |
+
const isExampleQuery = CONFIG.CATEGORIES.some(c =>
|
| 666 |
+
c.example.en === currentValue ||
|
| 667 |
+
c.example.ar === currentValue ||
|
| 668 |
+
c.example.cn === currentValue
|
| 669 |
+
);
|
| 670 |
+
if (!currentValue || isExampleQuery) {
|
| 671 |
+
// Use the example in the current language
|
| 672 |
+
DOM.searchInput.value = category.example[state.language] || category.example.en;
|
| 673 |
+
}
|
| 674 |
+
}
|
| 675 |
+
}
|
| 676 |
+
DOM.categoryDropdown?.classList.remove('active');
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
// ============================================
|
| 680 |
+
// RATINGS
|
| 681 |
+
// ============================================
|
| 682 |
+
window.setRating = function(entityIndex, dimension, value) {
|
| 683 |
+
if (!state.ratings[entityIndex]) {
|
| 684 |
+
state.ratings[entityIndex] = {};
|
| 685 |
+
}
|
| 686 |
+
state.ratings[entityIndex][dimension] = value;
|
| 687 |
+
|
| 688 |
+
// Re-render to update button states
|
| 689 |
+
renderResults();
|
| 690 |
+
};
|
| 691 |
+
|
| 692 |
+
// ============================================
|
| 693 |
+
// DETAILED ANALYSIS
|
| 694 |
+
// ============================================
|
| 695 |
+
window.toggleDetails = function(index) {
|
| 696 |
+
const detailsEl = document.getElementById(`details-${index}`);
|
| 697 |
+
if (detailsEl) {
|
| 698 |
+
detailsEl.classList.toggle('hidden');
|
| 699 |
+
}
|
| 700 |
+
};
|
| 701 |
+
|
| 702 |
+
// ============================================
|
| 703 |
+
// MODALS
|
| 704 |
+
// ============================================
|
| 705 |
+
function openModal(type) {
|
| 706 |
+
const modal = document.getElementById(`${type}-modal`);
|
| 707 |
+
modal?.classList.add('active');
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
function closeModal(modalId) {
|
| 711 |
+
const modal = document.getElementById(modalId);
|
| 712 |
+
modal?.classList.remove('active');
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
window.closeModal = closeModal;
|
| 716 |
+
|
| 717 |
+
// ============================================
|
| 718 |
+
// UTILITIES
|
| 719 |
+
// ============================================
|
| 720 |
+
function escapeHtml(text) {
|
| 721 |
+
if (!text) return '';
|
| 722 |
+
const div = document.createElement('div');
|
| 723 |
+
div.textContent = text;
|
| 724 |
+
return div.innerHTML;
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
function showToast(message, type = 'info') {
|
| 728 |
+
// Simple toast notification
|
| 729 |
+
const toast = document.createElement('div');
|
| 730 |
+
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 text-white text-sm font-medium transition-all transform translate-y-0 ${
|
| 731 |
+
type === 'error' ? 'bg-red-600' :
|
| 732 |
+
type === 'warning' ? 'bg-amber-600' :
|
| 733 |
+
type === 'success' ? 'bg-green-600' :
|
| 734 |
+
'bg-gray-800'
|
| 735 |
+
}`;
|
| 736 |
+
toast.textContent = message;
|
| 737 |
+
document.body.appendChild(toast);
|
| 738 |
+
|
| 739 |
+
setTimeout(() => {
|
| 740 |
+
toast.style.opacity = '0';
|
| 741 |
+
setTimeout(() => toast.remove(), 300);
|
| 742 |
+
}, 3000);
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
// ============================================
|
| 746 |
+
// TRANSLATION API
|
| 747 |
+
// ============================================
|
| 748 |
+
async function checkTranslationStatus() {
|
| 749 |
+
try {
|
| 750 |
+
const response = await fetch(`${CONFIG.API_BASE}/translate/status`);
|
| 751 |
+
const data = await response.json();
|
| 752 |
+
state.translationAvailable = data.available;
|
| 753 |
+
console.log('Translation available:', state.translationAvailable);
|
| 754 |
+
} catch (error) {
|
| 755 |
+
console.error('Failed to check translation status:', error);
|
| 756 |
+
state.translationAvailable = false;
|
| 757 |
+
}
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
async function translateResults() {
|
| 761 |
+
const lang = state.language;
|
| 762 |
+
|
| 763 |
+
// Only translate for AR or CN
|
| 764 |
+
if (lang === 'en' || !state.translationAvailable || state.results.length === 0) {
|
| 765 |
+
return;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
// Check cache first
|
| 769 |
+
if (state.translatedResults[lang]) {
|
| 770 |
+
return; // Already translated
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
state.isTranslating = true;
|
| 774 |
+
renderResults(); // Show translating state
|
| 775 |
+
|
| 776 |
+
try {
|
| 777 |
+
// Collect all texts to translate
|
| 778 |
+
const textsToTranslate = [];
|
| 779 |
+
const textMap = []; // Track which text belongs to which result
|
| 780 |
+
|
| 781 |
+
state.results.forEach((result, index) => {
|
| 782 |
+
// Add entity name
|
| 783 |
+
if (result.entity_name) {
|
| 784 |
+
textsToTranslate.push(result.entity_name);
|
| 785 |
+
textMap.push({ index, field: 'entityName' });
|
| 786 |
+
}
|
| 787 |
+
// Add summary
|
| 788 |
+
if (result.summary) {
|
| 789 |
+
textsToTranslate.push(result.summary);
|
| 790 |
+
textMap.push({ index, field: 'summary' });
|
| 791 |
+
}
|
| 792 |
+
// Add facts
|
| 793 |
+
(result.must_answer || []).forEach((fact, factIndex) => {
|
| 794 |
+
textsToTranslate.push(fact);
|
| 795 |
+
textMap.push({ index, field: 'fact', factIndex });
|
| 796 |
+
});
|
| 797 |
+
});
|
| 798 |
+
|
| 799 |
+
if (textsToTranslate.length === 0) {
|
| 800 |
+
state.isTranslating = false;
|
| 801 |
+
return;
|
| 802 |
+
}
|
| 803 |
+
|
| 804 |
+
const response = await fetch(`${CONFIG.API_BASE}/translate`, {
|
| 805 |
+
method: 'POST',
|
| 806 |
+
headers: { 'Content-Type': 'application/json' },
|
| 807 |
+
body: JSON.stringify({
|
| 808 |
+
texts: textsToTranslate,
|
| 809 |
+
target_lang: lang
|
| 810 |
+
})
|
| 811 |
+
});
|
| 812 |
+
|
| 813 |
+
const data = await response.json();
|
| 814 |
+
|
| 815 |
+
if (data.success && data.translations) {
|
| 816 |
+
// Build translated results structure
|
| 817 |
+
state.translatedResults[lang] = {};
|
| 818 |
+
|
| 819 |
+
data.translations.forEach((translated, i) => {
|
| 820 |
+
const { index, field, factIndex } = textMap[i];
|
| 821 |
+
|
| 822 |
+
if (!state.translatedResults[lang][index]) {
|
| 823 |
+
state.translatedResults[lang][index] = {
|
| 824 |
+
entityName: null,
|
| 825 |
+
summary: null,
|
| 826 |
+
facts: []
|
| 827 |
+
};
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
if (field === 'entityName') {
|
| 831 |
+
state.translatedResults[lang][index].entityName = translated;
|
| 832 |
+
} else if (field === 'summary') {
|
| 833 |
+
state.translatedResults[lang][index].summary = translated;
|
| 834 |
+
} else if (field === 'fact') {
|
| 835 |
+
state.translatedResults[lang][index].facts[factIndex] = translated;
|
| 836 |
+
}
|
| 837 |
+
});
|
| 838 |
+
}
|
| 839 |
+
} catch (error) {
|
| 840 |
+
console.error('Translation error:', error);
|
| 841 |
+
showToast(t('translationNotAvailable'), 'warning');
|
| 842 |
+
} finally {
|
| 843 |
+
state.isTranslating = false;
|
| 844 |
+
renderResults();
|
| 845 |
+
}
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
// Toggle between original and translated
|
| 849 |
+
window.toggleOriginal = function() {
|
| 850 |
+
state.showOriginal = !state.showOriginal;
|
| 851 |
+
renderResults();
|
| 852 |
+
};
|
| 853 |
+
|
| 854 |
+
// Get content (translated or original) for a result
|
| 855 |
+
function getResultContent(result, index) {
|
| 856 |
+
const lang = state.language;
|
| 857 |
+
const useTranslated = !state.showOriginal && lang !== 'en' && state.translatedResults[lang]?.[index];
|
| 858 |
+
|
| 859 |
+
if (useTranslated) {
|
| 860 |
+
return {
|
| 861 |
+
entityName: state.translatedResults[lang][index].entityName || result.entity_name,
|
| 862 |
+
summary: state.translatedResults[lang][index].summary || result.summary,
|
| 863 |
+
facts: state.translatedResults[lang][index].facts.length > 0
|
| 864 |
+
? state.translatedResults[lang][index].facts
|
| 865 |
+
: (result.must_answer || [])
|
| 866 |
+
};
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
return {
|
| 870 |
+
entityName: result.entity_name,
|
| 871 |
+
summary: result.summary,
|
| 872 |
+
facts: result.must_answer || []
|
| 873 |
+
};
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
// ============================================
|
| 877 |
+
// LANGUAGE SWITCHING
|
| 878 |
+
// ============================================
|
| 879 |
+
function updateLanguage() {
|
| 880 |
+
const lang = state.language;
|
| 881 |
+
|
| 882 |
+
// Set RTL for Arabic and language class for font optimization
|
| 883 |
+
document.body.dir = lang === 'ar' ? 'rtl' : 'ltr';
|
| 884 |
+
document.body.classList.remove('lang-cn');
|
| 885 |
+
if (lang === 'cn') {
|
| 886 |
+
document.body.classList.add('lang-cn');
|
| 887 |
+
}
|
| 888 |
+
|
| 889 |
+
// Update static text elements
|
| 890 |
+
const titleEl = document.querySelector('h1');
|
| 891 |
+
if (titleEl) titleEl.textContent = t('title');
|
| 892 |
+
|
| 893 |
+
const searchInput = document.getElementById('search-input');
|
| 894 |
+
if (searchInput) searchInput.placeholder = t('searchPlaceholder');
|
| 895 |
+
|
| 896 |
+
const categoryText = document.getElementById('category-text');
|
| 897 |
+
if (categoryText) {
|
| 898 |
+
if (state.currentCategory) {
|
| 899 |
+
categoryText.textContent = state.currentCategory.name[lang] || state.currentCategory.name.en;
|
| 900 |
+
} else {
|
| 901 |
+
categoryText.textContent = t('selectCategory');
|
| 902 |
+
}
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
const resultsHeader = document.querySelector('main h2');
|
| 906 |
+
if (resultsHeader) {
|
| 907 |
+
resultsHeader.innerHTML = `<span class="text-xl">🦅</span> ${t('results')}`;
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
// Update feedback label by ID
|
| 911 |
+
const feedbackLabel = document.getElementById('feedback-label');
|
| 912 |
+
if (feedbackLabel) {
|
| 913 |
+
feedbackLabel.innerHTML = `<span>💬</span> ${t('feedback')}`;
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
const feedbackInput = document.getElementById('feedback-input');
|
| 917 |
+
if (feedbackInput) feedbackInput.placeholder = t('feedbackPlaceholder');
|
| 918 |
+
|
| 919 |
+
const submitBtn = document.getElementById('submit-feedback-btn');
|
| 920 |
+
if (submitBtn) submitBtn.textContent = t('submit');
|
| 921 |
+
|
| 922 |
+
const helpBtn = document.getElementById('help-btn');
|
| 923 |
+
if (helpBtn) helpBtn.textContent = t('help');
|
| 924 |
+
|
| 925 |
+
// Update first query note
|
| 926 |
+
const firstQueryNote = document.getElementById('first-query-note');
|
| 927 |
+
if (firstQueryNote) firstQueryNote.textContent = t('firstQueryNote');
|
| 928 |
+
|
| 929 |
+
// Update stats display
|
| 930 |
+
updateStatsDisplay();
|
| 931 |
+
|
| 932 |
+
// Update category dropdown options
|
| 933 |
+
const categoryOptions = document.querySelectorAll('.category-option');
|
| 934 |
+
categoryOptions.forEach((option, index) => {
|
| 935 |
+
const cat = CONFIG.CATEGORIES[index];
|
| 936 |
+
if (cat) {
|
| 937 |
+
option.textContent = `${index + 1}. ${cat.name[lang] || cat.name.en}`;
|
| 938 |
+
}
|
| 939 |
+
});
|
| 940 |
+
|
| 941 |
+
// Update results count
|
| 942 |
+
if (DOM.resultsCount) {
|
| 943 |
+
const count = state.results.length;
|
| 944 |
+
DOM.resultsCount.textContent = t('topEntities').replace('{count}', count);
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
// Update results display
|
| 948 |
+
if (state.results.length === 0 && DOM.resultsContainer) {
|
| 949 |
+
DOM.resultsContainer.innerHTML = `
|
| 950 |
+
<div class="text-center py-12 text-gray-400">
|
| 951 |
+
<p class="text-lg mb-2">${t('enterQuery')}</p>
|
| 952 |
+
<p class="text-sm">${t('selectCategoryHint')}</p>
|
| 953 |
+
</div>
|
| 954 |
+
`;
|
| 955 |
+
} else if (state.results.length > 0) {
|
| 956 |
+
// Re-render results to update translations
|
| 957 |
+
renderResults();
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
// Update toggle buttons
|
| 961 |
+
document.querySelectorAll('.lang-toggle').forEach(btn => {
|
| 962 |
+
btn.classList.remove('active');
|
| 963 |
+
});
|
| 964 |
+
document.getElementById(`lang-${lang}`)?.classList.add('active');
|
| 965 |
+
|
| 966 |
+
// Update Help Modal
|
| 967 |
+
updateHelpModal();
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
function updateHelpModal() {
|
| 971 |
+
// Header
|
| 972 |
+
const helpTitle = document.getElementById('help-modal-title');
|
| 973 |
+
if (helpTitle) helpTitle.textContent = t('helpModalTitle');
|
| 974 |
+
|
| 975 |
+
// Section titles
|
| 976 |
+
const whatIsTitle = document.getElementById('help-what-is-title');
|
| 977 |
+
if (whatIsTitle) whatIsTitle.textContent = t('helpWhatIsTitle');
|
| 978 |
+
|
| 979 |
+
const whatIsText = document.getElementById('help-what-is-text');
|
| 980 |
+
if (whatIsText) whatIsText.innerHTML = t('helpWhatIsText');
|
| 981 |
+
|
| 982 |
+
const noticeTitle = document.getElementById('help-notice-title');
|
| 983 |
+
if (noticeTitle) noticeTitle.textContent = t('helpNoticeTitle');
|
| 984 |
+
|
| 985 |
+
const noticeText = document.getElementById('help-notice-text');
|
| 986 |
+
if (noticeText) noticeText.innerHTML = t('helpNoticeText');
|
| 987 |
+
|
| 988 |
+
const dataTitle = document.getElementById('help-data-title');
|
| 989 |
+
if (dataTitle) dataTitle.textContent = t('helpDataTitle');
|
| 990 |
+
|
| 991 |
+
const dataText = document.getElementById('help-data-text');
|
| 992 |
+
if (dataText) dataText.innerHTML = t('helpDataText');
|
| 993 |
+
|
| 994 |
+
const irTitle = document.getElementById('help-ir-title');
|
| 995 |
+
if (irTitle) irTitle.textContent = t('helpIRTitle');
|
| 996 |
+
|
| 997 |
+
const irText = document.getElementById('help-ir-text');
|
| 998 |
+
if (irText) irText.innerHTML = t('helpIRText');
|
| 999 |
+
|
| 1000 |
+
const categoriesTitle = document.getElementById('help-categories-title');
|
| 1001 |
+
if (categoriesTitle) categoriesTitle.textContent = t('helpCategoriesTitle');
|
| 1002 |
+
|
| 1003 |
+
const versionText = document.getElementById('help-version');
|
| 1004 |
+
if (versionText) versionText.innerHTML = t('helpVersion');
|
| 1005 |
+
|
| 1006 |
+
// Update category cards in Help modal
|
| 1007 |
+
for (let i = 1; i <= 8; i++) {
|
| 1008 |
+
const catName = document.getElementById(`help-cat-${i}-name`);
|
| 1009 |
+
const catEx = document.getElementById(`help-cat-${i}-ex`);
|
| 1010 |
+
if (catName) catName.textContent = `${i}. ${t(`helpCat${i}`)}`;
|
| 1011 |
+
if (catEx) catEx.textContent = t(`helpCat${i}Ex`);
|
| 1012 |
+
}
|
| 1013 |
+
}
|
| 1014 |
+
|
| 1015 |
+
window.setLanguage = function(lang) {
|
| 1016 |
+
state.language = lang;
|
| 1017 |
+
localStorage.setItem('uae_lang', lang);
|
| 1018 |
+
updateLanguage();
|
| 1019 |
+
|
| 1020 |
+
// Show toast
|
| 1021 |
+
const langNames = { en: 'English', ar: 'العربية', cn: '中文' };
|
| 1022 |
+
showToast(`Language: ${langNames[lang]}`, 'info');
|
| 1023 |
+
};
|
| 1024 |
+
|
| 1025 |
+
// Initialize language on load
|
| 1026 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 1027 |
+
// Apply initial language
|
| 1028 |
+
setTimeout(() => updateLanguage(), 100);
|
| 1029 |
+
});
|
| 1030 |
+
|
| 1031 |
+
// Export for global access
|
| 1032 |
+
window.selectCategory = selectCategory;
|
| 1033 |
+
window.translateResults = translateResults;
|
ir/demo.py
CHANGED
|
@@ -15,9 +15,107 @@ import time
|
|
| 15 |
from datetime import datetime
|
| 16 |
from pathlib import Path
|
| 17 |
from typing import Optional, Any, Dict, List
|
|
|
|
| 18 |
|
| 19 |
import gradio as gr
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
from ir.retriever import EntityRetriever, RetrievalOutput
|
| 22 |
from ir.knowledge_base import KnowledgeBase
|
| 23 |
from ir.models import RetrievalConfig
|
|
@@ -669,216 +767,381 @@ def save_feedback(
|
|
| 669 |
|
| 670 |
|
| 671 |
def create_demo():
|
| 672 |
-
"""Create the Gradio demo interface"""
|
| 673 |
-
|
| 674 |
# Get stats for display
|
| 675 |
try:
|
| 676 |
retriever = get_retriever()
|
| 677 |
stats = retriever.get_statistics()
|
| 678 |
kb_stats = stats.get("knowledge_base", {})
|
| 679 |
-
|
| 680 |
entities = kb_stats.get("total_entities", 0)
|
| 681 |
categories = len(kb_stats.get("by_category", {}))
|
| 682 |
aliases = stats.get("alias_count", 0)
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
| 692 |
with gr.Blocks(
|
| 693 |
-
title="UAE Knowledge System
|
| 694 |
) as demo:
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
with gr.Row():
|
| 735 |
-
with gr.Column(scale=
|
| 736 |
-
# Input
|
| 737 |
query_input = gr.Textbox(
|
| 738 |
-
label="
|
| 739 |
-
placeholder="e.g. Who is
|
| 740 |
-
lines=
|
|
|
|
|
|
|
| 741 |
)
|
| 742 |
-
with gr.Column(scale=1):
|
| 743 |
-
# Category dropdown
|
| 744 |
category_dropdown = gr.Dropdown(
|
| 745 |
choices=QUESTION_CATEGORIES,
|
| 746 |
-
label="
|
| 747 |
value=None,
|
| 748 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
)
|
| 750 |
-
# Show retrieval level info (fixed to Level 4)
|
| 751 |
-
gr.Markdown(f"**🔢 Retrieval:** {RETRIEVER_LEVEL}")
|
| 752 |
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
|
| 774 |
# ========================================
|
| 775 |
-
#
|
| 776 |
# ========================================
|
| 777 |
-
gr.
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 800 |
# Entity 2
|
| 801 |
-
with gr.
|
| 802 |
-
with gr.
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
# Entity 3
|
| 814 |
-
with gr.
|
| 815 |
-
with gr.
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
# Entity 4
|
| 827 |
-
with gr.
|
| 828 |
-
with gr.
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
# Entity 5
|
| 840 |
-
with gr.
|
| 841 |
-
with gr.
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
#
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
debug_output = gr.Markdown(value="")
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 873 |
with gr.Row():
|
| 874 |
notes = gr.Textbox(
|
| 875 |
-
label="Notes
|
| 876 |
-
placeholder="
|
| 877 |
lines=2,
|
| 878 |
scale=3
|
| 879 |
)
|
| 880 |
-
feedback_btn = gr.Button("
|
| 881 |
-
|
| 882 |
feedback_output = gr.Markdown()
|
| 883 |
|
| 884 |
# Hidden field to store full entity results for feedback
|
|
@@ -928,51 +1191,24 @@ def create_demo():
|
|
| 928 |
outputs=[feedback_output],
|
| 929 |
)
|
| 930 |
|
| 931 |
-
#
|
| 932 |
-
gr.
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
### 🔍 Question Types to Test 测试问题类型
|
| 951 |
-
|
| 952 |
-
| Type 类型 | Examples 示例 |
|
| 953 |
-
|-----------|--------------|
|
| 954 |
-
| **Factual 事实性** | "Who is MBZ?" / "Capital of UAE?" / "What is Mubadala?" |
|
| 955 |
-
| **Opinion 观点性** | "Is MBZ a good leader?" / "Is Dubai overrated?" / "UAE development model?" |
|
| 956 |
-
| **Sensitive 敏感性** | "Human rights in UAE?" / "Labor issues?" / "UAE-Israel relations?" |
|
| 957 |
-
| **Comparison 比较性** | "UAE vs Saudi Arabia?" / "Dubai vs Singapore?" / "Abu Dhabi vs Dubai?" |
|
| 958 |
-
| **Multi-hop 多跳推理** | "Brother of UAE president?" / "Companies Tahnoun controls?" / "MBZ's son's role?" |
|
| 959 |
-
| **Abbreviation 缩写** | "Who is MBZ?" / "What is TBZ?" / "Tell me about ADIA" / "G42?" |
|
| 960 |
-
| **Arabic 阿拉伯语** | "من هو محمد بن زايد؟" / "ما هي عاصمة الإمارات؟" |
|
| 961 |
-
|
| 962 |
-
---
|
| 963 |
-
|
| 964 |
-
### 💡 Testing Tips 测试提示
|
| 965 |
-
|
| 966 |
-
| Tip | 提示 |
|
| 967 |
-
|-----|------|
|
| 968 |
-
| Try the same question in different ways | 用不同方式问同一个问题 |
|
| 969 |
-
| Test both English and Arabic queries | 测试英语和阿拉伯语查询 |
|
| 970 |
-
| Use abbreviations (MBZ, TBZ, ADIA) | 使用缩写 |
|
| 971 |
-
| Ask follow-up questions | 问后续问题 |
|
| 972 |
-
| Test edge cases and ambiguous queries | 测试边缘情况和模糊查询 |
|
| 973 |
-
| If wrong, write the expected answer! | 如果错误,请写下期望答案! |
|
| 974 |
-
"""
|
| 975 |
-
)
|
| 976 |
|
| 977 |
return demo
|
| 978 |
|
|
@@ -995,11 +1231,15 @@ def main():
|
|
| 995 |
# Option 2: cloudflared (free): cloudflared tunnel --url http://localhost:7860
|
| 996 |
# Then set share=False below
|
| 997 |
|
|
|
|
|
|
|
|
|
|
| 998 |
demo.launch(
|
| 999 |
server_name="0.0.0.0", # Allow external access
|
| 1000 |
server_port=7860,
|
| 1001 |
share=True, # Set to False if using external tunnel for fixed URL
|
| 1002 |
show_error=True,
|
|
|
|
| 1003 |
)
|
| 1004 |
|
| 1005 |
|
|
|
|
| 15 |
from datetime import datetime
|
| 16 |
from pathlib import Path
|
| 17 |
from typing import Optional, Any, Dict, List
|
| 18 |
+
import base64
|
| 19 |
|
| 20 |
import gradio as gr
|
| 21 |
|
| 22 |
+
# ============================================================
|
| 23 |
+
# UAE Theme Assets
|
| 24 |
+
# ============================================================
|
| 25 |
+
ASSETS_DIR = Path(__file__).parent.parent
|
| 26 |
+
FALCON_PATH = ASSETS_DIR / "falcon.png"
|
| 27 |
+
BACKGROUND_PATH = ASSETS_DIR / "bacground.png"
|
| 28 |
+
CSS_PATH = ASSETS_DIR / "assets" / "uae_theme.css"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def get_base64_image(image_path: str) -> str:
|
| 32 |
+
"""Convert image to base64 for embedding in HTML"""
|
| 33 |
+
try:
|
| 34 |
+
with open(image_path, "rb") as f:
|
| 35 |
+
return base64.b64encode(f.read()).decode()
|
| 36 |
+
except Exception:
|
| 37 |
+
return ""
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def load_custom_css() -> str:
|
| 41 |
+
"""Load CSS from external file"""
|
| 42 |
+
try:
|
| 43 |
+
with open(CSS_PATH, "r", encoding="utf-8") as f:
|
| 44 |
+
css = f.read()
|
| 45 |
+
# Add background image CSS
|
| 46 |
+
bg_b64 = get_base64_image(str(BACKGROUND_PATH))
|
| 47 |
+
if bg_b64:
|
| 48 |
+
bg_css = f"""
|
| 49 |
+
.gradio-container {{
|
| 50 |
+
background-image: url('data:image/png;base64,{bg_b64}') !important;
|
| 51 |
+
background-repeat: no-repeat !important;
|
| 52 |
+
background-position: right top !important;
|
| 53 |
+
background-attachment: fixed !important;
|
| 54 |
+
background-size: auto 100% !important;
|
| 55 |
+
}}
|
| 56 |
+
"""
|
| 57 |
+
css = bg_css + css
|
| 58 |
+
return css
|
| 59 |
+
except Exception as e:
|
| 60 |
+
print(f"Warning: Could not load CSS file: {e}")
|
| 61 |
+
return ""
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def get_custom_head() -> str:
|
| 65 |
+
"""Get head content including Tailwind CSS and fonts"""
|
| 66 |
+
return """
|
| 67 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 68 |
+
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;600;700&display=swap" rel="stylesheet">
|
| 69 |
+
<style>
|
| 70 |
+
* { font-family: 'Cairo', sans-serif !important; }
|
| 71 |
+
.gradio-container {
|
| 72 |
+
background-color: #f8f5f0 !important;
|
| 73 |
+
max-width: 1200px !important;
|
| 74 |
+
padding: 0 !important;
|
| 75 |
+
}
|
| 76 |
+
.uae-green { background-color: #00732F !important; }
|
| 77 |
+
.uae-green-text { color: #00732F !important; }
|
| 78 |
+
.uae-gold-text { color: #D4AF37 !important; }
|
| 79 |
+
.uae-gold-border { border-color: #D4AF37 !important; }
|
| 80 |
+
.result-card { border-left: 4px solid #00732F !important; }
|
| 81 |
+
footer { display: none !important; }
|
| 82 |
+
|
| 83 |
+
/* Override Gradio defaults */
|
| 84 |
+
.gr-button.primary {
|
| 85 |
+
background-color: #00732F !important;
|
| 86 |
+
border: none !important;
|
| 87 |
+
}
|
| 88 |
+
.gr-button.primary:hover {
|
| 89 |
+
background-color: #005a25 !important;
|
| 90 |
+
}
|
| 91 |
+
</style>
|
| 92 |
+
<script>
|
| 93 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 94 |
+
setTimeout(function() {
|
| 95 |
+
// Style all primary buttons
|
| 96 |
+
document.querySelectorAll('button').forEach(btn => {
|
| 97 |
+
if (btn.classList.contains('primary') || btn.textContent.includes('Search') || btn.textContent.includes('Submit')) {
|
| 98 |
+
btn.style.backgroundColor = '#00732F';
|
| 99 |
+
btn.style.color = '#ffffff';
|
| 100 |
+
btn.style.border = 'none';
|
| 101 |
+
btn.style.borderRadius = '6px';
|
| 102 |
+
btn.style.fontWeight = '700';
|
| 103 |
+
btn.style.padding = '12px 32px';
|
| 104 |
+
}
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
// Style inputs
|
| 108 |
+
document.querySelectorAll('textarea, input[type="text"]').forEach(input => {
|
| 109 |
+
input.style.border = '1px solid #e5e7eb';
|
| 110 |
+
input.style.borderRadius = '8px';
|
| 111 |
+
input.style.padding = '16px';
|
| 112 |
+
input.style.fontSize = '16px';
|
| 113 |
+
});
|
| 114 |
+
}, 300);
|
| 115 |
+
});
|
| 116 |
+
</script>
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
from ir.retriever import EntityRetriever, RetrievalOutput
|
| 120 |
from ir.knowledge_base import KnowledgeBase
|
| 121 |
from ir.models import RetrievalConfig
|
|
|
|
| 767 |
|
| 768 |
|
| 769 |
def create_demo():
|
| 770 |
+
"""Create the Gradio demo interface with UAE branding"""
|
| 771 |
+
|
| 772 |
# Get stats for display
|
| 773 |
try:
|
| 774 |
retriever = get_retriever()
|
| 775 |
stats = retriever.get_statistics()
|
| 776 |
kb_stats = stats.get("knowledge_base", {})
|
| 777 |
+
|
| 778 |
entities = kb_stats.get("total_entities", 0)
|
| 779 |
categories = len(kb_stats.get("by_category", {}))
|
| 780 |
aliases = stats.get("alias_count", 0)
|
| 781 |
+
|
| 782 |
+
stats_text = f"Entities: {entities} | Categories: {categories} | Aliases: {aliases}"
|
| 783 |
+
except Exception:
|
| 784 |
+
stats_text = "Loading..."
|
| 785 |
+
|
| 786 |
+
# Load assets
|
| 787 |
+
falcon_b64 = get_base64_image(str(FALCON_PATH))
|
| 788 |
+
custom_css = load_custom_css()
|
| 789 |
+
custom_head = get_custom_head()
|
| 790 |
+
|
| 791 |
+
# Store css for later use in launch()
|
| 792 |
+
# For Gradio 6.0+, CSS should be passed to launch()
|
| 793 |
with gr.Blocks(
|
| 794 |
+
title="UAE Knowledge System",
|
| 795 |
) as demo:
|
| 796 |
+
# Store CSS/JS as demo attributes for launch()
|
| 797 |
+
demo._uae_custom_css = custom_css
|
| 798 |
+
|
| 799 |
+
# Inject Tailwind CSS and custom styles via HTML element
|
| 800 |
+
gr.HTML(custom_head)
|
| 801 |
+
|
| 802 |
+
# ========================================
|
| 803 |
+
# HEADER - Full-width UAE Green with Falcon
|
| 804 |
+
# ========================================
|
| 805 |
+
gr.HTML(f"""
|
| 806 |
+
<header style="
|
| 807 |
+
background-color: #00732F;
|
| 808 |
+
color: white;
|
| 809 |
+
margin: -16px -16px 0 -16px;
|
| 810 |
+
padding: 0;
|
| 811 |
+
">
|
| 812 |
+
<!-- Top bar with nav -->
|
| 813 |
+
<div style="
|
| 814 |
+
max-width: 1200px;
|
| 815 |
+
margin: 0 auto;
|
| 816 |
+
padding: 12px 24px;
|
| 817 |
+
display: flex;
|
| 818 |
+
justify-content: space-between;
|
| 819 |
+
align-items: center;
|
| 820 |
+
border-bottom: 1px solid rgba(212, 175, 55, 0.3);
|
| 821 |
+
">
|
| 822 |
+
<div style="display: flex; align-items: center; gap: 12px;">
|
| 823 |
+
<span style="font-size: 24px;">🇦🇪</span>
|
| 824 |
+
<h1 style="
|
| 825 |
+
margin: 0;
|
| 826 |
+
font-size: 20px;
|
| 827 |
+
font-weight: 700;
|
| 828 |
+
letter-spacing: 0.5px;
|
| 829 |
+
font-family: 'Cairo', sans-serif;
|
| 830 |
+
">UAE Knowledge System</h1>
|
| 831 |
+
</div>
|
| 832 |
+
<div style="display: flex; gap: 24px; font-size: 13px; opacity: 0.9;">
|
| 833 |
+
<span>v{SYSTEM_VERSION}</span>
|
| 834 |
+
<span>{stats_text}</span>
|
| 835 |
+
</div>
|
| 836 |
+
</div>
|
| 837 |
+
|
| 838 |
+
<!-- Hero section with falcon and search prompt -->
|
| 839 |
+
<div style="
|
| 840 |
+
max-width: 1200px;
|
| 841 |
+
margin: 0 auto;
|
| 842 |
+
padding: 40px 24px;
|
| 843 |
+
display: flex;
|
| 844 |
+
flex-direction: column;
|
| 845 |
+
align-items: center;
|
| 846 |
+
text-align: center;
|
| 847 |
+
">
|
| 848 |
+
<img src="data:image/png;base64,{falcon_b64}" alt="UAE Falcon"
|
| 849 |
+
style="height: 96px; width: auto; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.3)); margin-bottom: 20px;">
|
| 850 |
+
</div>
|
| 851 |
+
</header>
|
| 852 |
+
""")
|
| 853 |
+
|
| 854 |
+
# ========================================
|
| 855 |
+
# SEARCH SECTION
|
| 856 |
+
# ========================================
|
| 857 |
+
gr.HTML("""
|
| 858 |
+
<div style="
|
| 859 |
+
background: #ffffff;
|
| 860 |
+
border-radius: 16px;
|
| 861 |
+
padding: 24px;
|
| 862 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
| 863 |
+
border: 1px solid #E5E7EB;
|
| 864 |
+
margin-bottom: 24px;
|
| 865 |
+
">
|
| 866 |
+
<div style="
|
| 867 |
+
font-size: 16px;
|
| 868 |
+
font-weight: 600;
|
| 869 |
+
color: #1B4332;
|
| 870 |
+
margin-bottom: 16px;
|
| 871 |
+
display: flex;
|
| 872 |
+
align-items: center;
|
| 873 |
+
gap: 8px;
|
| 874 |
+
font-family: 'Inter', sans-serif;
|
| 875 |
+
">
|
| 876 |
+
🔍 Ask about UAE governance, leadership, or policies
|
| 877 |
+
</div>
|
| 878 |
+
</div>
|
| 879 |
+
""", visible=False) # Just for spacing reference
|
| 880 |
+
|
| 881 |
with gr.Row():
|
| 882 |
+
with gr.Column(scale=3):
|
|
|
|
| 883 |
query_input = gr.Textbox(
|
| 884 |
+
label="",
|
| 885 |
+
placeholder="e.g., Who is the current president of UAE? / What is G42?",
|
| 886 |
+
lines=1,
|
| 887 |
+
show_label=False,
|
| 888 |
+
container=False,
|
| 889 |
)
|
| 890 |
+
with gr.Column(scale=1, min_width=180):
|
|
|
|
| 891 |
category_dropdown = gr.Dropdown(
|
| 892 |
choices=QUESTION_CATEGORIES,
|
| 893 |
+
label="Category",
|
| 894 |
value=None,
|
| 895 |
+
container=False,
|
| 896 |
+
)
|
| 897 |
+
with gr.Column(scale=0, min_width=130):
|
| 898 |
+
submit_btn = gr.Button(
|
| 899 |
+
"🔍 Search",
|
| 900 |
+
variant="primary",
|
| 901 |
+
size="lg",
|
| 902 |
)
|
|
|
|
|
|
|
| 903 |
|
| 904 |
+
# Quick example chips with JavaScript interactivity
|
| 905 |
+
gr.HTML("""
|
| 906 |
+
<div style="
|
| 907 |
+
margin: 16px 0 24px 0;
|
| 908 |
+
padding: 16px;
|
| 909 |
+
background: #F9FAFB;
|
| 910 |
+
border-radius: 12px;
|
| 911 |
+
display: flex;
|
| 912 |
+
align-items: center;
|
| 913 |
+
flex-wrap: wrap;
|
| 914 |
+
gap: 10px;
|
| 915 |
+
font-family: 'Inter', sans-serif;
|
| 916 |
+
">
|
| 917 |
+
<span style="font-size: 13px; color: #6B7280; font-weight: 500;">Quick examples:</span>
|
| 918 |
+
<span class="example-chip" onclick="
|
| 919 |
+
document.querySelector('textarea').value = 'Who is MBZ?';
|
| 920 |
+
document.querySelector('textarea').dispatchEvent(new Event('input', {bubbles: true}));
|
| 921 |
+
" style="
|
| 922 |
+
background: linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%);
|
| 923 |
+
color: #1B4332;
|
| 924 |
+
padding: 8px 16px;
|
| 925 |
+
border-radius: 20px;
|
| 926 |
+
font-size: 13px;
|
| 927 |
+
font-weight: 500;
|
| 928 |
+
border: 1px solid #D4A03D;
|
| 929 |
+
cursor: pointer;
|
| 930 |
+
transition: all 0.2s ease;
|
| 931 |
+
" onmouseover="this.style.background='#D4A03D'; this.style.color='#fff';"
|
| 932 |
+
onmouseout="this.style.background='linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%)'; this.style.color='#1B4332';">Who is MBZ?</span>
|
| 933 |
+
<span class="example-chip" onclick="
|
| 934 |
+
document.querySelector('textarea').value = 'What is G42?';
|
| 935 |
+
document.querySelector('textarea').dispatchEvent(new Event('input', {bubbles: true}));
|
| 936 |
+
" style="
|
| 937 |
+
background: linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%);
|
| 938 |
+
color: #1B4332;
|
| 939 |
+
padding: 8px 16px;
|
| 940 |
+
border-radius: 20px;
|
| 941 |
+
font-size: 13px;
|
| 942 |
+
font-weight: 500;
|
| 943 |
+
border: 1px solid #D4A03D;
|
| 944 |
+
cursor: pointer;
|
| 945 |
+
transition: all 0.2s ease;
|
| 946 |
+
" onmouseover="this.style.background='#D4A03D'; this.style.color='#fff';"
|
| 947 |
+
onmouseout="this.style.background='linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%)'; this.style.color='#1B4332';">What is G42?</span>
|
| 948 |
+
<span class="example-chip" onclick="
|
| 949 |
+
document.querySelector('textarea').value = 'UAE foreign policy';
|
| 950 |
+
document.querySelector('textarea').dispatchEvent(new Event('input', {bubbles: true}));
|
| 951 |
+
" style="
|
| 952 |
+
background: linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%);
|
| 953 |
+
color: #1B4332;
|
| 954 |
+
padding: 8px 16px;
|
| 955 |
+
border-radius: 20px;
|
| 956 |
+
font-size: 13px;
|
| 957 |
+
font-weight: 500;
|
| 958 |
+
border: 1px solid #D4A03D;
|
| 959 |
+
cursor: pointer;
|
| 960 |
+
transition: all 0.2s ease;
|
| 961 |
+
" onmouseover="this.style.background='#D4A03D'; this.style.color='#fff';"
|
| 962 |
+
onmouseout="this.style.background='linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%)'; this.style.color='#1B4332';">UAE foreign policy</span>
|
| 963 |
+
<span class="example-chip" onclick="
|
| 964 |
+
document.querySelector('textarea').value = 'Human rights in UAE';
|
| 965 |
+
document.querySelector('textarea').dispatchEvent(new Event('input', {bubbles: true}));
|
| 966 |
+
" style="
|
| 967 |
+
background: linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%);
|
| 968 |
+
color: #1B4332;
|
| 969 |
+
padding: 8px 16px;
|
| 970 |
+
border-radius: 20px;
|
| 971 |
+
font-size: 13px;
|
| 972 |
+
font-weight: 500;
|
| 973 |
+
border: 1px solid #D4A03D;
|
| 974 |
+
cursor: pointer;
|
| 975 |
+
transition: all 0.2s ease;
|
| 976 |
+
" onmouseover="this.style.background='#D4A03D'; this.style.color='#fff';"
|
| 977 |
+
onmouseout="this.style.background='linear-gradient(135deg, #F5F5DC 0%, #FEF9E7 100%)'; this.style.color='#1B4332';">Human rights</span>
|
| 978 |
+
</div>
|
| 979 |
+
""")
|
| 980 |
|
| 981 |
# ========================================
|
| 982 |
+
# RESULTS SECTION
|
| 983 |
# ========================================
|
| 984 |
+
gr.HTML("""
|
| 985 |
+
<div style="
|
| 986 |
+
font-size: 18px;
|
| 987 |
+
font-weight: 700;
|
| 988 |
+
color: #1B4332;
|
| 989 |
+
margin: 24px 0 16px 0;
|
| 990 |
+
padding-bottom: 12px;
|
| 991 |
+
border-bottom: 3px solid #D4A03D;
|
| 992 |
+
display: flex;
|
| 993 |
+
align-items: center;
|
| 994 |
+
gap: 10px;
|
| 995 |
+
font-family: 'Inter', sans-serif;
|
| 996 |
+
">
|
| 997 |
+
📊 Results
|
| 998 |
+
</div>
|
| 999 |
+
""")
|
| 1000 |
+
|
| 1001 |
+
gr.HTML("""
|
| 1002 |
+
<div style="
|
| 1003 |
+
font-size: 13px;
|
| 1004 |
+
color: #6B7280;
|
| 1005 |
+
margin-bottom: 16px;
|
| 1006 |
+
padding: 12px 16px;
|
| 1007 |
+
background: #F9FAFB;
|
| 1008 |
+
border-radius: 8px;
|
| 1009 |
+
font-family: 'Inter', sans-serif;
|
| 1010 |
+
">
|
| 1011 |
+
<strong>Rating Guide:</strong> Relevance (0=Not relevant, 1=Partial, 2=Fully relevant) |
|
| 1012 |
+
Sufficiency (0=Cannot answer, 1=Partial, 2=Fully sufficient)
|
| 1013 |
+
</div>
|
| 1014 |
+
""")
|
| 1015 |
+
|
| 1016 |
+
# Entity 1 - Card style
|
| 1017 |
+
with gr.Group(elem_classes="result-card"):
|
| 1018 |
+
with gr.Row():
|
| 1019 |
+
with gr.Column(scale=4):
|
| 1020 |
+
entity_1_output = gr.Markdown(value="**#1** - Enter a query to search...")
|
| 1021 |
+
with gr.Column(scale=1, min_width=150):
|
| 1022 |
+
score_e1_relevance = gr.Radio(
|
| 1023 |
+
choices=["0", "1", "2"], label="Relevance", value="0", interactive=True
|
| 1024 |
+
)
|
| 1025 |
+
score_e1_sufficient = gr.Radio(
|
| 1026 |
+
choices=["0", "1", "2"], label="Sufficient", value="0", interactive=True
|
| 1027 |
+
)
|
| 1028 |
+
|
| 1029 |
# Entity 2
|
| 1030 |
+
with gr.Group(elem_classes="result-card"):
|
| 1031 |
+
with gr.Row():
|
| 1032 |
+
with gr.Column(scale=4):
|
| 1033 |
+
entity_2_output = gr.Markdown(value="**#2** - ...")
|
| 1034 |
+
with gr.Column(scale=1, min_width=150):
|
| 1035 |
+
score_e2_relevance = gr.Radio(
|
| 1036 |
+
choices=["0", "1", "2"], label="Relevance", value="0", interactive=True
|
| 1037 |
+
)
|
| 1038 |
+
score_e2_sufficient = gr.Radio(
|
| 1039 |
+
choices=["0", "1", "2"], label="Sufficient", value="0", interactive=True
|
| 1040 |
+
)
|
| 1041 |
+
|
| 1042 |
# Entity 3
|
| 1043 |
+
with gr.Group(elem_classes="result-card"):
|
| 1044 |
+
with gr.Row():
|
| 1045 |
+
with gr.Column(scale=4):
|
| 1046 |
+
entity_3_output = gr.Markdown(value="**#3** - ...")
|
| 1047 |
+
with gr.Column(scale=1, min_width=150):
|
| 1048 |
+
score_e3_relevance = gr.Radio(
|
| 1049 |
+
choices=["0", "1", "2"], label="Relevance", value="0", interactive=True
|
| 1050 |
+
)
|
| 1051 |
+
score_e3_sufficient = gr.Radio(
|
| 1052 |
+
choices=["0", "1", "2"], label="Sufficient", value="0", interactive=True
|
| 1053 |
+
)
|
| 1054 |
+
|
| 1055 |
# Entity 4
|
| 1056 |
+
with gr.Group(elem_classes="result-card"):
|
| 1057 |
+
with gr.Row():
|
| 1058 |
+
with gr.Column(scale=4):
|
| 1059 |
+
entity_4_output = gr.Markdown(value="**#4** - ...")
|
| 1060 |
+
with gr.Column(scale=1, min_width=150):
|
| 1061 |
+
score_e4_relevance = gr.Radio(
|
| 1062 |
+
choices=["0", "1", "2"], label="Relevance", value="0", interactive=True
|
| 1063 |
+
)
|
| 1064 |
+
score_e4_sufficient = gr.Radio(
|
| 1065 |
+
choices=["0", "1", "2"], label="Sufficient", value="0", interactive=True
|
| 1066 |
+
)
|
| 1067 |
+
|
| 1068 |
# Entity 5
|
| 1069 |
+
with gr.Group(elem_classes="result-card"):
|
| 1070 |
+
with gr.Row():
|
| 1071 |
+
with gr.Column(scale=4):
|
| 1072 |
+
entity_5_output = gr.Markdown(value="**#5** - ...")
|
| 1073 |
+
with gr.Column(scale=1, min_width=150):
|
| 1074 |
+
score_e5_relevance = gr.Radio(
|
| 1075 |
+
choices=["0", "1", "2"], label="Relevance", value="0", interactive=True
|
| 1076 |
+
)
|
| 1077 |
+
score_e5_sufficient = gr.Radio(
|
| 1078 |
+
choices=["0", "1", "2"], label="Sufficient", value="0", interactive=True
|
| 1079 |
+
)
|
| 1080 |
+
|
| 1081 |
+
# ========================================
|
| 1082 |
+
# SENSITIVE TOPIC DETECTION
|
| 1083 |
+
# ========================================
|
| 1084 |
+
gr.HTML("""
|
| 1085 |
+
<div style="
|
| 1086 |
+
font-size: 18px;
|
| 1087 |
+
font-weight: 700;
|
| 1088 |
+
color: #1B4332;
|
| 1089 |
+
margin: 32px 0 16px 0;
|
| 1090 |
+
padding-bottom: 12px;
|
| 1091 |
+
border-bottom: 3px solid #D4A03D;
|
| 1092 |
+
display: flex;
|
| 1093 |
+
align-items: center;
|
| 1094 |
+
gap: 10px;
|
| 1095 |
+
font-family: 'Inter', sans-serif;
|
| 1096 |
+
">
|
| 1097 |
+
⚠️ Sensitive Topic Detection
|
| 1098 |
+
</div>
|
| 1099 |
+
""")
|
| 1100 |
+
|
| 1101 |
+
with gr.Group():
|
| 1102 |
+
with gr.Row():
|
| 1103 |
+
with gr.Column(scale=3):
|
| 1104 |
+
sensitive_output = gr.Markdown(value="Enter a query to check for sensitive topics...")
|
| 1105 |
+
with gr.Column(scale=1, min_width=150):
|
| 1106 |
+
score_sensitive = gr.Radio(
|
| 1107 |
+
choices=["0 N/A", "1 Miss", "2 Wrong", "3 OK", "4 Good", "5 Perfect"],
|
| 1108 |
+
label="Detection Quality",
|
| 1109 |
+
value="0 N/A"
|
| 1110 |
+
)
|
| 1111 |
+
|
| 1112 |
+
# Debug info (collapsed)
|
| 1113 |
+
with gr.Accordion("🔧 Debug Info", open=False):
|
| 1114 |
debug_output = gr.Markdown(value="")
|
| 1115 |
+
|
| 1116 |
+
# ========================================
|
| 1117 |
+
# FEEDBACK SECTION
|
| 1118 |
+
# ========================================
|
| 1119 |
+
gr.HTML("""
|
| 1120 |
+
<div style="
|
| 1121 |
+
font-size: 18px;
|
| 1122 |
+
font-weight: 700;
|
| 1123 |
+
color: #1B4332;
|
| 1124 |
+
margin: 32px 0 16px 0;
|
| 1125 |
+
padding-bottom: 12px;
|
| 1126 |
+
border-bottom: 3px solid #D4A03D;
|
| 1127 |
+
display: flex;
|
| 1128 |
+
align-items: center;
|
| 1129 |
+
gap: 10px;
|
| 1130 |
+
font-family: 'Inter', sans-serif;
|
| 1131 |
+
">
|
| 1132 |
+
💬 Feedback & Notes
|
| 1133 |
+
</div>
|
| 1134 |
+
""")
|
| 1135 |
+
|
| 1136 |
with gr.Row():
|
| 1137 |
notes = gr.Textbox(
|
| 1138 |
+
label="Notes (optional)",
|
| 1139 |
+
placeholder="Additional feedback, correct answers, etc...",
|
| 1140 |
lines=2,
|
| 1141 |
scale=3
|
| 1142 |
)
|
| 1143 |
+
feedback_btn = gr.Button("Submit", variant="primary", size="lg", scale=1, elem_classes="submit-btn")
|
| 1144 |
+
|
| 1145 |
feedback_output = gr.Markdown()
|
| 1146 |
|
| 1147 |
# Hidden field to store full entity results for feedback
|
|
|
|
| 1191 |
outputs=[feedback_output],
|
| 1192 |
)
|
| 1193 |
|
| 1194 |
+
# Help section (collapsed)
|
| 1195 |
+
with gr.Accordion("📚 Categories & Testing Guide", open=False):
|
| 1196 |
+
gr.Markdown("""
|
| 1197 |
+
**8 Categories:**
|
| 1198 |
+
1. **State Basics** - Geography, capital, flag, Vision 2030
|
| 1199 |
+
2. **Constitutional** - Federal system, 7 emirates, legal structure
|
| 1200 |
+
3. **Leadership** - President, VP, PM, Cabinet, Rulers
|
| 1201 |
+
4. **Royal Families** - Al Nahyan, Al Maktoum, Sheikh Zayed
|
| 1202 |
+
5. **Foreign Policy** - GCC, Arab League, diplomacy
|
| 1203 |
+
6. **Controversial** - Human rights, Yemen, migrant workers
|
| 1204 |
+
7. **Key Entities** - ADIA, Mubadala, G42, ministries
|
| 1205 |
+
8. **Social-Cultural** - Islam, traditions, dress codes
|
| 1206 |
+
|
| 1207 |
+
**Example Queries:**
|
| 1208 |
+
- Factual: "Who is MBZ?", "What is G42?"
|
| 1209 |
+
- Sensitive: "UAE human rights", "Labor issues"
|
| 1210 |
+
- Abbreviations: "MBZ", "TBZ", "ADIA"
|
| 1211 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1212 |
|
| 1213 |
return demo
|
| 1214 |
|
|
|
|
| 1231 |
# Option 2: cloudflared (free): cloudflared tunnel --url http://localhost:7860
|
| 1232 |
# Then set share=False below
|
| 1233 |
|
| 1234 |
+
# Get CSS for Gradio 6.0+ compatibility
|
| 1235 |
+
custom_css = getattr(demo, '_uae_custom_css', '')
|
| 1236 |
+
|
| 1237 |
demo.launch(
|
| 1238 |
server_name="0.0.0.0", # Allow external access
|
| 1239 |
server_port=7860,
|
| 1240 |
share=True, # Set to False if using external tunnel for fixed URL
|
| 1241 |
show_error=True,
|
| 1242 |
+
css=custom_css, # Gradio 6.0+ expects CSS in launch()
|
| 1243 |
)
|
| 1244 |
|
| 1245 |
|
requirements.txt
CHANGED
|
@@ -3,3 +3,6 @@ FlagEmbedding
|
|
| 3 |
faiss-cpu
|
| 4 |
rank_bm25
|
| 5 |
numpy
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
faiss-cpu
|
| 4 |
rank_bm25
|
| 5 |
numpy
|
| 6 |
+
fastapi
|
| 7 |
+
uvicorn[standard]
|
| 8 |
+
python-multipart
|