Skip to content

Commit aa6cd67

Browse files
committed
UI appearance and performance improvements.
1 parent fde656d commit aa6cd67

File tree

3 files changed

+106
-80
lines changed

3 files changed

+106
-80
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## In Development
4+
- Made AI generation mode buttons smaller and arranged in a single row for better visibility on small screens
5+
- Fixed potential performance issue causing progressive slowdown during extended editing sessions
6+
- Removed 34 debug console.log statements that fired on every keystroke
7+
- Eliminated logging of full document text and section arrays during typing
8+
- Retained one-time initialization logs for startup troubleshooting
9+
- Optimized section parsing performance for smoother typing experience
10+
- Added 150ms debounce to parseSections to avoid expensive operations on every keystroke
11+
- Added length pre-filter to skip Levenshtein distance calculation when strings differ by >2x in length
12+
- Implemented index-based matching to check same-position sections first before searching all sections
13+
- Fixed suggestion panel stability: suggestions no longer flicker during typing
14+
- Keyup handler now only triggers cursor updates for navigation keys (arrows, Home, End, etc.)
15+
- Mode buttons and "Use suggestion" button no longer steal focus from the editor
16+
317
## 0.1.2
418
- Enhanced AI context generation to include multiple paragraphs (up to 2000 characters) instead of just adjacent paragraphs
519
- Frontend now collects context from multiple preceding and following sections

src/writing_assistant/app/static/script.js

Lines changed: 77 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class WritingAssistant {
3333
this.undoDelay = 1000; // 1 second delay before saving undo state
3434
this.isUndoRedoOperation = false;
3535

36+
// Debounce timer for parseSections (performance optimization)
37+
this.parseSectionsTimer = null;
38+
this.parseSectionsDelay = 150; // 150ms debounce delay
39+
3640
this.init();
3741
}
3842

@@ -125,17 +129,27 @@ class WritingAssistant {
125129
// Document text editing and cursor tracking
126130
documentTextarea.addEventListener('input', () => this.handleDocumentTextChange());
127131
documentTextarea.addEventListener('click', () => this.handleCursorChange());
128-
documentTextarea.addEventListener('keyup', () => this.handleCursorChange());
132+
// Only handle cursor change for navigation keys (not regular typing)
133+
documentTextarea.addEventListener('keyup', (e) => {
134+
const navigationKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
135+
'Home', 'End', 'PageUp', 'PageDown'];
136+
if (navigationKeys.includes(e.key)) {
137+
this.handleCursorChange();
138+
}
139+
});
129140

130141
// Mode button controls (Ideas, Rewrite, Improve, Proofread)
142+
// Prevent buttons from stealing focus from the editor
131143
document.querySelectorAll('.mode-btn').forEach(btn => {
144+
btn.addEventListener('mousedown', (e) => e.preventDefault());
132145
btn.addEventListener('click', () => {
133146
const mode = btn.getAttribute('data-mode');
134147
this.generateSuggestionForCurrentSection(mode);
135148
});
136149
});
137150

138-
// Use suggestion button
151+
// Use suggestion button - also prevent focus steal
152+
useSuggestionBtn.addEventListener('mousedown', (e) => e.preventDefault());
139153
useSuggestionBtn.addEventListener('click', () => this.useSuggestion());
140154

141155
// Initialize undo/redo system
@@ -443,19 +457,30 @@ class WritingAssistant {
443457
}
444458

445459
handleDocumentTextChange() {
446-
console.log('handleDocumentTextChange called');
447460
const textarea = document.getElementById('document-text');
448461
this.documentText = textarea.value;
449-
console.log('Current document text:', this.documentText);
450462

451463
// Save undo state if this wasn't an undo/redo operation
452464
if (!this.isUndoRedoOperation) {
453465
this.scheduleUndoStateSave();
454466
}
455467

456-
console.log('About to call parseSections');
457-
this.parseSections();
458-
this.handleCursorChange();
468+
// Debounce parseSections to avoid expensive operations on every keystroke
469+
// Note: handleCursorChange is called after parseSections completes, not here,
470+
// to avoid UI flickering due to stale section positions
471+
this.scheduleParseSections();
472+
}
473+
474+
scheduleParseSections() {
475+
// Clear existing timer
476+
if (this.parseSectionsTimer) {
477+
clearTimeout(this.parseSectionsTimer);
478+
}
479+
480+
// Schedule parseSections after delay
481+
this.parseSectionsTimer = setTimeout(() => {
482+
this.parseSections();
483+
}, this.parseSectionsDelay);
459484
}
460485

461486
handleTitleChange() {
@@ -477,8 +502,15 @@ class WritingAssistant {
477502
if (str1.length === 0 && str2.length === 0) return 1;
478503
if (str1.length === 0 || str2.length === 0) return 0;
479504

480-
// Use Levenshtein distance for accurate similarity calculation
505+
// Cheap length pre-filter: if one string is more than 2x the length of
506+
// the other, similarity can't exceed 0.5 (our threshold), so skip Levenshtein
507+
const minLen = Math.min(str1.length, str2.length);
481508
const maxLen = Math.max(str1.length, str2.length);
509+
if (maxLen > 2 * minLen) {
510+
return minLen / maxLen; // Return upper bound (actual similarity is lower)
511+
}
512+
513+
// Use Levenshtein distance for accurate similarity calculation
482514
const distance = this.levenshteinDistance(str1, str2);
483515
const similarity = 1 - (distance / maxLen);
484516

@@ -513,11 +545,8 @@ class WritingAssistant {
513545
}
514546

515547
parseSections() {
516-
console.log('parseSections called');
517548
const text = this.documentText;
518549
const oldSections = [...this.sections]; // Save previous sections
519-
console.log('Old sections count:', oldSections.length);
520-
console.log('Old sections:', oldSections);
521550
this.sections = [];
522551

523552
if (!text.trim()) {
@@ -530,7 +559,6 @@ class WritingAssistant {
530559

531560
// Split by double newlines (blank lines)
532561
const rawSections = text.split(/\n\s*\n/);
533-
console.log('Raw sections count:', rawSections.length);
534562
let currentPos = 0;
535563

536564
rawSections.forEach((sectionText, index) => {
@@ -551,52 +579,59 @@ class WritingAssistant {
551579
};
552580

553581
// Try to preserve generated_text from previous sections
554-
// Look for a section with the same or similar text content
555-
// Use findIndex to get the index so we can mark it as matched
556-
const matchingOldSectionIndex = oldSections.findIndex((oldSection, oldIndex) => {
557-
// Skip sections that have already been matched to another new section
558-
if (matchedOldSectionIndices.has(oldIndex)) {
559-
return false;
560-
}
561-
562-
// Skip sections without generated text
563-
if (!oldSection.generated_text) {
564-
return false;
565-
}
566-
567-
// Calculate similarity between ORIGINAL text (when suggestion was generated) and new text
568-
// This prevents the "moving target" bug where each keystroke is compared to the previous keystroke
582+
// Optimization: Try index-based matching first (most common case during typing)
583+
let matchingOldSectionIndex = -1;
584+
585+
// Step 1: Try matching at the same index first (fast path for normal editing)
586+
if (index < oldSections.length &&
587+
!matchedOldSectionIndices.has(index) &&
588+
oldSections[index].generated_text) {
589+
const oldSection = oldSections[index];
569590
const originalText = oldSection.original_text || oldSection.text;
570591
const similarity = this.calculateTextSimilarity(originalText, trimmed);
592+
if (similarity > 0.5) {
593+
matchingOldSectionIndex = index;
594+
}
595+
}
571596

572-
// Debug logging
573-
console.log('Similarity check:');
574-
console.log(' Original text (when generated):', originalText);
575-
console.log(' New text:', trimmed);
576-
console.log(' Similarity:', similarity);
577-
console.log(' Keep suggestion?', similarity > 0.5);
578-
579-
// Keep suggestion if more than 50% of the text is the same
580-
return similarity > 0.5;
581-
});
597+
// Step 2: Fall back to searching all old sections only if index-based match failed
598+
if (matchingOldSectionIndex === -1) {
599+
matchingOldSectionIndex = oldSections.findIndex((oldSection, oldIndex) => {
600+
// Skip the index we already checked
601+
if (oldIndex === index) {
602+
return false;
603+
}
604+
// Skip sections that have already been matched to another new section
605+
if (matchedOldSectionIndices.has(oldIndex)) {
606+
return false;
607+
}
608+
// Skip sections without generated text
609+
if (!oldSection.generated_text) {
610+
return false;
611+
}
612+
// Calculate similarity between ORIGINAL text and new text
613+
const originalText = oldSection.original_text || oldSection.text;
614+
const similarity = this.calculateTextSimilarity(originalText, trimmed);
615+
return similarity > 0.5;
616+
});
617+
}
582618

583619
if (matchingOldSectionIndex !== -1) {
584-
console.log('Preserving suggestion for section');
585620
const matchingOldSection = oldSections[matchingOldSectionIndex];
586621
newSection.generated_text = matchingOldSection.generated_text;
587622
// Also preserve the original text so we continue comparing against it
588623
newSection.original_text = matchingOldSection.original_text || matchingOldSection.text;
589624
// Mark this old section as matched so it won't be reused for other new sections
590625
matchedOldSectionIndices.add(matchingOldSectionIndex);
591-
} else {
592-
console.log('Clearing suggestion for section');
593626
}
594627

595628
this.sections.push(newSection);
596629
}
597630
});
598631

599632
this.updateSectionInfo();
633+
// Update cursor/suggestion panel after sections are rebuilt
634+
this.handleCursorChange();
600635
}
601636

602637
handleCursorChange() {
@@ -769,19 +804,9 @@ class WritingAssistant {
769804
// Save undo state before making changes
770805
this.saveUndoState();
771806

772-
// Debug section boundaries
773-
console.log('Section replacement debug:');
774-
console.log('Current section text:', JSON.stringify(currentSection.text));
775-
console.log('Section start pos:', currentSection.startPos);
776-
console.log('Section end pos:', currentSection.endPos);
777-
console.log('Generated text:', JSON.stringify(currentSection.generated_text));
778-
779807
const beforeSection = this.documentText.substring(0, currentSection.startPos);
780808
const afterSection = this.documentText.substring(currentSection.endPos);
781809

782-
console.log('Before section:', JSON.stringify(beforeSection.slice(-10))); // Last 10 chars
783-
console.log('After section:', JSON.stringify(afterSection.slice(0, 10))); // First 10 chars
784-
785810
// Clean the generated text to prevent extra blank lines
786811
const cleanGeneratedText = currentSection.generated_text.trim();
787812

@@ -1270,7 +1295,6 @@ class WritingAssistant {
12701295
applySavedAISettings() {
12711296
// No server call needed - metadata is sent with each generation request
12721297
// This method kept for compatibility but does nothing
1273-
console.log('applySavedAISettings: No action needed - using stateless approach');
12741298
}
12751299

12761300
loadCurrentDocumentToSettingsForm() {
@@ -1325,8 +1349,6 @@ class WritingAssistant {
13251349
model: documentData.metadata.model || ''
13261350
};
13271351

1328-
console.log('restoreDocumentMetadata: Setting documentMetadata to:', this.documentMetadata);
1329-
13301352
// Update the UI form fields AND the settings modal if it's open
13311353
// Use setTimeout to ensure DOM updates are processed
13321354
setTimeout(() => {
@@ -1358,7 +1380,6 @@ class WritingAssistant {
13581380
// Keep suggestion if more than 50% of the text is the same
13591381
if (similarity > 0.5) {
13601382
currentSection.generated_text = savedSection.generated_text;
1361-
console.log(`Restored generated_text for section ${i} (similarity: ${(similarity * 100).toFixed(1)}%):`, savedSection.generated_text.substring(0, 50) + '...');
13621383
}
13631384
}
13641385
}
@@ -1426,10 +1447,8 @@ class WritingAssistant {
14261447

14271448
// Clear redo stack when new state is saved
14281449
this.redoStack = [];
1429-
1450+
14301451
this.lastUndoState = currentState;
1431-
1432-
console.log(`Undo state saved. Stack size: ${this.undoStack.length}`);
14331452
}
14341453

14351454
undo() {
@@ -1523,7 +1542,6 @@ undo() {
15231542
}
15241543

15251544
this.showMessage('Undo applied', 'success');
1526-
console.log(`Undo applied. Undo stack: ${this.undoStack.length}, Redo stack: ${this.redoStack.length}`);
15271545
}
15281546

15291547
redo() {
@@ -1579,15 +1597,13 @@ undo() {
15791597
}
15801598

15811599
this.showMessage('Redo applied', 'success');
1582-
console.log(`Redo applied. Undo stack: ${this.undoStack.length}, Redo stack: ${this.redoStack.length}`);
15831600
}
1584-
1601+
15851602
clearUndoHistory() {
15861603
this.undoStack = [];
15871604
this.redoStack = [];
15881605
this.lastUndoState = null;
15891606
this.saveUndoState(); // Save current state as first undo point
1590-
console.log('Undo history cleared');
15911607
}
15921608

15931609
// Hotkey Management System
@@ -1717,8 +1733,6 @@ undo() {
17171733
// Update the system
17181734
this.updateButtonLabels();
17191735
this.setupHotkeyListener();
1720-
1721-
console.log('Hotkeys updated:', this.hotkeys);
17221736
}
17231737

17241738
async createNewDocument() {
@@ -2345,7 +2359,6 @@ undo() {
23452359
}
23462360

23472361
async loadDocumentFromServer(filename) {
2348-
console.log('loadDocumentFromServer: called with filename:', filename);
23492362
try {
23502363
// Get document data directly from the load endpoint
23512364
const response = await this.authFetch(`/documents/load/${filename}`);
@@ -2591,17 +2604,14 @@ undo() {
25912604
async performAutoSave() {
25922605
// Only auto-save if document has been saved at least once
25932606
if (!this.currentFilename) {
2594-
console.log('Auto-save skipped: Document has not been saved yet');
25952607
this.startAutoSaveTimer(); // Restart timer
25962608
return;
25972609
}
25982610

25992611
try {
2600-
console.log('Performing auto-save...');
26012612
await this.saveWithFilename(this.currentFilename, false, true);
26022613
this.lastSaveTime = new Date();
26032614
this.showMessage('Document auto-saved', 'success');
2604-
console.log('Auto-save completed successfully');
26052615
} catch (error) {
26062616
console.error('Auto-save failed:', error);
26072617
this.showMessage('Auto-save failed', 'error');
@@ -2664,8 +2674,6 @@ undo() {
26642674
body: formData,
26652675
keepalive: true
26662676
}));
2667-
2668-
console.log('Sync save performed on page exit');
26692677
} catch (error) {
26702678
console.error('Sync save failed:', error);
26712679
} finally {

0 commit comments

Comments
 (0)