smaru-4969 commited on
Commit
84e9692
·
verified ·
1 Parent(s): 450e005

Delete frontend/src/App.js

Browse files
Files changed (1) hide show
  1. frontend/src/App.js +0 -1268
frontend/src/App.js DELETED
@@ -1,1268 +0,0 @@
1
- /**
2
- * TheTruthSchool - Professional AI Assistant Interface
3
- *
4
- * Features:
5
- * - Dark mode with elegant theme switching
6
- * - Claude/ChatGPT-inspired professional design
7
- * - Multi-domain RAG with TheTruthSchool branding
8
- * - Smooth animations and modern UI
9
- */
10
-
11
- import React, { useState, useEffect, useRef, useCallback } from 'react';
12
- import ReactMarkdown from 'react-markdown';
13
- import remarkGfm from 'remark-gfm';
14
- import rehypeHighlight from 'rehype-highlight';
15
- import 'highlight.js/styles/atom-one-dark.css';
16
- import {
17
- Send,
18
- Upload,
19
- FileText,
20
- CheckCircle,
21
- XCircle,
22
- Menu,
23
- X,
24
- Loader2,
25
- Trash2,
26
- FolderOpen,
27
- RefreshCw,
28
- Moon,
29
- Sun,
30
- Sparkles
31
- } from 'lucide-react';
32
-
33
- // =============================================================================
34
- // Domain Configurations
35
- // =============================================================================
36
-
37
- const DOMAIN_CONFIGS = {
38
- medical: {
39
- name: 'Medical & Healthcare',
40
- description: 'Medical documents, research papers, clinical guidelines',
41
- color: '#3b82f6',
42
- fileTypes: ['.pdf', '.docx', '.xml', '.txt', '.doc', '.csv', '.xlsx'],
43
- icon: '🏥'
44
- },
45
- legal: {
46
- name: 'Legal & Compliance',
47
- description: 'Legal documents, contracts, regulations, case law',
48
- color: '#8b5cf6',
49
- fileTypes: ['.pdf', '.docx', '.txt', '.doc', '.csv', '.xlsx'],
50
- icon: '⚖️'
51
- },
52
- financial: {
53
- name: 'Financial & Analytics',
54
- description: 'Financial reports, analysis, market research',
55
- color: '#10b981',
56
- fileTypes: ['.pdf', '.xlsx', '.csv', '.json', '.xls'],
57
- icon: '💰'
58
- },
59
- technical: {
60
- name: 'Technical Documentation',
61
- description: 'Technical docs, APIs, code, system architecture',
62
- color: '#f97316',
63
- fileTypes: ['.pdf', '.md', '.docx', '.json', '.txt', '.rst', '.csv', '.xlsx'],
64
- icon: '⚙️'
65
- },
66
- academic: {
67
- name: 'Academic Research',
68
- description: 'Research papers, academic publications, studies',
69
- color: '#6366f1',
70
- fileTypes: ['.pdf', '.docx', '.tex', '.bib', '.txt', '.csv', '.xlsx'],
71
- icon: '🎓'
72
- }
73
- };
74
-
75
- const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
76
-
77
- // =============================================================================
78
- // Main Component
79
- // =============================================================================
80
-
81
- export default function TheTruthSchoolAI() {
82
- const getFromLocalStorage = (key, defaultValue) => {
83
- try {
84
- const item = window.localStorage.getItem(key);
85
- return item ? JSON.parse(item) : defaultValue;
86
- } catch (error) {
87
- console.error(`Error reading localStorage key "${key}":`, error);
88
- return defaultValue;
89
- }
90
- };
91
-
92
- // State Management
93
- const [darkMode, setDarkMode] = useState(() => getFromLocalStorage('darkMode', true));
94
- const [selectedDomain, setSelectedDomain] = useState(() => getFromLocalStorage('selectedDomain', 'medical'));
95
- const [currentView, setCurrentView] = useState('app');
96
- const [processingDocs, setProcessingDocs] = useState(() => getFromLocalStorage('processingDocs', []));
97
- const [processedDocs, setProcessedDocs] = useState([]);
98
- const [query, setQuery] = useState('');
99
- const [messages, setMessages] = useState(() => getFromLocalStorage('chatMessages', []));
100
- const [isQuerying, setIsQuerying] = useState(false);
101
- const [error, setError] = useState(null);
102
- const [showUploadModal, setShowUploadModal] = useState(false);
103
- const [isDragging, setIsDragging] = useState(false);
104
- const [showSidebar, setShowSidebar] = useState(true);
105
- const [enableWebSearch, setEnableWebSearch] = useState(() => getFromLocalStorage('enableWebSearch', false));
106
- const [webSearchOnly, setWebSearchOnly] = useState(() => getFromLocalStorage('webSearchOnly', false));
107
- const [urlInput, setUrlInput] = useState('');
108
- const [uploadMode, setUploadMode] = useState('file');
109
- const [fastMode, setFastMode] = useState(() => getFromLocalStorage('fastMode', false));
110
- const [enableCache, setEnableCache] = useState(() => getFromLocalStorage('enableCache', true));
111
- const [enableQueryImprovement, setEnableQueryImprovement] = useState(() => getFromLocalStorage('enableQueryImprovement', true));
112
- const [enableVerification, setEnableVerification] = useState(() => getFromLocalStorage('enableVerification', true));
113
- const [typingSpeed] = useState(0);
114
-
115
- const messagesEndRef = useRef(null);
116
- const fileInputRef = useRef(null);
117
- const typingIntervalRef = useRef(null);
118
-
119
- // Theme classes based on dark mode
120
- const theme = {
121
- bg: darkMode ? 'bg-[#0D0D0D]' : 'bg-white',
122
- bgSecondary: darkMode ? 'bg-[#171717]' : 'bg-gray-50',
123
- bgTertiary: darkMode ? 'bg-[#252525]' : 'bg-white',
124
- text: darkMode ? 'text-gray-100' : 'text-gray-900',
125
- textSecondary: darkMode ? 'text-gray-400' : 'text-gray-600',
126
- textMuted: darkMode ? 'text-gray-500' : 'text-gray-500',
127
- border: darkMode ? 'border-gray-800' : 'border-gray-200',
128
- borderLight: darkMode ? 'border-gray-700' : 'border-gray-300',
129
- hover: darkMode ? 'hover:bg-[#252525]' : 'hover:bg-gray-100',
130
- active: darkMode ? 'bg-[#252525]' : 'bg-blue-50',
131
- userMessage: darkMode ? 'bg-blue-600' : 'bg-blue-600',
132
- assistantMessage: darkMode ? 'bg-[#252525]' : 'bg-gray-100',
133
- input: darkMode ? 'bg-[#171717] border-gray-700 text-gray-100' : 'bg-white border-gray-300 text-gray-900',
134
- button: darkMode ? 'bg-[#252525] hover:bg-[#2D2D2D]' : 'bg-gray-100 hover:bg-gray-200'
135
- };
136
-
137
- const scrollToBottom = () => {
138
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
139
- };
140
-
141
- useEffect(() => {
142
- scrollToBottom();
143
- }, [messages]);
144
-
145
- // Persist to localStorage
146
- useEffect(() => {
147
- try {
148
- window.localStorage.setItem('darkMode', JSON.stringify(darkMode));
149
- window.localStorage.setItem('chatMessages', JSON.stringify(messages));
150
- window.localStorage.setItem('selectedDomain', JSON.stringify(selectedDomain));
151
- window.localStorage.setItem('processingDocs', JSON.stringify(processingDocs));
152
- window.localStorage.setItem('enableWebSearch', JSON.stringify(enableWebSearch));
153
- window.localStorage.setItem('webSearchOnly', JSON.stringify(webSearchOnly));
154
- window.localStorage.setItem('fastMode', JSON.stringify(fastMode));
155
- window.localStorage.setItem('enableCache', JSON.stringify(enableCache));
156
- window.localStorage.setItem('enableQueryImprovement', JSON.stringify(enableQueryImprovement));
157
- window.localStorage.setItem('enableVerification', JSON.stringify(enableVerification));
158
- } catch (error) {
159
- console.error('Error saving to localStorage:', error);
160
- }
161
- }, [darkMode, messages, selectedDomain, processingDocs, enableWebSearch, webSearchOnly, fastMode, enableCache, enableQueryImprovement, enableVerification]);
162
-
163
- // Fetch processed documents
164
- const fetchProcessedDocuments = useCallback(async () => {
165
- try {
166
- const response = await fetch(`${API_BASE_URL}/documents?domain=${selectedDomain}`);
167
- if (response.ok) {
168
- const data = await response.json();
169
- const fetchedDocs = data.documents || [];
170
- setProcessedDocs(prev => {
171
- const fetchedIds = new Set(fetchedDocs.map(d => d.id));
172
- const recentlyAdded = prev.filter(d => d.id && !fetchedIds.has(d.id));
173
- return [...fetchedDocs, ...recentlyAdded];
174
- });
175
- }
176
- } catch (err) {
177
- console.error('Error fetching documents:', err);
178
- }
179
- }, [selectedDomain]);
180
-
181
- // Check processing status
182
- const checkProcessingStatus = useCallback(async () => {
183
- const updatedProcessing = [];
184
- for (const doc of processingDocs) {
185
- try {
186
- const response = await fetch(`${API_BASE_URL}/status/${doc.processingId}`);
187
- if (response.ok) {
188
- const status = await response.json();
189
- if (status.status === 'completed') {
190
- setProcessedDocs(prev => [...prev, { ...doc, id: doc.processingId, status: 'completed' }]);
191
- } else if (status.status === 'failed') {
192
- setError(`Processing failed for ${doc.name}: ${status.error}`);
193
- } else {
194
- updatedProcessing.push({ ...doc, status: status.status });
195
- }
196
- }
197
- } catch (err) {
198
- console.error('Error checking status:', err);
199
- }
200
- }
201
- setProcessingDocs(updatedProcessing);
202
- }, [processingDocs]);
203
-
204
- useEffect(() => {
205
- fetchProcessedDocuments();
206
- }, [selectedDomain, fetchProcessedDocuments]);
207
-
208
- useEffect(() => {
209
- const interval = setInterval(() => {
210
- if (processingDocs.length > 0) {
211
- checkProcessingStatus();
212
- }
213
- }, 3000);
214
- return () => clearInterval(interval);
215
- }, [processingDocs, checkProcessingStatus]);
216
-
217
- // API Functions
218
- const handleFileUpload = async (files) => {
219
- if (!files || files.length === 0) return;
220
- setError(null);
221
- const newProcessingDocs = [];
222
-
223
- for (const file of files) {
224
- const fileExt = '.' + file.name.split('.').pop().toLowerCase();
225
- const allowedTypes = DOMAIN_CONFIGS[selectedDomain].fileTypes;
226
-
227
- if (!allowedTypes.includes(fileExt)) {
228
- setError(`File type ${fileExt} not supported for ${selectedDomain} domain.`);
229
- continue;
230
- }
231
-
232
- const formData = new FormData();
233
- formData.append('file', file);
234
- formData.append('domain', selectedDomain);
235
-
236
- try {
237
- const response = await fetch(`${API_BASE_URL}/upload`, {
238
- method: 'POST',
239
- body: formData
240
- });
241
-
242
- const data = await response.json();
243
- if (response.ok) {
244
- newProcessingDocs.push({
245
- name: file.name,
246
- domain: selectedDomain,
247
- processingId: data.processing_id,
248
- status: 'processing',
249
- uploadedAt: new Date().toISOString()
250
- });
251
- } else {
252
- setError(data.detail || 'Upload failed');
253
- }
254
- } catch (err) {
255
- console.error('Upload error:', err);
256
- setError(`Failed to upload ${file.name}: ${err.message}`);
257
- }
258
- }
259
-
260
- setProcessingDocs(prev => [...prev, ...newProcessingDocs]);
261
- setShowUploadModal(false);
262
- };
263
-
264
- const handleUrlUpload = async () => {
265
- if (!urlInput.trim()) {
266
- setError('Please enter a valid URL');
267
- return;
268
- }
269
-
270
- setError(null);
271
-
272
- try {
273
- const response = await fetch(`${API_BASE_URL}/upload-url`, {
274
- method: 'POST',
275
- headers: { 'Content-Type': 'application/json' },
276
- body: JSON.stringify({
277
- url: urlInput,
278
- domain: selectedDomain,
279
- convert_to_markdown: true
280
- })
281
- });
282
-
283
- const data = await response.json();
284
- if (response.ok) {
285
- setProcessingDocs(prev => [...prev, {
286
- name: urlInput,
287
- domain: selectedDomain,
288
- processingId: data.processing_id,
289
- status: 'processing',
290
- uploadedAt: new Date().toISOString()
291
- }]);
292
- setUrlInput('');
293
- setShowUploadModal(false);
294
- } else {
295
- setError(data.detail || 'URL upload failed');
296
- }
297
- } catch (err) {
298
- console.error('URL upload error:', err);
299
- setError(`Failed to upload URL: ${err.message}`);
300
- }
301
- };
302
-
303
- const startTypingEffect = useCallback((messageIndex, targetTextRef, isStreamingRef) => {
304
- if (typingIntervalRef.current) {
305
- clearInterval(typingIntervalRef.current);
306
- }
307
-
308
- let displayedLength = 0;
309
-
310
- typingIntervalRef.current = setInterval(() => {
311
- const targetText = targetTextRef.current || '';
312
- const isStillStreaming = isStreamingRef.current;
313
-
314
- if (displayedLength < targetText.length) {
315
- const charsToAdd = Math.max(1, Math.floor(typingSpeed / 10));
316
- displayedLength = Math.min(displayedLength + charsToAdd, targetText.length);
317
-
318
- setMessages(prev => {
319
- const newMessages = [...prev];
320
- if (newMessages[messageIndex]) {
321
- newMessages[messageIndex] = {
322
- ...newMessages[messageIndex],
323
- content: targetText.substring(0, displayedLength)
324
- };
325
- }
326
- return newMessages;
327
- });
328
- } else if (!isStillStreaming && displayedLength >= targetText.length) {
329
- clearInterval(typingIntervalRef.current);
330
- typingIntervalRef.current = null;
331
- }
332
- }, 30);
333
- }, [typingSpeed]);
334
-
335
- useEffect(() => {
336
- return () => {
337
- if (typingIntervalRef.current) {
338
- clearInterval(typingIntervalRef.current);
339
- }
340
- };
341
- }, []);
342
-
343
- const handleQuery = async () => {
344
- if (!query.trim()) return;
345
-
346
- setError(null);
347
- setIsQuerying(true);
348
-
349
- const userMessage = { role: 'user', content: query };
350
- setMessages(prev => [...prev, userMessage]);
351
- const currentQuery = query;
352
- setQuery('');
353
-
354
- const assistantMessageIndex = messages.length + 1;
355
- setMessages(prev => [...prev, {
356
- role: 'assistant',
357
- content: '',
358
- streaming: true,
359
- verification: null
360
- }]);
361
-
362
- const fullTextBufferRef = { current: '' };
363
- const isStreamingRef = { current: true };
364
- let typingStarted = false;
365
-
366
- try {
367
- const response = await fetch(`${API_BASE_URL}/query/stream`, {
368
- method: 'POST',
369
- headers: { 'Content-Type': 'application/json' },
370
- body: JSON.stringify({
371
- query: currentQuery,
372
- domain: selectedDomain,
373
- enable_verification: true,
374
- enable_web_search: enableWebSearch,
375
- web_search_only: webSearchOnly,
376
- fast_mode: fastMode,
377
- enable_cache: enableCache,
378
- enable_query_improvement: enableQueryImprovement,
379
- enable_verification_check: enableVerification
380
- })
381
- });
382
-
383
- if (!response.ok) {
384
- throw new Error(`HTTP error! status: ${response.status}`);
385
- }
386
-
387
- const reader = response.body.getReader();
388
- const decoder = new TextDecoder();
389
- let buffer = '';
390
-
391
- while (true) {
392
- const { done, value } = await reader.read();
393
-
394
- if (done) {
395
- break;
396
- }
397
-
398
- buffer += decoder.decode(value, { stream: true });
399
-
400
- const events = buffer.split('\n\n');
401
- buffer = events.pop() || '';
402
-
403
- for (const event of events) {
404
- if (!event.trim()) continue;
405
-
406
- const lines = event.split('\n');
407
- let eventType = 'message';
408
- let eventData = '';
409
-
410
- for (const line of lines) {
411
- if (line.startsWith('event:')) {
412
- eventType = line.substring(6).trim();
413
- } else if (line.startsWith('data:')) {
414
- eventData = line.substring(5).trim();
415
- }
416
- }
417
-
418
- if (eventData) {
419
- const data = JSON.parse(eventData);
420
-
421
- if (eventType === 'token') {
422
- fullTextBufferRef.current += data.content;
423
-
424
- if (!typingStarted && typingSpeed > 0) {
425
- typingStarted = true;
426
- startTypingEffect(assistantMessageIndex, fullTextBufferRef, isStreamingRef);
427
- } else if (typingSpeed === 0) {
428
- setMessages(prev => {
429
- const newMessages = [...prev];
430
- newMessages[assistantMessageIndex] = {
431
- ...newMessages[assistantMessageIndex],
432
- content: fullTextBufferRef.current
433
- };
434
- return newMessages;
435
- });
436
- }
437
-
438
- } else if (eventType === 'verification') {
439
- setMessages(prev => {
440
- const newMessages = [...prev];
441
- newMessages[assistantMessageIndex] = {
442
- ...newMessages[assistantMessageIndex],
443
- verification: data.content,
444
- streaming: false
445
- };
446
- return newMessages;
447
- });
448
-
449
- } else if (eventType === 'done') {
450
- isStreamingRef.current = false;
451
-
452
- setTimeout(() => {
453
- if (typingIntervalRef.current) {
454
- clearInterval(typingIntervalRef.current);
455
- typingIntervalRef.current = null;
456
- }
457
-
458
- setMessages(prev => {
459
- const newMessages = [...prev];
460
- newMessages[assistantMessageIndex] = {
461
- ...newMessages[assistantMessageIndex],
462
- streaming: false,
463
- content: fullTextBufferRef.current
464
- };
465
- return newMessages;
466
- });
467
- }, typingSpeed === 0 ? 0 : 500);
468
-
469
- } else if (eventType === 'error') {
470
- const errorMessage = data.content.message || 'An error occurred';
471
- const errorSuggestion = data.content.suggestion || '';
472
- setError(errorSuggestion ? `${errorMessage}\n\n${errorSuggestion}` : errorMessage);
473
-
474
- isStreamingRef.current = false;
475
-
476
- if (typingIntervalRef.current) {
477
- clearInterval(typingIntervalRef.current);
478
- typingIntervalRef.current = null;
479
- }
480
-
481
- setMessages(prev => {
482
- const newMessages = [...prev];
483
- newMessages[assistantMessageIndex] = {
484
- ...newMessages[assistantMessageIndex],
485
- content: fullTextBufferRef.current || errorMessage,
486
- streaming: false,
487
- error: true
488
- };
489
- return newMessages;
490
- });
491
- break;
492
- }
493
- }
494
- }
495
- }
496
-
497
- } catch (err) {
498
- console.error('Query error:', err);
499
- setError(`Query failed: ${err.message}`);
500
-
501
- if (typingIntervalRef.current) {
502
- clearInterval(typingIntervalRef.current);
503
- typingIntervalRef.current = null;
504
- }
505
-
506
- setMessages(prev => {
507
- const newMessages = [...prev];
508
- if (newMessages[assistantMessageIndex]) {
509
- newMessages[assistantMessageIndex] = {
510
- ...newMessages[assistantMessageIndex],
511
- content: newMessages[assistantMessageIndex].content || '[Error occurred]',
512
- streaming: false,
513
- error: true
514
- };
515
- }
516
- return newMessages;
517
- });
518
- } finally {
519
- setIsQuerying(false);
520
- }
521
- };
522
-
523
- const handleKeyPress = (e) => {
524
- if (e.key === 'Enter' && !e.shiftKey) {
525
- e.preventDefault();
526
- handleQuery();
527
- }
528
- };
529
-
530
- const handleDeleteDocument = async (docId, docName) => {
531
- if (!docId) {
532
- console.error('Document ID is undefined');
533
- setError('Cannot delete document: ID is missing');
534
- return;
535
- }
536
-
537
- const confirmed = window.confirm(
538
- `Are you sure you want to delete "${docName || 'this document'}"?\n\nThis action cannot be undone.`
539
- );
540
-
541
- if (!confirmed) {
542
- return;
543
- }
544
-
545
- try {
546
- const response = await fetch(`${API_BASE_URL}/documents/${docId}`, {
547
- method: 'DELETE'
548
- });
549
-
550
- const data = await response.json();
551
-
552
- if (response.ok && data.success) {
553
- setProcessedDocs(prev => prev.filter(doc => doc.id !== docId));
554
- await fetchProcessedDocuments();
555
- } else {
556
- const errorMsg = data.message || data.detail || 'Failed to delete document';
557
- setError(errorMsg);
558
- }
559
- } catch (err) {
560
- console.error('Error deleting document:', err);
561
- setError('Failed to delete document: ' + err.message);
562
- }
563
- };
564
-
565
- const handleDragOver = (e) => {
566
- e.preventDefault();
567
- setIsDragging(true);
568
- };
569
-
570
- const handleDragLeave = (e) => {
571
- e.preventDefault();
572
- setIsDragging(false);
573
- };
574
-
575
- const handleDrop = (e) => {
576
- e.preventDefault();
577
- setIsDragging(false);
578
- handleFileUpload(e.dataTransfer.files);
579
- };
580
-
581
- // =============================================================================
582
- // Render Functions
583
- // =============================================================================
584
-
585
- const renderNavigation = () => (
586
- <nav className={`${theme.bgTertiary} ${theme.border} border-b px-6 py-3`}>
587
- <div className="flex items-center justify-between max-w-7xl mx-auto">
588
- <div className="flex items-center space-x-8">
589
- <div className="flex items-center space-x-3">
590
- <div className="flex items-center space-x-2">
591
- <div className={`w-8 h-8 ${darkMode ? 'bg-gradient-to-br from-purple-500 to-blue-500' : 'bg-gradient-to-br from-blue-600 to-purple-600'} rounded-lg flex items-center justify-center`}>
592
- <Sparkles className="w-5 h-5 text-white" />
593
- </div>
594
- <h1 className={`text-xl font-bold ${theme.text}`}>TheTruthSchool</h1>
595
- </div>
596
- <span className={`text-sm ${theme.textMuted}`}>/ {DOMAIN_CONFIGS[selectedDomain].name}</span>
597
- </div>
598
-
599
- <div className="flex items-center space-x-1">
600
- <button
601
- onClick={() => setCurrentView('app')}
602
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
603
- currentView === 'app'
604
- ? `${darkMode ? 'text-blue-400 bg-blue-900/30' : 'text-blue-600 bg-blue-50'}`
605
- : `${theme.textSecondary} ${theme.hover}`
606
- }`}
607
- >
608
- Chat
609
- </button>
610
- <button
611
- onClick={() => setCurrentView('files')}
612
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
613
- currentView === 'files'
614
- ? `${darkMode ? 'text-blue-400 bg-blue-900/30' : 'text-blue-600 bg-blue-50'}`
615
- : `${theme.textSecondary} ${theme.hover}`
616
- }`}
617
- >
618
- Files
619
- </button>
620
- <button
621
- onClick={() => setCurrentView('settings')}
622
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
623
- currentView === 'settings'
624
- ? `${darkMode ? 'text-blue-400 bg-blue-900/30' : 'text-blue-600 bg-blue-50'}`
625
- : `${theme.textSecondary} ${theme.hover}`
626
- }`}
627
- >
628
- Settings
629
- </button>
630
- </div>
631
- </div>
632
-
633
- <div className="flex items-center space-x-2">
634
- <button
635
- onClick={() => setDarkMode(!darkMode)}
636
- className={`p-2 ${theme.textSecondary} ${theme.hover} rounded-md transition-colors`}
637
- >
638
- {darkMode ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
639
- </button>
640
- <button
641
- onClick={() => setShowSidebar(!showSidebar)}
642
- className={`p-2 ${theme.textSecondary} ${theme.hover} rounded-md transition-colors`}
643
- >
644
- {showSidebar ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
645
- </button>
646
- </div>
647
- </div>
648
- </nav>
649
- );
650
-
651
- const renderSidebar = () => (
652
- <div className={`${showSidebar ? 'w-64' : 'w-0'} transition-all duration-300 ${theme.bgSecondary} ${theme.border} border-r overflow-hidden`}>
653
- <div className="p-4 space-y-4">
654
- <div>
655
- <h3 className={`text-xs font-semibold ${theme.textMuted} uppercase mb-3`}>Domains</h3>
656
- <div className="space-y-1">
657
- {Object.entries(DOMAIN_CONFIGS).map(([key, config]) => (
658
- <button
659
- key={key}
660
- onClick={() => setSelectedDomain(key)}
661
- className={`w-full flex items-center space-x-3 px-3 py-2 rounded-lg text-sm transition-colors ${
662
- selectedDomain === key
663
- ? `${darkMode ? 'bg-blue-900/30 text-blue-400' : 'bg-blue-50 text-blue-700'} font-medium`
664
- : `${theme.textSecondary} ${theme.hover}`
665
- }`}
666
- >
667
- <span className="text-lg">{config.icon}</span>
668
- <span className="flex-1 text-left truncate">{config.name}</span>
669
- </button>
670
- ))}
671
- </div>
672
- </div>
673
-
674
- {processingDocs.length > 0 && (
675
- <div>
676
- <h3 className={`text-xs font-semibold ${theme.textMuted} uppercase mb-3`}>Processing</h3>
677
- <div className="space-y-2">
678
- {processingDocs.map((doc, idx) => (
679
- <div key={idx} className={`flex items-center space-x-2 px-3 py-2 ${darkMode ? 'bg-yellow-900/20' : 'bg-yellow-50'} rounded-lg`}>
680
- <Loader2 className={`w-4 h-4 ${darkMode ? 'text-yellow-400' : 'text-yellow-600'} animate-spin`} />
681
- <span className={`text-xs ${darkMode ? 'text-yellow-300' : 'text-yellow-800'} truncate flex-1`}>{doc.name}</span>
682
- </div>
683
- ))}
684
- </div>
685
- </div>
686
- )}
687
-
688
- {processedDocs.length > 0 && (
689
- <div>
690
- <h3 className={`text-xs font-semibold ${theme.textMuted} uppercase mb-3`}>
691
- Documents ({processedDocs.length})
692
- </h3>
693
- <div className="space-y-1 max-h-64 overflow-y-auto">
694
- {processedDocs.map((doc, idx) => (
695
- <div key={idx} className={`flex items-center space-x-2 px-3 py-2 ${theme.bgTertiary} rounded-lg ${theme.border} border group`}>
696
- <FileText className={`w-4 h-4 ${theme.textMuted}`} />
697
- <span className={`text-xs ${theme.textSecondary} truncate flex-1`}>{doc.name || `Document ${idx + 1}`}</span>
698
- <button
699
- onClick={() => handleDeleteDocument(doc.id, doc.name)}
700
- className="opacity-0 group-hover:opacity-100 transition-opacity"
701
- >
702
- <Trash2 className={`w-3 h-3 ${theme.textMuted} hover:text-red-600`} />
703
- </button>
704
- </div>
705
- ))}
706
- </div>
707
- </div>
708
- )}
709
-
710
- {messages.length > 0 && (
711
- <div className={`pt-4 ${theme.border} border-t`}>
712
- <button
713
- onClick={() => {
714
- if (window.confirm('Clear all chat history? This cannot be undone.')) {
715
- setMessages([]);
716
- window.localStorage.removeItem('chatMessages');
717
- }
718
- }}
719
- className={`w-full flex items-center justify-center space-x-2 px-3 py-2 text-sm text-red-500 hover:${darkMode ? 'bg-red-900/20' : 'bg-red-50'} rounded-lg transition-colors`}
720
- >
721
- <Trash2 className="w-4 h-4" />
722
- <span>Clear Chat</span>
723
- </button>
724
- </div>
725
- )}
726
- </div>
727
- </div>
728
- );
729
-
730
- const renderAppView = () => (
731
- <div className={`flex-1 flex flex-col ${theme.bg}`}>
732
- {messages.length === 0 ? (
733
- <div className="flex-1 flex flex-col items-center justify-center px-4">
734
- <div className="text-center max-w-2xl">
735
- <div className={`w-20 h-20 ${darkMode ? 'bg-gradient-to-br from-purple-500 to-blue-500' : 'bg-gradient-to-br from-blue-600 to-purple-600'} rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg`}>
736
- <Sparkles className="w-10 h-10 text-white" />
737
- </div>
738
- <h2 className={`text-4xl font-bold ${theme.text} mb-3`}>TheTruthSchool AI</h2>
739
- <p className={`${theme.textSecondary} mb-8 text-lg`}>
740
- Your intelligent assistant for document analysis and knowledge discovery
741
- </p>
742
-
743
- <div className="grid grid-cols-3 gap-4 text-left">
744
- <div className={`p-5 ${theme.bgSecondary} rounded-xl ${theme.border} border`}>
745
- <div className="text-3xl mb-3">📚</div>
746
- <h3 className={`font-semibold ${theme.text} mb-2`}>Smart Upload</h3>
747
- <p className={`text-sm ${theme.textSecondary}`}>Process PDFs, documents, and web content</p>
748
- </div>
749
- <div className={`p-5 ${theme.bgSecondary} rounded-xl ${theme.border} border`}>
750
- <div className="text-3xl mb-3">🧠</div>
751
- <h3 className={`font-semibold ${theme.text} mb-2`}>Deep Understanding</h3>
752
- <p className={`text-sm ${theme.textSecondary}`}>Advanced RAG with knowledge graphs</p>
753
- </div>
754
- <div className={`p-5 ${theme.bgSecondary} rounded-xl ${theme.border} border`}>
755
- <div className="text-3xl mb-3">✨</div>
756
- <h3 className={`font-semibold ${theme.text} mb-2`}>Multi-Domain</h3>
757
- <p className={`text-sm ${theme.textSecondary}`}>Optimized for healthcare, legal, finance & more</p>
758
- </div>
759
- </div>
760
- </div>
761
- </div>
762
- ) : (
763
- <div className="flex-1 overflow-y-auto px-4 py-6">
764
- <div className="max-w-3xl mx-auto space-y-6">
765
- {messages.map((msg, idx) => (
766
- <div key={idx} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
767
- <div className={`max-w-[80%] ${msg.role === 'user' ? 'bg-blue-600 text-white' : `${theme.assistantMessage} ${theme.text}`} rounded-2xl px-5 py-4 shadow-sm`}>
768
- {msg.role === 'user' ? (
769
- <p className="text-sm whitespace-pre-wrap">{msg.content}</p>
770
- ) : (
771
- <div className={`text-sm prose prose-sm max-w-none ${darkMode ? 'prose-invert' : ''}`}>
772
- <ReactMarkdown
773
- remarkPlugins={[remarkGfm]}
774
- rehypePlugins={[rehypeHighlight]}
775
- components={{
776
- code({ node, inline, className, children, ...props }) {
777
- return inline ? (
778
- <code className={`${darkMode ? 'bg-gray-700 text-gray-100' : 'bg-gray-200 text-gray-800'} px-1.5 py-0.5 rounded text-xs font-mono`} {...props}>
779
- {children}
780
- </code>
781
- ) : (
782
- <code className={className} {...props}>
783
- {children}
784
- </code>
785
- );
786
- },
787
- a({ node, children, ...props }) {
788
- return (
789
- <a className={`${darkMode ? 'text-blue-400' : 'text-blue-600'} hover:underline`} target="_blank" rel="noopener noreferrer" {...props}>
790
- {children}
791
- </a>
792
- );
793
- },
794
- table: ({ node, ...props }) => (
795
- <div className="overflow-x-auto my-4">
796
- <table className={`min-w-full divide-y ${darkMode ? 'divide-gray-700 border-gray-700' : 'divide-gray-300 border-gray-300'} border rounded-lg`} {...props} />
797
- </div>
798
- ),
799
- thead: ({ node, ...props }) => (
800
- <thead className={darkMode ? 'bg-gray-800' : 'bg-gray-100'} {...props} />
801
- ),
802
- tbody: ({ node, ...props }) => (
803
- <tbody className={`divide-y ${darkMode ? 'divide-gray-700 bg-gray-900' : 'divide-gray-200 bg-white'}`} {...props} />
804
- ),
805
- th: ({ node, ...props }) => (
806
- <th className={`px-4 py-3 text-left text-xs font-bold uppercase tracking-wider ${darkMode ? 'text-gray-300 border-gray-700' : 'text-gray-700 border-gray-300'} border-r last:border-r-0`} {...props} />
807
- ),
808
- td: ({ node, ...props }) => (
809
- <td className={`px-4 py-3 text-sm ${darkMode ? 'text-gray-300 border-gray-700' : 'text-gray-900 border-gray-200'} border-r last:border-r-0`} {...props} />
810
- ),
811
- tr: ({ node, ...props }) => (
812
- <tr className={darkMode ? 'hover:bg-gray-800' : 'hover:bg-gray-50'} {...props} />
813
- ),
814
- }}
815
- >
816
- {msg.content}
817
- </ReactMarkdown>
818
- </div>
819
- )}
820
- {msg.streaming && msg.role === 'assistant' && (
821
- <div className={`flex items-center space-x-1 ${theme.textMuted} text-sm mt-2`}>
822
- <span>Thinking</span>
823
- <span className="animate-pulse">...</span>
824
- </div>
825
- )}
826
- </div>
827
- </div>
828
- ))}
829
- <div ref={messagesEndRef} />
830
- </div>
831
- </div>
832
- )}
833
-
834
- {/* Bottom Input Bar */}
835
- <div className={`${theme.border} border-t ${theme.bgTertiary} px-4 py-4`}>
836
- <div className="max-w-3xl mx-auto">
837
- <div className="flex items-end space-x-3">
838
- <button
839
- onClick={() => setShowUploadModal(true)}
840
- className={`px-4 py-3 ${darkMode ? 'bg-blue-600 hover:bg-blue-700' : 'bg-blue-600 hover:bg-blue-700'} text-white rounded-xl transition-colors flex items-center space-x-2`}
841
- >
842
- <Upload className="w-4 h-4" />
843
- <span className="text-sm font-medium">Upload</span>
844
- </button>
845
-
846
- <textarea
847
- value={query}
848
- onChange={(e) => setQuery(e.target.value)}
849
- onKeyDown={handleKeyPress}
850
- placeholder="Message TheTruthSchool..."
851
- className={`flex-1 px-4 py-3 ${theme.input} rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none`}
852
- disabled={isQuerying}
853
- rows={1}
854
- style={{ minHeight: '48px', maxHeight: '200px' }}
855
- />
856
-
857
- <button
858
- onClick={handleQuery}
859
- disabled={isQuerying || !query.trim()}
860
- className={`p-3 ${darkMode ? 'bg-blue-600 hover:bg-blue-700' : 'bg-blue-600 hover:bg-blue-700'} text-white rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed`}
861
- >
862
- <Send className="w-5 h-5" />
863
- </button>
864
- </div>
865
-
866
- <div className="flex items-center justify-center space-x-6 mt-3">
867
- <label className="flex items-center space-x-2 cursor-pointer">
868
- <input
869
- type="checkbox"
870
- checked={enableWebSearch}
871
- onChange={(e) => {
872
- setEnableWebSearch(e.target.checked);
873
- if (e.target.checked && webSearchOnly) {
874
- setWebSearchOnly(false);
875
- }
876
- }}
877
- className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
878
- />
879
- <span className={`text-sm ${theme.textSecondary}`}>Enhance with Web Search</span>
880
- </label>
881
- <label className="flex items-center space-x-2 cursor-pointer">
882
- <input
883
- type="checkbox"
884
- checked={webSearchOnly}
885
- onChange={(e) => {
886
- setWebSearchOnly(e.target.checked);
887
- if (e.target.checked) {
888
- setEnableWebSearch(false);
889
- }
890
- }}
891
- className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
892
- />
893
- <span className={`text-sm ${theme.textSecondary}`}>Web Search Only</span>
894
- </label>
895
- </div>
896
-
897
- <p className={`text-xs ${theme.textMuted} mt-2 text-center`}>
898
- Press Enter to send • Shift+Enter for new line
899
- </p>
900
- </div>
901
- </div>
902
- </div>
903
- );
904
-
905
- const renderFilesView = () => (
906
- <div className={`flex-1 overflow-y-auto p-6 ${theme.bg}`}>
907
- <div className="max-w-5xl mx-auto">
908
- <div className="flex items-center justify-between mb-6">
909
- <div>
910
- <h2 className={`text-2xl font-bold ${theme.text}`}>Document Management</h2>
911
- <p className={theme.textSecondary}>Manage your uploaded and processed documents</p>
912
- </div>
913
- <div className="flex space-x-3">
914
- <button
915
- onClick={fetchProcessedDocuments}
916
- className={`flex items-center space-x-2 px-4 py-2 ${theme.button} ${theme.text} rounded-lg transition-colors`}
917
- >
918
- <RefreshCw className="w-4 h-4" />
919
- <span>Refresh</span>
920
- </button>
921
- <button
922
- onClick={() => setShowUploadModal(true)}
923
- className="flex items-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
924
- >
925
- <Upload className="w-4 h-4" />
926
- <span>Upload Documents</span>
927
- </button>
928
- </div>
929
- </div>
930
-
931
- {processingDocs.length > 0 && (
932
- <div className="mb-6">
933
- <h3 className={`text-lg font-semibold ${theme.text} mb-3`}>Processing Documents</h3>
934
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
935
- {processingDocs.map((doc, idx) => (
936
- <div key={idx} className={`flex items-center space-x-4 p-4 ${darkMode ? 'bg-yellow-900/20 border-yellow-800' : 'bg-yellow-50 border-yellow-200'} border rounded-lg`}>
937
- <Loader2 className={`w-8 h-8 ${darkMode ? 'text-yellow-400' : 'text-yellow-600'} animate-spin`} />
938
- <div className="flex-1">
939
- <p className={`font-medium ${theme.text}`}>{doc.name}</p>
940
- <p className={`text-sm ${theme.textSecondary}`}>Processing...</p>
941
- </div>
942
- </div>
943
- ))}
944
- </div>
945
- </div>
946
- )}
947
-
948
- <div>
949
- <h3 className={`text-lg font-semibold ${theme.text} mb-3`}>
950
- Processed Documents ({processedDocs.length})
951
- </h3>
952
- {processedDocs.length === 0 ? (
953
- <div className={`text-center py-12 ${theme.bgSecondary} rounded-lg`}>
954
- <FolderOpen className={`w-16 h-16 ${theme.textMuted} mx-auto mb-4`} />
955
- <p className={theme.textSecondary}>No documents processed yet</p>
956
- <button
957
- onClick={() => setShowUploadModal(true)}
958
- className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
959
- >
960
- Upload Your First Document
961
- </button>
962
- </div>
963
- ) : (
964
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
965
- {processedDocs.map((doc, idx) => (
966
- <div key={idx} className={`p-4 ${theme.bgTertiary} ${theme.border} border rounded-lg hover:shadow-lg transition-all group`}>
967
- <div className="flex items-start justify-between mb-3">
968
- <FileText className="w-8 h-8 text-blue-600" />
969
- <button
970
- onClick={() => handleDeleteDocument(doc.id, doc.name)}
971
- className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-gray-700 rounded"
972
- >
973
- <Trash2 className={`w-4 h-4 ${theme.textMuted} hover:text-red-500`} />
974
- </button>
975
- </div>
976
- <p className={`font-medium ${theme.text} mb-1 truncate`} title={doc.name}>{doc.name || `Document ${idx + 1}`}</p>
977
- <p className={`text-sm ${theme.textSecondary} mb-2`}>{DOMAIN_CONFIGS[doc.domain]?.name || selectedDomain}</p>
978
- <div className="flex items-center space-x-2">
979
- <CheckCircle className="w-4 h-4 text-green-500" />
980
- <span className={`text-xs ${theme.textSecondary}`}>Processed</span>
981
- </div>
982
- </div>
983
- ))}
984
- </div>
985
- )}
986
- </div>
987
- </div>
988
- </div>
989
- );
990
-
991
- const renderSettingsView = () => (
992
- <div className={`flex-1 overflow-y-auto p-6 ${theme.bg}`}>
993
- <div className="max-w-3xl mx-auto">
994
- <h2 className={`text-2xl font-bold ${theme.text} mb-6`}>Settings</h2>
995
-
996
- <div className="space-y-6">
997
- <div className={`${theme.bgTertiary} ${theme.border} border rounded-lg p-6`}>
998
- <h3 className={`text-lg font-semibold ${theme.text} mb-4`}>Appearance</h3>
999
- <div className="flex items-center justify-between">
1000
- <div>
1001
- <label className={`block text-sm font-medium ${theme.text}`}>Theme</label>
1002
- <p className={`text-xs ${theme.textSecondary} mt-1`}>Choose your preferred interface theme</p>
1003
- </div>
1004
- <button
1005
- onClick={() => setDarkMode(!darkMode)}
1006
- className={`px-4 py-2 ${theme.button} ${theme.text} rounded-lg transition-colors flex items-center space-x-2`}
1007
- >
1008
- {darkMode ? (
1009
- <>
1010
- <Sun className="w-4 h-4" />
1011
- <span>Light Mode</span>
1012
- </>
1013
- ) : (
1014
- <>
1015
- <Moon className="w-4 h-4" />
1016
- <span>Dark Mode</span>
1017
- </>
1018
- )}
1019
- </button>
1020
- </div>
1021
- </div>
1022
-
1023
- <div className={`${theme.bgTertiary} ${theme.border} border rounded-lg p-6`}>
1024
- <h3 className={`text-lg font-semibold ${theme.text} mb-4`}>Domain Configuration</h3>
1025
- <div className="space-y-3">
1026
- <div>
1027
- <label className={`block text-sm font-medium ${theme.text} mb-2`}>Current Domain</label>
1028
- <select
1029
- value={selectedDomain}
1030
- onChange={(e) => setSelectedDomain(e.target.value)}
1031
- className={`w-full px-4 py-2 ${theme.input} rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500`}
1032
- >
1033
- {Object.entries(DOMAIN_CONFIGS).map(([key, config]) => (
1034
- <option key={key} value={key}>{config.name}</option>
1035
- ))}
1036
- </select>
1037
- </div>
1038
- </div>
1039
- </div>
1040
-
1041
- <div className={`${theme.bgTertiary} ${theme.border} border rounded-lg p-6`}>
1042
- <h3 className={`text-lg font-semibold ${theme.text} mb-4`}>Performance Settings</h3>
1043
- <div className="space-y-4">
1044
- <div className="flex items-start space-x-3">
1045
- <input
1046
- type="checkbox"
1047
- id="fastMode"
1048
- checked={fastMode}
1049
- onChange={(e) => setFastMode(e.target.checked)}
1050
- className="w-5 h-5 text-blue-600 rounded focus:ring-blue-500 mt-0.5"
1051
- />
1052
- <div className="flex-1">
1053
- <label htmlFor="fastMode" className={`block text-sm font-medium ${theme.text} cursor-pointer`}>
1054
- Fast Mode
1055
- </label>
1056
- <p className={`text-xs ${theme.textSecondary} mt-1`}>
1057
- Use optimized parameters for 2-3x faster queries
1058
- </p>
1059
- </div>
1060
- </div>
1061
-
1062
- <div className="flex items-start space-x-3">
1063
- <input
1064
- type="checkbox"
1065
- id="enableCache"
1066
- checked={enableCache}
1067
- onChange={(e) => setEnableCache(e.target.checked)}
1068
- className="w-5 h-5 text-blue-600 rounded focus:ring-blue-500 mt-0.5"
1069
- />
1070
- <div className="flex-1">
1071
- <label htmlFor="enableCache" className={`block text-sm font-medium ${theme.text} cursor-pointer`}>
1072
- Enable Query Caching
1073
- </label>
1074
- <p className={`text-xs ${theme.textSecondary} mt-1`}>
1075
- Cache results for faster repeated queries
1076
- </p>
1077
- </div>
1078
- </div>
1079
-
1080
- <div className="flex items-start space-x-3">
1081
- <input
1082
- type="checkbox"
1083
- id="enableQueryImprovement"
1084
- checked={enableQueryImprovement}
1085
- onChange={(e) => setEnableQueryImprovement(e.target.checked)}
1086
- className="w-5 h-5 text-blue-600 rounded focus:ring-blue-500 mt-0.5"
1087
- />
1088
- <div className="flex-1">
1089
- <label htmlFor="enableQueryImprovement" className={`block text-sm font-medium ${theme.text} cursor-pointer`}>
1090
- Enable Query Improvement
1091
- </label>
1092
- <p className={`text-xs ${theme.textSecondary} mt-1`}>
1093
- Automatically enhance queries for better results
1094
- </p>
1095
- </div>
1096
- </div>
1097
-
1098
- <div className="flex items-start space-x-3">
1099
- <input
1100
- type="checkbox"
1101
- id="enableVerification"
1102
- checked={enableVerification}
1103
- onChange={(e) => setEnableVerification(e.target.checked)}
1104
- className="w-5 h-5 text-blue-600 rounded focus:ring-blue-500 mt-0.5"
1105
- />
1106
- <div className="flex-1">
1107
- <label htmlFor="enableVerification" className={`block text-sm font-medium ${theme.text} cursor-pointer`}>
1108
- Enable Answer Verification
1109
- </label>
1110
- <p className={`text-xs ${theme.textSecondary} mt-1`}>
1111
- Verify answer quality and accuracy with dual-LLM
1112
- </p>
1113
- </div>
1114
- </div>
1115
- </div>
1116
- </div>
1117
- </div>
1118
- </div>
1119
- </div>
1120
- );
1121
-
1122
- const renderUploadModal = () => {
1123
- if (!showUploadModal) return null;
1124
-
1125
- return (
1126
- <div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 p-4 backdrop-blur-sm">
1127
- <div className={`${theme.bgTertiary} rounded-2xl max-w-2xl w-full p-6 shadow-2xl`}>
1128
- <div className="flex items-center justify-between mb-6">
1129
- <h2 className={`text-2xl font-bold ${theme.text}`}>Upload Documents</h2>
1130
- <button
1131
- onClick={() => {
1132
- setShowUploadModal(false);
1133
- setUploadMode('file');
1134
- setUrlInput('');
1135
- }}
1136
- className={`p-2 ${theme.hover} rounded-lg`}
1137
- >
1138
- <X className={`w-5 h-5 ${theme.textSecondary}`} />
1139
- </button>
1140
- </div>
1141
-
1142
- <div className="flex items-center space-x-2 mb-6">
1143
- <button
1144
- onClick={() => setUploadMode('file')}
1145
- className={`flex-1 px-4 py-2 rounded-lg font-medium transition-colors ${
1146
- uploadMode === 'file'
1147
- ? 'bg-blue-600 text-white'
1148
- : `${theme.button} ${theme.text}`
1149
- }`}
1150
- >
1151
- Upload File
1152
- </button>
1153
- <button
1154
- onClick={() => setUploadMode('url')}
1155
- className={`flex-1 px-4 py-2 rounded-lg font-medium transition-colors ${
1156
- uploadMode === 'url'
1157
- ? 'bg-blue-600 text-white'
1158
- : `${theme.button} ${theme.text}`
1159
- }`}
1160
- >
1161
- Upload from URL
1162
- </button>
1163
- </div>
1164
-
1165
- {uploadMode === 'file' ? (
1166
- <div
1167
- onDragOver={handleDragOver}
1168
- onDragLeave={handleDragLeave}
1169
- onDrop={handleDrop}
1170
- className={`border-2 border-dashed rounded-xl p-12 text-center transition-colors ${
1171
- isDragging
1172
- ? 'border-blue-500 bg-blue-500/10'
1173
- : `${theme.borderLight}`
1174
- }`}
1175
- >
1176
- <Upload className={`w-16 h-16 ${theme.textMuted} mx-auto mb-4`} />
1177
- <h3 className={`text-lg font-semibold ${theme.text} mb-2`}>
1178
- Drop files here or click to browse
1179
- </h3>
1180
- <p className={`${theme.textSecondary} mb-4`}>
1181
- Supported: {DOMAIN_CONFIGS[selectedDomain].fileTypes.join(', ')}
1182
- </p>
1183
- <input
1184
- ref={fileInputRef}
1185
- type="file"
1186
- multiple
1187
- accept={DOMAIN_CONFIGS[selectedDomain].fileTypes.join(',')}
1188
- onChange={(e) => handleFileUpload(e.target.files)}
1189
- className="hidden"
1190
- />
1191
- <button
1192
- onClick={() => fileInputRef.current?.click()}
1193
- className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
1194
- >
1195
- Select Files
1196
- </button>
1197
- </div>
1198
- ) : (
1199
- <div className="space-y-4">
1200
- <div>
1201
- <label className={`block text-sm font-medium ${theme.text} mb-2`}>
1202
- Enter URL to fetch and process
1203
- </label>
1204
- <input
1205
- type="url"
1206
- value={urlInput}
1207
- onChange={(e) => setUrlInput(e.target.value)}
1208
- placeholder="https://example.com/document.pdf"
1209
- className={`w-full px-4 py-3 ${theme.input} rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500`}
1210
- onKeyDown={(e) => {
1211
- if (e.key === 'Enter') {
1212
- handleUrlUpload();
1213
- }
1214
- }}
1215
- />
1216
- </div>
1217
- <button
1218
- onClick={handleUrlUpload}
1219
- disabled={!urlInput.trim()}
1220
- className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
1221
- >
1222
- Fetch and Process URL
1223
- </button>
1224
- </div>
1225
- )}
1226
- </div>
1227
- </div>
1228
- );
1229
- };
1230
-
1231
- const renderError = () => {
1232
- if (!error) return null;
1233
-
1234
- return (
1235
- <div className={`fixed bottom-4 right-4 ${darkMode ? 'bg-red-900/90 border-red-800' : 'bg-red-50 border-red-200'} border rounded-lg p-4 max-w-md shadow-2xl backdrop-blur-sm`}>
1236
- <div className="flex items-start space-x-3">
1237
- <XCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
1238
- <div className="flex-1">
1239
- <p className={`text-sm ${darkMode ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
1240
- </div>
1241
- <button
1242
- onClick={() => setError(null)}
1243
- className="text-red-500 hover:text-red-600"
1244
- >
1245
- <X className="w-4 h-4" />
1246
- </button>
1247
- </div>
1248
- </div>
1249
- );
1250
- };
1251
-
1252
- return (
1253
- <div className={`h-screen flex flex-col ${theme.bg}`}>
1254
- {renderNavigation()}
1255
-
1256
- <div className="flex-1 flex overflow-hidden">
1257
- {renderSidebar()}
1258
-
1259
- {currentView === 'app' && renderAppView()}
1260
- {currentView === 'files' && renderFilesView()}
1261
- {currentView === 'settings' && renderSettingsView()}
1262
- </div>
1263
-
1264
- {renderUploadModal()}
1265
- {renderError()}
1266
- </div>
1267
- );
1268
- }