mirror of
https://github.com/vrtmrz/ring-a-secretary.git
synced 2026-07-22 07:40:33 +00:00
Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7f8e8a61a | ||
|
|
8c57ebbe19 | ||
|
|
7ed2a836f9 | ||
|
|
d6d79cf9f3 | ||
|
|
4fcf0bea63 | ||
|
|
4b7990a5aa | ||
|
|
4c34ac4d99 | ||
|
|
164f8d8c28 | ||
|
|
45087f6753 | ||
|
|
bab429cb61 | ||
|
|
a8b333af07 |
9 changed files with 474 additions and 444 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 vorotamoroz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -21,5 +21,9 @@ 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.
|
||||
|
||||
## License
|
||||
- MIT License
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const context = await esbuild.context({
|
|||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
|
|
|
|||
216
main.ts
216
main.ts
|
|
@ -1,216 +0,0 @@
|
|||
import { App, Editor, MarkdownRenderer, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
import { Configuration, OpenAIApi } from "openai"
|
||||
import axios from "axios";
|
||||
|
||||
interface RingASecretarySettings {
|
||||
token: string;
|
||||
defaultSystem: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: RingASecretarySettings = {
|
||||
token: "",
|
||||
defaultSystem: "",
|
||||
}
|
||||
|
||||
const roleSystem = "**SYSTEM**";
|
||||
const roleUser = "**USER**";
|
||||
const roleAssistant = "**ASSISTANT**";
|
||||
type DIALOGUE_ROLE = "**SYSTEM**" | "**USER**" | "**ASSISTANT**";
|
||||
type API_ROLE = "system" | "user" | "assistant";
|
||||
|
||||
const MarkToRole = {
|
||||
"**SYSTEM**": "system",
|
||||
"**USER**": "user",
|
||||
"**ASSISTANT**": "assistant",
|
||||
} as Record<DIALOGUE_ROLE, API_ROLE>;
|
||||
const WAIT_MARK = " WAITING FOR RESPONSE...";
|
||||
|
||||
export default class RingASecretaryPlugin extends Plugin {
|
||||
settings: RingASecretarySettings;
|
||||
configuration: Configuration;
|
||||
processingFile?: TFile;
|
||||
async askToAI(dialogue: string) {
|
||||
const messages = [] as { role: "system" | "user" | "assistant", content: string }[];
|
||||
|
||||
let currentRole = "user" as "user" | "system" | "assistant";
|
||||
let buffer = "";
|
||||
const dialogLines = dialogue.split("\n");
|
||||
for (const line of dialogLines) {
|
||||
let lineBuf = line.trim();
|
||||
if (lineBuf.startsWith("#")) continue;
|
||||
let newRole = "" as "" | API_ROLE;
|
||||
for (const [dialogueRole, APIRole] of Object.entries(MarkToRole)) {
|
||||
if (lineBuf.startsWith(dialogueRole)) {
|
||||
newRole = APIRole;
|
||||
lineBuf = lineBuf.substring(dialogueRole.length + 1);
|
||||
}
|
||||
}
|
||||
if (newRole != "" && buffer.trim() != "") {
|
||||
messages.push({ role: currentRole, content: buffer.trim() });
|
||||
currentRole = newRole;
|
||||
buffer = "";
|
||||
}
|
||||
buffer += lineBuf + "\n";
|
||||
}
|
||||
if (buffer != "") {
|
||||
messages.push({ role: currentRole, content: buffer });
|
||||
}
|
||||
|
||||
const openai = new OpenAIApi(this.configuration);
|
||||
const response = await openai.createChatCompletion({
|
||||
|
||||
|
||||
model: "gpt-3.5-turbo-0301",
|
||||
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 ?? "");
|
||||
|
||||
}
|
||||
async writeResponseToFile(response: string) {
|
||||
if (!this.processingFile) return;
|
||||
const file = this.processingFile;
|
||||
await app.vault.process(file, (data) => {
|
||||
return data.replace(WAIT_MARK, response);
|
||||
});
|
||||
app.vault.trigger("modify", file);
|
||||
this.processingFile = undefined;
|
||||
}
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
axios.defaults.adapter = "http";
|
||||
this.configuration = new Configuration({ apiKey: this.settings.token });
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor("aichat", (source, el, ctx) => {
|
||||
|
||||
const sourcePath = ctx.sourcePath;
|
||||
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 input = ops.createEl("textarea");
|
||||
const submit = ops.createEl("button", { text: "🤵" });
|
||||
const secInfo = ctx.getSectionInfo(el);
|
||||
ops.appendChild(span);
|
||||
ops.appendChild(input)
|
||||
ops.appendChild(submit);
|
||||
fx.appendChild(ops)
|
||||
el.replaceWith(fx);
|
||||
// ctx.
|
||||
const c = new AbortController();
|
||||
const submitFunc = async () => {
|
||||
if (source.contains(WAIT_MARK)) {
|
||||
new Notice("Some question is already in progress...", 5000)//TODO:MESSAGE
|
||||
return
|
||||
}
|
||||
const f = app.vault.getAbstractFileByPath(sourcePath);
|
||||
if (!f) {
|
||||
new Notice("Could not edit the file", 3000);
|
||||
return;
|
||||
}
|
||||
if (!(f instanceof TFile)) {
|
||||
new Notice("Could not edit the file", 3000);
|
||||
return;
|
||||
}
|
||||
const dataMain = source;
|
||||
const text = input.value;
|
||||
//TODO:ESCAPE MARKDOWN?
|
||||
const newBody = `${dataMain}\n${roleUser}:${text} \n${roleAssistant}: ${WAIT_MARK}`;
|
||||
|
||||
await app.vault.process(f, (data) => {
|
||||
const dataList = data.split("\n");
|
||||
const dataBefore = dataList.slice(0, (secInfo?.lineStart ?? 0) + 1);
|
||||
|
||||
const dataAfter = dataList.slice(secInfo?.lineEnd)
|
||||
|
||||
return dataBefore.join("\n") + `\n${newBody}\n` + dataAfter.join("\n");
|
||||
});
|
||||
this.processingFile = f;
|
||||
setTimeout(() => {
|
||||
this.askToAI(newBody).catch(err => {
|
||||
this.writeResponseToFile("Something has been occurred (PLUGIN)")
|
||||
});
|
||||
}, 20);
|
||||
app.vault.trigger("modify", f);
|
||||
}
|
||||
submit.addEventListener("click", submitFunc, { signal: c.signal });
|
||||
input.addEventListener("keydown", e => {
|
||||
console.dir(e);
|
||||
if (e.key == "Enter" && (e.shiftKey) && !e.isComposing) {
|
||||
e.preventDefault();
|
||||
submitFunc();
|
||||
input.value = "";
|
||||
}
|
||||
}, { signal: c.signal, capture: true })
|
||||
});
|
||||
|
||||
|
||||
this.addCommand({
|
||||
id: 'obs-chat-new-dialogue',
|
||||
name: 'New dialogue',
|
||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
||||
editor.replaceSelection(
|
||||
`${"```aichat"}
|
||||
${this.settings.defaultSystem ? `${roleSystem}:${this.settings.defaultSystem}` : `#${roleSystem}:`}
|
||||
${"```"}
|
||||
`
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
this.addSettingTab(new RingASecretarySettingTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class RingASecretarySettingTab extends PluginSettingTab {
|
||||
plugin: RingASecretaryPlugin;
|
||||
|
||||
constructor(app: App, plugin: RingASecretaryPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'Settings for Ring a secretary' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Token')
|
||||
.setDesc('The token of ChatGPT')
|
||||
.addText(text => text
|
||||
.setPlaceholder('sk-TrbCVkcuvcshu7b....')
|
||||
.setValue(this.plugin.settings.token)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.token = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
new Setting(containerEl)
|
||||
.setName('Initial prompt')
|
||||
.setDesc('Initial prompt; i.e., instructions and prerequisites presented to the AI.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Behave as a British English speaker and answer my questions')
|
||||
.setValue(this.plugin.settings.defaultSystem)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultSystem = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "ring-a-secretary",
|
||||
"name": "Ring a secretary",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.8",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "ChatGPT powered secretary",
|
||||
"description": "Yet another ChatGPT-powered digital secretary",
|
||||
"author": "vorotamoroz",
|
||||
"authorUrl": "https://github.com/vrtmrz",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
224
package-lock.json
generated
224
package-lock.json
generated
|
|
@ -1,22 +1,21 @@
|
|||
{
|
||||
"name": "ring-a-secretary",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.8",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ring-a-secretary",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.8",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"axios": "^1.3.4",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"openai": "^3.2.1",
|
||||
"openai-ext": "^1.2.6",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
|
|
@ -825,23 +824,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
|
|
@ -931,18 +913,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
|
@ -989,15 +959,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
|
@ -1406,40 +1367,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
|
@ -1750,27 +1677,6 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
|
@ -1830,24 +1736,11 @@
|
|||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz",
|
||||
"integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/openai/node_modules/axios": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
"node_modules/openai-ext": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/openai-ext/-/openai-ext-1.2.6.tgz",
|
||||
"integrity": "sha512-bva4Ql5G1lwBGeIBu0vI3mI9FeTyW/l2b7cq5kdEYasujzSAQxxqN9rPi4lSvjNuKofp3C0ravUMdTZkUtKlxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.1",
|
||||
|
|
@ -1973,12 +1866,6 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||
|
|
@ -2785,23 +2672,6 @@
|
|||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
|
|
@ -2870,15 +2740,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
|
@ -2914,12 +2775,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
|
@ -3241,23 +3096,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
|
@ -3495,21 +3333,6 @@
|
|||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
|
@ -3559,26 +3382,11 @@
|
|||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"openai": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz",
|
||||
"integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axios": "^0.26.0",
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
"openai-ext": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/openai-ext/-/openai-ext-1.2.6.tgz",
|
||||
"integrity": "sha512-bva4Ql5G1lwBGeIBu0vI3mI9FeTyW/l2b7cq5kdEYasujzSAQxxqN9rPi4lSvjNuKofp3C0ravUMdTZkUtKlxw==",
|
||||
"dev": true
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.9.1",
|
||||
|
|
@ -3665,12 +3473,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||
|
|
|
|||
|
|
@ -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.8",
|
||||
"description": "Yet another ChatGPT-powered digital secretary",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
@ -15,11 +15,10 @@
|
|||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"axios": "^1.3.4",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"openai": "^3.2.1",
|
||||
"openai-ext": "^1.2.6",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
|
|
|
|||
353
src/main.ts
Normal file
353
src/main.ts
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
import { App, Editor, MarkdownRenderer, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
// import { Configuration, OpenAIApi } from "openai"
|
||||
import { ClientStreamChatCompletionConfig, OpenAIExt } from "openai-ext";
|
||||
|
||||
interface RingASecretarySettings {
|
||||
token: string;
|
||||
defaultSystem: string;
|
||||
model: string;
|
||||
showConsumedTokens: boolean;
|
||||
template: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: RingASecretarySettings = {
|
||||
token: "",
|
||||
defaultSystem: "",
|
||||
model: 'gpt-3.5-turbo-0301',
|
||||
showConsumedTokens: false,
|
||||
template: "##temperature\n##top_p\n##max_tokens\n##presence_penalty"
|
||||
}
|
||||
|
||||
const roleSystem = "**SYSTEM**";
|
||||
const roleUser = "**USER**";
|
||||
const roleAssistant = "**ASSISTANT**";
|
||||
type DIALOGUE_ROLE = "**SYSTEM**" | "**USER**" | "**ASSISTANT**";
|
||||
type API_ROLE = "system" | "user" | "assistant";
|
||||
|
||||
const MarkToRole = {
|
||||
"**SYSTEM**": "system",
|
||||
"**USER**": "user",
|
||||
"**ASSISTANT**": "assistant",
|
||||
} as Record<DIALOGUE_ROLE, API_ROLE>;
|
||||
const RESPONSE_START_MARK = "<span class='ofx-response-start'></span>";
|
||||
const RESPONSE_END_MARK = "<span class='ofx-thinking'></span>";
|
||||
const WAIT_MARK_INITIAL = `${RESPONSE_START_MARK}Please, bear with me.`;
|
||||
|
||||
function replaceStringBetweenMarks(source: string, newPiece: string, fromMark: string, toMark: string) {
|
||||
const start = source.indexOf(fromMark);
|
||||
const end = source.indexOf(toMark);
|
||||
if (start == -1 || end == -1) return source;
|
||||
return source.substring(0, start) + newPiece + source.substring(end + toMark.length);
|
||||
}
|
||||
|
||||
// const WAIT_MARK = "<span class='ofx-response-start'></span><span class='ofx-thinking'></span>Please, bear with me.";
|
||||
export default class RingASecretaryPlugin extends Plugin {
|
||||
settings: RingASecretarySettings;
|
||||
configuration: ClientStreamChatCompletionConfig;
|
||||
request?: XMLHttpRequest;
|
||||
async askToAI(dialogue: string, targetFile: TFile, targetKey: string) {
|
||||
if (this.request) {
|
||||
new Notice("Some question is already in progress...", 5000);
|
||||
return
|
||||
}
|
||||
const messages = [] as { role: "system" | "user" | "assistant", content: string }[];
|
||||
let currentRole = "" as "" | "user" | "system" | "assistant";
|
||||
let buffer = "";
|
||||
// let temperature: number | undefine = 0; /* 0 -> 1[def] -> 2 */
|
||||
// let top_p: number | undefine = undefined; /* 1 def */
|
||||
// let max_tokens: number | undefined = undefined; /* inf def */
|
||||
// let presence_penalty: number | undefined = undefined; /* -2 -> 2[def] -> 2*/
|
||||
const options = {} as {
|
||||
temperature?: number,
|
||||
top_p?: number,
|
||||
max_tokens?: number,
|
||||
presence_penalty?: number
|
||||
}
|
||||
const dialogLines = dialogue.split("\n");
|
||||
const params = ["temperature", "top_p", "max_tokens", "presence_penalty"] as ("temperature" | "top_p" | "max_tokens" | "presence_penalty")[];
|
||||
for (const line of dialogLines) {
|
||||
let lineBuf = line.trim();
|
||||
if (lineBuf.startsWith("#")) {
|
||||
for (const param of params) {
|
||||
const paramHead = `##${param}`;
|
||||
if (lineBuf.startsWith(paramHead)) {
|
||||
const val = lineBuf.substring(paramHead.length).trim();
|
||||
if (val != "") options[param] = Number.parseFloat(val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let newRole = "" as "" | API_ROLE;
|
||||
for (const [dialogueRole, APIRole] of Object.entries(MarkToRole)) {
|
||||
if (lineBuf.startsWith(dialogueRole)) {
|
||||
newRole = APIRole;
|
||||
lineBuf = lineBuf.substring(dialogueRole.length + 1);
|
||||
}
|
||||
}
|
||||
if (newRole != "") {
|
||||
if (buffer.trim() != "") {
|
||||
messages.push({ role: currentRole == "" ? "user" : currentRole, content: buffer.trim() });
|
||||
buffer = "";
|
||||
}
|
||||
currentRole = newRole;
|
||||
}
|
||||
buffer += lineBuf + "\n";
|
||||
}
|
||||
if (buffer != "" && !buffer.contains(RESPONSE_START_MARK)) {
|
||||
messages.push({ role: currentRole == "" ? "user" : currentRole, content: buffer });
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const _this = this;
|
||||
this.request = OpenAIExt.streamClientChatCompletion({
|
||||
...options,
|
||||
model: this.settings.model,
|
||||
messages,
|
||||
}, {
|
||||
apiKey: this.settings.token,
|
||||
allEnvsAllowed: true,
|
||||
handler: {
|
||||
// Content contains the string draft, which may be partial. When isFinal is true, the completion is done.
|
||||
onContent(content, isFinal, xhr) {
|
||||
_this.applyResponse(content, isFinal, targetKey, targetFile, dialogue);
|
||||
},
|
||||
onDone(xhr) {
|
||||
//TODO
|
||||
_this.request = undefined;
|
||||
},
|
||||
onError(error, status, xhr) {
|
||||
_this.applyResponse(error.message, true, targetKey, targetFile, dialogue);
|
||||
console.error(error);
|
||||
_this.request = undefined;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/** Apply the response to the note while streaming, and write response to the file when it has been done. */
|
||||
async applyResponse(response: string, isDone: boolean, targetKey: string, targetFile: TFile, basedDialogue: string) {
|
||||
if (!isDone) {
|
||||
const data = basedDialogue;
|
||||
const out = replaceStringBetweenMarks(data, `${RESPONSE_START_MARK}${response}<span id='${targetKey}'></span>`, RESPONSE_START_MARK, RESPONSE_END_MARK);
|
||||
const els = document.querySelectorAll(`span#${targetKey}`);
|
||||
const el = els.length > 0 ? els[0] as HTMLSpanElement : undefined;
|
||||
if (el) {
|
||||
const p = el.matchParent("div.obsidian-fx") as HTMLDivElement;
|
||||
this.renderMarkdownToEl(p, out, targetFile.path)
|
||||
}
|
||||
return;
|
||||
}
|
||||
await this.app.vault.process(targetFile, (data) => {
|
||||
const out = replaceStringBetweenMarks(data, `${response}\n`, RESPONSE_START_MARK, RESPONSE_END_MARK);
|
||||
return out;
|
||||
});
|
||||
this.app.vault.trigger("modify", targetFile);
|
||||
|
||||
}
|
||||
renderMarkdownToEl(fx: HTMLDivElement, source: string, sourcePath: string) {
|
||||
// All pragmatic comments will be hidden, but just simple comments are shown as with strikethrough.
|
||||
const renderSource1 = `${source.replace(/^(##.*)$/mg, "<!-- $1 -->").replace(/^#(.*?)\s*$/mg, "~~$1~~")}`;
|
||||
// All lines can be deleted or cancelled
|
||||
const renderSource2 = renderSource1.split("\n").map((e, i) =>
|
||||
e.contains(RESPONSE_START_MARK) ? e.split(roleAssistant).join(`<a href="#" class="fx-toggle-comment" data-line="${i}" data-cancel="1">✋</a>${roleAssistant}`) : e.split(roleAssistant).join(`<a href="#" class="fx-toggle-comment" data-line="${i}">🗑️</a>${roleAssistant}`).split(roleUser).join(`<a href="#" class="fx-toggle-comment" data-line="${i}">🗑️</a>${roleUser}`))
|
||||
.join("\n")
|
||||
fx.replaceChildren("");
|
||||
|
||||
const renderSource = `> [!consult]+\n${renderSource2.replace(/^/mg, "> ")}`;
|
||||
MarkdownRenderer.renderMarkdown(renderSource, fx, sourcePath, this).then(() => {
|
||||
document.querySelectorAll(".fx-toggle-comment").forEach((e) => {
|
||||
e.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
const el = e.matchParent("div.obsidian-fx") as HTMLDivElement;
|
||||
if (!el) return;
|
||||
const filename = el.getAttribute("data-source-path");
|
||||
const line = Number.parseInt(el.getAttribute("data-context-line") as string);
|
||||
const commentLine = Number.parseInt(e.getAttribute("data-line") as string);
|
||||
if (!filename) return;
|
||||
const f = this.app.vault.getAbstractFileByPath(filename);
|
||||
if (!(f instanceof TFile)) {
|
||||
new Notice("Could not find the file", 3000);
|
||||
return;
|
||||
}
|
||||
const cancel = e.getAttribute("data-cancel");
|
||||
if (cancel == "1") {
|
||||
if (this.request) {
|
||||
this.request.abort();
|
||||
this.request = undefined;
|
||||
new Notice("Cancelled!", 5000);
|
||||
}
|
||||
}
|
||||
this.app.vault.process(f, (data) => {
|
||||
const f = data.split("\n");
|
||||
// Toggle commenting out, or remove it if it has been cancelled.
|
||||
f[line + commentLine + 1] = cancel == "1" ? "" : f[line + commentLine + 1].startsWith("#") ? f[line + commentLine + 1].substring(1) : `#${f[line + commentLine + 1]}`;
|
||||
return f.join("\n");
|
||||
});
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor("aichat", (source, el, ctx) => {
|
||||
const uniq = `obsidian-fx-${Date.now()}-${~~(Math.random() * 10000)}`;
|
||||
const fx = el.createDiv({ text: "", cls: ["obsidian-fx", uniq] });
|
||||
// Store the path and the line to modify later.
|
||||
const sourcePath = ctx.sourcePath;
|
||||
const secInfo = ctx.getSectionInfo(el);
|
||||
fx.setAttribute("data-source-path", sourcePath);
|
||||
fx.setAttribute("data-context-line", `${secInfo?.lineStart}`);
|
||||
this.renderMarkdownToEl(fx, source, sourcePath);
|
||||
const ops = el.createDiv({ text: "", cls: ["obsidian-fx-buttons"] });
|
||||
const span = ops.createEl("span", { text: "USER:", cls: "label" });
|
||||
const input = ops.createEl("textarea", { cls: "ai-dialogue-input" });
|
||||
const submit = ops.createEl("button", { text: "🤵" });
|
||||
ops.appendChild(span);
|
||||
ops.appendChild(input)
|
||||
ops.appendChild(submit);
|
||||
el.appendChild(fx);
|
||||
el.appendChild(ops)
|
||||
const c = new AbortController();
|
||||
const submitFunc = async () => {
|
||||
if (source.contains(RESPONSE_START_MARK)) {
|
||||
new Notice("Some question is already in progress... If not, please modify the code block directly.", 5000);
|
||||
return
|
||||
}
|
||||
const f = this.app.vault.getAbstractFileByPath(sourcePath);
|
||||
if (!f) {
|
||||
new Notice("Could not find the file", 3000);
|
||||
return;
|
||||
}
|
||||
if (!(f instanceof TFile)) {
|
||||
new Notice("Could not find the file", 3000);
|
||||
return;
|
||||
}
|
||||
const dataMain = source;
|
||||
const text = input.value;
|
||||
if (text.trim() == "") {
|
||||
new Notice("Request is empty");
|
||||
return;
|
||||
}
|
||||
//Note: ESCAPE MARKDOWN?
|
||||
const newBody = `${dataMain}\n${roleUser}: ${text}\n \n${roleAssistant}: ${WAIT_MARK_INITIAL}<span id='${uniq}'></span>${RESPONSE_END_MARK}`;
|
||||
|
||||
await this.app.vault.process(f, (data) => {
|
||||
const dataList = data.split("\n");
|
||||
const dataBefore = dataList.slice(0, (secInfo?.lineStart ?? 0) + 1);
|
||||
|
||||
const dataAfter = dataList.slice(secInfo?.lineEnd)
|
||||
|
||||
return dataBefore.join("\n") + `\n${newBody}\n` + dataAfter.join("\n");
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.askToAI(newBody, f, uniq).catch(err => {
|
||||
this.applyResponse(`Something has been occurred: ${err?.message}`, true, uniq, f, newBody);
|
||||
new Notice(`Something has been occurred: ${err?.message}`);
|
||||
});
|
||||
}, 20);
|
||||
this.app.vault.trigger("modify", f);
|
||||
}
|
||||
submit.addEventListener("click", submitFunc, { signal: c.signal });
|
||||
input.addEventListener("keydown", 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();
|
||||
input.value = "";
|
||||
}
|
||||
}, { signal: c.signal, capture: true })
|
||||
});
|
||||
|
||||
|
||||
this.addCommand({
|
||||
id: 'new-dialogue',
|
||||
name: 'New dialogue',
|
||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
||||
editor.replaceSelection(
|
||||
`${"````aichat"}
|
||||
${this.settings.template ? `${this.settings.template}\n` : ""}${this.settings.defaultSystem ? `${roleSystem}: ${this.settings.defaultSystem}` : `#${roleSystem}: `}
|
||||
${"````"}
|
||||
`
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
this.addSettingTab(new RingASecretarySettingTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class RingASecretarySettingTab extends PluginSettingTab {
|
||||
plugin: RingASecretaryPlugin;
|
||||
|
||||
constructor(app: App, plugin: RingASecretaryPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'Settings for Ring a secretary' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Token')
|
||||
.setDesc('The token of ChatGPT')
|
||||
.addText(text => text
|
||||
.setPlaceholder('sk-TrbCVkcuvcshu7b....')
|
||||
.setValue(this.plugin.settings.token)
|
||||
.onChange(async (value) => {
|
||||
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.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Behave as a British English speaker and answer my questions')
|
||||
.setValue(this.plugin.settings.defaultSystem)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultSystem = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
new Setting(containerEl)
|
||||
.setName('Conversation customising template')
|
||||
.setDesc('')
|
||||
.addTextArea(text => text
|
||||
.setPlaceholder("")
|
||||
.setValue(this.plugin.settings.template)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.template = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
85
styles.css
85
styles.css
|
|
@ -6,8 +6,7 @@ available in the app when your plugin is enabled.
|
|||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
.markdown-reading-view .obsidian-fx input,
|
||||
.markdown-reading-view .obsidian-fx button {
|
||||
.markdown-reading-view .obsidian-fx .obsidian-fx-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -16,18 +15,86 @@ If your plugin does not need CSS, delete this file.
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.obsidian-fx * {
|
||||
margin: auto 2px;
|
||||
.obsidian-fx-buttons button {
|
||||
flex-grow: 0;
|
||||
padding: 4px 8px 4px 8px;
|
||||
margin: 4px 0px 4px 4px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.obsidian-fx textarea {
|
||||
.obsidian-fx-buttons .label {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.is-phone .obsidian-fx-buttons .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.obsidian-fx-buttons 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 .obsidian-fx input,
|
||||
.obsidian-fx button {} */
|
||||
|
||||
.obsidian-fx-buttons textarea:focus {
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
outline-style: none;
|
||||
}
|
||||
|
||||
.obsidian-fx:has(.callout.is-collapsed)+.obsidian-fx-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ofx-thinking {
|
||||
animation: ofx-thinking-animation 1s infinite;
|
||||
}
|
||||
|
||||
.ofx-thinking::before {
|
||||
content: " ......";
|
||||
animation: ofx-thinking-animation 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes ofx-thinking-animation {
|
||||
0% {
|
||||
content: ". .....";
|
||||
}
|
||||
|
||||
20% {
|
||||
content: ".. ....";
|
||||
}
|
||||
|
||||
40% {
|
||||
content: "... ...";
|
||||
}
|
||||
|
||||
60% {
|
||||
content: ".... ..";
|
||||
}
|
||||
|
||||
80% {
|
||||
content: "..... .";
|
||||
}
|
||||
|
||||
100% {
|
||||
content: " ...... ";
|
||||
}
|
||||
}
|
||||
|
||||
.callout[data-callout="consult"] {
|
||||
--callout-icon: concierge-bell;
|
||||
}
|
||||
Loading…
Reference in a new issue