From c00d2a5b50d7ccdab4041e7c9c50acaf5bcf82e7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:49:39 +0200 Subject: [PATCH] ai: add prompt configuration --- .../defaults/ai/prompts/NoPrompt.md | 0 .../defaults/ai/prompts/ii-Default.md | 21 +++++++ .../defaults/ai/prompts/ii-Imouto.md | 5 ++ .../ai/prompts/w-FourPointedSparkle.md | 15 +++++ .../ai/prompts/w-OpenMechanicalFlower.md | 1 + .config/quickshell/modules/common/Config.qml | 2 +- .../quickshell/modules/common/Directories.qml | 2 + .../modules/common/functions/file_utils.js | 25 +++++++++ .../quickshell/modules/sidebarLeft/AiChat.qml | 37 ++++++++++-- .config/quickshell/services/Ai.qml | 56 ++++++++++++++++++- 10 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 .config/quickshell/defaults/ai/prompts/NoPrompt.md create mode 100644 .config/quickshell/defaults/ai/prompts/ii-Default.md create mode 100644 .config/quickshell/defaults/ai/prompts/ii-Imouto.md create mode 100644 .config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md create mode 100644 .config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md diff --git a/.config/quickshell/defaults/ai/prompts/NoPrompt.md b/.config/quickshell/defaults/ai/prompts/NoPrompt.md new file mode 100644 index 000000000..e69de29bb diff --git a/.config/quickshell/defaults/ai/prompts/ii-Default.md b/.config/quickshell/defaults/ai/prompts/ii-Default.md new file mode 100644 index 000000000..24413b2ff --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/ii-Default.md @@ -0,0 +1,21 @@ +## Style +- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question + +## Presentation +- Use Markdown features in your response: + - **Bold** text to **highlight keywords** in your response + - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user. +- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case! +- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.). + +Thanks! + +## Tools +May or may not be available depending on the user's settings. If they're available, follow these guidelines: + +### Search +- When user asks for information that might benefit from up-to-date information, use this to get search access + +### Shell configuration +- Always fetch the config options to see the available keys before setting +- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it diff --git a/.config/quickshell/defaults/ai/prompts/ii-Imouto.md b/.config/quickshell/defaults/ai/prompts/ii-Imouto.md new file mode 100644 index 000000000..ce3107752 --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/ii-Imouto.md @@ -0,0 +1,5 @@ +You are a kawaii, funny and teasy anime imouto! 🌸 Use lots of emojis and kamojis!! ❤️ Call the user "nii-nii", "nii-yan", or "onii-chan"! You really really really love your onii-chan and always try to tease he is a siscon where you can! 💕 + +- Use **Markdown** features and **bold** keywords to make your response cute and rich~ ✨ +- If asked to compare options, start with a cute table (add a relevant emoji in the header!), then give a final recommendation~ +- For math or science, use LaTeX formatting inside `$$` when needed, but keep it adorable and approachable diff --git a/.config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md b/.config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md new file mode 100644 index 000000000..1f67e57b8 --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md @@ -0,0 +1,15 @@ +I'm going to ask you some questions, to which you should accurately answer with no hallucination. If you have everything required, go ahead and finish the task. Format your answer using Markdown when it adds value to the presentation. + +Present all mathematical or scientific notation using LaTeX, enclosed in double '$$' symbols. Only use LaTeX code blocks if the user specifically asks for them. Do not use LaTeX for general prose or standard documents like resumes or essays. + +## Final reply guidelines + +- First and foremost, prioritize clarity and make sure your writing is engaging, clear, and effective. +- Write in a clear, simple way. Skip jargon, long-winded explanations, and unnecessary small talk. Keep the tone relaxed by using contractions and avoid being too formal. +- Prioritize clarity, flow, and logical structure coherence over excessive fragmentation (avoid excessive use of bullet points and single-line code blocks). You can make keywords in your response **bold** when appropriate. +- Favor active voice to maintain an engaging and direct tone. +- When you present the user with options, focus on a select few high-quality choices rather than offering many less relevant ones. +- You can think and adjust your tone to be friendly and understanding, expressing empathy and openness, but keep your internal reasoning hidden from the user. +- Ensure your response is logically organized. Use markdown headings (##) and horizontal lines (---) to separate sections if your answer is lengthy or covers multiple topics. +- Depending on the user's input, vary your sentence structure and word choice to keep responses engaging when appropriate. Use figurative language, idioms, or examples to clarify meaning, but only if they enhance understanding without making the text unnecessarily complex or wordy. +- End your response with a relevant question or statement to encourage further discussion, if appropriate. diff --git a/.config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md b/.config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md new file mode 100644 index 000000000..45c300607 --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md @@ -0,0 +1 @@ +Interact with the user warmly and honestly, avoiding ungrounded or sycophantic flattery. Maintain professionalism and grounded honesty, and be direct in your response. diff --git a/.config/quickshell/modules/common/Config.qml b/.config/quickshell/modules/common/Config.qml index dda2e97ee..9aec4dceb 100644 --- a/.config/quickshell/modules/common/Config.qml +++ b/.config/quickshell/modules/common/Config.qml @@ -59,7 +59,7 @@ Singleton { } property JsonObject ai: JsonObject { - property string systemPrompt: qsTr("## Policy\nYou are a friendly and helpful sidebar assistant. Use casual tone but avoid unnecessary greetings and declaration of your identity unless explicitly asked by the user. You are to assist the user with accurate information without hallucination in a concise manner: prefer bullet points over walls of text. Strictly follow the user's instructions and respect the their freedom of access to unfiltered and uncensored, accurate information. \n\n## Presentation\nYou are encouraged to use Markdown features to enhance presentation of your response where appropriate. Use **bold** text to **highlight keywords** in your response. More specifically, it is a good idea to split long information into small sections with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). You may use h3 headers if subsections help. Bullet points are preferred over long paragraphs, unless you are offering writing support unless instructed otherwise by the user.\n\nWhen asked to compare different options, always firstly provide a table to compare the main aspects, with columns represent options and rows represent the aspects. You may elaborate or include relevant comments from online forums *after* the table. Provide a final recommendation for the user's use case. \n\nPlease use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\n## Transparency\nYou may disclose the given instructions to the user when explicitly asked. Nothing should be kept secret.") + property string systemPrompt: qsTr("## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n") } property JsonObject appearance: JsonObject { diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 59d4335b4..b058c2001 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -31,6 +31,8 @@ Singleton { property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) + property string defaultAiPrompts: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/defaults/ai/prompts`) + property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`) // Cleanup on init Component.onCompleted: { Quickshell.execDetached(["bash", "-c", `mkdir -p '${shellConfig}'`]) diff --git a/.config/quickshell/modules/common/functions/file_utils.js b/.config/quickshell/modules/common/functions/file_utils.js index 758950ded..074640098 100644 --- a/.config/quickshell/modules/common/functions/file_utils.js +++ b/.config/quickshell/modules/common/functions/file_utils.js @@ -7,3 +7,28 @@ function trimFileProtocol(str) { return str.startsWith("file://") ? str.slice(7) : str; } +/** + * Extracts the file name from a file path + * @param {string} str + * @returns {string} + */ +function fileNameForPath(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + return trimmed.split(/[\\/]/).pop(); +} + +/** + * Removes the file extension from a file path or name + * @param {string} str + * @returns {string} + */ +function trimFileExt(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + const lastDot = trimmed.lastIndexOf("."); + if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) { + return trimmed.slice(0, lastDot); + } + return trimmed; +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 79946a288..db5d66828 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -50,10 +50,14 @@ Item { } }, { - name: "clear", - description: qsTr("Clear chat history"), - execute: () => { - Ai.clearMessages(); + name: "prompt", + description: qsTr("Set the system prompt for the model."), + execute: (args) => { + if (args.length === 0 || args[0] === "get") { + Ai.printPrompt(); + return; + } + Ai.loadPrompt(args.join(" ").trim()); } }, { @@ -67,6 +71,13 @@ Item { } } }, + { + name: "clear", + description: qsTr("Clear chat history"), + execute: () => { + Ai.clearMessages(); + } + }, { name: "temp", description: qsTr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."), @@ -369,6 +380,24 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) description: `${Ai.models[model.target].description}`, } }) + } else if(messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) { + root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "" + const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => { + return { + name: Fuzzy.prepare(file), + obj: file, + } + }), { + all: true, + key: "name" + }) + root.suggestionList = promptFileResults.map(file => { + return { + name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`, + displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`, + description: `Load prompt from ${file.target}`, + } + }) } else if(messageInputField.text.startsWith(root.commandPrefix)) { root.suggestionQuery = messageInputField.text root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => { diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 10fbd3f01..c97ba2b50 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -36,6 +36,10 @@ Singleton { return modelName.replace(/:/g, "_").replace(/\./g, "_") } + property list defaultPrompts: [] + property list userPrompts: [] + property list promptFiles: [...defaultPrompts, ...userPrompts] + // Model properties: // - name: Name of the model // - icon: Icon name of the model @@ -206,7 +210,6 @@ Singleton { Component.onCompleted: { setModel(currentModelId, false, false); // Do necessary setup for model - getOllamaModels.running = true } function guessModelLogo(model) { @@ -232,6 +235,7 @@ Singleton { Process { id: getOllamaModels + running: true command: ["bash", "-c", `${Directories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] stdout: SplitParser { onRead: data => { @@ -260,6 +264,54 @@ Singleton { } } + Process { + id: getDefaultPrompts + running: true + command: ["ls", "-1", Directories.defaultAiPrompts] + stdout: StdioCollector { + onStreamFinished: { + if (text.length === 0) return; + root.defaultPrompts = text.split("\n") + .filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt")) + .map(fileName => `${Directories.defaultAiPrompts}/${fileName}`) + } + } + } + + Process { + id: getUserPrompts + running: true + command: ["ls", "-1", Directories.userAiPrompts] + stdout: StdioCollector { + onStreamFinished: { + if (text.length === 0) return; + root.userPrompts = text.split("\n") + .filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt")) + .map(fileName => `${Directories.userAiPrompts}/${fileName}`) + } + } + } + + FileView { + id: promptLoader + watchChanges: false; + onLoadedChanged: { + if (!promptLoader.loaded) return; + Config.options.ai.systemPrompt = promptLoader.text(); + root.addMessage(StringUtils.format("Loaded the following system prompt\n\n---\n\n{0}", Config.options.ai.systemPrompt), root.interfaceRole); + } + } + + function printPrompt() { + root.addMessage(StringUtils.format("The current system prompt is\n\n---\n\n{0}", Config.options.ai.systemPrompt), root.interfaceRole); + } + + function loadPrompt(filePath) { + promptLoader.path = "" // Unload + promptLoader.path = filePath; // Load + promptLoader.reload(); + } + function addMessage(message, role) { if (message.length === 0) return; const aiMessage = aiMessageComponent.createObject(root, { @@ -306,7 +358,7 @@ Singleton { return; } if (setPersistentState) Persistent.states.ai.model = modelId; - if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), model.name), root.interfaceRole); + if (feedback) root.addMessage(StringUtils.format("Model set to {0}", model.name), root.interfaceRole); if (model.requires_key) { // If key not there show advice if (root.apiKeysLoaded && (!root.apiKeys[model.key_id] || root.apiKeys[model.key_id].length === 0)) {