Compare commits

...

4 commits
0.0.5 ... main

Author SHA1 Message Date
vorotamoroz
b7f8e8a61a bump 2023-06-20 17:37:08 +09:00
vorotamoroz
8c57ebbe19 Fixed:
- Now the layout could not be broken even if there are multiple conversations.
Improved:
- We can interrupt the response from the assistant.
- We can remove the existing statements.
2023-06-20 17:36:04 +09:00
vorotamoroz
7ed2a836f9 Improved:
- No longer comments will be shown on a callout.
2023-05-09 17:41:43 +09:00
vorotamoroz
d6d79cf9f3 Improved:
- Response is now streaming
- Conversation is now customisable (i.e. temperature).

Fixed:
- Now conversation will be separated with an empty line to render markdown correctly.

Removed:
- No longer consuming tokens are counted.
2023-05-09 12:47:06 +09:00
5 changed files with 184 additions and 277 deletions

View file

@ -1,7 +1,7 @@
{
"id": "ring-a-secretary",
"name": "Ring a secretary",
"version": "0.0.5",
"version": "0.0.8",
"minAppVersion": "0.15.0",
"description": "Yet another ChatGPT-powered digital secretary",
"author": "vorotamoroz",

224
package-lock.json generated
View file

@ -1,22 +1,21 @@
{
"name": "ring-a-secretary",
"version": "0.0.5",
"version": "0.0.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ring-a-secretary",
"version": "0.0.5",
"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",

View file

@ -1,6 +1,6 @@
{
"name": "ring-a-secretary",
"version": "0.0.5",
"version": "0.0.8",
"description": "Yet another ChatGPT-powered digital secretary",
"main": "main.js",
"scripts": {
@ -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"
},

View file

@ -1,12 +1,13 @@
import { App, Editor, MarkdownRenderer, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
import { Configuration, OpenAIApi } from "openai"
import axios from "axios";
// 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 = {
@ -14,6 +15,7 @@ const DEFAULT_SETTINGS: RingASecretarySettings = {
defaultSystem: "",
model: 'gpt-3.5-turbo-0301',
showConsumedTokens: false,
template: "##temperature\n##top_p\n##max_tokens\n##presence_penalty"
}
const roleSystem = "**SYSTEM**";
@ -27,20 +29,55 @@ const MarkToRole = {
"**USER**": "user",
"**ASSISTANT**": "assistant",
} as Record<DIALOGUE_ROLE, API_ROLE>;
const WAIT_MARK = " <span class='ofx-thinking'></span>Please, bear with me.";
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: Configuration;
processingFile?: TFile;
async askToAI(dialogue: string) {
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 = "user" as "user" | "system" | "assistant";
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("#")) continue;
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)) {
@ -48,67 +85,133 @@ export default class RingASecretaryPlugin extends Plugin {
lineBuf = lineBuf.substring(dialogueRole.length + 1);
}
}
if (newRole != "" && buffer.trim() != "") {
messages.push({ role: currentRole, content: buffer.trim() });
if (newRole != "") {
if (buffer.trim() != "") {
messages.push({ role: currentRole == "" ? "user" : currentRole, content: buffer.trim() });
buffer = "";
}
currentRole = newRole;
buffer = "";
}
buffer += lineBuf + "\n";
}
if (buffer != "") {
messages.push({ role: currentRole, content: buffer });
if (buffer != "" && !buffer.contains(RESPONSE_START_MARK)) {
messages.push({ role: currentRole == "" ? "user" : currentRole, content: buffer });
}
const openai = new OpenAIApi(this.configuration);
const response = await openai.createChatCompletion({
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _this = this;
this.request = OpenAIExt.streamClientChatCompletion({
...options,
model: this.settings.model,
messages
// temperature: 0,
// max_tokens: 7,
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;
},
},
});
const responseContent = response.data.choices[0].message?.content;
this.writeResponseToFile(responseContent ?? "");
if (this.settings.showConsumedTokens && response.data.usage) {
new Notice(`Consumed: ${response.data.usage.total_tokens} tokens`);
}
/** 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;
}
}
/** Write response to processing file by replacing placeholder. */
async writeResponseToFile(response: string) {
if (!this.processingFile) return;
const file = this.processingFile;
await this.app.vault.process(file, (data) => {
return data.replace(WAIT_MARK, response);
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", file);
this.processingFile = undefined;
}
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();
axios.defaults.adapter = "http";
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 fx = el.createDiv({ text: "", cls: ["obsidian-fx"] });
const renderSource = `> [!consult]+\n${source.replace(/^/mg, "> ")}`;
MarkdownRenderer.renderMarkdown(renderSource, fx, sourcePath, this)
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: "🤵" });
const secInfo = ctx.getSectionInfo(el);
ops.appendChild(span);
ops.appendChild(input)
ops.appendChild(submit);
fx.appendChild(ops)
el.replaceWith(fx);
// ctx.
el.appendChild(fx);
el.appendChild(ops)
const c = new AbortController();
const submitFunc = async () => {
if (source.contains(WAIT_MARK)) {
if (source.contains(RESPONSE_START_MARK)) {
new Notice("Some question is already in progress... If not, please modify the code block directly.", 5000);
return
}
@ -128,7 +231,7 @@ export default class RingASecretaryPlugin extends Plugin {
return;
}
//Note: ESCAPE MARKDOWN?
const newBody = `${dataMain}\n${roleUser}: ${text} \n${roleAssistant}: ${WAIT_MARK}`;
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");
@ -138,10 +241,9 @@ export default class RingASecretaryPlugin extends Plugin {
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: ${err?.message}`);
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);
@ -169,7 +271,7 @@ export default class RingASecretaryPlugin extends Plugin {
editorCallback: async (editor: Editor, view: MarkdownView) => {
editor.replaceSelection(
`${"````aichat"}
${this.settings.defaultSystem ? `${roleSystem}: ${this.settings.defaultSystem}` : `#${roleSystem}: `}
${this.settings.template ? `${this.settings.template}\n` : ""}${this.settings.defaultSystem ? `${roleSystem}: ${this.settings.defaultSystem}` : `#${roleSystem}: `}
${"````"}
`
)
@ -185,12 +287,10 @@ ${"````"}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.configuration = new Configuration({ apiKey: this.settings.token });
}
async saveSettings() {
await this.saveData(this.settings);
this.configuration = new Configuration({ apiKey: this.settings.token });
}
}
@ -240,11 +340,13 @@ class RingASecretarySettingTab extends PluginSettingTab {
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)
.setName('Conversation customising template')
.setDesc('')
.addTextArea(text => text
.setPlaceholder("")
.setValue(this.plugin.settings.template)
.onChange(async (value) => {
this.plugin.settings.showConsumedTokens = value;
this.plugin.settings.template = value;
await this.plugin.saveSettings();
}));
}

View file

@ -6,7 +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 .obsidian-fx-buttons{
.markdown-reading-view .obsidian-fx .obsidian-fx-buttons {
display: none;
}
@ -33,7 +33,7 @@ If your plugin does not need CSS, delete this file.
}
.obsidian-fx textarea {
.obsidian-fx-buttons textarea {
flex-grow: 1;
min-height: 3ex;
resize: vertical;
@ -50,17 +50,21 @@ If your plugin does not need CSS, delete this file.
font-family: var(--font-editor);
}
.obsidian-fx textarea:focus {
.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::after {
.ofx-thinking::before {
content: " ......";
animation: ofx-thinking-animation 1s infinite;
}
@ -93,4 +97,4 @@ If your plugin does not need CSS, delete this file.
.callout[data-callout="consult"] {
--callout-icon: concierge-bell;
}
}