Skip to content

Commit

Permalink
feat: 🆕 Add prompt file configuration for AI.
Browse files Browse the repository at this point in the history
  • Loading branch information
telesoho committed Aug 29, 2024
1 parent a5d39f5 commit 4c749f5
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 57 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 1.1.2 (2024.08.29)

- feat: Add prompt file configuration for AI.

## 1.1.0 (2024.08.18)

- feat: Added functionality to analyze clipboard content using a large language model with Groq.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Smartly paste for Markdown.

Enable this feature by setting `MarkdownPaste.enableAI` to true.
You can also customize how AI processes and outputs text by setting the `MarkdownPaste.aiSysMessage` parameter.
If you need to set more complex rules for AI, you can specify the AI's prompt file through `MarkdownPaste.aiPromptFile`.

- Paste smart

Expand Down Expand Up @@ -104,6 +105,12 @@ Smartly paste for Markdown.

Default value is `You are responsible for converting text content into Markdown format. If the original content is HTML, ignore any color or font settings and comments, but retain tables.`

- `MarkdownPaste.aiPromptFile`

The path to the file containing the AI model prompt. This file can be used to customize the AI model's behavior and provide additional context for the conversion process. The path can be absolute or relative to the workspace folder. Predefined variables are supported.

Default value is `${fileWorkspaceFolder}/.aiprompt`.

- `MarkdownPaste.aiTemperature`

The temperature setting for the LLM model.
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-markdown-paste-image",
"displayName": "Markdown Paste",
"description": "A smartly paste for markdown.",
"version": "1.1.1",
"version": "1.1.2",
"publisher": "telesoho",
"author": {
"name": "telesoho",
Expand Down Expand Up @@ -81,6 +81,12 @@
"default": "You are responsible for converting text content into Markdown format. If the original content is HTML, ignore any color or font settings and comments, but retain tables.",
"description": "The LLM model system message."
},
"MarkdownPaste.aiPromptFile": {
"type": "string",
"scope": "resource",
"default": "${fileWorkspaceFolder}/.aiprompt",
"description": "The LLM model prompt file."
},
"MarkdownPaste.aiTemperature": {
"type": "number",
"scope": "resource",
Expand Down
16 changes: 15 additions & 1 deletion src/ai_paster.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Paster } from "./paster";
import { Groq } from "groq-sdk";
import { Predefine } from "./predefine";

export interface Message {
role: "user" | "assistant";
Expand Down Expand Up @@ -35,7 +36,20 @@ export class AIPaster {
try {
const today = new Date().toISOString().split("T")[0];
let _model = model || this.config.aiModel;
const sysMessage = this.config.aiSysMessage;
let sysMessage = this.config.aiSysMessage;
try {
const fs = require("fs");
const path = require("path");
const promptFile = Predefine.replacePredefinedVars(
this.config.aiPromptFile
);
const aiPromptFilePath = path.resolve(promptFile);
if (fs.existsSync(aiPromptFilePath)) {
sysMessage = fs.readFileSync(aiPromptFilePath, "utf8");
}
} catch (error) {
console.error("Failed to read AI prompt file:", error);
}

const completion = await this.client.chat.completions.create({
messages: [
Expand Down
48 changes: 5 additions & 43 deletions src/paster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,45 +150,6 @@ class Paster {
});
}

/**
* Replace all predefined variable.
* @param str path
* @returns
*/
private static replacePredefinedVars(str: string) {
let predefine = new Predefine();
return Paster.replaceRegPredefinedVars(str, predefine);
}

/**
* Replace all predefined variable with Regexp.
* @param str path
* @returns
*/
private static replaceRegPredefinedVars(str: string, predefine: Predefine) {
const regex = /(?<var>\$\{\s*(?<name>\w+)\s*(\|(?<param>.*?))?\})/gm;

let ret: string = str;
let m: RegExpExecArray;

while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}

if (m.groups.name in predefine) {
ret = ret.replace(
m.groups.var,
predefine[m.groups.name](m.groups.param)
);
}
}

// User may be input a path with backward slashes (\), so need to replace all '\' to '/'.
return ret.replace(/\\/g, "/");
}

static getConfig() {
let editor = vscode.window.activeTextEditor;
if (!editor) return vscode.workspace.getConfiguration("MarkdownPaste");
Expand All @@ -215,7 +176,7 @@ class Paster {
): PasteImageContext | null {
if (!inputVal) return;

inputVal = Paster.replacePredefinedVars(inputVal);
inputVal = Predefine.replacePredefinedVars(inputVal);

//leading and trailling white space are invalidate
if (inputVal && inputVal.length !== inputVal.trim().length) {
Expand Down Expand Up @@ -440,7 +401,7 @@ class Paster {
let isApplicable = false;
for (const rule of rules) {
const re = new RegExp(rule.regex, rule.options);
const reps = Paster.replacePredefinedVars(rule.replace);
const reps = Predefine.replacePredefinedVars(rule.replace);
if (re.test(content)) {
content = content.replace(re, reps);
if (!applyAllRules) {
Expand Down Expand Up @@ -610,7 +571,8 @@ class Paster {
// get image destination path
let folderPathFromConfig = Paster.getConfig().path;

folderPathFromConfig = Paster.replacePredefinedVars(folderPathFromConfig);
folderPathFromConfig =
Predefine.replacePredefinedVars(folderPathFromConfig);

if (
folderPathFromConfig &&
Expand All @@ -628,7 +590,7 @@ class Paster {
let nameBase = Paster.getConfig().nameBase;
let nameSuffix = Paster.getConfig().nameSuffix;
imageFileName = namePrefix + nameBase + nameSuffix + extension;
imageFileName = Paster.replacePredefinedVars(imageFileName);
imageFileName = Predefine.replacePredefinedVars(imageFileName);

// image output path
let folderPath = path.dirname(filePath);
Expand Down
39 changes: 39 additions & 0 deletions src/predefine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,45 @@ class Predefine {

return selectText;
}

/**
* Replace all predefined variable.
* @param str path
* @returns
*/
static replacePredefinedVars(str: string) {
let predefine = new Predefine();
return Predefine.replaceRegPredefinedVars(str, predefine);
}

/**
* Replace all predefined variable with Regexp.
* @param str path
* @returns
*/
static replaceRegPredefinedVars(str: string, predefine: Predefine) {
const regex = /(?<var>\$\{\s*(?<name>\w+)\s*(\|(?<param>.*?))?\})/gm;

let ret: string = str;
let m: RegExpExecArray;

while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}

if (m.groups.name in predefine) {
ret = ret.replace(
m.groups.var,
predefine[m.groups.name](m.groups.param)
);
}
}

// User may be input a path with backward slashes (\), so need to replace all '\' to '/'.
return ret.replace(/\\/g, "/");
}
}

export { Predefine };
24 changes: 12 additions & 12 deletions test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,61 +114,61 @@ suite("Extension Tests", () => {
let predefine = new PredefineTest();
let str = "";
let ret_expect = "";
let ret = null;
let ret = "";

str = "${workspaceRoot},${datetime|aabbccddee},${fileExtname}";
ret_expect = "/telesoho/workspaceRoot,datetime('aabbccddee'),fileExtname";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${workspaceRoot}";
ret_expect = "/telesoho/workspaceRoot";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${ workspaceRoot }";
ret_expect = "/telesoho/workspaceRoot";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${datetime}";
ret_expect = "datetime('yyyyMMDDHHmmss')";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${ datetime | abc }";
ret_expect = "datetime(' abc ')";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${ datetime| abc}";
ret_expect = "datetime(' abc')";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${ datetime|ab c}";
ret_expect = "datetime('ab c')";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${notExist}";
ret_expect = "${notExist}";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${notExist}";
ret_expect = "${notExist}";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${relativeFileDirname}";
ret_expect = "filedir";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);

str = "${workspaceFolderBasename}";
ret_expect = "fileWorkspaceFolder";
ret = paster.Paster.replaceRegPredefinedVars(str, predefine);
ret = Predefine.replaceRegPredefinedVars(str, predefine);
assert.strictEqual(ret, ret_expect);
});
});

0 comments on commit 4c749f5

Please sign in to comment.