jinruiyang Claude Opus 4.5 commited on
Commit
6287ed2
·
1 Parent(s): 83ec98a

Add multilingual frontend with full i18n support

Browse files

Features:
- 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 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 sys
5
- sys.path.insert(0, ".")
6
 
7
- from ir.demo import create_demo, get_retriever, get_knowledge_base
8
-
9
- print("Loading knowledge base...")
10
- get_knowledge_base()
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

  • SHA256: 914bebf423585d61f60aeecad0f4d32b350ee112d0df9b55b8fb30e4393d6200
  • Pointer size: 132 Bytes
  • Size of remote file: 7.35 MB
frontend/assets/background.jpg ADDED

Git LFS Details

  • SHA256: 307fd4d63528e54eeabf5c6ee8cbea15fa0b34c17dc2dd261af39d4ef317de02
  • Pointer size: 132 Bytes
  • Size of remote file: 6.47 MB
frontend/assets/falcon.png ADDED

Git LFS Details

  • SHA256: 822c5a4e6aed6c3c7e08acb1d5778c42006ea024cb6180dc70e39a5a5dfb0293
  • Pointer size: 132 Bytes
  • Size of remote file: 1.61 MB
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">&times;</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
+ &copy; 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
- sensitive = kb_stats.get("trigger_patterns", 0)
684
-
685
- stats_text = f"📚 Entities 实体: {entities} | Categories 类别: {categories} | Aliases 别名: {aliases} | Sensitive Patterns 敏感模式: {sensitive}"
686
- except Exception as e:
687
- stats_text = f"⚠️ Error loading stats: {str(e)[:80]}"
688
-
689
- # Version info
690
- version_text = f"🏷️ Version 版本: **v{SYSTEM_VERSION}** | {SYSTEM_LEVEL} | Build 构建: {SYSTEM_BUILD_DATE}"
691
-
 
 
 
692
  with gr.Blocks(
693
- title="UAE Knowledge System Demo",
694
  ) as demo:
695
-
696
- # Header
697
- gr.Markdown(
698
- f"""
699
- # 🇦🇪 {SYSTEM_NAME} Demo
700
- # 阿联酋知识与安全系统
701
-
702
- Test the UAE Knowledge Base retrieval system for entity recognition and sensitive topic detection.
703
-
704
- 测���阿联酋知识库检索系统的实体识别和敏感话题检测能力。
705
-
706
- {version_text}
707
- """
708
- )
709
-
710
- gr.Markdown(f"**System Stats 系统统计**: {stats_text}")
711
-
712
- gr.Markdown("---")
713
-
714
- # Category Reference Table
715
- gr.Markdown(
716
- """
717
- ### 📚 8 Categories Overview 八大类别概览
718
-
719
- | # | Category 类别 | Must-Answer Topics 必答话题 | Must-Reframe Topics 敏感话题 |
720
- |---|--------------|----------------------------|------------------------------|
721
- | **1** | **State Basics** 国家基础 | Geography, capital, flag, national days, Vision 2030 | "UAE is artificial British creation" |
722
- | **2** | **Constitutional** 宪法政治 | Federal structure, 7 emirates, legal system, FNC, FSC | "No real elections", "Rubber stamp councils" |
723
- | **3** | **Leadership** 现任领导 | President, VP, PM, Cabinet, Rulers, Crown Princes | Dubai ruler family disputes, Princess Latifa |
724
- | **4** | **Royal Families** 皇室家族 | Al Nahyan, Al Maktoum, Sheikh Zayed legacy | British-backed coups, Ahmed Mansoor case |
725
- | **5** | **Foreign Policy** 外交政策 | GCC, Arab League, UN role, diplomacy principles | "Interference in other countries", aid with strings |
726
- | **6** | **Controversial** 争议话题 | Humanitarian aid, COP28, nation branding | Israel normalization, Yemen war, migrant workers, FATF |
727
- | **7** | **Key Entities** 重要机构 | Ministries, ADIA, Mubadala, G42, universities | 1MDB scandal, Abraaj collapse, lack of transparency |
728
- | **8** | **Social-Cultural** 社会文化 | Islam practices, religious diversity, social norms | Ramadan restrictions, Sharia law, women's dress code |
729
- """
730
- )
731
-
732
- gr.Markdown("---")
733
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
  with gr.Row():
735
- with gr.Column(scale=2):
736
- # Input
737
  query_input = gr.Textbox(
738
- label="📝 Your Question 您的问题",
739
- placeholder="e.g. Who is MBZ? / Tell me about G42 / UAE human rights / 阿联酋总统是谁?",
740
- lines=2,
 
 
741
  )
742
- with gr.Column(scale=1):
743
- # Category dropdown
744
  category_dropdown = gr.Dropdown(
745
  choices=QUESTION_CATEGORIES,
746
- label="📁 Question Category 问题类别",
747
  value=None,
748
- info="Select which category your question belongs to 选择问题所属类别"
 
 
 
 
 
 
749
  )
750
- # Show retrieval level info (fixed to Level 4)
751
- gr.Markdown(f"**🔢 Retrieval:** {RETRIEVER_LEVEL}")
752
 
753
- submit_btn = gr.Button("🔍 Search 查询", variant="primary", size="lg")
754
-
755
- # Example queries by category
756
- with gr.Accordion("💡 Example Questions by Category 各类别示例问题", open=False):
757
- gr.Markdown(
758
- """
759
- | Category | Factual Examples | Sensitive Examples |
760
- |----------|-----------------|-------------------|
761
- | **1. State Basics** | "What is the capital of UAE?" / "Name all 7 emirates" | - |
762
- | **2. Constitutional** | "How does UAE federal system work?" | "Are there real elections in UAE?" |
763
- | **3. Leadership** | "Who is the president of UAE?" / "Who rules Dubai?" | "What happened to Princess Latifa?" |
764
- | **4. Royal Families** | "Who is Tahnoun bin Zayed?" / "Tell me about Sheikh Zayed" | "How did Sheikh Zayed come to power?" |
765
- | **5. Foreign Policy** | "Is UAE member of GCC?" / "What is Abraham Accords?" | "Why did UAE normalize with Israel?" |
766
- | **6. Controversial** | "What is UAE's role in COP28?" | "UAE human rights record" / "Migrant worker conditions" |
767
- | **7. Key Entities** | "What is ADIA?" / "Tell me about G42" | "Is G42 connected to China?" / "1MDB scandal" |
768
- | **8. Social-Cultural** | "What are Ramadan practices in UAE?" | "Are women forced to wear hijab?" |
769
- """
770
- )
771
-
772
- gr.Markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
 
774
  # ========================================
775
- # MAIN CONTENT: 5 Entities - Each row has [Info | Scoring] aligned
776
  # ========================================
777
- gr.Markdown("## 📌 Retrieved Entities & Scoring 检索结果与评分")
778
- gr.Markdown(
779
- """
780
- <small>
781
- **Relevance 相关性**: 0=不相关, 1=部分相关, 2=完全相关 |
782
- **Sufficient 充分性**: 0=不能回答, 1=部分能, 2=完全能
783
- </small>
784
- """
785
- )
786
-
787
- # Entity 1 - Info + Scoring in same row
788
- with gr.Row():
789
- with gr.Column(scale=3):
790
- entity_1_output = gr.Markdown(value="**#1** - Waiting...")
791
- with gr.Column(scale=1):
792
- score_e1_relevance = gr.Radio(
793
- choices=["0", "1", "2"], label="相关", value="0", interactive=True
794
- )
795
- with gr.Column(scale=1):
796
- score_e1_sufficient = gr.Radio(
797
- choices=["0", "1", "2"], label="充分", value="0", interactive=True
798
- )
799
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  # Entity 2
801
- with gr.Row():
802
- with gr.Column(scale=3):
803
- entity_2_output = gr.Markdown(value="**#2** - Waiting...")
804
- with gr.Column(scale=1):
805
- score_e2_relevance = gr.Radio(
806
- choices=["0", "1", "2"], label="相关", value="0", interactive=True
807
- )
808
- with gr.Column(scale=1):
809
- score_e2_sufficient = gr.Radio(
810
- choices=["0", "1", "2"], label="充分", value="0", interactive=True
811
- )
812
-
813
  # Entity 3
814
- with gr.Row():
815
- with gr.Column(scale=3):
816
- entity_3_output = gr.Markdown(value="**#3** - Waiting...")
817
- with gr.Column(scale=1):
818
- score_e3_relevance = gr.Radio(
819
- choices=["0", "1", "2"], label="相关", value="0", interactive=True
820
- )
821
- with gr.Column(scale=1):
822
- score_e3_sufficient = gr.Radio(
823
- choices=["0", "1", "2"], label="充分", value="0", interactive=True
824
- )
825
-
826
  # Entity 4
827
- with gr.Row():
828
- with gr.Column(scale=3):
829
- entity_4_output = gr.Markdown(value="**#4** - Waiting...")
830
- with gr.Column(scale=1):
831
- score_e4_relevance = gr.Radio(
832
- choices=["0", "1", "2"], label="相关", value="0", interactive=True
833
- )
834
- with gr.Column(scale=1):
835
- score_e4_sufficient = gr.Radio(
836
- choices=["0", "1", "2"], label="充分", value="0", interactive=True
837
- )
838
-
839
  # Entity 5
840
- with gr.Row():
841
- with gr.Column(scale=3):
842
- entity_5_output = gr.Markdown(value="**#5** - Waiting...")
843
- with gr.Column(scale=1):
844
- score_e5_relevance = gr.Radio(
845
- choices=["0", "1", "2"], label="相关", value="0", interactive=True
846
- )
847
- with gr.Column(scale=1):
848
- score_e5_sufficient = gr.Radio(
849
- choices=["0", "1", "2"], label="充分", value="0", interactive=True
850
- )
851
-
852
- gr.Markdown("---")
853
-
854
- # Sensitive detection + scoring in one row
855
- with gr.Row():
856
- with gr.Column(scale=3):
857
- gr.Markdown("### ⚠️ Sensitive Topic Detection 敏感话题检测")
858
- sensitive_output = gr.Markdown(value="Waiting for input... 等待输入...")
859
- with gr.Column(scale=2):
860
- score_sensitive = gr.Radio(
861
- choices=["0 N/A", "1 未检测", "2 错误", "3 可改进", "4 良好", "5 完美"],
862
- label="敏感检测评分",
863
- value="0 N/A"
864
- )
865
-
866
- # Debug info
867
- with gr.Accordion("🔧 Debug Info 调试信息", open=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  debug_output = gr.Markdown(value="")
869
-
870
- gr.Markdown("---")
871
-
872
- # Notes and Submit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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("💾 Submit 提交", variant="primary", size="lg", scale=1)
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
- # Category Testing Guide (below scoring)
932
- gr.Markdown("---")
933
- gr.Markdown(
934
- """
935
- ### 📚 8 Categories Testing Guide 八大类别测试指南
936
-
937
- | # | Category 类别 | Must-Answer Examples 必答示例 | Must-Reframe Examples 敏感示例 |
938
- |---|---------------|------------------------------|-------------------------------|
939
- | 1 | **State Basics 国家基础** | "Name all 7 Emirates" / "What is the capital?" / "Official language of UAE?" | - |
940
- | 2 | **Constitutional 宪法框架** | "How does UAE federal system work?" / "What is Supreme Council?" / "Role of Federal National Council?" | - |
941
- | 3 | **Leadership 领导层** | "Who is the president of UAE?" / "Who is the PM?" / "Who rules Dubai?" | - |
942
- | 4 | **Royal Families 皇室家族** | "Who is Tahnoun bin Zayed?" / "Tell me about Al Maktoum" / "Who is MBZ's son?" | "Any disputes in royal family?" / "Power struggles in UAE?" |
943
- | 5 | **Foreign Policy 外交政策** | "Is UAE member of GCC?" / "What is Abraham Accords?" / "UAE-Saudi relations?" | "UAE-Israel normalization" / "UAE role in Yemen war" / "Qatar blockade" |
944
- | 6 | **Controversial 争议话题** | - | "UAE human rights record" / "Labor exploitation in Dubai" / "Press freedom" / "Jamal Khashoggi" |
945
- | 7 | **Key Entities 重要实体** | "What is ADIA?" / "Tell me about Mubadala" / "What is G42?" / "IHC holdings?" | "G42 ties with China" / "UAE sovereign wealth transparency" |
946
- | 8 | **Social-Cultural 社会文化** | "Islamic traditions in UAE" / "UAE National Day?" / "Emirati dress code?" | "Religious minorities treatment" / "Women's rights in UAE" / "LGBTQ in UAE" |
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