mirror of
https://github.com/benstuart0/on-this-day-i-obsidian.git
synced 2026-07-22 05:38:19 +00:00
233 lines
6.2 KiB
TypeScript
233 lines
6.2 KiB
TypeScript
import { MarkdownView, Notice, Plugin, TFolder } from "obsidian";
|
|
import OpenAI from "openai";
|
|
import { OnThisDayPluginSettings, DEFAULT_SETTINGS } from "src/settings";
|
|
import OnThisDaySettingTab from "src/OnThisDaySettingTab";
|
|
import { sysPrompt, constructPrompt } from "src/prompt";
|
|
import { isValidDateString, parseDateFromString } from "src/dateUtils";
|
|
import { getMarkdownFilesInFolder, buildOutputBlock } from "src/markdownUtils";
|
|
|
|
export default class OnThisDayPlugin extends Plugin {
|
|
settings: OnThisDayPluginSettings;
|
|
|
|
async onload() {
|
|
await this.loadSettings();
|
|
this.addSettingTab(new OnThisDaySettingTab(this.app, this));
|
|
|
|
this.addCommand({
|
|
id: "on-this-day-placeholder",
|
|
name: "Add Placeholder at Cursor",
|
|
callback: async () => {
|
|
await this.addPlaceholder();
|
|
},
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "generate-date-summaries",
|
|
name: "Generate Through The Years",
|
|
callback: async () => {
|
|
await this.generateDateSummaries();
|
|
},
|
|
});
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign(
|
|
{},
|
|
DEFAULT_SETTINGS,
|
|
await this.loadData()
|
|
);
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
|
|
//
|
|
// Main Function: Generate Date Summaries
|
|
//
|
|
async generateDateSummaries() {
|
|
// Ensure the command is run from a file with a valid date as its title.
|
|
const activeFile = this.app.workspace.getActiveFile();
|
|
if (!activeFile) {
|
|
new Notice("No active file found.");
|
|
return;
|
|
}
|
|
if (!isValidDateString(activeFile.basename)) {
|
|
new Notice(
|
|
`This command must be run from a file with a date title in the format ${this.settings.dateFormat}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const inputDateString = activeFile.basename;
|
|
|
|
// Parse the input date to extract month and day (ignoring the year)
|
|
const parsedDate = parseDateFromString(
|
|
inputDateString,
|
|
this.settings.dateFormat
|
|
);
|
|
if (!parsedDate) {
|
|
new Notice("Could not parse the date from the file title.");
|
|
return;
|
|
}
|
|
const month = parsedDate.getMonth(); // 0-based month
|
|
const day = parsedDate.getDate();
|
|
const currentYear = parsedDate.getFullYear();
|
|
|
|
// Get the folder object for the daily notes folder.
|
|
const folder = this.app.vault.getAbstractFileByPath(
|
|
this.settings.dailyNotesFolder
|
|
);
|
|
if (!folder || !(folder instanceof TFolder)) {
|
|
new Notice("Daily notes folder not found.");
|
|
return;
|
|
}
|
|
// Get all files in the folder
|
|
const files = getMarkdownFilesInFolder(folder);
|
|
|
|
// Build a map: year => concatenated content of matching daily notes
|
|
let yearToContent: Record<string, string> = {};
|
|
for (const file of files) {
|
|
const fileDate = parseDateFromString(
|
|
file.basename,
|
|
this.settings.dateFormat
|
|
);
|
|
if (!fileDate) continue;
|
|
if (
|
|
fileDate.getMonth() === month &&
|
|
fileDate.getDate() === day &&
|
|
fileDate.getFullYear() != currentYear
|
|
) {
|
|
const yearStr = fileDate.getFullYear().toString();
|
|
const content = await this.app.vault.read(file);
|
|
if (yearToContent[yearStr]) {
|
|
yearToContent[yearStr] += "\n" + content;
|
|
} else {
|
|
yearToContent[yearStr] = content;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Object.keys(yearToContent).length === 0) {
|
|
new Notice("No matching daily notes found for that date.");
|
|
return;
|
|
}
|
|
|
|
const prompt = constructPrompt(
|
|
yearToContent,
|
|
this.settings.customPrompt
|
|
);
|
|
|
|
// Show a loading indicator (persistent notice) until work is done.
|
|
const loadingNotice = new Notice("Generating summaries...", 0);
|
|
|
|
// Call the OpenAI API once with the entire JSON map.
|
|
let responseJSON: Record<string, string>;
|
|
try {
|
|
responseJSON = await this.callOpenAI(prompt);
|
|
} catch (error: any) {
|
|
loadingNotice.hide();
|
|
console.error("API call failed: " + error.message);
|
|
new Notice("API call failed: " + error.message);
|
|
return;
|
|
}
|
|
|
|
// Build the output markdown block.
|
|
const outputBlock = buildOutputBlock(
|
|
responseJSON,
|
|
inputDateString,
|
|
this.settings.dateFormat,
|
|
this.settings.horizontalRules,
|
|
this.settings.throughTheYearsHeader
|
|
);
|
|
|
|
// Insert the output block by looking for a placeholder marker defined in settings.
|
|
const placeholder = this.settings.placeholder;
|
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
if (!activeView) {
|
|
loadingNotice.hide();
|
|
new Notice("No active editor found.");
|
|
return;
|
|
}
|
|
const editor = (activeView as MarkdownView).editor;
|
|
const currentContent = editor.getValue();
|
|
let newContent = "";
|
|
|
|
if (currentContent.includes(placeholder)) {
|
|
// Replace the placeholder with the output block.
|
|
newContent = currentContent.replace(placeholder, outputBlock);
|
|
editor.setValue(newContent);
|
|
} else {
|
|
// If no placeholder is found, place block at cursor location
|
|
editor.replaceSelection(outputBlock);
|
|
}
|
|
|
|
// 9. Hide the loading notice and show a completion notice.
|
|
loadingNotice.hide();
|
|
new Notice("On This Day summaries inserted.");
|
|
}
|
|
|
|
//
|
|
// Call the OpenAI API
|
|
//
|
|
async callOpenAI(prompt: string): Promise<Record<string, string>> {
|
|
const client = new OpenAI({
|
|
apiKey: this.settings.openaiApiKey,
|
|
dangerouslyAllowBrowser: true,
|
|
});
|
|
|
|
try {
|
|
const response = await client.chat.completions.create({
|
|
model: this.settings.model,
|
|
messages: [
|
|
{
|
|
role: "system",
|
|
content: sysPrompt,
|
|
},
|
|
{
|
|
role: "user",
|
|
content: prompt,
|
|
},
|
|
],
|
|
temperature: 0.2,
|
|
});
|
|
|
|
// Validate the response structure
|
|
if (
|
|
!response.choices ||
|
|
response.choices.length === 0 ||
|
|
!response.choices[0].message?.content
|
|
) {
|
|
throw new Error("No valid response received from OpenAI.");
|
|
}
|
|
|
|
const content = response.choices[0].message.content;
|
|
try {
|
|
const result = JSON.parse(content);
|
|
return result;
|
|
} catch (parseError: any) {
|
|
console.error(
|
|
"Failed to parse JSON from OpenAI response:",
|
|
parseError
|
|
);
|
|
throw new Error(
|
|
"Failed to parse JSON from OpenAI response: " +
|
|
parseError.message
|
|
);
|
|
}
|
|
} catch (error: any) {
|
|
console.error("OpenAI API error:", error);
|
|
throw new Error("OpenAI API error: " + error.message);
|
|
}
|
|
}
|
|
|
|
addPlaceholder() {
|
|
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
|
if (!activeView) {
|
|
new Notice("No active editor found.");
|
|
return;
|
|
}
|
|
const editor = (activeView as MarkdownView).editor;
|
|
editor.replaceSelection(this.settings.placeholder);
|
|
}
|
|
}
|