mirror of
https://github.com/qgrail/obsidian-ai-assistant.git
synced 2026-07-22 07:40:27 +00:00
parent
2bb600224f
commit
358fbc2485
9 changed files with 621 additions and 1970 deletions
15
README.md
15
README.md
|
|
@ -1,10 +1,10 @@
|
|||
# Obsidian AI Assistant
|
||||
|
||||
Simple plugin to enable interactions with AI models such as [OpenAI ChatGPT](https://openai.com/blog/chatgpt), [OpenAI DALL·E2](https://openai.com/product/dall-e-2), [OpenAI Whisper](https://openai.com/research/whisper) directly from your [Obsidian](https://obsidian.md/) notes.
|
||||
Simple plugin to enable interactions with AI models such as [OpenAI ChatGPT](https://openai.com/blog/chatgpt), [OpenAI DALL·E](https://openai.com/product/dall-e-3), [OpenAI Whisper](https://openai.com/research/whisper) directly from your [Obsidian](https://obsidian.md/) notes.
|
||||
|
||||
The current available features of this plugin are:
|
||||
- 🤖 Text assistant with GPT-3.5 and GPT-4,
|
||||
- 🖼 Image generation with DALL·E2,
|
||||
- 🤖 Text assistant with GPT-4 and GPT-3.5,
|
||||
- 🖼 Image generation with DALL·E3 and DALL·E2,
|
||||
- 🗣 Speech to text with Whisper.
|
||||
|
||||
## How to use
|
||||
|
|
@ -44,14 +44,13 @@ The transcript will be immediately added to your note at your cursor location.
|
|||
|
||||
## Settings
|
||||
### Text Assistant
|
||||
- **Model choice**: choice of the text model. Currently `gpt-3.5-turbo` and `gpt-4` are supported. (There is still a
|
||||
[waitlist](https://openai.com/waitlist/gpt-4-api) to access GPT-4. If you have not been invited, GPT-4 will not work here.)
|
||||
- **Model choice**: choice of the text model. Currently `gpt-3.5-turbo`, `gpt-4-turbo` and `gpt-4` are supported.
|
||||
- **Maximum number of tokens** in the generated answer
|
||||
- **Replace or Add below**: In prompt mode, after having selected text from your note and enter your prompt,
|
||||
you can decide to replace your text by the assistant answer or to paste it bellow.
|
||||
|
||||
### Image Assistant
|
||||
- The model used is **DALL·E2**,
|
||||
- You can switch between **DALL·E3** and **DALL·E2**,
|
||||
- Change the default folder of generated images.
|
||||
|
||||
### Speech to Text
|
||||
|
|
@ -75,7 +74,3 @@ You can install the [AI Assistant](https://obsidian.md/plugins?id=ai-assistant)
|
|||
## Requirements
|
||||
|
||||
To use this plugin, you need an official API key from [OpenAI](https://platform.openai.com/account/api-keys).
|
||||
|
||||
## Limitations
|
||||
|
||||
This plugin is currently not compatible with iPadOS.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 MiB After Width: | Height: | Size: 5 MiB |
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ai-assistant",
|
||||
"name": "AI Assistant",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "AI Assistant plugin for Obsidian",
|
||||
"author": "Quentin Grail",
|
||||
|
|
|
|||
2384
package-lock.json
generated
2384
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-ai-assistant",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"description": "AI Assistant plugin for Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -12,17 +12,17 @@
|
|||
"author": "Quentin Grail",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"@types/node": "^20.8.10",
|
||||
"@typescript-eslint/eslint-plugin": "6.10.0",
|
||||
"@typescript-eslint/parser": "6.10.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"tslib": "2.6.2",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^3.2.1",
|
||||
"prettier": "^2.8.7"
|
||||
"openai": "^4.16.1",
|
||||
"prettier": "^3.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
src/main.ts
35
src/main.ts
|
|
@ -7,12 +7,13 @@ import {
|
|||
Setting,
|
||||
} from "obsidian";
|
||||
import { ChatModal, ImageModal, PromptModal, SpeechModal } from "./modal";
|
||||
import { OpenAI } from "./openai_api";
|
||||
import { OpenAIAssistant } from "./openai_api";
|
||||
|
||||
interface AiAssistantSettings {
|
||||
mySetting: string;
|
||||
apiKey: string;
|
||||
modelName: string;
|
||||
imageModelName: string;
|
||||
maxTokens: number;
|
||||
replaceSelection: boolean;
|
||||
imgFolder: string;
|
||||
|
|
@ -23,6 +24,7 @@ const DEFAULT_SETTINGS: AiAssistantSettings = {
|
|||
mySetting: "default",
|
||||
apiKey: "",
|
||||
modelName: "gpt-3.5-turbo",
|
||||
imageModelName: "dall-e-3",
|
||||
maxTokens: 500,
|
||||
replaceSelection: true,
|
||||
imgFolder: "AiAssistant/Assets",
|
||||
|
|
@ -31,10 +33,10 @@ const DEFAULT_SETTINGS: AiAssistantSettings = {
|
|||
|
||||
export default class AiAssistantPlugin extends Plugin {
|
||||
settings: AiAssistantSettings;
|
||||
openai: OpenAI;
|
||||
openai: OpenAIAssistant;
|
||||
|
||||
build_api() {
|
||||
this.openai = new OpenAI(
|
||||
this.openai = new OpenAIAssistant(
|
||||
this.settings.apiKey,
|
||||
this.settings.modelName,
|
||||
this.settings.maxTokens
|
||||
|
|
@ -76,7 +78,8 @@ export default class AiAssistantPlugin extends Plugin {
|
|||
editor.replaceSelection(answer.trim());
|
||||
}
|
||||
},
|
||||
false
|
||||
false,
|
||||
{}
|
||||
).open();
|
||||
},
|
||||
});
|
||||
|
|
@ -89,9 +92,11 @@ export default class AiAssistantPlugin extends Plugin {
|
|||
this.app,
|
||||
async (prompt: { [key: string]: string }) => {
|
||||
const answer = await this.openai.img_api_call(
|
||||
this.settings.imageModelName,
|
||||
prompt["prompt_text"],
|
||||
prompt["img_size"],
|
||||
parseInt(prompt["num_img"])
|
||||
parseInt(prompt["num_img"]),
|
||||
prompt["is_hd"] === "true"
|
||||
);
|
||||
if (answer) {
|
||||
const imageModal = new ImageModal(
|
||||
|
|
@ -103,7 +108,8 @@ export default class AiAssistantPlugin extends Plugin {
|
|||
imageModal.open();
|
||||
}
|
||||
},
|
||||
true
|
||||
true,
|
||||
{ model: this.settings.imageModelName }
|
||||
).open();
|
||||
},
|
||||
});
|
||||
|
|
@ -175,6 +181,7 @@ class AiAssistantSettingTab extends PluginSettingTab {
|
|||
dropdown
|
||||
.addOptions({
|
||||
"gpt-3.5-turbo": "gpt-3.5-turbo",
|
||||
"gpt-4-1106-preview": "gpt-4-turbo",
|
||||
"gpt-4": "gpt-4",
|
||||
})
|
||||
.setValue(this.plugin.settings.modelName)
|
||||
|
|
@ -234,6 +241,22 @@ class AiAssistantSettingTab extends PluginSettingTab {
|
|||
}
|
||||
})
|
||||
);
|
||||
new Setting(containerEl)
|
||||
.setName("Image Model Name")
|
||||
.setDesc("Select your model")
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"dall-e-3": "dall-e-3",
|
||||
"dall-e-2": "dall-e-2",
|
||||
})
|
||||
.setValue(this.plugin.settings.imageModelName)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.imageModelName = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.build_api();
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl("h3", { text: "Speech to Text" });
|
||||
new Setting(containerEl)
|
||||
|
|
|
|||
79
src/modal.ts
79
src/modal.ts
|
|
@ -13,16 +13,22 @@ export class PromptModal extends Modal {
|
|||
param_dict: { [key: string]: string };
|
||||
onSubmit: (input_dict: object) => void;
|
||||
is_img_modal: boolean;
|
||||
settings: { [key: string]: string };
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
onSubmit: (x: object) => void,
|
||||
is_img_modal: boolean
|
||||
is_img_modal: boolean,
|
||||
settings: { [key: string]: string }
|
||||
) {
|
||||
super(app);
|
||||
this.onSubmit = onSubmit;
|
||||
this.settings = settings;
|
||||
this.is_img_modal = is_img_modal;
|
||||
this.param_dict = { img_size: "256x256", num_img: "1" };
|
||||
this.param_dict = {
|
||||
num_img: "1",
|
||||
is_hd: "true",
|
||||
};
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
|
|
@ -74,18 +80,21 @@ export class PromptModal extends Modal {
|
|||
});
|
||||
desc1.innerText = "Resolution";
|
||||
|
||||
const desc2 = prompt_left_container.createEl("p", {
|
||||
cls: "description",
|
||||
});
|
||||
desc2.innerText = "Num images";
|
||||
|
||||
const prompt_right_container = prompt_container.createEl("div", {
|
||||
cls: "prompt-right-container",
|
||||
});
|
||||
|
||||
const resolution_dropdown =
|
||||
prompt_right_container.createEl("select");
|
||||
const options = ["256x256", "512x512", "1024x1024"];
|
||||
|
||||
let options = ["256x256", "512x512", "1024x1024"];
|
||||
this.param_dict["img_size"] = "256x256";
|
||||
|
||||
if (this.settings["model"] === "dall-e-3") {
|
||||
options = ["1024x1024", "1792x1024", "1024x1792"];
|
||||
this.param_dict["img_size"] = "1024x1024";
|
||||
}
|
||||
|
||||
options.forEach((option) => {
|
||||
const optionEl = resolution_dropdown.createEl("option", {
|
||||
text: option,
|
||||
|
|
@ -100,24 +109,44 @@ export class PromptModal extends Modal {
|
|||
this.param_dict["img_size"] = selectElement.value;
|
||||
});
|
||||
|
||||
const num_img_dropdown = prompt_right_container.createEl("select");
|
||||
const num_choices = [...Array(10).keys()].map((x) =>
|
||||
(x + 1).toString()
|
||||
);
|
||||
|
||||
num_choices.forEach((option) => {
|
||||
const optionEl = num_img_dropdown.createEl("option", {
|
||||
text: option,
|
||||
if (this.settings["model"] === "dall-e-2") {
|
||||
const desc2 = prompt_left_container.createEl("p", {
|
||||
cls: "description",
|
||||
});
|
||||
optionEl.value = option;
|
||||
if (option === this.param_dict["num_img"]) {
|
||||
optionEl.selected = true;
|
||||
}
|
||||
});
|
||||
num_img_dropdown.addEventListener("change", (event) => {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
this.param_dict["num_img"] = selectElement.value;
|
||||
});
|
||||
desc2.innerText = "Num images";
|
||||
|
||||
const num_img_dropdown =
|
||||
prompt_right_container.createEl("select");
|
||||
const num_choices = [...Array(10).keys()].map((x) =>
|
||||
(x + 1).toString()
|
||||
);
|
||||
num_choices.forEach((option) => {
|
||||
const optionEl = num_img_dropdown.createEl("option", {
|
||||
text: option,
|
||||
});
|
||||
optionEl.value = option;
|
||||
if (option === this.param_dict["num_img"]) {
|
||||
optionEl.selected = true;
|
||||
}
|
||||
});
|
||||
num_img_dropdown.addEventListener("change", (event) => {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
this.param_dict["num_img"] = selectElement.value;
|
||||
});
|
||||
}
|
||||
if (this.settings["model"] === "dall-e-3") {
|
||||
const desc2 = prompt_left_container.createEl("p", {
|
||||
cls: "description",
|
||||
});
|
||||
desc2.innerText = "HD?";
|
||||
const is_hd = prompt_right_container.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
is_hd.checked = this.param_dict["is_hd"] === "true";
|
||||
is_hd.addEventListener("change", (event) => {
|
||||
this.param_dict["is_hd"] = is_hd.checked.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
import { MarkdownRenderer, MarkdownView, Notice } from "obsidian";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { Configuration, OpenAIApi } = require("openai");
|
||||
import { OpenAI } from "openai";
|
||||
|
||||
class CustomFormData extends FormData {
|
||||
getHeaders() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// class CustomFormData extends FormData {
|
||||
// getHeaders() {
|
||||
// return {};
|
||||
// }
|
||||
// }
|
||||
|
||||
export class OpenAI {
|
||||
export class OpenAIAssistant {
|
||||
modelName: string;
|
||||
apiFun: any;
|
||||
maxTokens: number;
|
||||
apiKey: string;
|
||||
|
||||
constructor(apiKey: string, modelName: string, maxTokens: number) {
|
||||
const configuration = new Configuration({
|
||||
this.apiFun = new OpenAI({
|
||||
apiKey: apiKey,
|
||||
formDataCtor: CustomFormData,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
this.apiFun = new OpenAIApi(configuration);
|
||||
this.modelName = modelName;
|
||||
this.maxTokens = maxTokens;
|
||||
this.apiKey = apiKey;
|
||||
|
|
@ -107,17 +105,25 @@ export class OpenAI {
|
|||
};
|
||||
|
||||
img_api_call = async (
|
||||
model: string,
|
||||
prompt: string,
|
||||
img_size: string,
|
||||
num_img: number
|
||||
num_img: number,
|
||||
is_hd: boolean
|
||||
) => {
|
||||
try {
|
||||
const response = await this.apiFun.createImage({
|
||||
prompt: prompt,
|
||||
n: num_img,
|
||||
size: img_size,
|
||||
});
|
||||
return response.data.data.map((x: any) => x.url);
|
||||
const params: { [key: string]: string | number } = {};
|
||||
params.model = model;
|
||||
params.prompt = prompt;
|
||||
params.n = num_img;
|
||||
params.size = img_size;
|
||||
|
||||
if (model === "dall-e-3" && is_hd) {
|
||||
params.quality = "hd";
|
||||
}
|
||||
|
||||
const response = await this.apiFun.images.generate(params);
|
||||
return response.data.map((x: any) => x.url);
|
||||
} catch (err) {
|
||||
new Notice("## OpenAI API ## " + err);
|
||||
}
|
||||
|
|
@ -125,14 +131,11 @@ export class OpenAI {
|
|||
|
||||
whisper_api_call = async (input: Blob, language: string) => {
|
||||
try {
|
||||
const completion = await this.apiFun.createTranscription(
|
||||
input,
|
||||
"whisper-1",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
language
|
||||
);
|
||||
const completion = await this.apiFun.audio.transcriptions.create({
|
||||
file: input,
|
||||
model: "whisper-1",
|
||||
language: language,
|
||||
});
|
||||
return completion.data.text;
|
||||
} catch (err) {
|
||||
new Notice("## OpenAI API ## " + err);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@
|
|||
.prompt-modal-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
column-gap: 0.3em;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
|
|
@ -68,7 +70,8 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
row-gap: 0.1em;
|
||||
align-items: center;
|
||||
row-gap: 0.2em;
|
||||
}
|
||||
|
||||
.speech-modal-container{
|
||||
|
|
|
|||
Loading…
Reference in a new issue