better UX - processing status, open when done

This commit is contained in:
Chris Lettieri 2025-04-16 11:40:53 -04:00
parent 5fd9802300
commit d73a5b6859
2 changed files with 168 additions and 1 deletions

View file

@ -1,13 +1,16 @@
import { Plugin, Notice, TFile } from 'obsidian';
import { Plugin, Notice, TFile, WorkspaceLeaf } from 'obsidian';
import { OpenAugiSettings, DEFAULT_SETTINGS } from './types/settings';
import { OpenAIService } from './services/openai-service';
import { FileService } from './services/file-service';
import { OpenAugiSettingTab } from './ui/settings-tab';
import { LoadingIndicator } from './ui/loading-indicator';
import { sanitizeFilename } from './utils/filename-utils';
export default class OpenAugiPlugin extends Plugin {
settings: OpenAugiSettings;
openAIService: OpenAIService;
fileService: FileService;
loadingIndicator: LoadingIndicator;
async onload() {
// Load settings
@ -16,6 +19,14 @@ export default class OpenAugiPlugin extends Plugin {
// Initialize services
this.initializeServices();
// Initialize loading indicator
this.app.workspace.onLayoutReady(() => {
const statusBar = this.addStatusBarItem();
if (statusBar.parentElement) {
this.loadingIndicator = new LoadingIndicator(statusBar.parentElement);
}
});
// Add command to manually parse a transcript file
this.addCommand({
id: 'parse-transcript',
@ -46,16 +57,36 @@ export default class OpenAugiPlugin extends Plugin {
);
}
/**
* Open a file in a new tab
* @param filePath The path to the file to open
*/
private async openFileInNewTab(filePath: string): Promise<void> {
const file = this.app.vault.getAbstractFileByPath(filePath);
if (!file || !(file instanceof TFile)) {
console.error(`File not found: ${filePath}`);
return;
}
const leaf = this.app.workspace.getLeaf('tab');
await leaf.openFile(file);
}
/**
* Process a transcript file
* @param file The file to process
*/
private async processTranscriptFile(file: TFile): Promise<void> {
try {
// Show loading indicator
this.loadingIndicator?.show('Processing voice transcript');
// Read file content
const content = await this.app.vault.read(file);
// Check if API key is set
if (!this.settings.apiKey) {
this.loadingIndicator?.hide();
new Notice('Please set your OpenAI API key in the plugin settings');
return;
}
@ -69,8 +100,20 @@ export default class OpenAugiPlugin extends Plugin {
// Write result to files
await this.fileService.writeTranscriptFiles(file.basename, parsedData);
// Hide loading indicator
this.loadingIndicator?.hide();
// Show success message
new Notice(`Successfully parsed transcript: ${file.basename}`);
// Open the summary file in a new tab
const sanitizedFilename = sanitizeFilename(file.basename);
const summaryPath = `${this.settings.summaryFolder}/${sanitizedFilename} - summary.md`;
await this.openFileInNewTab(summaryPath);
} catch (error) {
// Hide loading indicator
this.loadingIndicator?.hide();
console.error('Failed to parse transcript:', error);
new Notice('Failed to parse transcript. Check console for details.');
}

124
src/ui/loading-indicator.ts Normal file
View file

@ -0,0 +1,124 @@
import { WorkspaceLeaf } from 'obsidian';
/**
* Modal loading indicator that shows processing status
*/
export class LoadingIndicator {
private statusBarItem: HTMLElement | null = null;
private loadingContainer: HTMLElement | null = null;
private dotElements: HTMLElement[] = [];
private animationInterval: number | null = null;
private statusBar: HTMLElement;
constructor(statusBar: HTMLElement) {
this.statusBar = statusBar;
}
/**
* Show the loading indicator with a message
* @param message The message to display
*/
show(message: string): void {
this.hide(); // Clear any existing indicators first
// Create status bar item
this.statusBarItem = this.statusBar.createEl('div', {
cls: 'status-bar-item mod-clickable',
attr: {
id: 'openaugi-status',
style: 'display: flex; align-items: center; gap: 8px; color: var(--text-accent);'
}
});
// Create icon
const iconEl = this.statusBarItem.createEl('span', {
cls: 'status-bar-item-icon',
attr: {
style: 'display: flex; align-items: center;'
}
});
// Create loading spinner
const spinner = iconEl.createEl('span', {
cls: 'openaugi-spinner',
attr: {
style: `
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid var(--text-accent);
border-radius: 50%;
border-top-color: transparent;
animation: openaugi-spin 1s linear infinite;
`
}
});
// Add CSS animation
const styleEl = document.head.createEl('style');
styleEl.textContent = `
@keyframes openaugi-spin {
to {
transform: rotate(360deg);
}
}
`;
// Create text container
const textEl = this.statusBarItem.createEl('span', {
text: message,
attr: {
style: 'font-size: 13px; font-weight: 500;'
}
});
// Create dot animation container
this.loadingContainer = this.statusBarItem.createEl('span', {
attr: { style: 'display: flex; gap: 2px;' }
});
// Create three dots for the animation
for (let i = 0; i < 3; i++) {
const dot = this.loadingContainer.createEl('span', {
text: '.',
attr: {
style: 'opacity: 0; transition: opacity 0.3s ease; font-weight: bold;'
}
});
this.dotElements.push(dot);
}
// Start dot animation
let currentDot = 0;
this.animationInterval = window.setInterval(() => {
// Reset all dots
this.dotElements.forEach(dot => dot.style.opacity = '0');
// Show dots up to current dot
for (let i = 0; i <= currentDot; i++) {
this.dotElements[i].style.opacity = '1';
}
// Increment and wrap around
currentDot = (currentDot + 1) % this.dotElements.length;
}, 500);
}
/**
* Hide the loading indicator
*/
hide(): void {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
if (this.statusBarItem) {
this.statusBarItem.remove();
this.statusBarItem = null;
}
this.dotElements = [];
this.loadingContainer = null;
}
}