Amodit commited on
Commit
a09e579
·
1 Parent(s): a010e6c

Migrate to Full Stack Web App

Browse files
Files changed (4) hide show
  1. main.py +11 -18
  2. static/app.js +257 -0
  3. static/index.html +186 -0
  4. static/styles.css +468 -0
main.py CHANGED
@@ -9,6 +9,7 @@ import datetime
9
  from typing import Optional, List, Dict, Any
10
  from fastapi import FastAPI, UploadFile, File, HTTPException, Form, BackgroundTasks, Depends
11
  from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
 
12
  from fastapi.middleware.cors import CORSMiddleware
13
  from pydantic import BaseModel, Field, validator
14
  import io
@@ -65,6 +66,9 @@ app.add_middleware(
65
  allow_headers=["*"],
66
  )
67
 
 
 
 
68
  # =============================================================================
69
  # PYDANTIC MODELS FOR REQUEST/RESPONSE VALIDATION
70
  # =============================================================================
@@ -656,28 +660,17 @@ async def get_contract_videos(contract_id: str):
656
 
657
  @app.get("/", tags=["System"])
658
  async def root():
659
- """API root endpoint with comprehensive information"""
 
 
 
 
 
660
  return {
661
  "message": "Jan-Contract Enhanced API",
662
  "version": "2.1.0",
663
  "description": "Comprehensive API for India's informal workforce",
664
- "features": [
665
- "Contract Generation",
666
- "Scheme Discovery",
667
- "Document Analysis",
668
- "AI Assistant",
669
- "Media Processing"
670
- ],
671
- "endpoints": {
672
- "health": "/health",
673
- "contracts": "/api/v1/contracts/generate",
674
- "schemes": "/api/v1/schemes/find",
675
- "demystify": "/api/v1/demystify/upload",
676
- "assistant": "/api/v1/assistant/chat",
677
- "media": "/api/v1/media/upload-video"
678
- },
679
- "docs": "/docs",
680
- "redoc": "/redoc"
681
  }
682
 
683
  # =============================================================================
 
9
  from typing import Optional, List, Dict, Any
10
  from fastapi import FastAPI, UploadFile, File, HTTPException, Form, BackgroundTasks, Depends
11
  from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
12
+ from fastapi.staticfiles import StaticFiles
13
  from fastapi.middleware.cors import CORSMiddleware
14
  from pydantic import BaseModel, Field, validator
15
  import io
 
66
  allow_headers=["*"],
67
  )
68
 
69
+ # Mount Static Files
70
+ app.mount("/static", StaticFiles(directory="static"), name="static")
71
+
72
  # =============================================================================
73
  # PYDANTIC MODELS FOR REQUEST/RESPONSE VALIDATION
74
  # =============================================================================
 
660
 
661
  @app.get("/", tags=["System"])
662
  async def root():
663
+ """Serve the Single Page Application"""
664
+ return FileResponse("static/index.html")
665
+
666
+ @app.get("/api/info", tags=["System"])
667
+ async def api_info():
668
+ """API information endpoint"""
669
  return {
670
  "message": "Jan-Contract Enhanced API",
671
  "version": "2.1.0",
672
  "description": "Comprehensive API for India's informal workforce",
673
+ "docs": "/docs"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
  }
675
 
676
  # =============================================================================
static/app.js ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ // Navigation Logic
3
+ const navLinks = document.querySelectorAll('.nav-links li');
4
+ const sections = document.querySelectorAll('main section');
5
+
6
+ navLinks.forEach(link => {
7
+ link.addEventListener('click', () => {
8
+ // Remove active class from all
9
+ navLinks.forEach(l => l.classList.remove('active'));
10
+ sections.forEach(s => {
11
+ s.classList.add('hidden-section');
12
+ s.classList.remove('active-section');
13
+ });
14
+
15
+ // Add active to clicked
16
+ link.classList.add('active');
17
+ const targetId = link.getAttribute('data-tab');
18
+ const targetSection = document.getElementById(targetId);
19
+ targetSection.classList.remove('hidden-section');
20
+ setTimeout(() => targetSection.classList.add('active-section'), 10);
21
+ });
22
+ });
23
+
24
+ // --- CONTRACT GENERATOR ---
25
+ const btnGenerate = document.getElementById('btn-generate-contract');
26
+ const contractInput = document.getElementById('contract-input');
27
+ const contractResult = document.getElementById('contract-result');
28
+ const contractText = document.getElementById('contract-text');
29
+ const contractTrivia = document.getElementById('contract-trivia');
30
+ const triviaContent = document.getElementById('trivia-content');
31
+
32
+ btnGenerate.addEventListener('click', async () => {
33
+ const text = contractInput.value.trim();
34
+ if (!text) return showToast('Please describe the agreement first.', 'error');
35
+
36
+ setLoading(btnGenerate, true);
37
+ try {
38
+ const resp = await fetch('/api/v1/contracts/generate', {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify({ user_request: text })
42
+ });
43
+ const data = await resp.json();
44
+
45
+ if (data.success) {
46
+ contractResult.classList.remove('hidden');
47
+ contractText.innerHTML = marked.parse(data.data.contract);
48
+
49
+ // Render Trivia
50
+ if (data.data.legal_trivia && data.data.legal_trivia.trivia) {
51
+ contractTrivia.classList.remove('hidden');
52
+ triviaContent.innerHTML = data.data.legal_trivia.trivia.map(t => `
53
+ <div class="trivia-item">
54
+ <h5>${t.point}</h5>
55
+ <p>${t.explanation}</p>
56
+ <a href="${t.source_url}" target="_blank">Source <i class="fa-solid fa-arrow-up-right-from-square"></i></a>
57
+ </div>
58
+ `).join('');
59
+ } else {
60
+ contractTrivia.classList.add('hidden');
61
+ }
62
+ showToast('Contract generated successfully!');
63
+ } else {
64
+ showToast(data.message || 'Error generating contract', 'error');
65
+ }
66
+ } catch (e) {
67
+ showToast('Connection failed', 'error');
68
+ } finally {
69
+ setLoading(btnGenerate, false);
70
+ }
71
+ });
72
+
73
+ // --- SCHEME FINDER ---
74
+ const btnFindSchemes = document.getElementById('btn-find-schemes');
75
+ const schemeInput = document.getElementById('scheme-input');
76
+ const schemesList = document.getElementById('schemes-list');
77
+ const schemesLoader = document.getElementById('schemes-loader');
78
+
79
+ btnFindSchemes.addEventListener('click', async () => {
80
+ const profile = schemeInput.value.trim();
81
+ if (!profile) return showToast('Please enter a profile.', 'error');
82
+
83
+ schemesList.innerHTML = '';
84
+ schemesLoader.classList.remove('hidden');
85
+
86
+ try {
87
+ const resp = await fetch('/api/v1/schemes/find', {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: JSON.stringify({ user_profile: profile })
91
+ });
92
+ const data = await resp.json();
93
+
94
+ if (data.success && data.data.schemes) {
95
+ schemesList.innerHTML = data.data.schemes.map(s => `
96
+ <div class="scheme-card">
97
+ <h4>${s.scheme_name}</h4>
98
+ <p>${s.description}</p>
99
+ <br>
100
+ <small><strong>Target:</strong> ${s.target_audience}</small>
101
+ <br>
102
+ <a href="${s.official_link}" target="_blank">Visit Official Site &rarr;</a>
103
+ </div>
104
+ `).join('');
105
+ } else {
106
+ schemesList.innerHTML = '<p class="text-dim">No schemes found. Try a different description.</p>';
107
+ }
108
+ } catch (e) {
109
+ showToast('Search failed', 'error');
110
+ } finally {
111
+ schemesLoader.classList.add('hidden');
112
+ }
113
+ });
114
+
115
+ // --- DEMYSTIFIER ---
116
+ const dropZone = document.getElementById('drop-zone');
117
+ const fileInput = document.getElementById('pdf-upload');
118
+ const analysisView = document.getElementById('analysis-view');
119
+ let currentSessionId = null;
120
+
121
+ dropZone.addEventListener('click', () => fileInput.click());
122
+ fileInput.addEventListener('change', handleFileUpload);
123
+
124
+ // Drag and Drop
125
+ dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.style.borderColor = 'var(--primary)'; });
126
+ dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.style.borderColor = 'var(--border)'; });
127
+ dropZone.addEventListener('drop', (e) => {
128
+ e.preventDefault();
129
+ dropZone.style.borderColor = 'var(--border)';
130
+ if (e.dataTransfer.files.length) {
131
+ handleFileUpload({ target: { files: e.dataTransfer.files } });
132
+ }
133
+ });
134
+
135
+ async function handleFileUpload(e) {
136
+ const file = e.target.files[0];
137
+ if (!file || file.type !== 'application/pdf') return showToast('Please upload a PDF', 'error');
138
+
139
+ // Show simplified loading
140
+ dropZone.innerHTML = '<div class="loader"></div><p>Analyzing Document...</p>';
141
+
142
+ const formData = new FormData();
143
+ formData.append('file', file);
144
+
145
+ try {
146
+ const resp = await fetch('/api/v1/demystify/upload', {
147
+ method: 'POST',
148
+ body: formData
149
+ });
150
+ const data = await resp.json();
151
+
152
+ if (data.success) {
153
+ currentSessionId = data.data.session_id;
154
+ dropZone.classList.add('hidden');
155
+ analysisView.classList.remove('hidden');
156
+
157
+ document.getElementById('doc-summary').innerHTML = `<p>${data.data.report.summary}</p>`;
158
+ document.getElementById('doc-terms').innerHTML = data.data.report.key_terms.map(t => `
159
+ <div class="term-item" style="margin-bottom:1rem; padding-bottom:1rem; border-bottom:1px solid rgba(255,255,255,0.05)">
160
+ <strong style="color:var(--secondary)">${t.term}:</strong> ${t.explanation}
161
+ </div>
162
+ `).join('');
163
+ } else {
164
+ showToast('Analysis failed', 'error');
165
+ resetUpload();
166
+ }
167
+ } catch (e) {
168
+ showToast('Upload error', 'error');
169
+ resetUpload();
170
+ }
171
+ }
172
+
173
+ // Doc Chat
174
+ const btnDocChat = document.getElementById('btn-doc-chat');
175
+ const docChatInput = document.getElementById('doc-chat-input');
176
+ const docChatMessages = document.getElementById('doc-chat-messages');
177
+
178
+ btnDocChat.addEventListener('click', () => sendChat(docChatInput, docChatMessages, '/api/v1/demystify/chat', { session_id: currentSessionId }));
179
+
180
+ // --- GENERAL ASSISTANT ---
181
+ const btnGeneralChat = document.getElementById('btn-general-chat');
182
+ const generalChatInput = document.getElementById('general-chat-input');
183
+ const generalChatMessages = document.getElementById('general-chat-messages');
184
+
185
+ btnGeneralChat.addEventListener('click', () => sendChat(generalChatInput, generalChatMessages, '/api/v1/assistant/chat', {}));
186
+
187
+ // Chat Helper
188
+ async function sendChat(inputEl, containerEl, endpoint, extraPayload) {
189
+ const question = inputEl.value.trim();
190
+ if (!question) return;
191
+
192
+ // Add User Message
193
+ addMessage(containerEl, question, 'user');
194
+ inputEl.value = '';
195
+
196
+ // Add Loading Bubble
197
+ const loadingId = addMessage(containerEl, '<i class="fa-solid fa-ellipsis fa-fade"></i>', 'ai');
198
+
199
+ try {
200
+ const resp = await fetch(endpoint, {
201
+ method: 'POST',
202
+ headers: { 'Content-Type': 'application/json' },
203
+ body: JSON.stringify({ question, ...extraPayload })
204
+ });
205
+ const data = await resp.json();
206
+
207
+ // Remove Loader and Add Response
208
+ document.getElementById(loadingId).remove();
209
+
210
+ const answer = data.data.answer || data.data.response || "I couldn't understand that.";
211
+ addMessage(containerEl, marked.parse(answer), 'ai');
212
+
213
+ } catch (e) {
214
+ document.getElementById(loadingId).remove();
215
+ addMessage(containerEl, "Sorry, something went wrong.", 'ai');
216
+ }
217
+ }
218
+
219
+ function addMessage(container, html, type) {
220
+ const id = 'msg-' + Date.now();
221
+ const div = document.createElement('div');
222
+ div.className = `message ${type}`;
223
+ div.id = id;
224
+ div.innerHTML = `
225
+ <div class="avatar"><i class="fa-solid ${type === 'ai' ? 'fa-robot' : 'fa-user'}"></i></div>
226
+ <div class="bubble">${html}</div>
227
+ `;
228
+ container.appendChild(div);
229
+ container.scrollTop = container.scrollHeight;
230
+ return id;
231
+ }
232
+
233
+ // Utilities
234
+ function showToast(msg, type = 'success') {
235
+ const toast = document.getElementById('toast');
236
+ toast.textContent = msg;
237
+ toast.style.background = type === 'error' ? '#ef4444' : '#10b981';
238
+ toast.classList.remove('hidden');
239
+ setTimeout(() => toast.classList.add('hidden'), 3000);
240
+ }
241
+
242
+ function setLoading(btn, isLoading) {
243
+ if (isLoading) {
244
+ btn.dataset.original = btn.innerHTML;
245
+ btn.innerHTML = '<div class="loader" style="width:20px; height:20px; margin:0; border-width:2px;"></div>';
246
+ btn.disabled = true;
247
+ } else {
248
+ btn.innerHTML = btn.dataset.original;
249
+ btn.disabled = false;
250
+ }
251
+ }
252
+
253
+ function resetUpload() {
254
+ dropZone.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i><h3>Drag & Drop PDF here</h3><p>or click to browse</p><input type="file" id="pdf-upload" accept="application/pdf" hidden>';
255
+ dropZone.classList.remove('hidden');
256
+ }
257
+ });
static/index.html ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Jan-Contract | AI Legal Assistant</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <link rel="stylesheet" href="styles.css">
12
+ </head>
13
+ <body>
14
+ <div class="app-container">
15
+ <!-- Sidebar Navigation -->
16
+ <nav class="sidebar">
17
+ <div class="logo">
18
+ <i class="fa-solid fa-scale-balanced"></i>
19
+ <span>Jan-Contract</span>
20
+ </div>
21
+
22
+ <ul class="nav-links">
23
+ <li class="active" data-tab="contract">
24
+ <i class="fa-solid fa-file-signature"></i>
25
+ <span>Contract Generator</span>
26
+ </li>
27
+ <li data-tab="schemes">
28
+ <i class="fa-solid fa-hand-holding-dollar"></i>
29
+ <span>Scheme Finder</span>
30
+ </li>
31
+ <li data-tab="demystifier">
32
+ <i class="fa-solid fa-magnifying-glass-chart"></i>
33
+ <span>Document Analyzer</span>
34
+ </li>
35
+ <li data-tab="assistant">
36
+ <i class="fa-solid fa-robot"></i>
37
+ <span>AI Assistant</span>
38
+ </li>
39
+ </ul>
40
+
41
+ <div class="footer">
42
+ <p>Powered by Gemini AI</p>
43
+ <div class="status-indicator">
44
+ <div class="dot"></div> System Online
45
+ </div>
46
+ </div>
47
+ </nav>
48
+
49
+ <!-- Main Content Area -->
50
+ <main class="main-content">
51
+
52
+ <!-- Contract Generator Section -->
53
+ <section id="contract" class="active-section">
54
+ <div class="header-hero">
55
+ <h1>Create Digital Agreements</h1>
56
+ <p>Draft professional contracts instantly with AI. Just describe what you need.</p>
57
+ </div>
58
+
59
+ <div class="split-view">
60
+ <div class="input-card glass-panel">
61
+ <h3><i class="fa-regular fa-comment-dots"></i> Describe Agreement</h3>
62
+ <textarea id="contract-input" placeholder="E.g., I, Rajesh, agree to hire Suresh as a driver for 6 months at Rs. 15,000 per month..."></textarea>
63
+ <button id="btn-generate-contract" class="primary-btn">
64
+ <i class="fa-solid fa-wand-magic-sparkles"></i> Generate Contract
65
+ </button>
66
+ </div>
67
+
68
+ <div class="output-card glass-panel hidden" id="contract-result">
69
+ <div class="card-header">
70
+ <h3><i class="fa-solid fa-file-contract"></i> Drafted Contract</h3>
71
+ <button id="btn-download-pdf" class="icon-btn" title="Download PDF"><i class="fa-solid fa-download"></i></button>
72
+ </div>
73
+ <div class="markdown-preview" id="contract-text"></div>
74
+
75
+ <div class="trivia-box hidden" id="contract-trivia">
76
+ <h4><i class="fa-solid fa-lightbulb"></i> Legal Insights</h4>
77
+ <div id="trivia-content"></div>
78
+ </div>
79
+
80
+ <!-- Video Consent Placeholder -->
81
+ <div class="video-consent-area">
82
+ <h4><i class="fa-solid fa-video"></i> Video Consent</h4>
83
+ <p>Record a video stating your name and agreement to the terms.</p>
84
+ <button class="secondary-btn"><i class="fa-solid fa-camera"></i> Record Video (Coming Soon)</button>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </section>
89
+
90
+ <!-- Scheme Finder Section -->
91
+ <section id="schemes" class="hidden-section">
92
+ <div class="header-hero">
93
+ <h1>Government Schemes</h1>
94
+ <p>Find financial aid and benefits tailored to your profile.</p>
95
+ </div>
96
+
97
+ <div class="center-view">
98
+ <div class="search-bar glass-panel">
99
+ <input type="text" id="scheme-input" placeholder="Describe your profile (e.g., A 40-year-old female farmer in Punjab with 2 acres of land)">
100
+ <button id="btn-find-schemes" class="search-btn"><i class="fa-solid fa-search"></i></button>
101
+ </div>
102
+
103
+ <div id="schemes-loader" class="loader hidden"></div>
104
+
105
+ <div id="schemes-list" class="cards-grid">
106
+ <!-- Schemes will be injected here -->
107
+ </div>
108
+ </div>
109
+ </section>
110
+
111
+ <!-- Demystifier Section -->
112
+ <section id="demystifier" class="hidden-section">
113
+ <div class="header-hero">
114
+ <h1>Document Demystifier</h1>
115
+ <p>Upload confusing legal documents and let AI explain them simply.</p>
116
+ </div>
117
+
118
+ <div class="upload-area glass-panel" id="drop-zone">
119
+ <i class="fa-solid fa-cloud-arrow-up"></i>
120
+ <h3>Drag & Drop PDF here</h3>
121
+ <p>or click to browse</p>
122
+ <input type="file" id="pdf-upload" accept="application/pdf" hidden>
123
+ </div>
124
+
125
+ <div id="analysis-view" class="hidden">
126
+ <div class="analysis-grid">
127
+ <div class="summary-card glass-panel">
128
+ <h3><i class="fa-solid fa-list-check"></i> Summary</h3>
129
+ <div id="doc-summary"></div>
130
+ </div>
131
+
132
+ <div class="terms-card glass-panel">
133
+ <h3><i class="fa-solid fa-book-open"></i> Key Terms</h3>
134
+ <div id="doc-terms"></div>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="chat-card glass-panel">
139
+ <div class="chat-header">
140
+ <h3><i class="fa-regular fa-comments"></i> Ask Questions</h3>
141
+ </div>
142
+ <div class="chat-messages" id="doc-chat-messages">
143
+ <div class="message ai">
144
+ <div class="avatar"><i class="fa-solid fa-robot"></i></div>
145
+ <div class="bubble">I've analyzed your document. What would you like to know?</div>
146
+ </div>
147
+ </div>
148
+ <div class="chat-input-area">
149
+ <input type="text" id="doc-chat-input" placeholder="Ask a question about this file...">
150
+ <button id="btn-doc-chat" class="send-btn"><i class="fa-solid fa-paper-plane"></i></button>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </section>
155
+
156
+ <!-- AI Assistant Section -->
157
+ <section id="assistant" class="hidden-section">
158
+ <div class="header-hero">
159
+ <h1>General Legal Assistant</h1>
160
+ <p>Ask anything about Indian law, rights, or procedures.</p>
161
+ </div>
162
+
163
+ <div class="chat-container glass-panel">
164
+ <div class="chat-messages" id="general-chat-messages">
165
+ <div class="message ai">
166
+ <div class="avatar"><i class="fa-solid fa-robot"></i></div>
167
+ <div class="bubble">Namaste! I am your AI legal assistant. How can I help you today?</div>
168
+ </div>
169
+ </div>
170
+ <div class="chat-input-area">
171
+ <input type="text" id="general-chat-input" placeholder="Type your question here...">
172
+ <button id="btn-general-chat" class="send-btn"><i class="fa-solid fa-paper-plane"></i></button>
173
+ </div>
174
+ </div>
175
+ </section>
176
+
177
+ </main>
178
+ </div>
179
+
180
+ <!-- Toast Notification -->
181
+ <div id="toast" class="toast hidden">Request Successful</div>
182
+
183
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
184
+ <script src="app.js"></script>
185
+ </body>
186
+ </html>
static/styles.css ADDED
@@ -0,0 +1,468 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary: #4f46e5;
3
+ --primary-dark: #4338ca;
4
+ --secondary: #ec4899;
5
+ --bg-dark: #0f172a;
6
+ --bg-panel: rgba(30, 41, 59, 0.7);
7
+ --text-light: #f8fafc;
8
+ --text-dim: #94a3b8;
9
+ --border: rgba(255, 255, 255, 0.1);
10
+ --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
11
+ --gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ }
13
+
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: 'Outfit', sans-serif;
22
+ background-color: var(--bg-dark);
23
+ color: var(--text-light);
24
+ height: 100vh;
25
+ overflow: hidden;
26
+ background-image:
27
+ radial-gradient(circle at 10% 20%, rgba(79, 70, 229, 0.15) 0%, transparent 20%),
28
+ radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 20%);
29
+ }
30
+
31
+ /* Layout */
32
+ .app-container {
33
+ display: flex;
34
+ height: 100%;
35
+ }
36
+
37
+ /* Sidebar */
38
+ .sidebar {
39
+ width: 260px;
40
+ background: var(--bg-panel);
41
+ backdrop-filter: blur(12px);
42
+ border-right: 1px solid var(--border);
43
+ display: flex;
44
+ flex-direction: column;
45
+ padding: 1.5rem;
46
+ }
47
+
48
+ .logo {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 12px;
52
+ font-size: 1.5rem;
53
+ font-weight: 700;
54
+ margin-bottom: 3rem;
55
+ color: var(--text-light);
56
+ }
57
+
58
+ .logo i {
59
+ color: var(--primary);
60
+ font-size: 1.8rem;
61
+ }
62
+
63
+ .nav-links {
64
+ list-style: none;
65
+ flex: 1;
66
+ }
67
+
68
+ .nav-links li {
69
+ padding: 1rem;
70
+ margin-bottom: 0.5rem;
71
+ border-radius: 12px;
72
+ cursor: pointer;
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 12px;
76
+ color: var(--text-dim);
77
+ transition: all 0.3s ease;
78
+ }
79
+
80
+ .nav-links li:hover {
81
+ background: rgba(255, 255, 255, 0.05);
82
+ color: var(--text-light);
83
+ transform: translateX(4px);
84
+ }
85
+
86
+ .nav-links li.active {
87
+ background: var(--gradient);
88
+ color: white;
89
+ box-shadow: 0 4px 15px rgba(118, 75, 162, 0.4);
90
+ }
91
+
92
+ .footer .status-indicator {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 8px;
96
+ font-size: 0.8rem;
97
+ color: var(--text-dim);
98
+ margin-top: 5px;
99
+ }
100
+
101
+ .dot {
102
+ width: 8px;
103
+ height: 8px;
104
+ background-color: #10b981;
105
+ border-radius: 50%;
106
+ box-shadow: 0 0 8px #10b981;
107
+ }
108
+
109
+ /* Main Content */
110
+ .main-content {
111
+ flex: 1;
112
+ overflow-y: auto;
113
+ padding: 2rem 3rem;
114
+ position: relative;
115
+ }
116
+
117
+ section {
118
+ max-width: 1200px;
119
+ margin: 0 auto;
120
+ opacity: 1;
121
+ transition: opacity 0.4s ease;
122
+ }
123
+
124
+ .hidden-section {
125
+ display: none;
126
+ opacity: 0;
127
+ }
128
+
129
+ .header-hero {
130
+ margin-bottom: 2.5rem;
131
+ }
132
+
133
+ .header-hero h1 {
134
+ font-size: 2.5rem;
135
+ background: var(--gradient);
136
+ -webkit-background-clip: text;
137
+ -webkit-text-fill-color: transparent;
138
+ margin-bottom: 0.5rem;
139
+ }
140
+
141
+ .header-hero p {
142
+ color: var(--text-dim);
143
+ font-size: 1.1rem;
144
+ }
145
+
146
+ /* Glass Panels */
147
+ .glass-panel {
148
+ background: var(--bg-panel);
149
+ border: 1px solid var(--border);
150
+ border-radius: 20px;
151
+ padding: 2rem;
152
+ backdrop-filter: blur(10px);
153
+ box-shadow: var(--glass-shadow);
154
+ }
155
+
156
+ /* Contract Section */
157
+ .split-view {
158
+ display: grid;
159
+ grid-template-columns: 1fr 1fr;
160
+ gap: 2rem;
161
+ height: calc(100vh - 200px);
162
+ }
163
+
164
+ .input-card {
165
+ display: flex;
166
+ flex-direction: column;
167
+ }
168
+
169
+ .input-card h3, .output-card h3 {
170
+ margin-bottom: 1rem;
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 10px;
174
+ }
175
+
176
+ textarea {
177
+ flex: 1;
178
+ background: rgba(0, 0, 0, 0.2);
179
+ border: 1px solid var(--border);
180
+ border-radius: 12px;
181
+ padding: 1rem;
182
+ color: var(--text-light);
183
+ font-family: 'Outfit', sans-serif;
184
+ font-size: 1rem;
185
+ resize: none;
186
+ margin-bottom: 1.5rem;
187
+ outline: none;
188
+ transition: border-color 0.3s;
189
+ }
190
+
191
+ textarea:focus {
192
+ border-color: var(--primary);
193
+ }
194
+
195
+ .primary-btn {
196
+ background: var(--gradient);
197
+ border: none;
198
+ padding: 1rem;
199
+ border-radius: 12px;
200
+ color: white;
201
+ font-weight: 600;
202
+ cursor: pointer;
203
+ font-size: 1rem;
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ gap: 10px;
208
+ transition: transform 0.2s, box-shadow 0.2s;
209
+ }
210
+
211
+ .primary-btn:hover {
212
+ transform: translateY(-2px);
213
+ box-shadow: 0 6px 20px rgba(118, 75, 162, 0.4);
214
+ }
215
+
216
+ .hidden {
217
+ display: none !important;
218
+ }
219
+
220
+ .markdown-preview {
221
+ background: rgba(0, 0, 0, 0.2);
222
+ border-radius: 12px;
223
+ padding: 1rem;
224
+ height: 400px;
225
+ overflow-y: auto;
226
+ margin-bottom: 1rem;
227
+ font-size: 0.95rem;
228
+ line-height: 1.6;
229
+ }
230
+
231
+ .card-header {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ align-items: center;
235
+ margin-bottom: 1rem;
236
+ }
237
+
238
+ .icon-btn {
239
+ background: rgba(255, 255, 255, 0.1);
240
+ border: none;
241
+ width: 36px;
242
+ height: 36px;
243
+ border-radius: 50%;
244
+ color: var(--text-light);
245
+ cursor: pointer;
246
+ transition: background 0.2s;
247
+ }
248
+
249
+ .icon-btn:hover {
250
+ background: var(--primary);
251
+ }
252
+
253
+ /* Schemes Section */
254
+ .center-view {
255
+ max-width: 800px;
256
+ margin: 0 auto;
257
+ }
258
+
259
+ .search-bar {
260
+ display: flex;
261
+ gap: 1rem;
262
+ padding: 1rem;
263
+ margin-bottom: 2rem;
264
+ }
265
+
266
+ .search-bar input {
267
+ flex: 1;
268
+ background: transparent;
269
+ border: none;
270
+ color: var(--text-light);
271
+ font-size: 1.1rem;
272
+ outline: none;
273
+ }
274
+
275
+ .search-btn {
276
+ background: var(--primary);
277
+ border: none;
278
+ width: 48px;
279
+ height: 48px;
280
+ border-radius: 12px;
281
+ color: white;
282
+ cursor: pointer;
283
+ }
284
+
285
+ .cards-grid {
286
+ display: grid;
287
+ gap: 1.5rem;
288
+ }
289
+
290
+ .scheme-card {
291
+ background: rgba(255, 255, 255, 0.03);
292
+ border: 1px solid var(--border);
293
+ border-radius: 16px;
294
+ padding: 1.5rem;
295
+ transition: all 0.3s;
296
+ }
297
+
298
+ .scheme-card:hover {
299
+ background: rgba(255, 255, 255, 0.06);
300
+ transform: translateY(-3px);
301
+ border-color: var(--primary);
302
+ }
303
+
304
+ .scheme-card h4 {
305
+ color: var(--secondary);
306
+ font-size: 1.2rem;
307
+ margin-bottom: 0.5rem;
308
+ }
309
+
310
+ .scheme-card a {
311
+ display: inline-block;
312
+ margin-top: 1rem;
313
+ color: var(--primary);
314
+ text-decoration: none;
315
+ font-size: 0.9rem;
316
+ font-weight: 600;
317
+ }
318
+
319
+ /* Chat Interface */
320
+ .chat-container {
321
+ height: calc(100vh - 250px);
322
+ display: flex;
323
+ flex-direction: column;
324
+ }
325
+
326
+ .chat-messages {
327
+ flex: 1;
328
+ overflow-y: auto;
329
+ padding: 1rem;
330
+ display: flex;
331
+ flex-direction: column;
332
+ gap: 1.5rem;
333
+ }
334
+
335
+ .message {
336
+ display: flex;
337
+ gap: 1rem;
338
+ max-width: 80%;
339
+ }
340
+
341
+ .message.user {
342
+ align-self: flex-end;
343
+ flex-direction: row-reverse;
344
+ }
345
+
346
+ .avatar {
347
+ width: 36px;
348
+ height: 36px;
349
+ border-radius: 50%;
350
+ background: var(--gradient);
351
+ display: flex;
352
+ align-items: center;
353
+ justify-content: center;
354
+ font-size: 0.9rem;
355
+ }
356
+
357
+ .message.user .avatar {
358
+ background: var(--secondary);
359
+ }
360
+
361
+ .bubble {
362
+ background: rgba(255, 255, 255, 0.05);
363
+ padding: 1rem;
364
+ border-radius: 16px;
365
+ border-top-left-radius: 0;
366
+ line-height: 1.5;
367
+ }
368
+
369
+ .message.user .bubble {
370
+ background: var(--primary);
371
+ border-radius: 16px;
372
+ border-top-right-radius: 0;
373
+ }
374
+
375
+ .chat-input-area {
376
+ margin-top: 1rem;
377
+ display: flex;
378
+ gap: 10px;
379
+ background: rgba(0, 0, 0, 0.3);
380
+ padding: 10px;
381
+ border-radius: 16px;
382
+ }
383
+
384
+ .chat-input-area input {
385
+ flex: 1;
386
+ background: transparent;
387
+ border: none;
388
+ color: white;
389
+ padding: 0.5rem;
390
+ outline: none;
391
+ }
392
+
393
+ .send-btn {
394
+ background: transparent;
395
+ border: none;
396
+ color: var(--primary);
397
+ width: 40px;
398
+ cursor: pointer;
399
+ font-size: 1.2rem;
400
+ }
401
+
402
+ .send-btn:hover {
403
+ color: var(--text-light);
404
+ }
405
+
406
+ /* Drag and Drop */
407
+ .upload-area {
408
+ border: 2px dashed var(--border);
409
+ text-align: center;
410
+ padding: 4rem;
411
+ cursor: pointer;
412
+ transition: all 0.3s;
413
+ }
414
+
415
+ .upload-area:hover {
416
+ border-color: var(--primary);
417
+ background: rgba(79, 70, 229, 0.05);
418
+ }
419
+
420
+ .upload-area i {
421
+ font-size: 3rem;
422
+ color: var(--text-dim);
423
+ margin-bottom: 1rem;
424
+ }
425
+
426
+ /* Loader */
427
+ .loader {
428
+ border: 3px solid rgba(255,255,255,0.1);
429
+ border-top: 3px solid var(--primary);
430
+ border-radius: 50%;
431
+ width: 40px;
432
+ height: 40px;
433
+ animation: spin 1s linear infinite;
434
+ margin: 2rem auto;
435
+ }
436
+
437
+ @keyframes spin {
438
+ 0% { transform: rotate(0deg); }
439
+ 100% { transform: rotate(360deg); }
440
+ }
441
+
442
+ /* Toast */
443
+ .toast {
444
+ position: fixed;
445
+ bottom: 2rem;
446
+ right: 2rem;
447
+ background: #10b981;
448
+ color: white;
449
+ padding: 1rem 2rem;
450
+ border-radius: 12px;
451
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
452
+ animation: slideIn 0.3s ease;
453
+ }
454
+
455
+ @keyframes slideIn {
456
+ from { transform: translateY(100px); opacity: 0; }
457
+ to { transform: translateY(0); opacity: 1; }
458
+ }
459
+
460
+ /* Responsive */
461
+ @media (max-width: 900px) {
462
+ .app-container { flex-direction: column; }
463
+ .sidebar { width: 100%; height: auto; flex-direction: row; padding: 1rem; align-items: center; justify-content: space-between; }
464
+ .nav-links { display: flex; gap: 1rem; margin-bottom: 0; }
465
+ .nav-links li span { display: none; }
466
+ .logo, .footer { margin: 0; }
467
+ .split-view { grid-template-columns: 1fr; height: auto; }
468
+ }