mirror of
https://github.com/manibatra/obsidian-text2anki-openai.git
synced 2026-07-22 07:40:28 +00:00
Init plugin
This commit is contained in:
parent
e8ebfe0fa9
commit
8c33e0cccb
3 changed files with 4084 additions and 97 deletions
323
main.ts
323
main.ts
|
|
@ -1,137 +1,266 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
import { App, Plugin, PluginSettingTab, Setting, Notice } from "obsidian";
|
||||
import { Configuration, OpenAIApi } from "openai";
|
||||
interface FlashcardGeneratorSettings {
|
||||
modelName: string;
|
||||
apiKey: string;
|
||||
deckName: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
}
|
||||
const DEFAULT_SETTINGS: FlashcardGeneratorSettings = {
|
||||
apiKey: "",
|
||||
deckName: "Generated Flashcards",
|
||||
modelName: "gpt-4",
|
||||
};
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
export default class FlashcardGeneratorPlugin extends Plugin {
|
||||
settings: FlashcardGeneratorSettings;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
openai: any;
|
||||
|
||||
async onload() {
|
||||
console.log("loading Flashcard Generator plugin");
|
||||
|
||||
await this.loadSettings();
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('This is a notice!');
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
name: 'Sample editor command',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
console.log(editor.getSelection());
|
||||
editor.replaceSelection('Sample Editor Command');
|
||||
}
|
||||
});
|
||||
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-complex',
|
||||
name: 'Open sample modal (complex)',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView) {
|
||||
// If checking is true, we're simply "checking" if the command can be run.
|
||||
// If checking is false, then we want to actually perform the operation.
|
||||
if (!checking) {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
id: "generate-flashcards",
|
||||
name: "Generate flashcards from bullet points in current file",
|
||||
callback: () => this.generateFlashcardsFromCurrentFile(),
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
|
||||
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
||||
// Using this function will automatically remove the event listener when this plugin is disabled.
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
});
|
||||
|
||||
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.addSettingTab(new FlashcardGeneratorSettingTab(this.app, this));
|
||||
|
||||
// this.registerDomEvent(document, "click", (event: MouseEvent) => {
|
||||
// console.log("click", event);
|
||||
// });
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData()
|
||||
);
|
||||
const configuration = new Configuration({
|
||||
apiKey: this.settings.apiKey,
|
||||
});
|
||||
|
||||
this.openai = new OpenAIApi(configuration);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
async generateFlashcardsFromCurrentFile(
|
||||
prompt = "",
|
||||
deckName: string = this.settings.deckName
|
||||
) {
|
||||
if (!this.settings.apiKey) {
|
||||
new Notice(
|
||||
"OpenAI API key is required for Flashcard Generator plugin to work"
|
||||
);
|
||||
}
|
||||
|
||||
const noteFile = this.app.workspace.getActiveFile(); // Currently Open Note
|
||||
if (!noteFile?.name) return; // Nothing Open
|
||||
|
||||
const text = await this.app.vault.read(noteFile);
|
||||
|
||||
if (!text) {
|
||||
new Notice(`Couldn't retrieve any text`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.openai.createChatCompletion({
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are an AnkiAssistant that will create flashcards to be used in the Anki App.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
prompt ||
|
||||
"You are an AnkiAssistant that will create flashcards to be used in the Anki App. You should use HTML to format parts of the output according to Anki format. Provide code examples and anything that assists in recall. Separate the 'Front' and 'Back' of each flashcard with ||. Only use it once in the flashcard. Every flashcard should be separated by '======='",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Create flashcards from the following text:" + text,
|
||||
},
|
||||
],
|
||||
model: this.settings.modelName,
|
||||
temperature: 0.2,
|
||||
presence_penalty: -0.2,
|
||||
});
|
||||
|
||||
const generatedFlashcards =
|
||||
response.data.choices[0].message.content;
|
||||
|
||||
let deckId = await this.getDeckId(deckName);
|
||||
|
||||
if (!deckId) {
|
||||
new Notice(
|
||||
`Could not find deck with name '${this.settings.deckName}'. Creating it`
|
||||
);
|
||||
await this.createDeck(deckName);
|
||||
deckId = await this.getDeckId(deckName);
|
||||
}
|
||||
|
||||
for (const flashcard of generatedFlashcards
|
||||
.trim()
|
||||
.split("=======")) {
|
||||
if (flashcard.length > 0) {
|
||||
let [front, back] = flashcard.split("||");
|
||||
front = front.replace("Front:", "").trim();
|
||||
back = back.replace("Back:", "").trim();
|
||||
|
||||
await this.addCardToDeck(deckId, {
|
||||
front,
|
||||
back,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new Notice("Flashcards generated and added to Anki!");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice("An error occurred while generating flashcards");
|
||||
}
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('Woah!');
|
||||
async getDeckId(name: string) {
|
||||
try {
|
||||
const decks = await this.invokeAnkiConnect("deckNamesAndIds");
|
||||
for (const deck of decks) {
|
||||
if (deck.name === name) {
|
||||
return deck.id;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
async createDeck(deckName: string) {
|
||||
try {
|
||||
await this.invokeAnkiConnect("createDeck", {
|
||||
deck: deckName,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice("An error occurred while creating the deck");
|
||||
}
|
||||
}
|
||||
|
||||
async addCardToDeck(deckId: number, note: { front: string; back: string }) {
|
||||
const { front, back } = note;
|
||||
|
||||
try {
|
||||
await this.invokeAnkiConnect("addNote", {
|
||||
note: {
|
||||
deckName: this.settings.deckName,
|
||||
modelName: "Basic",
|
||||
fields: {
|
||||
Front: front,
|
||||
Back: back,
|
||||
},
|
||||
options: {
|
||||
allowDuplicate: false,
|
||||
},
|
||||
tags: ["generated"],
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice("An error occurred while adding card to deck");
|
||||
}
|
||||
}
|
||||
|
||||
async invokeAnkiConnect(action: string, params?: any) {
|
||||
const response = await fetch("http://localhost:8765", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
action,
|
||||
params,
|
||||
version: 6,
|
||||
}),
|
||||
});
|
||||
|
||||
const parsedResponse = await response.json();
|
||||
|
||||
if (parsedResponse.hasOwnProperty("error")) {
|
||||
throw new Error(parsedResponse.error);
|
||||
}
|
||||
|
||||
return parsedResponse.result;
|
||||
}
|
||||
|
||||
onunload() {
|
||||
console.log("unloading Flashcard Generator plugin");
|
||||
}
|
||||
}
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
class FlashcardGeneratorSettingTab extends PluginSettingTab {
|
||||
plugin: FlashcardGeneratorPlugin;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
constructor(app: App, plugin: FlashcardGeneratorPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'});
|
||||
containerEl.createEl("h2", {
|
||||
text: "Flashcard Generator plugin Settings",
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(async (value) => {
|
||||
console.log('Secret: ' + value);
|
||||
this.plugin.settings.mySetting = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
.setName("OpenAI API Key")
|
||||
.setDesc("API key required for the OpenAI API")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Enter API key")
|
||||
.setValue(this.plugin.settings.apiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Anki Deck Name")
|
||||
.setDesc("Name of the Deck in Anki where flashcards will be added")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Enter Deck Name")
|
||||
.setValue(this.plugin.settings.deckName)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.deckName = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("OpenAI GPT Model Name")
|
||||
.setDesc("Name of the OpenAI GPT model to use")
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("gpt-4", "gpt-4")
|
||||
.addOption("davinci", "davinci")
|
||||
.setValue(this.plugin.settings.modelName)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.modelName = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3855
package-lock.json
generated
Normal file
3855
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,5 +20,8 @@
|
|||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue