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';
|
2025-04-16 15:40:53 +00:00
|
|
|
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;
|
2025-04-16 15:40:53 +00:00
|
|
|
loadingIndicator: LoadingIndicator;
|
2025-04-16 15:33:35 +00:00
|
|
|
|
|
|
|
|
async onload() {
|
|
|
|
|
// Load settings
|
|
|
|
|
await this.loadSettings();
|
|
|
|
|
|
|
|
|
|
// Initialize services
|
|
|
|
|
this.initializeServices();
|
|
|
|
|
|
2025-04-16 15:40:53 +00:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
2025-04-16 15:40:53 +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 {
|
2025-04-16 15:40:53 +00:00
|
|
|
// 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) {
|
2025-04-16 15:40:53 +00:00
|
|
|
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);
|
|
|
|
|
|
2025-04-16 15:40:53 +00:00
|
|
|
// 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`);
|
2025-04-16 15:40:53 +00:00
|
|
|
|
|
|
|
|
// 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) {
|
2025-04-16 15:40:53 +00:00
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
}
|