oneword-ai 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
onewordai/web/app.js ADDED
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Frontend JavaScript for OneWord AI
3
+ * Handles file upload, API communication, and UI updates
4
+ */
5
+
6
+ // ========== STATE ==========
7
+ let uploadedFileId = null;
8
+ let currentJobId = null;
9
+ let pollingInterval = null;
10
+
11
+ // ========== DOM ELEMENTS ==========
12
+ const dropzone = document.getElementById('dropzone');
13
+ const fileInput = document.getElementById('fileInput');
14
+ const fileInfo = document.getElementById('fileInfo');
15
+ const fileName = document.getElementById('fileName');
16
+ const fileSize = document.getElementById('fileSize');
17
+ const processBtn = document.getElementById('processBtn');
18
+ const progressCard = document.getElementById('progressCard');
19
+ const downloadCard = document.getElementById('downloadCard');
20
+ const progressFill = document.getElementById('progressFill');
21
+ const progressText = document.getElementById('progressText');
22
+ const statusText = document.getElementById('statusText');
23
+ const downloadBtn = document.getElementById('downloadBtn');
24
+ const resetBtn = document.getElementById('resetBtn');
25
+ const cancelBtn = document.getElementById('cancelBtn');
26
+ const instaPopup = document.getElementById('instaPopup');
27
+ const closePopup = document.getElementById('closePopup');
28
+
29
+ const modelSelect = document.getElementById('modelSelect');
30
+ const languageSelect = document.getElementById('languageSelect');
31
+ const modeSelect = document.getElementById('modeSelect');
32
+
33
+ // Prevent accidental page reload during processing
34
+ let isProcessing = false;
35
+
36
+ window.addEventListener('beforeunload', (e) => {
37
+ if (isProcessing) {
38
+ e.preventDefault();
39
+ e.returnValue = ''; // Required for Chrome
40
+ return ''; // Required for other browsers
41
+ }
42
+ });
43
+
44
+ // ========== FILE UPLOAD ==========
45
+ dropzone.addEventListener('click', () => {
46
+ fileInput.click();
47
+ });
48
+
49
+ dropzone.addEventListener('dragover', (e) => {
50
+ e.preventDefault();
51
+ dropzone.classList.add('dragover');
52
+ });
53
+
54
+ dropzone.addEventListener('dragleave', () => {
55
+ dropzone.classList.remove('dragover');
56
+ });
57
+
58
+ dropzone.addEventListener('drop', (e) => {
59
+ e.preventDefault();
60
+ dropzone.classList.remove('dragover');
61
+
62
+ const files = e.dataTransfer.files;
63
+ if (files.length > 0) {
64
+ handleFile(files[0]);
65
+ }
66
+ });
67
+
68
+ fileInput.addEventListener('change', (e) => {
69
+ if (e.target.files.length > 0) {
70
+ handleFile(e.target.files[0]);
71
+ }
72
+ });
73
+
74
+ async function handleFile(file) {
75
+ // Validate file size (100MB limit)
76
+ const maxSize = 100 * 1024 * 1024;
77
+ if (file.size > maxSize) {
78
+ alert('File too large! Maximum size is 100MB.');
79
+ return;
80
+ }
81
+
82
+ // Show file info
83
+ fileName.textContent = `📁 ${file.name}`;
84
+ fileSize.textContent = `📊 ${formatFileSize(file.size)}`;
85
+ fileInfo.classList.remove('hidden');
86
+
87
+ // Upload file
88
+ statusText.textContent = 'Uploading...';
89
+ progressCard.classList.remove('hidden');
90
+
91
+ const formData = new FormData();
92
+ formData.append('file', file);
93
+
94
+ try {
95
+ const response = await fetch('/api/upload', {
96
+ method: 'POST',
97
+ body: formData
98
+ });
99
+
100
+ if (!response.ok) {
101
+ throw new Error('Upload failed');
102
+ }
103
+
104
+ const data = await response.json();
105
+ uploadedFileId = data.file_id;
106
+
107
+ // Enable process button
108
+ processBtn.disabled = false;
109
+ progressCard.classList.add('hidden');
110
+
111
+ } catch (error) {
112
+ alert('Upload failed: ' + error.message);
113
+ progressCard.classList.add('hidden');
114
+ }
115
+ }
116
+
117
+ function formatFileSize(bytes) {
118
+ if (bytes === 0) return '0 Bytes';
119
+ const k = 1024;
120
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
121
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
122
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
123
+ }
124
+
125
+ // ========== PROCESSING ==========
126
+ processBtn.addEventListener('click', async () => {
127
+ if (!uploadedFileId) {
128
+ alert('Please upload a file first');
129
+ return;
130
+ }
131
+
132
+ // Get config
133
+ const model = modelSelect.value;
134
+ const language = languageSelect.value || null;
135
+ const mode = modeSelect.value;
136
+
137
+ // Start processing
138
+ const formData = new FormData();
139
+ formData.append('file_id', uploadedFileId);
140
+ formData.append('model', model);
141
+ if (language) {
142
+ formData.append('language', language);
143
+ }
144
+ formData.append('mode', mode);
145
+
146
+ try {
147
+ const response = await fetch('/api/process', {
148
+ method: 'POST',
149
+ body: formData
150
+ });
151
+
152
+ if (!response.ok) {
153
+ throw new Error('Processing failed to start');
154
+ }
155
+
156
+ const data = await response.json();
157
+ currentJobId = data.job_id;
158
+
159
+ // Show progress card
160
+ progressCard.classList.remove('hidden');
161
+ processBtn.disabled = true;
162
+
163
+ // Start polling
164
+ startPolling();
165
+
166
+ } catch (error) {
167
+ alert('Error: ' + error.message);
168
+ }
169
+ });
170
+
171
+ // ========== POLLING ==========
172
+ // ========== POLLING ==========
173
+ let simulatedProgress = 0;
174
+
175
+ function startPolling() {
176
+ simulatedProgress = 0;
177
+
178
+ // Status polling
179
+ pollingInterval = setInterval(async () => {
180
+ try {
181
+ const response = await fetch(`/api/status/${currentJobId}`);
182
+
183
+ if (response.status === 404) {
184
+ stopPolling();
185
+ alert('Connection lost or server restarted. Please try uploading again.');
186
+ resetUI();
187
+ return;
188
+ }
189
+
190
+ if (!response.ok) {
191
+ throw new Error('Status check failed');
192
+ }
193
+
194
+ const job = await response.json();
195
+
196
+ // Update UI
197
+ updateProgress(job);
198
+
199
+ // Check if complete
200
+ if (job.status === 'completed') {
201
+ stopPolling();
202
+ // Force 100% just in case
203
+ progressFill.style.width = '100%';
204
+ progressText.textContent = '100%';
205
+ setTimeout(() => showDownload(), 500);
206
+ } else if (job.status === 'failed') {
207
+ stopPolling();
208
+ alert('Processing failed: ' + job.error);
209
+ resetUI();
210
+ } else if (job.status === 'cancelled') {
211
+ stopPolling();
212
+ alert('Process was cancelled.');
213
+ resetUI();
214
+ }
215
+
216
+ } catch (error) {
217
+ console.error('Polling error:', error);
218
+ }
219
+ }, 1000);
220
+
221
+ // Simulation interval remains the same
222
+ simulationInterval = setInterval(() => {
223
+ if (simulatedProgress < 90) {
224
+ // Slower progress as it gets higher
225
+ const increment = simulatedProgress < 30 ? 2 : (simulatedProgress < 60 ? 1 : 0.5);
226
+ simulatedProgress += increment;
227
+
228
+ // Only update visualization if real progress isn't higher
229
+ const currentReal = parseFloat(progressFill.style.width) || 0;
230
+ if (simulatedProgress > currentReal) {
231
+ progressFill.style.width = simulatedProgress + '%';
232
+ progressText.textContent = Math.round(simulatedProgress) + '%';
233
+ }
234
+ }
235
+ }, 500);
236
+
237
+ // Set processing flag
238
+ isProcessing = true;
239
+ }
240
+
241
+ let simulationInterval = null;
242
+
243
+ // Cancel button handler
244
+ cancelBtn.addEventListener('click', async () => {
245
+ if (!currentJobId) return;
246
+
247
+ if (confirm('Are you sure you want to cancel? Any progress will be lost.')) {
248
+ try {
249
+ await fetch(`/api/cancel/${currentJobId}`, { method: 'POST' });
250
+ stopPolling();
251
+ resetUI();
252
+ alert('Process cancelled successfully.');
253
+ } catch (error) {
254
+ console.error('Cancel failed:', error);
255
+ alert('Failed to cancel. The process may have already completed.');
256
+ }
257
+ }
258
+ });
259
+
260
+ function stopPolling() {
261
+ if (pollingInterval) {
262
+ clearInterval(pollingInterval);
263
+ pollingInterval = null;
264
+ }
265
+ if (simulationInterval) {
266
+ clearInterval(simulationInterval);
267
+ simulationInterval = null;
268
+ }
269
+ isProcessing = false; // Clear processing flag
270
+ }
271
+
272
+ function updateProgress(job) {
273
+ const realProgress = job.progress || 0;
274
+
275
+ // Calculate display progress
276
+ // If real progress is < 5% (likely downloading), don't simulate too far ahead
277
+ // Max simulation cap: 95%
278
+ let displayProgress = realProgress;
279
+
280
+ if (realProgress < simulatedProgress) {
281
+ // We are simulating ahead
282
+ // But if real is 0 (downloading), cap simulation at 10% so we don't look like we're transcribing
283
+ if (realProgress === 0 && simulatedProgress > 10) {
284
+ simulatedProgress = 10;
285
+ } else {
286
+ simulatedProgress += (95 - simulatedProgress) * 0.05;
287
+ }
288
+ displayProgress = simulatedProgress;
289
+ } else {
290
+ // Real progress caught up
291
+ simulatedProgress = realProgress;
292
+ }
293
+
294
+ progressFill.style.width = displayProgress + '%';
295
+
296
+ // Update Text Logic: Priority to Backend Message
297
+ if (job.status_message) {
298
+ progressText.textContent = `${job.status_message} (${Math.round(displayProgress)}%)`;
299
+ } else {
300
+ progressText.textContent = `Processing... ${Math.round(displayProgress)}%`;
301
+ }
302
+ // Update status text - PRIORITY TO BACKEND MESSAGE
303
+ if (job.status_message) {
304
+ // Use the backend's custom message (e.g., "Downloading...", "Transcribing...")
305
+ statusText.textContent = job.status_message;
306
+ } else if (job.status === 'processing') {
307
+ statusText.textContent = '🧠 Transcribing with Whisper... (This may take a moment)';
308
+ } else if (job.status === 'pending') {
309
+ statusText.textContent = '⏳ Waiting to start...';
310
+ }
311
+ }
312
+
313
+ function showDownload() {
314
+ progressCard.classList.add('hidden');
315
+ downloadCard.classList.remove('hidden');
316
+
317
+ // Show Instagram popup after 2 seconds
318
+ setTimeout(() => {
319
+ instaPopup.classList.remove('hidden');
320
+ }, 2000);
321
+ }
322
+
323
+ // ========== DOWNLOAD ==========
324
+ downloadBtn.addEventListener('click', () => {
325
+ if (currentJobId) {
326
+ window.location.href = `/api/download/${currentJobId}`;
327
+ }
328
+ });
329
+
330
+ // ========== RESET ==========
331
+ resetBtn.addEventListener('click', () => {
332
+ resetUI();
333
+ });
334
+
335
+ function resetUI() {
336
+ uploadedFileId = null;
337
+ currentJobId = null;
338
+ fileInfo.classList.add('hidden');
339
+ progressCard.classList.add('hidden');
340
+ downloadCard.classList.add('hidden');
341
+ processBtn.disabled = true;
342
+ progressFill.style.width = '0%';
343
+ progressText.textContent = '0%';
344
+ fileInput.value = '';
345
+ }
346
+
347
+ // ========== POPUP ==========
348
+ closePopup.addEventListener('click', () => {
349
+ instaPopup.classList.add('hidden');
350
+ });
351
+
352
+ instaPopup.addEventListener('click', (e) => {
353
+ if (e.target === instaPopup) {
354
+ instaPopup.classList.add('hidden');
355
+ }
356
+ });
357
+
358
+ // Show popup on load
359
+ setTimeout(() => {
360
+ instaPopup.classList.remove('hidden');
361
+ }, 1000);
@@ -0,0 +1,154 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>OneWord AI - Subtitle Generator</title>
8
+ <link rel="stylesheet" href="style.css">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="grid-bg"></div>
13
+
14
+ <div class="container">
15
+ <!-- Header -->
16
+ <header class="header">
17
+ <h1 class="title">OneWord AI</h1>
18
+ <p class="subtitle">Cinematic Subtitle Generator</p>
19
+ </header>
20
+
21
+ <!-- Upload Section -->
22
+ <div class="card upload-card">
23
+ <div class="card-header">
24
+ <h2>Upload Video/Audio</h2>
25
+ </div>
26
+
27
+ <div id="dropzone" class="dropzone">
28
+ <div class="dropzone-content">
29
+ <svg class="upload-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
30
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
31
+ <polyline points="17 8 12 3 7 8"></polyline>
32
+ <line x1="12" y1="3" x2="12" y2="15"></line>
33
+ </svg>
34
+ <p class="dropzone-text">Drag & drop your file here</p>
35
+ <p class="dropzone-subtext">or click to browse</p>
36
+ <p class="file-limit">Max 100MB • MP4, MP3, WAV</p>
37
+ </div>
38
+ <input type="file" id="fileInput" accept="video/*,audio/*" hidden>
39
+ </div>
40
+
41
+ <div id="fileInfo" class="file-info hidden">
42
+ <p id="fileName"></p>
43
+ <p id="fileSize"></p>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Configuration Section -->
48
+ <div class="card config-card">
49
+ <div class="card-header">
50
+ <h2>Configuration</h2>
51
+ </div>
52
+
53
+ <div class="form-grid">
54
+ <!-- Model Selection -->
55
+ <div class="form-group">
56
+ <label for="modelSelect">Whisper Model</label>
57
+ <select id="modelSelect" class="select-input">
58
+ <option value="medium" selected>Medium (High Quality)</option>
59
+ <option value="large">Large (Best Quality)</option>
60
+ <option value="Oriserve/Whisper-Hindi2Hinglish-Prime">Hindi2Hinglish (Recommended)</option>
61
+ </select>
62
+ </div>
63
+
64
+ <!-- Language Selection -->
65
+ <div class="form-group">
66
+ <label for="languageSelect">Language</label>
67
+ <select id="languageSelect" class="select-input">
68
+ <option value="">Auto Detect</option>
69
+ <option value="en">English</option>
70
+ <option value="hi">Hindi</option>
71
+ <option value="ur">Urdu</option>
72
+ <option value="es">Spanish</option>
73
+ </select>
74
+ </div>
75
+
76
+ <!-- Subtitle Mode -->
77
+ <div class="form-group">
78
+ <label for="modeSelect">Subtitle Mode</label>
79
+ <select id="modeSelect" class="select-input">
80
+ <option value="oneword" selected>One Word</option>
81
+ <option value="twoword">Two Word Punch</option>
82
+ <option value="phrase">Phrase Mode</option>
83
+ </select>
84
+ </div>
85
+ </div>
86
+
87
+ <button id="processBtn" class="btn-primary" disabled>
88
+ Generate Subtitles
89
+ </button>
90
+ </div>
91
+
92
+ <!-- Progress Section -->
93
+ <div id="progressCard" class="card progress-card hidden">
94
+ <div class="card-header">
95
+ <h2>Processing</h2>
96
+ </div>
97
+
98
+ <div class="progress-container">
99
+ <div class="progress-bar">
100
+ <div id="progressFill" class="progress-fill"></div>
101
+ </div>
102
+ <p id="progressText" class="progress-text">0%</p>
103
+ </div>
104
+
105
+ <p id="statusText" class="status-text">Initializing...</p>
106
+
107
+ <p style="color: #ff6b6b; font-weight: bold; margin-top: 15px; font-size: 0.9rem;">
108
+ ⚠️ DO NOT RELOAD this page or the process will be cancelled!
109
+ </p>
110
+
111
+ <button id="cancelBtn" class="btn-secondary" style="margin-top: 15px; background: #ff6b6b;">
112
+ ❌ Cancel Process
113
+ </button>
114
+ </div>
115
+
116
+ <!-- Download Section -->
117
+ <div id="downloadCard" class="card download-card hidden">
118
+ <div class="card-header">
119
+ <h2>✅ Complete!</h2>
120
+ </div>
121
+
122
+ <button id="downloadBtn" class="btn-download">
123
+ Download SRT File
124
+ </button>
125
+
126
+ <button id="resetBtn" class="btn-secondary">
127
+ Process Another File
128
+ </button>
129
+ </div>
130
+
131
+ <!-- Instagram Popup -->
132
+ <div id="instaPopup" class="popup-overlay hidden">
133
+ <div class="popup-card">
134
+ <button id="closePopup" class="popup-close">×</button>
135
+ <h3>Developed by Ambrish</h3>
136
+ <p>Help me reach 1000 followers! 🚀</p>
137
+ <p style="font-size: 0.9rem; margin-top: -10px; margin-bottom: 20px; opacity: 0.8;">Get updates on more
138
+ free AI tools</p>
139
+ <a href="https://instagram.com/ambrish.yadav.1" target="_blank" class="btn-instagram">
140
+ Follow @ambrish.yadav.1
141
+ </a>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Footer -->
146
+ <footer class="footer">
147
+ <p>Made with ❤️ by <strong>Ambrish</strong></p>
148
+ </footer>
149
+ </div>
150
+
151
+ <script src="app.js"></script>
152
+ </body>
153
+
154
+ </html>