sayanAIAI commited on
Commit
2884a69
·
verified ·
1 Parent(s): 72c1e04

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +349 -165
templates/index.html CHANGED
@@ -1,181 +1,365 @@
1
  <!doctype html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8" />
5
- <title>AI Text Summarizer</title>
 
 
6
  <style>
7
- body {
8
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
- margin: 40px auto;
10
- max-width: 600px;
11
- padding: 0 20px;
12
- background: #f5f5f5;
13
- color: #222;
 
 
14
  }
15
- textarea {
16
- width: 100%;
17
- height: 150px;
18
- padding: 12px;
19
- border-radius: 6px;
20
- border: 1px solid #ccc;
21
- font-size: 1rem;
22
- resize: vertical;
23
- box-sizing: border-box;
24
- }
25
- button {
26
- margin-top: 15px;
27
- padding: 12px 24px;
28
- font-size: 1.1rem;
29
- border-radius: 6px;
30
- border: none;
31
- background-color: #0078d7;
32
- color: white;
33
- cursor: pointer;
34
- transition: background-color 0.3s ease;
35
- }
36
- button:hover {
37
- background-color: #005ea3;
38
- }
39
- .summary-container {
40
- margin-top: 30px;
41
- background: white;
42
- border-radius: 8px;
43
- box-shadow: 0 0 10px rgba(0,0,0,0.1);
44
- padding: 20px;
45
- font-size: 1.1rem;
46
- line-height: 1.5;
47
- white-space: pre-wrap;
48
- position: relative;
49
- }
50
- /* Loader style */
51
- #loader {
52
- display: none;
53
- margin: 20px auto;
54
- border: 5px solid #f3f3f3;
55
- border-top: 5px solid #0078d7;
56
- border-radius: 50%;
57
- width: 40px;
58
- height: 40px;
59
- animation: spin 1s linear infinite;
60
- }
61
- @keyframes spin {
62
- 0% { transform: rotate(0deg);}
63
- 100% { transform: rotate(360deg);}
64
- }
65
- /* Copy button */
66
- #copy-btn {
67
- position: absolute;
68
- top: 10px;
69
- right: 10px;
70
- background: #0078d7;
71
- color: white;
72
- border: none;
73
- padding: 6px 12px;
74
- border-radius: 4px;
75
- cursor: pointer;
76
- font-size: 0.9rem;
77
- transition: background-color 0.3s ease;
78
- }
79
- #copy-btn:hover {
80
- background-color: #005ea3;
81
  }
82
- #copy-btn:active {
83
- background-color: #004077;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
  </style>
86
  </head>
87
  <body>
88
- <h1>AI Text Summarizer</h1>
89
- <form id="summary-form">
90
- <textarea name="text" id="text-input" placeholder="Paste your text here..." required></textarea>
91
- <div id="word-count">Words: 0</div>
92
- <div id="warning" style="color:red; display:none;"></div>
93
- <br />
94
- <button type="submit" id="summarize-btn">Summarize</button>
95
- </form>
96
-
97
- <!-- Loader Spinner -->
98
- <div id="loader"></div>
99
-
100
- <!-- Error Message -->
101
- <div id="error-box" style="display:none; background:#ffe0e0; color:#a00; padding:10px; border-radius:5px; margin-top:10px;"></div>
102
-
103
- <!-- Summary Output -->
104
- <div id="summary" class="summary-container" style="display:none;">
105
- <button id="copy-btn">Copy</button>
106
- <h2>Summary:</h2>
107
- <p id="summary-text"></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </div>
109
 
110
- <script>
111
- const form = document.getElementById('summary-form');
112
- const loader = document.getElementById('loader');
113
- const summaryContainer = document.getElementById('summary');
114
- const summaryText = document.getElementById('summary-text');
115
- const copyBtn = document.getElementById('copy-btn');
116
- const textInput = document.getElementById('text-input');
117
- const wordCount = document.getElementById('word-count');
118
- const warning = document.getElementById('warning');
119
- const errorBox = document.getElementById('error-box');
120
- const summarizeBtn = document.getElementById('summarize-btn');
121
-
122
- // Live word count
123
- textInput.addEventListener('input', () => {
124
- const words = textInput.value.trim().split(/\s+/).filter(Boolean).length;
125
- wordCount.textContent = `Words: ${words}`;
126
- if (words < 10) {
127
- warning.textContent = "Please enter at least 10 words.";
128
- warning.style.display = 'block';
129
- } else {
130
- warning.style.display = 'none';
131
- }
132
- });
133
-
134
- form.addEventListener('submit', async (e) => {
135
- e.preventDefault();
136
- const text = textInput.value.trim();
137
- const words = text.split(/\s+/).filter(Boolean).length;
138
-
139
- // Check minimum words
140
- if (words < 10) {
141
- warning.style.display = 'block';
142
- return;
143
- }
144
-
145
- loader.style.display = 'block';
146
- summaryContainer.style.display = 'none';
147
- errorBox.style.display = 'none';
148
- summarizeBtn.disabled = true;
149
-
150
- try {
151
- const response = await fetch('/summarize', {
152
- method: 'POST',
153
- headers: { 'Content-Type': 'application/json' },
154
- body: JSON.stringify({ text }),
155
- });
156
-
157
- if (!response.ok) {
158
- throw new Error('Failed to summarize text. Please try again.');
159
- }
160
-
161
- const data = await response.json();
162
- summaryText.textContent = data.summary;
163
- summaryContainer.style.display = 'block';
164
- } catch (err) {
165
- errorBox.textContent = err.message;
166
- errorBox.style.display = 'block';
167
- } finally {
168
- loader.style.display = 'none';
169
- summarizeBtn.disabled = false;
170
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  });
172
-
173
- copyBtn.addEventListener('click', () => {
174
- navigator.clipboard.writeText(summaryText.textContent).then(() => {
175
- copyBtn.textContent = 'Copied!';
176
- setTimeout(() => (copyBtn.textContent = 'Copy'), 1500);
177
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  });
179
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  </body>
181
  </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" />
6
+ <title>Summarizer — SummarizeAI</title>
7
+ <meta name="description" content="SummarizeAI — fast, private text summarizer." />
8
  <style>
9
+ :root{
10
+ --bg: #071018;
11
+ --card: rgba(255,255,255,0.02);
12
+ --muted: #9fb2c5;
13
+ --accent: #0ea5ff;
14
+ --glass: rgba(255,255,255,0.03);
15
+ --radius: 12px;
16
+ --maxw: 880px;
17
+ --ease: cubic-bezier(.2,.9,.3,1);
18
  }
19
+ *{box-sizing:border-box}
20
+ html,body{height:100%;margin:0;font-family:Inter,system-ui,-apple-system,"Segoe UI",Roboto,Arial;color:#e6eef6;background:
21
+ radial-gradient(800px 400px at 10% 10%, rgba(14,165,255,0.03), transparent 6%),
22
+ linear-gradient(180deg,#061018 0%,var(--bg) 60%);}
23
+ .page{max-width:var(--maxw);margin:28px auto;padding:22px}
24
+ header{display:flex;align-items:center;justify-content:space-between;gap:12px}
25
+ .brand{display:flex;gap:12px;align-items:center}
26
+ .logo{width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,var(--accent),#6ee7b7);display:flex;align-items:center;justify-content:center;color:#04293a;font-weight:700}
27
+ header h1{font-size:1.05rem;margin:0}
28
+ .sub{color:var(--muted);font-size:0.9rem;margin-top:6px}
29
+
30
+ /* main card */
31
+ .card{margin-top:18px;background:linear-gradient(180deg, rgba(255,255,255,0.015), rgba(255,255,255,0.01));
32
+ border-radius:var(--radius);padding:18px;border:1px solid rgba(255,255,255,0.03);box-shadow:0 18px 40px rgba(2,6,23,0.5)}
33
+ .controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between}
34
+ .left-controls{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
35
+ .select, .btn, .small-btn {
36
+ border-radius:10px;padding:10px 12px;border:1px solid rgba(255,255,255,0.03);background:transparent;color:inherit;font-weight:600;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
+ .btn{background:linear-gradient(90deg,var(--accent), #60a5fa);color:#04293a;cursor:pointer;box-shadow:0 10px 30px rgba(14,165,255,0.08);border:0}
39
+ .btn:disabled{opacity:0.6;cursor:not-allowed}
40
+ .small-btn{font-size:0.9rem;padding:8px 10px;cursor:pointer}
41
+ .input-row{display:flex;gap:12px;margin-top:12px;align-items:flex-start}
42
+ textarea#text-input{flex:1;min-height:220px;resize:vertical;padding:14px;border-radius:10px;border:1px solid rgba(255,255,255,0.03);
43
+ background:transparent;color:inherit;font-size:1rem;line-height:1.5}
44
+ .side-panel{width:260px;min-width:220px}
45
+ .meta{color:var(--muted);font-size:0.9rem;margin-top:8px}
46
+ .word-count{color:var(--muted);font-size:0.9rem;margin-top:8px}
47
+
48
+ /* loader & progress */
49
+ #loader{display:none;margin:16px auto;border-radius:50%;width:36px;height:36px;border:4px solid rgba(255,255,255,0.06);border-top-color:var(--accent);animation:spin 1s linear infinite}
50
+ @keyframes spin{to{transform:rotate(360deg)}}
51
+ .progress{height:8px;background:rgba(255,255,255,0.03);border-radius:999px;overflow:hidden;margin-top:10px}
52
+ .progress > i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--accent), #60a5fa);transition:width .25s ease}
53
+
54
+ /* output */
55
+ .summary-box{margin-top:16px;background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);padding:14px;border-radius:10px;border:1px solid rgba(255,255,255,0.02)}
56
+ .summary-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}
57
+ .summary-text{white-space:pre-wrap;font-size:1rem;color:#e6eef6}
58
+
59
+ /* history */
60
+ .history{margin-top:14px;display:flex;gap:8px;flex-wrap:wrap}
61
+ .pill{background:rgba(255,255,255,0.02);padding:8px 10px;border-radius:999px;border:1px solid rgba(255,255,255,0.02);font-size:0.9rem;color:var(--muted);cursor:pointer}
62
+
63
+ /* warning / error */
64
+ .msg {margin-top:10px;padding:10px;border-radius:8px;font-weight:600}
65
+ .error{background:linear-gradient(180deg, rgba(255,40,60,0.06), rgba(255,40,60,0.02));color:#ffb5b5;border:1px solid rgba(255,40,60,0.06)}
66
+ .hint{color:var(--muted);font-size:0.9rem;margin-top:10px}
67
+
68
+ @media (max-width:860px){
69
+ .input-row{flex-direction:column}
70
+ .side-panel{width:100%}
71
  }
72
  </style>
73
  </head>
74
  <body>
75
+ <div class="page" role="main">
76
+ <header>
77
+ <div class="brand">
78
+ <div class="logo">SA</div>
79
+ <div>
80
+ <h1>SummarizeAI — Summarizer</h1>
81
+ <div class="sub">Fast, private summaries — paste text below or click "Send demo text" from the hub.</div>
82
+ </div>
83
+ </div>
84
+ <div>
85
+ <button id="themeToggle" class="small-btn" aria-pressed="false">Toggle Theme</button>
86
+ </div>
87
+ </header>
88
+
89
+ <div class="card" aria-live="polite">
90
+ <div class="controls">
91
+ <div class="left-controls">
92
+ <select id="length" class="select" aria-label="Summary length">
93
+ <option value="short">Short (1-2 lines)</option>
94
+ <option value="medium" selected>Medium</option>
95
+ <option value="long">Long (detailed)</option>
96
+ </select>
97
+
98
+ <select id="tone" class="select" aria-label="Tone">
99
+ <option value="neutral" selected>Neutral</option>
100
+ <option value="formal">Formal</option>
101
+ <option value="casual">Casual</option>
102
+ <option value="bullet">Bulleted</option>
103
+ </select>
104
+
105
+ <button id="exampleBtn" class="small-btn" title="Insert example">Example</button>
106
+ <button id="clearBtn" class="small-btn" title="Clear input">Clear</button>
107
+ </div>
108
+
109
+ <div style="display:flex;gap:8px;align-items:center">
110
+ <button id="summarize-btn" class="btn">Summarize</button>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="input-row">
115
+ <textarea id="text-input" placeholder="Paste your article, meeting notes, or long text here..." aria-label="Text input"></textarea>
116
+
117
+ <aside class="side-panel" aria-hidden>
118
+ <div class="meta">Quick tips</div>
119
+ <div class="hint">• Aim for 50–1500 words for best results.<br>• Use the "Tone" for style tailoring.</div>
120
+
121
+ <div class="word-count" id="word-count">Words: 0</div>
122
+ <div style="margin-top:10px" id="wordWarning" class="msg" style="display:none"></div>
123
+
124
+ <div class="progress" aria-hidden><i id="prog"></i></div>
125
+
126
+ <div style="margin-top:14px;font-size:0.95rem;color:var(--muted)">Saved history</div>
127
+ <div class="history" id="history"></div>
128
+ </aside>
129
+ </div>
130
+
131
+ <div id="loader" role="status" aria-hidden="true"></div>
132
+ <div id="error-box" class="msg error" style="display:none"></div>
133
+
134
+ <div id="summary-area" style="display:none">
135
+ <div class="summary-box" role="region" aria-label="Summary result">
136
+ <div class="summary-text" id="summary-text"></div>
137
+ <div class="summary-actions">
138
+ <button id="copy-btn" class="small-btn">Copy</button>
139
+ <button id="download-btn" class="small-btn">Download .txt</button>
140
+ <button id="save-btn" class="small-btn">Save</button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
  </div>
146
 
147
+ <script>
148
+ /* ---------------------------
149
+ Config & helpers
150
+ ----------------------------*/
151
+ const API_ENDPOINT = '/summarize'; // change if needed
152
+ const textInput = document.getElementById('text-input');
153
+ const summarizeBtn = document.getElementById('summarize-btn');
154
+ const loader = document.getElementById('loader');
155
+ const prog = document.getElementById('prog');
156
+ const summaryArea = document.getElementById('summary-area');
157
+ const summaryText = document.getElementById('summary-text');
158
+ const copyBtn = document.getElementById('copy-btn');
159
+ const downloadBtn = document.getElementById('download-btn');
160
+ const saveBtn = document.getElementById('save-btn');
161
+ const wordCountEl = document.getElementById('word-count');
162
+ const wordWarning = document.getElementById('wordWarning');
163
+ const errorBox = document.getElementById('error-box');
164
+ const historyEl = document.getElementById('history');
165
+ const lengthSelect = document.getElementById('length');
166
+ const toneSelect = document.getElementById('tone');
167
+
168
+ let currentSummary = '';
169
+ let isDark = true;
170
+
171
+ /* ---------------------------
172
+ Word count & validation
173
+ ----------------------------*/
174
+ function updateWordCount(){
175
+ const words = textInput.value.trim().split(/\s+/).filter(Boolean).length;
176
+ wordCountEl.textContent = `Words: ${words}`;
177
+ if(words < 10){
178
+ wordWarning.textContent = 'Please enter at least 10 words to get a good summary.';
179
+ wordWarning.style.display = 'block';
180
+ } else {
181
+ wordWarning.style.display = 'none';
182
+ }
183
+ }
184
+ textInput.addEventListener('input', updateWordCount);
185
+ updateWordCount();
186
+
187
+ /* ---------------------------
188
+ Small UI utilities
189
+ ----------------------------*/
190
+ function showLoader(on=true){
191
+ loader.style.display = on ? 'block' : 'none';
192
+ prog.style.width = on ? '36%' : '0%';
193
+ }
194
+ function setProgress(p){
195
+ prog.style.width = `${Math.max(0,Math.min(100,p))}%`;
196
+ }
197
+ function showError(msg){
198
+ errorBox.textContent = msg;
199
+ errorBox.style.display = 'block';
200
+ }
201
+ function clearError(){
202
+ errorBox.style.display = 'none';
203
+ }
204
+
205
+ /* ---------------------------
206
+ Fetch summary (POST)
207
+ ----------------------------*/
208
+ async function fetchSummary(text, opts={}){
209
+ const body = {
210
+ text,
211
+ length: opts.length || lengthSelect.value,
212
+ tone: opts.tone || toneSelect.value
213
+ };
214
+ // Start UI
215
+ showError(''); clearError();
216
+ summaryArea.style.display = 'none';
217
+ showLoader(true);
218
+ summarizeBtn.disabled = true;
219
+
220
+ try{
221
+ // small progress simulation
222
+ setProgress(20);
223
+ const res = await fetch(API_ENDPOINT, {
224
+ method: 'POST',
225
+ headers: {'Content-Type':'application/json'},
226
+ body: JSON.stringify(body)
227
  });
228
+ setProgress(65);
229
+ if(!res.ok) throw new Error(`Server error: ${res.status}`);
230
+ const data = await res.json();
231
+ currentSummary = data.summary || data?.result || '';
232
+ summaryText.textContent = currentSummary;
233
+ summaryArea.style.display = 'block';
234
+ setProgress(100);
235
+ }catch(err){
236
+ showError(err.message || 'Failed to summarize. Try again later.');
237
+ }finally{
238
+ showLoader(false);
239
+ summarizeBtn.disabled = false;
240
+ setTimeout(()=> setProgress(0), 300);
241
+ }
242
+ }
243
+
244
+ /* ---------------------------
245
+ UI button handlers
246
+ ----------------------------*/
247
+ document.getElementById('exampleBtn').addEventListener('click', ()=>{
248
+ textInput.value = "OpenAI launched a new model today that focuses on making text summarization far more accurate for long-form content. Engineers and researchers praised the model's efficiency in tests.";
249
+ updateWordCount();
250
+ });
251
+
252
+ document.getElementById('clearBtn').addEventListener('click', ()=>{
253
+ textInput.value = '';
254
+ updateWordCount();
255
+ });
256
+
257
+ summarizeBtn.addEventListener('click', (e)=>{
258
+ e.preventDefault();
259
+ const txt = textInput.value.trim();
260
+ if(!txt || txt.split(/\s+/).filter(Boolean).length < 10){
261
+ wordWarning.style.display = 'block';
262
+ return;
263
+ }
264
+ fetchSummary(txt);
265
+ });
266
+
267
+ copyBtn.addEventListener('click', ()=>{
268
+ if(!currentSummary) return;
269
+ navigator.clipboard.writeText(currentSummary).then(()=>{
270
+ copyBtn.textContent = 'Copied!';
271
+ setTimeout(()=> copyBtn.textContent = 'Copy',1400);
272
+ });
273
+ });
274
+
275
+ downloadBtn.addEventListener('click', ()=>{
276
+ if(!currentSummary) return;
277
+ const blob = new Blob([currentSummary], {type:'text/plain;charset=utf-8'});
278
+ const url = URL.createObjectURL(blob);
279
+ const a = document.createElement('a');
280
+ a.href = url;
281
+ a.download = 'summary.txt';
282
+ document.body.appendChild(a); a.click(); a.remove();
283
+ URL.revokeObjectURL(url);
284
+ });
285
+
286
+ saveBtn.addEventListener('click', ()=>{
287
+ if(!currentSummary) return;
288
+ const item = {text: textInput.value, summary: currentSummary, time: Date.now()};
289
+ const list = JSON.parse(localStorage.getItem('sa_history') || '[]');
290
+ list.unshift(item);
291
+ localStorage.setItem('sa_history', JSON.stringify(list.slice(0,20)));
292
+ renderHistory();
293
+ });
294
+
295
+ /* ---------------------------
296
+ History (localStorage)
297
+ ----------------------------*/
298
+ function renderHistory(){
299
+ const list = JSON.parse(localStorage.getItem('sa_history') || '[]');
300
+ historyEl.innerHTML = '';
301
+ list.forEach((it, idx)=>{
302
+ const pill = document.createElement('button');
303
+ pill.className = 'pill';
304
+ pill.textContent = new Date(it.time).toLocaleString();
305
+ pill.title = (it.summary || '').slice(0,200);
306
+ pill.addEventListener('click', ()=> {
307
+ textInput.value = it.text;
308
+ updateWordCount();
309
+ summaryText.textContent = it.summary;
310
+ summaryArea.style.display = 'block';
311
  });
312
+ historyEl.appendChild(pill);
313
+ });
314
+ }
315
+ renderHistory();
316
+
317
+ /* ---------------------------
318
+ PostMessage prefill (hub -> iframe)
319
+ listens for messages of form {type:'summarizer:prefill', text: '...'}
320
+ ----------------------------*/
321
+ window.addEventListener('message', (ev)=>{
322
+ try{
323
+ const d = ev.data || {};
324
+ if(d && d.type === 'summarizer:prefill' && typeof d.text === 'string'){
325
+ textInput.value = d.text;
326
+ updateWordCount();
327
+ // optional: auto-open or auto-run
328
+ // fetchSummary(d.text);
329
+ }
330
+ }catch(e){ /* ignore */ }
331
+ }, false);
332
+
333
+ /* ---------------------------
334
+ Support ?text= URL param
335
+ ----------------------------*/
336
+ (function prefillFromQuery(){
337
+ try{
338
+ const params = new URLSearchParams(location.search);
339
+ const t = params.get('text');
340
+ if(t){
341
+ textInput.value = t;
342
+ updateWordCount();
343
+ }
344
+ }catch(e){}
345
+ })();
346
+
347
+ /* ---------------------------
348
+ Theme toggle (light fallback)
349
+ ----------------------------*/
350
+ document.getElementById('themeToggle').addEventListener('click', ()=>{
351
+ isDark = !isDark;
352
+ document.documentElement.style.setProperty('--bg', isDark? '#071018':'#f7fafc');
353
+ document.body.style.color = isDark? '#e6eef6':'#0b1220';
354
+ document.querySelectorAll('.card, .summary-box').forEach(el=> el.style.background = isDark? 'linear-gradient(180deg, rgba(255,255,255,0.015), rgba(255,255,255,0.01))' : 'linear-gradient(180deg, rgba(2,6,23,0.02), rgba(2,6,23,0.01))');
355
+ document.getElementById('themeToggle').setAttribute('aria-pressed', String(isDark));
356
+ });
357
+
358
+ /* ---------------------------
359
+ Small accessibility / keyboard
360
+ ----------------------------*/
361
+ textInput.addEventListener('keydown', (e)=> { if(e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); summarizeBtn.click(); } });
362
+
363
+ </script>
364
  </body>
365
  </html>