Update models (#18)

* feat: add new model support
This commit is contained in:
Quentin 2023-11-07 14:01:09 +01:00 committed by GitHub
parent 2bb600224f
commit 358fbc2485
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 621 additions and 1970 deletions

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
}
}

View file

@ -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)

View file

@ -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();
});
}
}
}

View file

@ -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);

View file

@ -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{