- Fix styles

- Add showing consumed tokens
- Model can be configurable.
This commit is contained in:
vorotamoroz 2023-03-16 11:44:44 +09:00
parent cd9b506a1f
commit a8b333af07
6 changed files with 122 additions and 24 deletions

View file

@ -21,5 +21,6 @@ Note: We need the [API key of ChatGPT](https://platform.openai.com/account/api-k
2. That is all. Now, it is time to chat!
## Memo
- The dialogue is saved as a code block. We can continue the conversation anytime we want.
- The dialogue is saved as a code block. We can resume the conversation anytime we want.
- Also, they are searchable.
- Long consultation consumes many tokens. If the dialogue gets longer, starting a brand-new one is recommended.

57
main.ts
View file

@ -5,11 +5,15 @@ import axios from "axios";
interface RingASecretarySettings {
token: string;
defaultSystem: string;
model: string;
showConsumedTokens: boolean;
}
const DEFAULT_SETTINGS: RingASecretarySettings = {
token: "",
defaultSystem: "",
model: 'gpt-3.5-turbo-0301',
showConsumedTokens: false,
}
const roleSystem = "**SYSTEM**";
@ -23,7 +27,7 @@ const MarkToRole = {
"**USER**": "user",
"**ASSISTANT**": "assistant",
} as Record<DIALOGUE_ROLE, API_ROLE>;
const WAIT_MARK = " WAITING FOR RESPONSE...";
const WAIT_MARK = " <span class='ofx-thinking'></span>Please, bear with me.";
export default class RingASecretaryPlugin extends Plugin {
settings: RingASecretarySettings;
@ -58,18 +62,20 @@ export default class RingASecretaryPlugin extends Plugin {
const openai = new OpenAIApi(this.configuration);
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo-0301",
model: this.settings.model,
messages
// temperature: 0,
// max_tokens: 7,
});
const responseContent = response.data.choices[0].message?.content;
// const responseRole = response.data.choices[0].message?.role;
this.writeResponseToFile(responseContent ?? "");
if (this.settings.showConsumedTokens && response.data.usage) {
new Notice(`Consumed: ${response.data.usage.total_tokens} tokens`);
}
}
/** Write response to processing file by replacing placeholder. */
async writeResponseToFile(response: string) {
if (!this.processingFile) return;
const file = this.processingFile;
@ -90,7 +96,7 @@ export default class RingASecretaryPlugin extends Plugin {
const fx = el.createDiv({ text: "", cls: ["obsidian-fx"] });
MarkdownRenderer.renderMarkdown(source, fx, sourcePath, this)
const ops = el.createDiv({ text: "", cls: ["obsidian-fx-buttons"] });
const span = ops.createSpan({ text: "USER:" });
const span = ops.createEl("span", { text: "USER:", cls: "label" });
const input = ops.createEl("textarea");
const submit = ops.createEl("button", { text: "🤵" });
const secInfo = ctx.getSectionInfo(el);
@ -103,21 +109,25 @@ export default class RingASecretaryPlugin extends Plugin {
const c = new AbortController();
const submitFunc = async () => {
if (source.contains(WAIT_MARK)) {
new Notice("Some question is already in progress...", 5000)//TODO:MESSAGE
new Notice("Some question is already in progress... If not, please modify the code block directly.", 5000);
return
}
const f = app.vault.getAbstractFileByPath(sourcePath);
if (!f) {
new Notice("Could not edit the file", 3000);
new Notice("Could not find the file", 3000);
return;
}
if (!(f instanceof TFile)) {
new Notice("Could not edit the file", 3000);
new Notice("Could not find the file", 3000);
return;
}
const dataMain = source;
const text = input.value;
//TODO:ESCAPE MARKDOWN?
if (text.trim() == "") {
new Notice("Request is empty");
return;
}
//Note: ESCAPE MARKDOWN?
const newBody = `${dataMain}\n${roleUser}:${text} \n${roleAssistant}: ${WAIT_MARK}`;
await app.vault.process(f, (data) => {
@ -131,14 +141,19 @@ export default class RingASecretaryPlugin extends Plugin {
this.processingFile = f;
setTimeout(() => {
this.askToAI(newBody).catch(err => {
this.writeResponseToFile("Something has been occurred (PLUGIN)")
this.writeResponseToFile(`Something has been occurred: ${err?.message}`);
new Notice(`Something has been occurred: ${err?.message}`);
});
}, 20);
app.vault.trigger("modify", f);
}
submit.addEventListener("click", submitFunc, { signal: c.signal });
input.addEventListener("keydown", e => {
console.dir(e);
setTimeout(() => {
if (input.clientHeight < input.scrollHeight) {
input.style.height = `${input.scrollHeight + 4}px`;
}
}, 10);
if (e.key == "Enter" && (e.shiftKey) && !e.isComposing) {
e.preventDefault();
submitFunc();
@ -202,6 +217,16 @@ class RingASecretarySettingTab extends PluginSettingTab {
this.plugin.settings.token = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Model')
.setDesc('Model')
.addText(text => text
.setPlaceholder('gpt-3.5-turbo-0301')
.setValue(this.plugin.settings.model)
.onChange(async (value) => {
this.plugin.settings.model = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Initial prompt')
.setDesc('Initial prompt; i.e., instructions and prerequisites presented to the AI.')
@ -212,5 +237,13 @@ class RingASecretarySettingTab extends PluginSettingTab {
this.plugin.settings.defaultSystem = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Show consumed tokens')
.setDesc('When enabled, consumed tokens will be notified after each turn.')
.addToggle(toggle => toggle.setValue(this.plugin.settings.showConsumedTokens)
.onChange(async (value) => {
this.plugin.settings.showConsumedTokens = value;
await this.plugin.saveSettings();
}));
}
}

View file

@ -1,9 +1,9 @@
{
"id": "ring-a-secretary",
"name": "Ring a secretary",
"version": "0.0.1",
"version": "0.0.2",
"minAppVersion": "0.15.0",
"description": "ChatGPT powered secretary",
"description": "Yet another ChatGPT-powered digital secretary",
"author": "vorotamoroz",
"authorUrl": "https://github.com/vrtmrz",
"isDesktopOnly": false

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "ring-a-secretary",
"version": "0.0.1",
"version": "0.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ring-a-secretary",
"version": "0.0.1",
"version": "0.0.2",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",

View file

@ -1,7 +1,7 @@
{
"name": "ring-a-secretary",
"version": "0.0.1",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"version": "0.0.2",
"description": "Yet another ChatGPT-powered digital secretary",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",

View file

@ -16,18 +16,82 @@ If your plugin does not need CSS, delete this file.
justify-content: center;
align-items: center;
margin: auto;
padding: 4px;
}
.obsidian-fx-buttons button {
flex-grow: 0;
padding: 4px 8px 4px 8px;
margin: 4px 0px 4px 4px;
width: auto;
}
.obsidian-fx-buttons .label {
margin-right: 4px;
}
.is-phone .obsidian-fx-buttons .label {
display: none;
}
.obsidian-fx * {
margin: auto 2px;
margin: auto auto;
}
.obsidian-fx textarea {
flex-grow: 1;
min-height: 3ex;
resize: vertical;
-webkit-appearance: none;
outline: none;
outline-style: none;
box-shadow: none;
border: 0px;
border-bottom: 1px solid var(--divider-color);
padding: 2px 4px;
font-size: var(--font-adaptive-normal);
font-weight: var(--normal-weight);
line-height: var(--line-height);
font-family: var(--font-editor);
}
.obsidian-fx textarea:focus {
-webkit-appearance: none;
outline: none;
outline-style: none;
}
.ofx-thinking {
animation: ofx-thinking-animation 1s infinite;
}
.ofx-thinking::after {
content: " ......";
animation: ofx-thinking-animation 1s infinite;
}
@keyframes ofx-thinking-animation {
0% {
content: ". .....";
}
20% {
content: ".. ....";
}
40% {
content: "... ...";
}
60% {
content: ".... ..";
}
80% {
content: "..... .";
}
100% {
content: " ...... ";
}
}
/*
.obsidian-fx textarea .obsidian-fx input,
.obsidian-fx button {} */