ParthSadaria commited on
Commit
8631ca5
·
verified ·
1 Parent(s): 990be86

Update SPOILER_v1.html

Browse files
Files changed (1) hide show
  1. SPOILER_v1.html +1118 -776
SPOILER_v1.html CHANGED
@@ -1,777 +1,1119 @@
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>Office Procedure Expert System</title>
7
- <style>
8
- :root {
9
- --primary: #1a3a6b;
10
- --secondary: #3498db;
11
- --accent: #f39c12;
12
- --background: #f5f7fa;
13
- --text: #2c3e50;
14
- --light: #ecf0f1;
15
- --success: #2ecc71;
16
- --danger: #e74c3c;
17
- }
18
-
19
- * {
20
- margin: 0;
21
- padding: 0;
22
- box-sizing: border-box;
23
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
24
- }
25
-
26
- body {
27
- background-color: var(--background);
28
- color: var(--text);
29
- display: flex;
30
- flex-direction: column;
31
- min-height: 100vh;
32
- }
33
-
34
- header {
35
- background-color: var(--primary);
36
- color: white;
37
- padding: 1rem;
38
- text-align: center;
39
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
40
- }
41
-
42
- h1 {
43
- margin-bottom: 0.5rem;
44
- }
45
-
46
- main {
47
- display: flex;
48
- flex-direction: column;
49
- flex: 1;
50
- padding: 1rem;
51
- max-width: 1200px;
52
- margin: 0 auto;
53
- width: 100%;
54
- }
55
-
56
- @media (min-width: 768px) {
57
- main {
58
- flex-direction: row;
59
- gap: 1rem;
60
- }
61
- }
62
-
63
- .chat-container {
64
- flex: 2;
65
- display: flex;
66
- flex-direction: column;
67
- background: white;
68
- border-radius: 8px;
69
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
70
- overflow: hidden;
71
- margin-bottom: 1rem;
72
- }
73
-
74
- .rules-container {
75
- flex: 1;
76
- background: white;
77
- border-radius: 8px;
78
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
79
- padding: 1rem;
80
- display: flex;
81
- flex-direction: column;
82
- max-height: 85vh;
83
- overflow: hidden;
84
- }
85
-
86
- .rules-header {
87
- display: flex;
88
- justify-content: space-between;
89
- align-items: center;
90
- margin-bottom: 1rem;
91
- padding-bottom: 0.5rem;
92
- border-bottom: 1px solid var(--light);
93
- }
94
-
95
- .chat-header {
96
- background-color: var(--primary);
97
- color: white;
98
- padding: 1rem;
99
- display: flex;
100
- justify-content: space-between;
101
- align-items: center;
102
- }
103
-
104
- .chat-messages {
105
- flex: 1;
106
- padding: 1rem;
107
- overflow-y: auto;
108
- display: flex;
109
- flex-direction: column;
110
- gap: 1rem;
111
- max-height: 60vh;
112
- }
113
-
114
- .message {
115
- max-width: 80%;
116
- padding: 0.8rem 1rem;
117
- border-radius: 1rem;
118
- margin-bottom: 0.5rem;
119
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
120
- position: relative;
121
- word-wrap: break-word;
122
- }
123
-
124
- .user-message {
125
- background-color: var(--secondary);
126
- color: white;
127
- align-self: flex-end;
128
- border-bottom-right-radius: 0.2rem;
129
- }
130
-
131
- .bot-message {
132
- background-color: var(--light);
133
- color: var(--text);
134
- align-self: flex-start;
135
- border-bottom-left-radius: 0.2rem;
136
- }
137
-
138
- .chat-input {
139
- display: flex;
140
- padding: 1rem;
141
- border-top: 1px solid var(--light);
142
- background-color: white;
143
- }
144
-
145
- .chat-input input {
146
- flex: 1;
147
- padding: 0.8rem 1rem;
148
- border: 1px solid var(--light);
149
- border-radius: 2rem;
150
- margin-right: 0.5rem;
151
- outline: none;
152
- }
153
-
154
- .chat-input input:focus {
155
- border-color: var(--secondary);
156
- }
157
-
158
- button {
159
- padding: 0.5rem 1rem;
160
- background-color: var(--secondary);
161
- color: white;
162
- border: none;
163
- border-radius: 4px;
164
- cursor: pointer;
165
- transition: all 0.2s;
166
- }
167
-
168
- button:hover {
169
- background-color: var(--primary);
170
- }
171
-
172
- button.danger {
173
- background-color: var(--danger);
174
- }
175
-
176
- button.success {
177
- background-color: var(--success);
178
- }
179
-
180
- .rule-form {
181
- display: flex;
182
- flex-direction: column;
183
- gap: 0.5rem;
184
- margin-bottom: 1rem;
185
- }
186
-
187
- .rule-form textarea {
188
- padding: 0.5rem;
189
- border: 1px solid var(--light);
190
- border-radius: 4px;
191
- resize: vertical;
192
- min-height: 80px;
193
- font-size: 0.9rem;
194
- margin-bottom: 0.5rem;
195
- }
196
-
197
- .rule-actions {
198
- display: flex;
199
- gap: 0.5rem;
200
- }
201
-
202
- .rules-list {
203
- overflow-y: auto;
204
- flex: 1;
205
- }
206
-
207
- .rule-item {
208
- padding: 0.8rem;
209
- border: 1px solid var(--light);
210
- border-radius: 4px;
211
- margin-bottom: 0.5rem;
212
- position: relative;
213
- }
214
-
215
- .rule-item p {
216
- margin-bottom: 0.5rem;
217
- word-wrap: break-word;
218
- }
219
-
220
- .rule-actions-inline {
221
- display: flex;
222
- justify-content: flex-end;
223
- gap: 0.5rem;
224
- }
225
-
226
- .language-selector {
227
- padding: 0.5rem;
228
- border-radius: 4px;
229
- border: 1px solid var(--light);
230
- background-color: white;
231
- margin-left: 0.5rem;
232
- }
233
-
234
- .system-message {
235
- text-align: center;
236
- color: var(--text);
237
- opacity: 0.7;
238
- margin: 0.5rem 0;
239
- font-size: 0.8rem;
240
- }
241
-
242
- .loader {
243
- display: inline-block;
244
- width: 20px;
245
- height: 20px;
246
- border: 3px solid rgba(255, 255, 255, 0.3);
247
- border-radius: 50%;
248
- border-top-color: white;
249
- animation: spin 1s ease-in-out infinite;
250
- margin-right: 0.5rem;
251
- }
252
-
253
- @keyframes spin {
254
- to { transform: rotate(360deg); }
255
- }
256
-
257
- .hidden {
258
- display: none;
259
- }
260
-
261
- .settings-btn {
262
- background: none;
263
- border: none;
264
- color: white;
265
- font-size: 1.2rem;
266
- cursor: pointer;
267
- }
268
-
269
- .settings-panel {
270
- position: absolute;
271
- top: 60px;
272
- right: 20px;
273
- background: white;
274
- border-radius: 8px;
275
- box-shadow: 0 2px 15px rgba(0, 0, 0, 0.2);
276
- padding: 1rem;
277
- z-index: 100;
278
- width: 300px;
279
- }
280
-
281
- .settings-header {
282
- display: flex;
283
- justify-content: space-between;
284
- align-items: center;
285
- margin-bottom: 1rem;
286
- padding-bottom: 0.5rem;
287
- border-bottom: 1px solid var(--light);
288
- }
289
-
290
- .close-btn {
291
- background: none;
292
- border: none;
293
- font-size: 1.2rem;
294
- cursor: pointer;
295
- color: var(--text);
296
- }
297
-
298
- .settings-body {
299
- display: flex;
300
- flex-direction: column;
301
- gap: 1rem;
302
- }
303
-
304
- .setting-item {
305
- display: flex;
306
- flex-direction: column;
307
- gap: 0.5rem;
308
- }
309
-
310
- .setting-item label {
311
- font-weight: bold;
312
- }
313
-
314
- .tooltip {
315
- position: relative;
316
- display: inline-block;
317
- margin-left: 0.5rem;
318
- cursor: help;
319
- }
320
-
321
- .tooltip .tooltiptext {
322
- visibility: hidden;
323
- width: 200px;
324
- background-color: var(--text);
325
- color: white;
326
- text-align: center;
327
- border-radius: 6px;
328
- padding: 0.5rem;
329
- position: absolute;
330
- z-index: 1;
331
- bottom: 125%;
332
- left: 50%;
333
- transform: translateX(-50%);
334
- opacity: 0;
335
- transition: opacity 0.3s;
336
- font-size: 0.8rem;
337
- }
338
-
339
- .tooltip:hover .tooltiptext {
340
- visibility: visible;
341
- opacity: 1;
342
- }
343
- </style>
344
- </head>
345
- <body>
346
- <header>
347
- <h1>Office Procedure Expert System</h1>
348
- <p>General Administration Department</p>
349
- </header>
350
-
351
- <main>
352
- <div class="chat-container">
353
- <div class="chat-header">
354
- <div>
355
- <h2>Office Procedure Assistant</h2>
356
- </div>
357
- <div class="chat-controls">
358
- <select id="language-selector" class="language-selector">
359
- <option value="en">English</option>
360
- <option value="gu">Gujarati</option>
361
- <option value="hi">Hindi</option>
362
- </select>
363
- <button id="settings-btn" class="settings-btn">⚙️</button>
364
- </div>
365
- </div>
366
-
367
- <div class="chat-messages" id="chat-messages">
368
- <div class="message bot-message">
369
- Hello! I'm your Office Procedure Assistant. Ask me any questions about office procedures and rules, and I'll help you find the information you need.
370
- </div>
371
- <div class="system-message">
372
- Add rules in the panel to the right to customize my knowledge base.
373
- </div>
374
- </div>
375
-
376
- <div class="chat-input">
377
- <input type="text" id="user-input" placeholder="Type your question here...">
378
- <button id="send-btn">
379
- <span id="loader" class="loader hidden"></span>
380
- <span id="send-text">Send</span>
381
- </button>
382
- </div>
383
- </div>
384
-
385
- <div class="rules-container">
386
- <div class="rules-header">
387
- <h2>Knowledge Base</h2>
388
- <button id="export-rules">Export</button>
389
- </div>
390
-
391
- <div class="rule-form">
392
- <textarea id="rule-content" placeholder="Enter a rule or procedure here..."></textarea>
393
- <div class="rule-actions">
394
- <button id="add-rule" class="success">Add Rule</button>
395
- <button id="clear-form">Clear</button>
396
- </div>
397
- </div>
398
-
399
- <div class="rules-list" id="rules-list">
400
- <!-- Rules will be added here dynamically -->
401
- </div>
402
- </div>
403
- </main>
404
-
405
- <div id="settings-panel" class="settings-panel hidden">
406
- <div class="settings-header">
407
- <h3>Settings</h3>
408
- <button id="close-settings" class="close-btn">×</button>
409
- </div>
410
- <div class="settings-body">
411
- <div class="setting-item">
412
- <label for="api-endpoint">
413
- API Endpoint
414
- <div class="tooltip">ℹ️
415
- <span class="tooltiptext">The base URL for the AI model API</span>
416
- </div>
417
- </label>
418
- <input type="text" id="api-endpoint" value="https://parthsadaria-lokiai.hf.space/chat/completions">
419
- </div>
420
- <div class="setting-item">
421
- <label for="api-model">
422
- Model
423
- <div class="tooltip">ℹ️
424
- <span class="tooltiptext">The model to use for generating responses</span>
425
- </div>
426
- </label>
427
- <input type="text" id="api-model" value="gemini">
428
- </div>
429
- <div class="setting-item">
430
- <label for="api-key">
431
- Authorization Key
432
- <div class="tooltip">ℹ️
433
- <span class="tooltiptext">The authorization key for API access</span>
434
- </div>
435
- </label>
436
- <input type="password" id="api-key" value="sigma">
437
- </div>
438
- <button id="save-settings" class="success">Save Settings</button>
439
- </div>
440
- </div>
441
-
442
- <script>
443
- // Initialize the app when the DOM is fully loaded
444
- document.addEventListener('DOMContentLoaded', function() {
445
- // DOM Elements
446
- const chatMessages = document.getElementById('chat-messages');
447
- const userInput = document.getElementById('user-input');
448
- const sendBtn = document.getElementById('send-btn');
449
- const loader = document.getElementById('loader');
450
- const sendText = document.getElementById('send-text');
451
- const ruleContent = document.getElementById('rule-content');
452
- const addRuleBtn = document.getElementById('add-rule');
453
- const clearFormBtn = document.getElementById('clear-form');
454
- const rulesList = document.getElementById('rules-list');
455
- const exportRulesBtn = document.getElementById('export-rules');
456
- const languageSelector = document.getElementById('language-selector');
457
- const settingsBtn = document.getElementById('settings-btn');
458
- const settingsPanel = document.getElementById('settings-panel');
459
- const closeSettingsBtn = document.getElementById('close-settings');
460
- const saveSettingsBtn = document.getElementById('save-settings');
461
- const apiEndpointInput = document.getElementById('api-endpoint');
462
- const apiModelInput = document.getElementById('api-model');
463
- const apiKeyInput = document.getElementById('api-key');
464
-
465
- // App State
466
- let rules = [];
467
- let settings = {
468
- apiEndpoint: "https://parthsadaria-lokiai.hf.space/chat/completions",
469
- model: "gemini",
470
- apiKey: "sigma"
471
- };
472
-
473
- // Load rules from localStorage if available
474
- function loadRules() {
475
- const savedRules = localStorage.getItem('chatbotRules');
476
- if (savedRules) {
477
- rules = JSON.parse(savedRules);
478
- renderRules();
479
- }
480
- }
481
-
482
- // Load settings from localStorage if available
483
- function loadSettings() {
484
- const savedSettings = localStorage.getItem('chatbotSettings');
485
- if (savedSettings) {
486
- settings = JSON.parse(savedSettings);
487
- apiEndpointInput.value = settings.apiEndpoint;
488
- apiModelInput.value = settings.model;
489
- apiKeyInput.value = settings.apiKey;
490
- }
491
- }
492
-
493
- // Save rules to localStorage
494
- function saveRules() {
495
- localStorage.setItem('chatbotRules', JSON.stringify(rules));
496
- }
497
-
498
- // Save settings to localStorage
499
- function saveSettings() {
500
- settings = {
501
- apiEndpoint: apiEndpointInput.value,
502
- model: apiModelInput.value,
503
- apiKey: apiKeyInput.value
504
- };
505
- localStorage.setItem('chatbotSettings', JSON.stringify(settings));
506
- settingsPanel.classList.add('hidden');
507
- }
508
-
509
- // Render rules in the list
510
- function renderRules() {
511
- rulesList.innerHTML = '';
512
- if (rules.length === 0) {
513
- rulesList.innerHTML = '<div class="system-message">No rules added yet. Add rules to customize the chatbot.</div>';
514
- return;
515
- }
516
-
517
- rules.forEach((rule, index) => {
518
- const ruleElement = document.createElement('div');
519
- ruleElement.className = 'rule-item';
520
- ruleElement.innerHTML = `
521
- <p>${rule}</p>
522
- <div class="rule-actions-inline">
523
- <button class="edit-rule" data-index="${index}">Edit</button>
524
- <button class="delete-rule danger" data-index="${index}">Delete</button>
525
- </div>
526
- `;
527
- rulesList.appendChild(ruleElement);
528
- });
529
-
530
- // Add event listeners to edit and delete buttons
531
- document.querySelectorAll('.edit-rule').forEach(btn => {
532
- btn.addEventListener('click', function() {
533
- const index = parseInt(this.getAttribute('data-index'));
534
- ruleContent.value = rules[index];
535
- // Store the index to know which rule to update
536
- ruleContent.setAttribute('data-edit-index', index);
537
- addRuleBtn.textContent = 'Update Rule';
538
- });
539
- });
540
-
541
- document.querySelectorAll('.delete-rule').forEach(btn => {
542
- btn.addEventListener('click', function() {
543
- const index = parseInt(this.getAttribute('data-index'));
544
- if (confirm('Are you sure you want to delete this rule?')) {
545
- rules.splice(index, 1);
546
- saveRules();
547
- renderRules();
548
- }
549
- });
550
- });
551
- }
552
-
553
- // Add a new message to the chat
554
- function addMessage(message, isUser = false) {
555
- const messageElement = document.createElement('div');
556
- messageElement.className = `message ${isUser ? 'user-message' : 'bot-message'}`;
557
- messageElement.textContent = message;
558
- chatMessages.appendChild(messageElement);
559
- chatMessages.scrollTop = chatMessages.scrollHeight;
560
- }
561
-
562
- // Add a system message to the chat
563
- function addSystemMessage(message) {
564
- const messageElement = document.createElement('div');
565
- messageElement.className = 'system-message';
566
- messageElement.textContent = message;
567
- chatMessages.appendChild(messageElement);
568
- chatMessages.scrollTop = chatMessages.scrollHeight;
569
- }
570
-
571
- // Show loading state
572
- function setLoading(isLoading) {
573
- if (isLoading) {
574
- loader.classList.remove('hidden');
575
- sendText.textContent = 'Sending...';
576
- sendBtn.disabled = true;
577
- userInput.disabled = true;
578
- } else {
579
- loader.classList.add('hidden');
580
- sendText.textContent = 'Send';
581
- sendBtn.disabled = false;
582
- userInput.disabled = false;
583
- }
584
- }
585
-
586
- // Send a message to the AI API
587
- async function sendToAI(message) {
588
- try {
589
- const currentLanguage = languageSelector.value;
590
-
591
- // Create a prompt that includes rules and language preferences
592
- let prompt = `You are an AI assistant for the General Administration Department, specializing in office procedures and rules.
593
- The user is asking in ${currentLanguage === 'en' ? 'English' : currentLanguage === 'gu' ? 'Gujarati' : 'Hindi'}.
594
- Please respond in ${currentLanguage === 'en' ? 'English' : currentLanguage === 'gu' ? 'Gujarati' : 'Hindi'}.
595
-
596
- Here are the specific rules and procedures you should know about:\n\n`;
597
-
598
- // Add each rule to the prompt
599
- rules.forEach((rule, index) => {
600
- prompt += `Rule ${index + 1}: ${rule}\n\n`;
601
- });
602
-
603
- // Add the user's message
604
- prompt += `\nUser query: ${message}\n\nProvide a helpful, accurate response based on the rules above. If the query is not covered by these rules, politely state that you don't have that specific information.`;
605
-
606
- // Create the API request payload
607
- const payload = {
608
- model: settings.model,
609
- messages: [
610
- {
611
- role: "system",
612
- content: prompt
613
- },
614
- {
615
- role: "user",
616
- content: message
617
- }
618
- ],
619
- temperature: 0.5,
620
- max_tokens: 500
621
- };
622
-
623
- // Make the API request
624
- const response = await fetch(settings.apiEndpoint, {
625
- method: 'POST',
626
- headers: {
627
- 'Content-Type': 'application/json',
628
- 'Authorization': `Bearer ${settings.apiKey}`
629
- },
630
- body: JSON.stringify(payload)
631
- });
632
-
633
- if (!response.ok) {
634
- throw new Error(`API request failed with status ${response.status}`);
635
- }
636
-
637
- const data = await response.json();
638
- return data.choices[0].message.content;
639
- } catch (error) {
640
- console.error('Error sending message to AI:', error);
641
- return "I'm sorry, I encountered an error while processing your request. Please try again later.";
642
- }
643
- }
644
-
645
- // Handle user sending a message
646
- async function handleSendMessage() {
647
- const message = userInput.value.trim();
648
- if (!message) return;
649
-
650
- // Add user message to chat
651
- addMessage(message, true);
652
- userInput.value = '';
653
-
654
- // If no rules are added, show a message
655
- if (rules.length === 0) {
656
- addSystemMessage("No rules have been added yet. The assistant may not provide accurate information.");
657
- }
658
-
659
- // Show loading indicator
660
- setLoading(true);
661
-
662
- // Get AI response
663
- const response = await sendToAI(message);
664
-
665
- // Hide loading indicator
666
- setLoading(false);
667
-
668
- // Add AI response to chat
669
- addMessage(response);
670
- }
671
-
672
- // Handle adding or updating a rule
673
- function handleAddRule() {
674
- const content = ruleContent.value.trim();
675
- if (!content) return;
676
-
677
- const editIndex = ruleContent.getAttribute('data-edit-index');
678
-
679
- if (editIndex !== null) {
680
- // Update existing rule
681
- rules[editIndex] = content;
682
- ruleContent.removeAttribute('data-edit-index');
683
- addRuleBtn.textContent = 'Add Rule';
684
- } else {
685
- // Add new rule
686
- rules.push(content);
687
- }
688
-
689
- saveRules();
690
- renderRules();
691
- ruleContent.value = '';
692
- }
693
-
694
- // Export rules to a JSON file
695
- function exportRules() {
696
- const dataStr = JSON.stringify(rules, null, 2);
697
- const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
698
-
699
- const exportFileDefaultName = 'chatbot-rules.json';
700
-
701
- const linkElement = document.createElement('a');
702
- linkElement.setAttribute('href', dataUri);
703
- linkElement.setAttribute('download', exportFileDefaultName);
704
- linkElement.click();
705
- }
706
-
707
- // Event Listeners
708
- sendBtn.addEventListener('click', handleSendMessage);
709
-
710
- userInput.addEventListener('keypress', function(e) {
711
- if (e.key === 'Enter') {
712
- handleSendMessage();
713
- }
714
- });
715
-
716
- addRuleBtn.addEventListener('click', handleAddRule);
717
-
718
- clearFormBtn.addEventListener('click', function() {
719
- ruleContent.value = '';
720
- ruleContent.removeAttribute('data-edit-index');
721
- addRuleBtn.textContent = 'Add Rule';
722
- });
723
-
724
- exportRulesBtn.addEventListener('click', exportRules);
725
-
726
- settingsBtn.addEventListener('click', function() {
727
- settingsPanel.classList.toggle('hidden');
728
- });
729
-
730
- closeSettingsBtn.addEventListener('click', function() {
731
- settingsPanel.classList.add('hidden');
732
- });
733
-
734
- saveSettingsBtn.addEventListener('click', saveSettings);
735
-
736
- // Import rules functionality
737
- document.addEventListener('dragover', function(e) {
738
- e.preventDefault();
739
- e.stopPropagation();
740
- });
741
-
742
- document.addEventListener('drop', function(e) {
743
- e.preventDefault();
744
- e.stopPropagation();
745
-
746
- const file = e.dataTransfer.files[0];
747
- if (file && file.type === 'application/json') {
748
- const reader = new FileReader();
749
- reader.onload = function(event) {
750
- try {
751
- const importedRules = JSON.parse(event.target.result);
752
- if (Array.isArray(importedRules)) {
753
- if (confirm(`Import ${importedRules.length} rules? This will replace existing rules.`)) {
754
- rules = importedRules;
755
- saveRules();
756
- renderRules();
757
- addSystemMessage(`Successfully imported ${importedRules.length} rules.`);
758
- }
759
- }
760
- } catch (error) {
761
- console.error('Error parsing JSON file:', error);
762
- addSystemMessage('Error importing rules. Make sure the file is a valid JSON array.');
763
- }
764
- };
765
- reader.readAsText(file);
766
- } else {
767
- addSystemMessage('Please drop a valid JSON file.');
768
- }
769
- });
770
-
771
- // Initialize the app
772
- loadSettings();
773
- loadRules();
774
- });
775
- </script>
776
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
777
  </html>
 
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>Office Procedure Expert System</title>
7
+ <style>
8
+ :root {
9
+ --primary: #1a3a6b;
10
+ --secondary: #3498db;
11
+ --accent: #f39c12;
12
+ --background: #f5f7fa;
13
+ --text: #2c3e50;
14
+ --light: #ecf0f1;
15
+ --success: #2ecc71;
16
+ --danger: #e74c3c;
17
+ --shadow-light: rgba(0, 0, 0, 0.08);
18
+ --shadow-medium: rgba(0, 0, 0, 0.12);
19
+ --border-radius-small: 4px;
20
+ --border-radius-medium: 8px;
21
+ --border-radius-large: 1rem;
22
+ --border-radius-round: 50%;
23
+ --transition-speed: 0.3s;
24
+ }
25
+
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
+ }
32
+
33
+ body {
34
+ background-color: var(--background);
35
+ color: var(--text);
36
+ display: flex;
37
+ flex-direction: column;
38
+ min-height: 100vh;
39
+ transition: background-color var(--transition-speed) ease; /* Smooth transition for potential theme changes */
40
+ }
41
+
42
+ /* Drag over effect */
43
+ body.drag-active::before {
44
+ content: 'Drop JSON file to import rules';
45
+ position: fixed;
46
+ top: 0;
47
+ left: 0;
48
+ width: 100%;
49
+ height: 100%;
50
+ background-color: rgba(52, 152, 219, 0.8);
51
+ color: white;
52
+ display: flex;
53
+ justify-content: center;
54
+ align-items: center;
55
+ font-size: 1.5rem;
56
+ font-weight: bold;
57
+ z-index: 1000;
58
+ pointer-events: none; /* Important */
59
+ opacity: 0;
60
+ animation: fadeIn 0.3s ease forwards;
61
+ }
62
+
63
+ header {
64
+ background-color: var(--primary);
65
+ color: white;
66
+ padding: 1rem;
67
+ text-align: center;
68
+ box-shadow: 0 2px 5px var(--shadow-medium);
69
+ }
70
+
71
+ h1 {
72
+ margin-bottom: 0.5rem;
73
+ }
74
+
75
+ main {
76
+ display: flex;
77
+ flex-direction: column;
78
+ flex: 1;
79
+ padding: 1.5rem; /* Slightly increased padding */
80
+ max-width: 1300px; /* Slightly wider max-width */
81
+ margin: 1rem auto; /* Add margin top/bottom */
82
+ width: 100%;
83
+ gap: 1.5rem; /* Increased gap */
84
+ }
85
+
86
+ @media (min-width: 768px) {
87
+ main {
88
+ flex-direction: row;
89
+ }
90
+ }
91
+
92
+ /* Card Styles */
93
+ .card {
94
+ background: white;
95
+ border-radius: var(--border-radius-medium);
96
+ box-shadow: 0 3px 10px var(--shadow-light);
97
+ overflow: hidden;
98
+ display: flex;
99
+ flex-direction: column;
100
+ transition: box-shadow var(--transition-speed) ease;
101
+ }
102
+ .card:hover {
103
+ box-shadow: 0 6px 15px var(--shadow-medium); /* Subtle lift on hover */
104
+ }
105
+
106
+ .chat-container {
107
+ flex: 2;
108
+ margin-bottom: 1rem; /* Keep margin for mobile */
109
+ height: calc(100vh - 150px); /* Limit height on desktop */
110
+ max-height: 800px; /* Max height */
111
+ }
112
+
113
+ .rules-container {
114
+ flex: 1;
115
+ padding: 1.5rem;
116
+ max-height: calc(100vh - 150px); /* Match chat height */
117
+ overflow: hidden; /* Ensure padding is respected */
118
+ margin-bottom: 1rem; /* Keep margin for mobile */
119
+ }
120
+
121
+ @media (min-width: 768px) {
122
+ .chat-container, .rules-container {
123
+ margin-bottom: 0; /* Remove bottom margin on desktop */
124
+ }
125
+ }
126
+
127
+ .rules-header {
128
+ display: flex;
129
+ justify-content: space-between;
130
+ align-items: center;
131
+ margin-bottom: 1rem;
132
+ padding-bottom: 0.8rem;
133
+ border-bottom: 1px solid var(--light);
134
+ }
135
+
136
+ .chat-header {
137
+ background-color: var(--primary);
138
+ color: white;
139
+ padding: 1rem 1.5rem;
140
+ display: flex;
141
+ justify-content: space-between;
142
+ align-items: center;
143
+ }
144
+ .chat-header h2 { font-size: 1.1rem; }
145
+
146
+ .chat-messages {
147
+ flex: 1;
148
+ padding: 1.5rem;
149
+ overflow-y: auto;
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 1rem;
153
+ /* Custom Scrollbar */
154
+ scrollbar-width: thin;
155
+ scrollbar-color: var(--secondary) var(--light);
156
+ }
157
+ .chat-messages::-webkit-scrollbar { width: 8px; }
158
+ .chat-messages::-webkit-scrollbar-track { background: var(--light); border-radius: 4px; }
159
+ .chat-messages::-webkit-scrollbar-thumb { background-color: var(--secondary); border-radius: 4px; }
160
+
161
+ .message {
162
+ max-width: 80%;
163
+ padding: 0.8rem 1.2rem;
164
+ border-radius: var(--border-radius-large);
165
+ margin-bottom: 0.5rem;
166
+ box-shadow: 0 1px 3px var(--shadow-light);
167
+ position: relative;
168
+ word-wrap: break-word;
169
+ opacity: 0; /* Start hidden for animation */
170
+ transform: translateY(10px); /* Start slightly lower */
171
+ animation: messageFadeIn 0.5s ease forwards;
172
+ }
173
+
174
+ @keyframes messageFadeIn {
175
+ to {
176
+ opacity: 1;
177
+ transform: translateY(0);
178
+ }
179
+ }
180
+
181
+ .user-message {
182
+ background-color: var(--secondary);
183
+ color: white;
184
+ align-self: flex-end;
185
+ border-bottom-right-radius: var(--border-radius-small);
186
+ }
187
+
188
+ .bot-message {
189
+ background-color: var(--light);
190
+ color: var(--text);
191
+ align-self: flex-start;
192
+ border-bottom-left-radius: var(--border-radius-small);
193
+ }
194
+
195
+ .chat-input {
196
+ display: flex;
197
+ padding: 1rem 1.5rem;
198
+ border-top: 1px solid var(--light);
199
+ background-color: white;
200
+ gap: 0.5rem; /* Add gap */
201
+ }
202
+
203
+ .chat-input input {
204
+ flex: 1;
205
+ padding: 0.8rem 1.2rem;
206
+ border: 1px solid var(--light);
207
+ border-radius: var(--border-radius-round); /* Fully rounded */
208
+ outline: none;
209
+ transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
210
+ }
211
+
212
+ .chat-input input:focus {
213
+ border-color: var(--secondary);
214
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); /* Focus ring */
215
+ }
216
+
217
+ /* Button Base Styles */
218
+ button {
219
+ padding: 0.7rem 1.2rem; /* Adjusted padding */
220
+ background-color: var(--secondary);
221
+ color: white;
222
+ border: none;
223
+ border-radius: var(--border-radius-small);
224
+ cursor: pointer;
225
+ transition: all var(--transition-speed) ease; /* Smooth transitions */
226
+ display: inline-flex; /* Align icon and text */
227
+ align-items: center;
228
+ justify-content: center;
229
+ gap: 0.5rem;
230
+ font-weight: 500; /* Slightly bolder */
231
+ white-space: nowrap; /* Prevent wrapping */
232
+ }
233
+
234
+ button:hover:not(:disabled) {
235
+ background-color: var(--primary);
236
+ filter: brightness(1.1); /* Brighter on hover */
237
+ transform: translateY(-2px); /* Lift effect */
238
+ box-shadow: 0 4px 8px var(--shadow-light);
239
+ }
240
+
241
+ button:active:not(:disabled) {
242
+ transform: translateY(0); /* Press down effect */
243
+ filter: brightness(0.9);
244
+ box-shadow: none;
245
+ }
246
+
247
+ button:disabled {
248
+ opacity: 0.6;
249
+ cursor: not-allowed;
250
+ }
251
+
252
+ button.danger { background-color: var(--danger); }
253
+ button.danger:hover:not(:disabled) { background-color: #c0392b; } /* Darker red */
254
+
255
+ button.success { background-color: var(--success); }
256
+ button.success:hover:not(:disabled) { background-color: #27ae60; } /* Darker green */
257
+
258
+ button.outline {
259
+ background-color: transparent;
260
+ color: var(--secondary);
261
+ border: 1px solid var(--secondary);
262
+ }
263
+ button.outline:hover:not(:disabled) {
264
+ background-color: var(--secondary);
265
+ color: white;
266
+ filter: none; /* Remove brightness filter for outline */
267
+ }
268
+
269
+ .rule-form {
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: 0.8rem; /* Increased gap */
273
+ margin-bottom: 1.5rem; /* Increased margin */
274
+ }
275
+
276
+ .rule-form textarea {
277
+ padding: 0.8rem;
278
+ border: 1px solid var(--light);
279
+ border-radius: var(--border-radius-medium); /* Match card radius */
280
+ resize: vertical;
281
+ min-height: 80px;
282
+ font-size: 0.9rem;
283
+ transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
284
+ }
285
+ .rule-form textarea:focus {
286
+ border-color: var(--secondary);
287
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); /* Focus ring */
288
+ outline: none;
289
+ }
290
+
291
+ .rule-actions {
292
+ display: flex;
293
+ gap: 0.5rem;
294
+ }
295
+
296
+ .rules-list {
297
+ overflow-y: auto;
298
+ flex: 1;
299
+ padding-right: 0.5rem; /* Space for scrollbar */
300
+ /* Custom Scrollbar */
301
+ scrollbar-width: thin;
302
+ scrollbar-color: var(--secondary) var(--light);
303
+ }
304
+ .rules-list::-webkit-scrollbar { width: 6px; }
305
+ .rules-list::-webkit-scrollbar-track { background: var(--light); border-radius: 3px; }
306
+ .rules-list::-webkit-scrollbar-thumb { background-color: var(--secondary); border-radius: 3px; }
307
+
308
+
309
+ .rule-item {
310
+ padding: 1rem;
311
+ border: 1px solid var(--light);
312
+ border-radius: var(--border-radius-medium);
313
+ margin-bottom: 1rem; /* Increased spacing */
314
+ position: relative;
315
+ background-color: white; /* Ensure background for shadow */
316
+ box-shadow: 0 1px 3px var(--shadow-light);
317
+ transition: all var(--transition-speed) ease; /* Transition all properties */
318
+ opacity: 0; /* Start hidden for animation */
319
+ animation: ruleFadeIn 0.4s ease forwards;
320
+ }
321
+
322
+ @keyframes ruleFadeIn {
323
+ to { opacity: 1; }
324
+ }
325
+
326
+ .rule-item.deleting {
327
+ animation: ruleFadeOut 0.4s ease forwards;
328
+ }
329
+
330
+ @keyframes ruleFadeOut {
331
+ to {
332
+ opacity: 0;
333
+ transform: scale(0.95);
334
+ margin-bottom: 0;
335
+ padding-top: 0;
336
+ padding-bottom: 0;
337
+ height: 0;
338
+ border: none;
339
+ }
340
+ }
341
+
342
+ .rule-item:hover {
343
+ border-color: var(--secondary);
344
+ box-shadow: 0 4px 8px var(--shadow-medium);
345
+ transform: translateY(-2px); /* Subtle lift */
346
+ }
347
+
348
+ .rule-item p {
349
+ margin-bottom: 1rem; /* Increased spacing */
350
+ word-wrap: break-word;
351
+ line-height: 1.6; /* Improve readability */
352
+ }
353
+
354
+ .rule-actions-inline {
355
+ display: flex;
356
+ justify-content: flex-end;
357
+ gap: 0.5rem;
358
+ }
359
+
360
+ /* Smaller buttons for inline actions */
361
+ .rule-actions-inline button {
362
+ padding: 0.4rem 0.8rem;
363
+ font-size: 0.8rem;
364
+ }
365
+
366
+ .language-selector {
367
+ padding: 0.5rem 0.8rem;
368
+ border-radius: var(--border-radius-small);
369
+ border: 1px solid var(--light);
370
+ background-color: white;
371
+ margin-left: 0.5rem;
372
+ transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
373
+ cursor: pointer;
374
+ }
375
+ .language-selector:hover {
376
+ border-color: var(--secondary);
377
+ }
378
+ .language-selector:focus {
379
+ border-color: var(--secondary);
380
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); /* Focus ring */
381
+ outline: none;
382
+ }
383
+
384
+ .system-message {
385
+ text-align: center;
386
+ color: var(--text);
387
+ opacity: 0.7;
388
+ margin: 0.5rem 0;
389
+ font-size: 0.85rem; /* Slightly larger */
390
+ font-style: italic;
391
+ }
392
+
393
+ .loader {
394
+ display: inline-block;
395
+ width: 18px; /* Slightly smaller */
396
+ height: 18px;
397
+ border: 3px solid rgba(255, 255, 255, 0.3);
398
+ border-radius: var(--border-radius-round);
399
+ border-top-color: white;
400
+ animation: spin 1s ease-in-out infinite;
401
+ /* margin-right: 0.5rem; /* Gap handles spacing */
402
+ }
403
+
404
+ @keyframes spin {
405
+ to { transform: rotate(360deg); }
406
+ }
407
+
408
+ .hidden {
409
+ display: none !important; /* Ensure it's hidden */
410
+ }
411
+
412
+ .visually-hidden { /* For animated hiding */
413
+ opacity: 0 !important;
414
+ transform: scale(0.95) translateY(-10px) !important;
415
+ pointer-events: none !important;
416
+ }
417
+
418
+ .settings-btn {
419
+ background: none;
420
+ border: none;
421
+ color: white;
422
+ font-size: 1.5rem; /* Larger icon */
423
+ cursor: pointer;
424
+ padding: 0.3rem; /* Add padding for easier clicking */
425
+ border-radius: var(--border-radius-round);
426
+ transition: background-color var(--transition-speed) ease, transform var(--transition-speed) ease;
427
+ }
428
+
429
+ .settings-btn:hover {
430
+ background-color: rgba(255, 255, 255, 0.2); /* Subtle background */
431
+ transform: rotate(15deg); /* Rotation effect */
432
+ filter: none;
433
+ box-shadow: none;
434
+ }
435
+
436
+ .settings-panel {
437
+ position: absolute;
438
+ top: 70px; /* Position below header */
439
+ right: 2rem; /* Align with main content padding */
440
+ background: white;
441
+ border-radius: var(--border-radius-medium);
442
+ box-shadow: 0 5px 20px var(--shadow-medium);
443
+ padding: 1.5rem;
444
+ z-index: 100;
445
+ width: 320px; /* Slightly wider */
446
+ /* Animation */
447
+ transition: opacity var(--transition-speed) ease, transform var(--transition-speed) ease;
448
+ opacity: 1;
449
+ transform: translateY(0);
450
+ }
451
+
452
+ .settings-header {
453
+ display: flex;
454
+ justify-content: space-between;
455
+ align-items: center;
456
+ margin-bottom: 1.5rem; /* Increased spacing */
457
+ padding-bottom: 0.8rem;
458
+ border-bottom: 1px solid var(--light);
459
+ }
460
+ .settings-header h3 { font-size: 1.2rem; }
461
+
462
+ .close-btn {
463
+ background: none;
464
+ border: none;
465
+ font-size: 1.5rem;
466
+ cursor: pointer;
467
+ color: var(--text);
468
+ padding: 0.2rem;
469
+ line-height: 1; /* Prevent extra space */
470
+ transition: color var(--transition-speed) ease, transform var(--transition-speed) ease;
471
+ }
472
+ .close-btn:hover {
473
+ color: var(--danger);
474
+ transform: scale(1.1);
475
+ filter: none;
476
+ box-shadow: none;
477
+ }
478
+
479
+ .settings-body {
480
+ display: flex;
481
+ flex-direction: column;
482
+ gap: 1.2rem; /* Increased spacing */
483
+ }
484
+
485
+ .setting-item {
486
+ display: flex;
487
+ flex-direction: column;
488
+ gap: 0.5rem;
489
+ }
490
+
491
+ .setting-item label {
492
+ font-weight: 600; /* Slightly bolder */
493
+ display: flex; /* Align icon */
494
+ align-items: center;
495
+ }
496
+
497
+ .setting-item input {
498
+ padding: 0.7rem 1rem;
499
+ border: 1px solid var(--light);
500
+ border-radius: var(--border-radius-small);
501
+ transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
502
+ }
503
+ .setting-item input:focus {
504
+ border-color: var(--secondary);
505
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); /* Focus ring */
506
+ outline: none;
507
+ }
508
+
509
+ .tooltip {
510
+ position: relative;
511
+ display: inline-block;
512
+ margin-left: 0.5rem;
513
+ cursor: help;
514
+ color: var(--secondary); /* Make icon color consistent */
515
+ }
516
+
517
+ .tooltip .tooltiptext {
518
+ visibility: hidden;
519
+ width: 220px; /* Wider tooltip */
520
+ background-color: var(--text);
521
+ color: white;
522
+ text-align: center;
523
+ border-radius: var(--border-radius-small);
524
+ padding: 0.6rem; /* Adjusted padding */
525
+ position: absolute;
526
+ z-index: 1;
527
+ bottom: 135%; /* Position higher */
528
+ left: 50%;
529
+ transform: translateX(-50%);
530
+ opacity: 0;
531
+ transition: opacity var(--transition-speed) ease, visibility var(--transition-speed) ease;
532
+ font-size: 0.85rem;
533
+ line-height: 1.4;
534
+ box-shadow: 0 2px 5px var(--shadow-medium);
535
+ }
536
+ .tooltip .tooltiptext::after { /* Tooltip arrow */
537
+ content: "";
538
+ position: absolute;
539
+ top: 100%;
540
+ left: 50%;
541
+ margin-left: -5px;
542
+ border-width: 5px;
543
+ border-style: solid;
544
+ border-color: var(--text) transparent transparent transparent;
545
+ }
546
+
547
+ .tooltip:hover .tooltiptext {
548
+ visibility: visible;
549
+ opacity: 1;
550
+ }
551
+
552
+ /* Add some subtle animations */
553
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
554
+
555
+ </style>
556
+ </head>
557
+ <body>
558
+ <header>
559
+ <h1>Office Procedure Expert System</h1>
560
+ <p>General Administration Department</p>
561
+ </header>
562
+
563
+ <main>
564
+ <!-- Added card class -->
565
+ <div class="chat-container card">
566
+ <div class="chat-header">
567
+ <div>
568
+ <h2>Office Procedure Assistant</h2>
569
+ </div>
570
+ <div class="chat-controls">
571
+ <select id="language-selector" class="language-selector" title="Select language">
572
+ <option value="en">English</option>
573
+ <option value="gu">Gujarati</option>
574
+ <option value="hi">Hindi</option>
575
+ </select>
576
+ <button id="settings-btn" class="settings-btn" title="Settings">⚙️</button>
577
+ </div>
578
+ </div>
579
+
580
+ <div class="chat-messages" id="chat-messages">
581
+ <div class="message bot-message">
582
+ Hello! I'm your Office Procedure Assistant. Ask me any questions about office procedures and rules, and I'll help you find the information you need.
583
+ </div>
584
+ <div class="system-message">
585
+ Add rules in the panel to the right or drag & drop a JSON file onto the page to import rules.
586
+ </div>
587
+ </div>
588
+
589
+ <div class="chat-input">
590
+ <input type="text" id="user-input" placeholder="Type your question here...">
591
+ <button id="send-btn" title="Send message">
592
+ <span id="loader" class="loader hidden"></span>
593
+ <span id="send-text">Send</span>
594
+ <!-- Feather icons replacement for Send icon -->
595
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-send"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
596
+ </button>
597
+ </div>
598
+ </div>
599
+
600
+ <!-- Added card class -->
601
+ <div class="rules-container card">
602
+ <div class="rules-header">
603
+ <h2>Knowledge Base</h2>
604
+ <!-- Added outline class and icon -->
605
+ <button id="export-rules" class="outline" title="Export rules as JSON">
606
+ Export
607
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
608
+ </button>
609
+ </div>
610
+
611
+ <div class="rule-form">
612
+ <textarea id="rule-content" placeholder="Enter a rule or procedure here..."></textarea>
613
+ <div class="rule-actions">
614
+ <!-- Added icon -->
615
+ <button id="add-rule" class="success" title="Add or update rule">
616
+ <span id="add-rule-text">Add Rule</span>
617
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
618
+ </button>
619
+ <!-- Added outline class and icon -->
620
+ <button id="clear-form" class="outline" title="Clear form">
621
+ Clear
622
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
623
+ </button>
624
+ </div>
625
+ </div>
626
+
627
+ <div class="rules-list" id="rules-list">
628
+ <!-- Rules will be added here dynamically -->
629
+ </div>
630
+ </div>
631
+ </main>
632
+
633
+ <!-- Added visually-hidden class for animation -->
634
+ <div id="settings-panel" class="settings-panel visually-hidden">
635
+ <div class="settings-header">
636
+ <h3>Settings</h3>
637
+ <button id="close-settings" class="close-btn" title="Close settings">×</button>
638
+ </div>
639
+ <div class="settings-body">
640
+ <div class="setting-item">
641
+ <label for="api-endpoint">
642
+ API Endpoint
643
+ <div class="tooltip">ℹ️
644
+ <span class="tooltiptext">The base URL for the AI model API (e.g., Hugging Face Space, local endpoint).</span>
645
+ </div>
646
+ </label>
647
+ <input type="text" id="api-endpoint" value="https://parthsadaria-lokiai.hf.space/chat/completions">
648
+ </div>
649
+ <div class="setting-item">
650
+ <label for="api-model">
651
+ Model
652
+ <div class="tooltip">ℹ️
653
+ <span class="tooltiptext">The specific model identifier to use (e.g., 'gemini', 'mistralai/Mistral-7B-Instruct-v0.1').</span>
654
+ </div>
655
+ </label>
656
+ <input type="text" id="api-model" value="gemini">
657
+ </div>
658
+ <div class="setting-item">
659
+ <label for="api-key">
660
+ Authorization Key
661
+ <div class="tooltip">ℹ️
662
+ <span class="tooltiptext">Your API key or token for authentication (often prefixed with 'Bearer '). Keep this secure.</span>
663
+ </div>
664
+ </label>
665
+ <input type="password" id="api-key" value="sigma">
666
+ </div>
667
+ <!-- Added icon -->
668
+ <button id="save-settings" class="success" title="Save API settings">
669
+ Save Settings
670
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-save"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
671
+ </button>
672
+ </div>
673
+ </div>
674
+
675
+ <script>
676
+ document.addEventListener('DOMContentLoaded', function() {
677
+ // DOM Elements (Grouped for clarity)
678
+ const chatMessages = document.getElementById('chat-messages');
679
+ const userInput = document.getElementById('user-input');
680
+ const sendBtn = document.getElementById('send-btn');
681
+ const loader = document.getElementById('loader');
682
+ const sendText = document.getElementById('send-text');
683
+ const sendIcon = sendBtn.querySelector('svg'); // Get the send icon
684
+
685
+ const ruleContent = document.getElementById('rule-content');
686
+ const addRuleBtn = document.getElementById('add-rule');
687
+ const addRuleText = document.getElementById('add-rule-text');
688
+ const clearFormBtn = document.getElementById('clear-form');
689
+ const rulesList = document.getElementById('rules-list');
690
+ const exportRulesBtn = document.getElementById('export-rules');
691
+
692
+ const languageSelector = document.getElementById('language-selector');
693
+
694
+ const settingsBtn = document.getElementById('settings-btn');
695
+ const settingsPanel = document.getElementById('settings-panel');
696
+ const closeSettingsBtn = document.getElementById('close-settings');
697
+ const saveSettingsBtn = document.getElementById('save-settings');
698
+ const apiEndpointInput = document.getElementById('api-endpoint');
699
+ const apiModelInput = document.getElementById('api-model');
700
+ const apiKeyInput = document.getElementById('api-key');
701
+
702
+ // App State
703
+ let rules = [];
704
+ let settings = {
705
+ apiEndpoint: "https://parthsadaria-lokiai.hf.space/chat/completions",
706
+ model: "gemini",
707
+ apiKey: "sigma"
708
+ };
709
+
710
+ // --- Initialization ---
711
+ loadSettings();
712
+ loadRules();
713
+ setupDragAndDrop(); // Initialize drag & drop listeners
714
+
715
+ // --- Local Storage Functions ---
716
+ function loadRules() {
717
+ const savedRules = localStorage.getItem('chatbotRules');
718
+ if (savedRules) {
719
+ try {
720
+ rules = JSON.parse(savedRules);
721
+ } catch (e) {
722
+ console.error("Error parsing rules from localStorage:", e);
723
+ rules = []; // Reset if corrupted
724
+ localStorage.removeItem('chatbotRules'); // Clear corrupted data
725
+ }
726
+ }
727
+ renderRules();
728
+ }
729
+
730
+ function loadSettings() {
731
+ const savedSettings = localStorage.getItem('chatbotSettings');
732
+ if (savedSettings) {
733
+ try {
734
+ settings = JSON.parse(savedSettings);
735
+ } catch (e) {
736
+ console.error("Error parsing settings from localStorage:", e);
737
+ // Keep default settings if parsing fails
738
+ localStorage.removeItem('chatbotSettings'); // Clear corrupted data
739
+ }
740
+ }
741
+ // Always update input fields from the current 'settings' object
742
+ apiEndpointInput.value = settings.apiEndpoint;
743
+ apiModelInput.value = settings.model;
744
+ apiKeyInput.value = settings.apiKey;
745
+ }
746
+
747
+ function saveRules() {
748
+ localStorage.setItem('chatbotRules', JSON.stringify(rules));
749
+ }
750
+
751
+ function saveSettings() {
752
+ settings = {
753
+ apiEndpoint: apiEndpointInput.value.trim(),
754
+ model: apiModelInput.value.trim(),
755
+ apiKey: apiKeyInput.value.trim() // Trim whitespace
756
+ };
757
+ localStorage.setItem('chatbotSettings', JSON.stringify(settings));
758
+ closeSettingsPanel(); // Close panel after saving
759
+ addSystemMessage("Settings saved successfully."); // Feedback
760
+ }
761
+
762
+ // --- UI Rendering and Updates ---
763
+ function renderRules() {
764
+ rulesList.innerHTML = ''; // Clear previous list
765
+ if (rules.length === 0) {
766
+ rulesList.innerHTML = '<div class="system-message">No rules added yet. Add rules or drag & drop a JSON file.</div>';
767
+ return;
768
+ }
769
+
770
+ rules.forEach((rule, index) => {
771
+ const ruleElement = document.createElement('div');
772
+ ruleElement.className = 'rule-item';
773
+ ruleElement.dataset.index = index; // Use data attribute for index
774
+ ruleElement.innerHTML = `
775
+ <p>${escapeHtml(rule)}</p> <!-- Escape HTML to prevent XSS -->
776
+ <div class="rule-actions-inline">
777
+ <button class="edit-rule outline" title="Edit rule">
778
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
779
+ </button>
780
+ <button class="delete-rule danger" title="Delete rule">
781
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
782
+ </button>
783
+ </div>
784
+ `;
785
+
786
+ // Add event listeners directly here for simplicity
787
+ ruleElement.querySelector('.edit-rule').addEventListener('click', handleEditRule);
788
+ ruleElement.querySelector('.delete-rule').addEventListener('click', handleDeleteRule);
789
+
790
+ rulesList.appendChild(ruleElement);
791
+ });
792
+ }
793
+
794
+ // Function to escape HTML special characters
795
+ function escapeHtml(unsafe) {
796
+ if (!unsafe) return '';
797
+ return unsafe
798
+ .replace(/&/g, "&")
799
+ .replace(/</g, "<")
800
+ .replace(/>/g, ">")
801
+ .replace(/"/g, """)
802
+ .replace(/'/g, "'");
803
+ }
804
+
805
+ function addMessage(message, isUser = false) {
806
+ const messageElement = document.createElement('div');
807
+ messageElement.className = `message ${isUser ? 'user-message' : 'bot-message'}`;
808
+ // Use textContent to prevent XSS from user input or bot response
809
+ messageElement.textContent = message;
810
+ chatMessages.appendChild(messageElement);
811
+ // Scroll to bottom smoothly
812
+ chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
813
+ }
814
+
815
+ function addSystemMessage(message) {
816
+ const messageElement = document.createElement('div');
817
+ messageElement.className = 'system-message';
818
+ messageElement.textContent = message;
819
+ chatMessages.appendChild(messageElement);
820
+ chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
821
+ }
822
+
823
+ function setLoading(isLoading) {
824
+ if (isLoading) {
825
+ loader.classList.remove('hidden');
826
+ sendText.classList.add('hidden'); // Hide text
827
+ sendIcon.classList.add('hidden'); // Hide icon
828
+ sendBtn.disabled = true;
829
+ userInput.disabled = true;
830
+ } else {
831
+ loader.classList.add('hidden');
832
+ sendText.classList.remove('hidden'); // Show text
833
+ sendIcon.classList.remove('hidden'); // Show icon
834
+ sendBtn.disabled = false;
835
+ userInput.disabled = false;
836
+ userInput.focus(); // Refocus input after sending
837
+ }
838
+ }
839
+
840
+ function openSettingsPanel() {
841
+ settingsPanel.classList.remove('visually-hidden');
842
+ }
843
+
844
+ function closeSettingsPanel() {
845
+ settingsPanel.classList.add('visually-hidden');
846
+ }
847
+
848
+ // --- Core Logic Functions ---
849
+ async function sendToAI(message) {
850
+ // Basic validation for settings
851
+ if (!settings.apiEndpoint || !settings.model || !settings.apiKey) {
852
+ addSystemMessage("API settings are incomplete. Please configure them in the settings panel.");
853
+ openSettingsPanel(); // Prompt user to open settings
854
+ return "Error: API settings are not configured.";
855
+ }
856
+
857
+ try {
858
+ const currentLanguage = languageSelector.value;
859
+ let context = rules.length > 0
860
+ ? `Based on the following rules:\n\n${rules.map((r, i) => `Rule ${i + 1}: ${r}`).join('\n\n')}\n\n`
861
+ : "You are a general office procedure assistant.\n\n";
862
+
863
+ const systemPrompt = `You are an AI assistant for the General Administration Department, specializing in office procedures and rules. The user is interacting in ${currentLanguage === 'en' ? 'English' : currentLanguage === 'gu' ? 'Gujarati' : 'Hindi'}. Respond ONLY in ${currentLanguage === 'en' ? 'English' : currentLanguage === 'gu' ? 'Gujarati' : 'Hindi'}. ${context}Answer the user's query concisely and accurately. If the query is outside the scope of the provided rules or general office procedures, politely state that you cannot answer. Do not invent information.`;
864
+
865
+ const payload = {
866
+ model: settings.model,
867
+ messages: [
868
+ { role: "system", content: systemPrompt },
869
+ { role: "user", content: message }
870
+ ],
871
+ temperature: 0.5,
872
+ max_tokens: 500
873
+ };
874
+
875
+ const response = await fetch(settings.apiEndpoint, {
876
+ method: 'POST',
877
+ headers: {
878
+ 'Content-Type': 'application/json',
879
+ 'Authorization': `Bearer ${settings.apiKey}` // Standard Bearer token format
880
+ },
881
+ body: JSON.stringify(payload)
882
+ });
883
+
884
+ if (!response.ok) {
885
+ let errorMsg = `API request failed with status ${response.status}`;
886
+ try {
887
+ const errorData = await response.json();
888
+ errorMsg += `: ${errorData.error?.message || JSON.stringify(errorData)}`;
889
+ } catch(e) { /* Ignore if response body isn't JSON */ }
890
+ throw new Error(errorMsg);
891
+ }
892
+
893
+ const data = await response.json();
894
+
895
+ // Handle different possible response structures
896
+ let botResponse = "Sorry, I couldn't get a valid response."; // Default
897
+ if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) {
898
+ botResponse = data.choices[0].message.content.trim();
899
+ } else if (data.message && data.message.content) { // Some APIs might have a different structure
900
+ botResponse = data.message.content.trim();
901
+ } else {
902
+ console.warn("Unexpected API response structure:", data);
903
+ }
904
+
905
+ return botResponse;
906
+
907
+ } catch (error) {
908
+ console.error('Error sending message to AI:', error);
909
+ // Provide more specific error feedback if possible
910
+ const userFriendlyError = error.message.includes("Failed to fetch")
911
+ ? "I couldn't connect to the AI service. Please check the API Endpoint and your network connection."
912
+ : error.message.includes("401") || error.message.includes("Unauthorized")
913
+ ? "Authentication failed. Please check your Authorization Key in the settings."
914
+ : error.message.includes("404")
915
+ ? "The AI model or endpoint was not found. Please check the API Endpoint and Model settings."
916
+ : "I encountered an error. Please check the console for details or try again later.";
917
+ return userFriendlyError;
918
+ }
919
+ }
920
+
921
+ async function handleSendMessage() {
922
+ const message = userInput.value.trim();
923
+ if (!message) return;
924
+
925
+ addMessage(message, true);
926
+ userInput.value = '';
927
+
928
+ if (rules.length === 0 && !localStorage.getItem('rulesWarned')) {
929
+ addSystemMessage("Tip: Add specific rules in the knowledge base for more accurate answers.");
930
+ localStorage.setItem('rulesWarned', 'true'); // Show only once per session maybe? Or use sessionStorage
931
+ }
932
+
933
+ setLoading(true);
934
+ const response = await sendToAI(message);
935
+ setLoading(false);
936
+ addMessage(response);
937
+ }
938
+
939
+ function handleAddOrUpdateRule() {
940
+ const content = ruleContent.value.trim();
941
+ if (!content) return;
942
+
943
+ const editIndex = ruleContent.dataset.editIndex; // Use data attribute
944
+
945
+ if (editIndex !== undefined) {
946
+ // Update existing rule
947
+ rules[parseInt(editIndex)] = content;
948
+ delete ruleContent.dataset.editIndex; // Clean up attribute
949
+ addRuleText.textContent = 'Add Rule';
950
+ addRuleBtn.title = "Add rule";
951
+ addSystemMessage(`Rule ${parseInt(editIndex) + 1} updated.`);
952
+ } else {
953
+ // Add new rule
954
+ rules.push(content);
955
+ addSystemMessage(`Rule ${rules.length} added.`);
956
+ }
957
+
958
+ saveRules();
959
+ renderRules(); // Re-render the list
960
+ ruleContent.value = ''; // Clear the textarea
961
+ // Focus textarea again for quick multi-add
962
+ // ruleContent.focus();
963
+ }
964
+
965
+ function handleEditRule(event) {
966
+ const ruleItem = event.target.closest('.rule-item');
967
+ const index = parseInt(ruleItem.dataset.index);
968
+ ruleContent.value = rules[index];
969
+ ruleContent.dataset.editIndex = index; // Store index for update
970
+ addRuleText.textContent = 'Update Rule'; // Change button text
971
+ addRuleBtn.title = "Update rule";
972
+ ruleContent.focus(); // Focus textarea for editing
973
+ }
974
+
975
+ function handleDeleteRule(event) {
976
+ if (!confirm('Are you sure you want to delete this rule?')) {
977
+ return;
978
+ }
979
+ const ruleItem = event.target.closest('.rule-item');
980
+ const index = parseInt(ruleItem.dataset.index);
981
+
982
+ // Add deleting class for animation
983
+ ruleItem.classList.add('deleting');
984
+
985
+ // Wait for animation to finish before removing from data and DOM
986
+ setTimeout(() => {
987
+ rules.splice(index, 1);
988
+ saveRules();
989
+ // Re-render might be simpler than just removing the element,
990
+ // especially if indices need updating, but removing directly is smoother.
991
+ // ruleItem.remove(); // Remove directly from DOM
992
+ renderRules(); // Or re-render the whole list to update indices correctly
993
+ addSystemMessage(`Rule deleted.`);
994
+ }, 400); // Match animation duration
995
+ }
996
+
997
+ function exportRules() {
998
+ if (rules.length === 0) {
999
+ addSystemMessage("No rules to export.");
1000
+ return;
1001
+ }
1002
+ const dataStr = JSON.stringify(rules, null, 2); // Pretty print JSON
1003
+ const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
1004
+ const exportFileDefaultName = `chatbot-rules-${new Date().toISOString().slice(0,10)}.json`;
1005
+
1006
+ const linkElement = document.createElement('a');
1007
+ linkElement.setAttribute('href', dataUri);
1008
+ linkElement.setAttribute('download', exportFileDefaultName);
1009
+ linkElement.click();
1010
+ linkElement.remove(); // Clean up
1011
+ addSystemMessage("Rules exported successfully.");
1012
+ }
1013
+
1014
+ function importRules(importedRules) {
1015
+ if (!Array.isArray(importedRules) || !importedRules.every(item => typeof item === 'string')) {
1016
+ addSystemMessage('Import failed: File does not contain a valid array of strings.');
1017
+ console.error('Invalid import data:', importedRules);
1018
+ return;
1019
+ }
1020
+
1021
+ if (confirm(`Import ${importedRules.length} rules? This will replace all existing rules.`)) {
1022
+ rules = importedRules;
1023
+ saveRules();
1024
+ renderRules();
1025
+ addSystemMessage(`Successfully imported ${importedRules.length} rules.`);
1026
+ }
1027
+ }
1028
+
1029
+ // --- Drag and Drop ---
1030
+ function setupDragAndDrop() {
1031
+ const body = document.body;
1032
+
1033
+ body.addEventListener('dragenter', (e) => {
1034
+ e.preventDefault();
1035
+ e.stopPropagation();
1036
+ body.classList.add('drag-active');
1037
+ });
1038
+
1039
+ body.addEventListener('dragover', (e) => {
1040
+ e.preventDefault(); // Necessary to allow drop
1041
+ e.stopPropagation();
1042
+ body.classList.add('drag-active'); // Keep class while hovering
1043
+ });
1044
+
1045
+ body.addEventListener('dragleave', (e) => {
1046
+ e.preventDefault();
1047
+ e.stopPropagation();
1048
+ // Only remove if leaving the window, not just hovering over child elements
1049
+ if (e.relatedTarget === null || !body.contains(e.relatedTarget)) {
1050
+ body.classList.remove('drag-active');
1051
+ }
1052
+ });
1053
+
1054
+ body.addEventListener('drop', (e) => {
1055
+ e.preventDefault();
1056
+ e.stopPropagation();
1057
+ body.classList.remove('drag-active');
1058
+
1059
+ const file = e.dataTransfer.files[0];
1060
+ if (file && file.type === 'application/json') {
1061
+ const reader = new FileReader();
1062
+ reader.onload = function(event) {
1063
+ try {
1064
+ const content = JSON.parse(event.target.result);
1065
+ importRules(content); // Use the import function
1066
+ } catch (error) {
1067
+ console.error('Error parsing JSON file:', error);
1068
+ addSystemMessage('Error importing rules: Could not parse JSON file. Ensure it is a valid JSON array of strings.');
1069
+ }
1070
+ };
1071
+ reader.onerror = function() {
1072
+ console.error('Error reading file:', reader.error);
1073
+ addSystemMessage('Error reading the dropped file.');
1074
+ }
1075
+ reader.readAsText(file);
1076
+ } else if (file) {
1077
+ addSystemMessage('Import failed: Please drop a valid JSON file (.json).');
1078
+ }
1079
+ });
1080
+ }
1081
+
1082
+
1083
+ // --- Event Listeners ---
1084
+ sendBtn.addEventListener('click', handleSendMessage);
1085
+ userInput.addEventListener('keypress', (e) => {
1086
+ if (e.key === 'Enter' && !e.shiftKey) { // Send on Enter, allow Shift+Enter for newline
1087
+ e.preventDefault(); // Prevent default newline in input
1088
+ handleSendMessage();
1089
+ }
1090
+ });
1091
+
1092
+ addRuleBtn.addEventListener('click', handleAddOrUpdateRule);
1093
+ clearFormBtn.addEventListener('click', () => {
1094
+ ruleContent.value = '';
1095
+ delete ruleContent.dataset.editIndex; // Clear edit state if any
1096
+ addRuleText.textContent = 'Add Rule';
1097
+ addRuleBtn.title = "Add rule";
1098
+ });
1099
+
1100
+ exportRulesBtn.addEventListener('click', exportRules);
1101
+
1102
+ settingsBtn.addEventListener('click', openSettingsPanel);
1103
+ closeSettingsBtn.addEventListener('click', closeSettingsPanel);
1104
+ saveSettingsBtn.addEventListener('click', saveSettings);
1105
+
1106
+ // Close settings panel if clicking outside of it
1107
+ document.addEventListener('click', (event) => {
1108
+ if (!settingsPanel.classList.contains('visually-hidden') &&
1109
+ !settingsPanel.contains(event.target) &&
1110
+ event.target !== settingsBtn &&
1111
+ !settingsBtn.contains(event.target)) {
1112
+ closeSettingsPanel();
1113
+ }
1114
+ });
1115
+
1116
+ });
1117
+ </script>
1118
+ </body>
1119
  </html>