diff --git a/CHANGELOG.md b/CHANGELOG.md index bf4e044..03b92b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 114e2c8..d7759c1 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/package.json b/package.json index 6ce0155..19d3352 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/ai_paster.ts b/src/ai_paster.ts index 724d7a9..63c0c5d 100644 --- a/src/ai_paster.ts +++ b/src/ai_paster.ts @@ -1,5 +1,6 @@ import { Paster } from "./paster"; import { Groq } from "groq-sdk"; +import { Predefine } from "./predefine"; export interface Message { role: "user" | "assistant"; @@ -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: [ diff --git a/src/paster.ts b/src/paster.ts index f9623d0..381b9c1 100644 --- a/src/paster.ts +++ b/src/paster.ts @@ -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 = /(?\$\{\s*(?\w+)\s*(\|(?.*?))?\})/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"); @@ -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) { @@ -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) { @@ -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 && @@ -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); diff --git a/src/predefine.ts b/src/predefine.ts index a3447c3..769efc0 100644 --- a/src/predefine.ts +++ b/src/predefine.ts @@ -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 = /(?\$\{\s*(?\w+)\s*(\|(?.*?))?\})/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 }; diff --git a/test/suite/extension.test.ts b/test/suite/extension.test.ts index d9dd878..9eaeb35 100644 --- a/test/suite/extension.test.ts +++ b/test/suite/extension.test.ts @@ -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); }); });