Skip to content

Commit fca8013

Browse files
committed
.
1 parent 1068517 commit fca8013

File tree

9 files changed

+468
-41
lines changed

9 files changed

+468
-41
lines changed

src/api/blueprints/config_routes.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,18 @@ def get_config_path():
4747
logger.setLevel(logging.DEBUG)
4848

4949

50-
def create_config_blueprint():
51-
"""Create and configure the config blueprint"""
50+
def create_config_blueprint(server_session_id=None):
51+
"""Create and configure the config blueprint
52+
53+
Args:
54+
server_session_id: Server session ID from state manager (optional, generates new if not provided)
55+
"""
5256
bp = Blueprint('config', __name__)
5357

54-
# Store server startup time to detect restarts
55-
startup_time = int(time.time())
58+
# Store server startup time/session ID to detect restarts
59+
# Use provided session_id from state_manager if available, otherwise generate new
60+
# Ensure it's an integer for consistency with health check response
61+
startup_time = int(server_session_id) if server_session_id else int(time.time())
5662

5763
@bp.route('/')
5864
def serve_interface():
@@ -73,7 +79,8 @@ def health_check():
7379
"translate_module": "loaded",
7480
"ollama_default_endpoint": DEFAULT_OLLAMA_API_ENDPOINT,
7581
"supported_formats": ["txt", "epub", "srt"],
76-
"startup_time": startup_time # Used to detect server restarts
82+
"startup_time": startup_time, # Used to detect server restarts
83+
"session_id": startup_time # Alias for compatibility with LifecycleManager
7784
})
7885

7986
@bp.route('/api/models', methods=['GET', 'POST'])

src/api/routes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def configure_routes(app, state_manager, output_dir, start_translation_job, sock
3535
"""
3636

3737
# Register config and health check routes
38-
config_bp = create_config_blueprint()
38+
# Pass server_session_id from state_manager to ensure consistency
39+
config_bp = create_config_blueprint(server_session_id=state_manager.server_session_id)
3940
app.register_blueprint(config_bp)
4041

4142
# Register translation management routes

src/api/translation_state.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212

1313
def generate_server_session_id() -> str:
14-
"""Generate a unique session ID for this server instance."""
15-
return str(uuid.uuid4())
14+
"""Generate a unique session ID for this server instance using timestamp."""
15+
import time
16+
return str(int(time.time()))
1617

1718

1819
class TranslationStateManager:

src/web/static/js/core/settings-manager.js

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,29 @@ import { ApiClient } from './api-client.js';
1010
import { DomHelpers } from '../ui/dom-helpers.js';
1111
import { MessageLogger } from '../ui/message-logger.js';
1212

13-
const STORAGE_KEY = 'tbl_user_preferences';
13+
// Storage configuration with versioning
14+
const STORAGE_VERSION = 1;
15+
const STORAGE_KEY_PREFIX = 'tbl_user_preferences';
16+
const STORAGE_KEY = `${STORAGE_KEY_PREFIX}_v${STORAGE_VERSION}`;
17+
18+
/**
19+
* Validate user preferences structure
20+
* @param {any} data - Data to validate
21+
* @returns {boolean} True if valid
22+
*/
23+
function validatePreferences(data) {
24+
if (!data || typeof data !== 'object') return false;
25+
26+
// Check version
27+
if (!('version' in data)) return false;
28+
29+
// Validate types for known fields (non-exhaustive, just critical ones)
30+
if ('ttsEnabled' in data && typeof data.ttsEnabled !== 'boolean') return false;
31+
if ('textCleanup' in data && typeof data.textCleanup !== 'boolean') return false;
32+
if ('refineTranslation' in data && typeof data.refineTranslation !== 'boolean') return false;
33+
34+
return true;
35+
}
1436

1537
/**
1638
* Flag to prevent localStorage from overriding .env default model
@@ -58,6 +80,9 @@ export const SettingsManager = {
5880
* Initialize settings manager - load saved preferences and setup auto-save
5981
*/
6082
initialize() {
83+
// Clean up old storage versions
84+
this.cleanupOldStorageVersions();
85+
6186
this.loadLocalPreferences();
6287
// Setup auto-save listeners after a short delay to avoid triggering during initial load
6388
setTimeout(() => {
@@ -66,6 +91,41 @@ export const SettingsManager = {
6691
}, 500);
6792
},
6893

94+
/**
95+
* Clean up old localStorage versions
96+
*/
97+
cleanupOldStorageVersions() {
98+
try {
99+
// Remove old non-versioned key
100+
const oldKey = 'tbl_user_preferences';
101+
if (localStorage.getItem(oldKey)) {
102+
// Migrate data from old key before removing
103+
const oldData = localStorage.getItem(oldKey);
104+
if (oldData) {
105+
try {
106+
const parsed = JSON.parse(oldData);
107+
// Add version and save to new key
108+
parsed.version = STORAGE_VERSION;
109+
localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
110+
} catch (e) {
111+
console.warn('Could not migrate old preferences:', e);
112+
}
113+
}
114+
localStorage.removeItem(oldKey);
115+
}
116+
117+
// Remove any other versions (future-proofing)
118+
for (let i = 0; i < STORAGE_VERSION; i++) {
119+
const oldVersionKey = `${STORAGE_KEY_PREFIX}_v${i}`;
120+
if (localStorage.getItem(oldVersionKey)) {
121+
localStorage.removeItem(oldVersionKey);
122+
}
123+
}
124+
} catch (error) {
125+
console.warn('Failed to cleanup old storage versions:', error);
126+
}
127+
},
128+
69129
/**
70130
* Setup event listeners for auto-save on all settings elements
71131
* @private
@@ -140,8 +200,30 @@ export const SettingsManager = {
140200
getLocalPreferences() {
141201
try {
142202
const stored = localStorage.getItem(STORAGE_KEY);
143-
return stored ? JSON.parse(stored) : {};
144-
} catch {
203+
204+
if (!stored) return {};
205+
206+
const parsed = JSON.parse(stored);
207+
208+
// Validate structure
209+
if (!validatePreferences(parsed)) {
210+
console.warn('Invalid preferences structure, resetting to defaults');
211+
localStorage.removeItem(STORAGE_KEY);
212+
return {};
213+
}
214+
215+
// Check version compatibility
216+
if (parsed.version !== STORAGE_VERSION) {
217+
console.warn(`Preferences version mismatch (found ${parsed.version}, expected ${STORAGE_VERSION})`);
218+
// Could implement migration here in the future
219+
localStorage.removeItem(STORAGE_KEY);
220+
return {};
221+
}
222+
223+
return parsed;
224+
} catch (error) {
225+
console.error('Failed to load preferences from localStorage:', error);
226+
MessageLogger.addLog('⚠️ Could not load saved preferences');
145227
return {};
146228
}
147229
},
@@ -153,10 +235,23 @@ export const SettingsManager = {
153235
saveLocalPreferences(prefs) {
154236
try {
155237
const current = this.getLocalPreferences();
156-
const updated = { ...current, ...prefs };
238+
const updated = {
239+
...current,
240+
...prefs,
241+
version: STORAGE_VERSION,
242+
timestamp: Date.now()
243+
};
244+
157245
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
158-
} catch {
159-
// Preference save failed silently
246+
} catch (error) {
247+
console.error('Failed to save preferences to localStorage:', error);
248+
249+
// Check if it's a quota exceeded error
250+
if (error.name === 'QuotaExceededError') {
251+
MessageLogger.addLog('⚠️ Browser storage full, could not save preferences');
252+
} else {
253+
MessageLogger.addLog('⚠️ Failed to save preferences');
254+
}
160255
}
161256
},
162257

src/web/static/js/index.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ function wireModuleEvents() {
377377
/**
378378
* Initialize all modules in proper order
379379
*/
380-
function initializeModules() {
380+
async function initializeModules() {
381381

382382
// 1. Core infrastructure
383383
initializeState();
@@ -399,7 +399,9 @@ function initializeModules() {
399399
FileManager.initialize();
400400

401401
// 5. Translation modules
402-
TranslationTracker.initialize();
402+
// IMPORTANT: await TranslationTracker.initialize() because it's now async
403+
// It needs to check server session before restoring state
404+
await TranslationTracker.initialize();
403405
ProgressManager.reset();
404406
ResumeManager.initialize();
405407

@@ -828,10 +830,14 @@ if (typeof window !== 'undefined') {
828830
* Start application when DOM is ready
829831
*/
830832
if (document.readyState === 'loading') {
831-
document.addEventListener('DOMContentLoaded', initializeModules);
833+
document.addEventListener('DOMContentLoaded', async () => {
834+
await initializeModules();
835+
});
832836
} else {
833-
// DOM already loaded
834-
initializeModules();
837+
// DOM already loaded - initialize immediately
838+
(async () => {
839+
await initializeModules();
840+
})();
835841
}
836842

837843
// ========================================

src/web/static/js/translation/batch-controller.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ export const BatchController = {
123123
* Start batch translation
124124
*/
125125
async startBatchTranslation() {
126+
// Wait for TranslationTracker to be fully initialized
127+
if (!TranslationTracker.isInitialized || !TranslationTracker.isInitialized()) {
128+
console.warn('TranslationTracker not yet initialized, waiting...');
129+
// Wait a bit and try again
130+
await new Promise(resolve => setTimeout(resolve, 100));
131+
if (!TranslationTracker.isInitialized || !TranslationTracker.isInitialized()) {
132+
MessageLogger.showMessage('⚠️ System still initializing, please wait...', 'warning');
133+
return;
134+
}
135+
}
136+
126137
const isBatchActive = StateManager.getState('translation.isBatchActive') || false;
127138
const filesToProcess = StateManager.getState('files.toProcess') || [];
128139

@@ -295,6 +306,25 @@ export const BatchController = {
295306
fileToTranslate.translationId = data.translation_id;
296307
updateFileStatusInList(fileToTranslate.name, 'Submitted', data.translation_id);
297308

309+
// Show progress section immediately (don't wait for WebSocket)
310+
// Use requestAnimationFrame to ensure immediate DOM update
311+
DomHelpers.show('progressSection');
312+
DomHelpers.show('interruptBtn');
313+
314+
// Force immediate DOM render
315+
requestAnimationFrame(() => {
316+
const progressSection = DomHelpers.getElement('progressSection');
317+
if (progressSection) {
318+
progressSection.style.display = 'block';
319+
}
320+
});
321+
322+
// Update title immediately
323+
this.updateTranslationTitle(fileToTranslate);
324+
325+
// Show initial message
326+
MessageLogger.addLog(`⏳ Translation submitted for ${fileToTranslate.name}...`);
327+
298328
// Remove file from processing list immediately when translation starts
299329
this.removeFileFromProcessingList(fileToTranslate.name);
300330

0 commit comments

Comments
 (0)