bitsofchris_openaugi-obsidi.../src/main.ts

228 lines
7.6 KiB
TypeScript
Raw Normal View History

2025-04-17 18:52:08 +00:00
import { Plugin, Notice, TFile } from 'obsidian';
2025-04-16 15:33:35 +00:00
import { OpenAugiSettings, DEFAULT_SETTINGS } from './types/settings';
import { OpenAIService } from './services/openai-service';
import { FileService } from './services/file-service';
2025-04-17 18:52:08 +00:00
import { DistillService } from './services/distill-service';
2025-04-16 15:33:35 +00:00
import { OpenAugiSettingTab } from './ui/settings-tab';
import { LoadingIndicator } from './ui/loading-indicator';
import { sanitizeFilename } from './utils/filename-utils';
2025-04-16 15:33:35 +00:00
2025-04-17 18:52:08 +00:00
/**
* A simple tokeinzer to estimate the number of tokens
* @param text Text to count tokens from
* @returns Approximate token count
*/
function estimateTokens(text: string): number {
// Rough estimate: 1 token is approximately 4 characters
return Math.ceil(text.length / 4);
}
2025-04-16 15:33:35 +00:00
export default class OpenAugiPlugin extends Plugin {
settings: OpenAugiSettings;
openAIService: OpenAIService;
fileService: FileService;
2025-04-17 18:52:08 +00:00
distillService: DistillService;
loadingIndicator: LoadingIndicator;
2025-04-16 15:33:35 +00:00
async onload() {
// Load settings
await this.loadSettings();
// Initialize services
this.initializeServices();
// Initialize loading indicator
this.app.workspace.onLayoutReady(() => {
const statusBar = this.addStatusBarItem();
if (statusBar.parentElement) {
this.loadingIndicator = new LoadingIndicator(statusBar.parentElement);
}
});
2025-04-16 15:33:35 +00:00
// Add command to manually parse a transcript file
this.addCommand({
id: 'parse-transcript',
name: 'Parse Transcript',
callback: async () => {
const activeFile = this.app.workspace.getActiveFile();
if (activeFile && activeFile.extension === 'md') {
await this.processTranscriptFile(activeFile);
} else {
new Notice('Please open a markdown transcript file first');
}
}
});
2025-04-17 18:52:08 +00:00
// Add command to distill linked notes
this.addCommand({
id: 'distill-notes',
name: 'Distill Linked Notes',
callback: async () => {
const activeFile = this.app.workspace.getActiveFile();
if (activeFile && activeFile.extension === 'md') {
await this.distillLinkedNotes(activeFile);
} else {
new Notice('Please open a markdown file first');
}
}
});
2025-04-16 15:33:35 +00:00
// Add settings tab
this.addSettingTab(new OpenAugiSettingTab(this.app, this));
}
/**
* Initialize services with current settings
*/
private initializeServices(): void {
this.openAIService = new OpenAIService(this.settings.apiKey);
this.fileService = new FileService(
this.app,
this.settings.summaryFolder,
this.settings.notesFolder
);
2025-04-17 18:52:08 +00:00
this.distillService = new DistillService(
this.app,
this.openAIService
);
2025-04-16 15:33:35 +00:00
}
/**
* 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);
}
2025-04-16 15:33:35 +00:00
/**
* 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
2025-04-16 15:33:35 +00:00
const content = await this.app.vault.read(file);
// Check if API key is set
if (!this.settings.apiKey) {
this.loadingIndicator?.hide();
2025-04-16 15:33:35 +00:00
new Notice('Please set your OpenAI API key in the plugin settings');
return;
}
2025-04-17 18:52:08 +00:00
// Display character and token count
new Notice(`Processing transcript: ${file.basename}\nCharacters: ${content.length}\nEst. Tokens: ${estimateTokens(content)}`);
2025-04-16 15:33:35 +00:00
// Update openAIService with latest API key
this.openAIService = new OpenAIService(this.settings.apiKey);
// Parse transcript
const parsedData = await this.openAIService.parseTranscript(content);
// Write result to files
await this.fileService.writeTranscriptFiles(file.basename, parsedData);
// Hide loading indicator
this.loadingIndicator?.hide();
// Show success message
2025-04-17 18:52:08 +00:00
new Notice(`Successfully parsed transcript: ${file.basename}\nCreated ${parsedData.notes.length} atomic notes`);
// 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);
2025-04-16 15:33:35 +00:00
} catch (error) {
// Hide loading indicator
this.loadingIndicator?.hide();
2025-04-16 15:33:35 +00:00
console.error('Failed to parse transcript:', error);
new Notice('Failed to parse transcript. Check console for details.');
}
}
2025-04-17 18:52:08 +00:00
/**
* Distill linked notes
* @param rootFile The root file containing links to distill
*/
private async distillLinkedNotes(rootFile: TFile): Promise<void> {
try {
// Show loading indicator
this.loadingIndicator?.show('Distilling linked notes');
// 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;
}
// Update services with latest API key
this.openAIService = new OpenAIService(this.settings.apiKey);
this.distillService = new DistillService(this.app, this.openAIService);
// Get root content for initial notice
const rootContent = await this.app.vault.read(rootFile);
new Notice(`Processing note: ${rootFile.basename}\nCharacters: ${rootContent.length}\nEst. Tokens: ${estimateTokens(rootContent)}`);
// Get linked files
const linkedFiles = await this.distillService.getLinkedNotes(rootFile);
// Aggregate linked content
const { content: linkedContent, sourceNotes } = await this.distillService.aggregateContent(linkedFiles);
// Combine content
const combinedContent = `# Root Note: ${rootFile.basename}\n\n${rootContent}\n\n${linkedContent}`;
const combinedTokens = estimateTokens(combinedContent);
// Show combined content notice
new Notice(`Combined content from ${linkedFiles.length} linked notes\nTotal characters: ${combinedContent.length}\nEst. total tokens: ${combinedTokens}`);
// Distill content from linked notes
const distilledData = await this.distillService.distillFromRootNote(
rootFile,
combinedContent,
sourceNotes
);
// Write result to files
const summaryPath = await this.fileService.writeDistilledFiles(rootFile, distilledData);
// Hide loading indicator
this.loadingIndicator?.hide();
// Show success message
new Notice(`Successfully distilled notes from: ${rootFile.basename}\nCreated ${distilledData.notes.length} atomic notes`);
// Open the summary file in a new tab
await this.openFileInNewTab(summaryPath);
} catch (error) {
// Hide loading indicator
this.loadingIndicator?.hide();
console.error('Failed to distill notes:', error);
new Notice('Failed to distill notes. Check console for details.');
}
}
2025-04-16 15:33:35 +00:00
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
// Reinitialize services with new settings
this.initializeServices();
}
}