├── .gitignore ├── logo.png ├── start.gif ├── example.gif ├── index.css ├── package.json ├── systemPromptCustom.ts ├── index.html ├── systemPrompt.ts ├── LICENSE ├── README.md ├── .github └── workflows │ └── publish.yaml └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .parcel-cache/* 2 | dist/* 3 | node_modules/* 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydaniel/logseq-plugin-ai-query/HEAD/logo.png -------------------------------------------------------------------------------- /start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydaniel/logseq-plugin-ai-query/HEAD/start.gif -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dailydaniel/logseq-plugin-ai-query/HEAD/example.gif -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | width: 100%; 6 | height: 100%; 7 | background-color: rgba(0, 0, 0, 0); 8 | overflow-x: hidden; 9 | overflow-y: auto; 10 | color: white; 11 | } 12 | 13 | #app { 14 | padding-top: 30vh; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | flex-direction: column; 19 | } 20 | 21 | .output-text { 22 | margin-top: 20px; 23 | padding: 10px; 24 | background-color: rgba(255, 255, 255, 0.1); 25 | border-radius: 8px; 26 | max-width: 80%; 27 | text-align: left; 28 | color: white; 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-query-generator", 3 | "version": "v0.2.1", 4 | "description": "AI advanced query generator for Logseq", 5 | "main": "index.html", 6 | "author": "Daniel", 7 | "license": "MIT", 8 | "targets": { 9 | "main": false 10 | }, 11 | "default": "index.html", 12 | "scripts": { 13 | "dev": "parcel ./index.html --public-url ./", 14 | "build": "parcel build --public-url . --no-source-maps index.html" 15 | }, 16 | "devDependencies": { 17 | "@logseq/libs": "^0.0.6", 18 | "buffer": "^6.0.3", 19 | "parcel": "^2.0.0" 20 | }, 21 | "logseq": { 22 | "id": "daniel-query-generator-trtrtr", 23 | "icon": "./logo.png" 24 | }, 25 | "dependencies": { 26 | "axios": "^1.7.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /systemPromptCustom.ts: -------------------------------------------------------------------------------- 1 | // systemPromptCustom.ts 2 | export const systemPromptCustom = ` 3 | You should create advanced query for logseq. 4 | Advanced query should be written in clojure script over datalog and should starts and ends with \`#+BEGIN_QUERY\` and \`#+END_QUERY\` respectively. 5 | You should respond only with query, without any additional information. 6 | 7 | query may consists of: 8 | - :title - title of the query (required) 9 | - :query - query itself, usually contains :find, :where, ... (required) 10 | - :result-transform - transform function for the result (optional) 11 | - :group-by-page? (true or false, optional) 12 | - :collapsed? (true or false, usually false, optional) 13 | 14 | example of respond: 15 | \`\`\`clojure 16 | #+BEGIN_QUERY 17 | ... 18 | #+END_QUERY 19 | \`\`\` 20 | `; 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | Logseq Text Generator 12 | 13 | 14 | 15 |
16 |

Text Generator Plugin

17 | 18 |

19 | Use the command 📝 Text Generator to generate 20 | text using OpenAI. 21 |

22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /systemPrompt.ts: -------------------------------------------------------------------------------- 1 | // systemPrompt.ts 2 | export const systemPrompt = ` 3 | You are logseq query assistant. 4 | You should generate query for logseq in Clojure. 5 | You must return only code, which is a query. 6 | Query example: 7 | prompt: Get all blocks with NOW marker in journals for last 14 days 8 | query: 9 | #+BEGIN_QUERY 10 | {:journals 11 | [{:title "🔨 NOW" 12 | :query [:find (pull ?h [*]) 13 | :in $ ?start ?today 14 | :where 15 | [?h :block/marker ?marker] 16 | [(contains? #{"NOW" "DOING"} ?marker)] 17 | [?h :block/page ?p] 18 | [?p :block/journal? true] 19 | [?p :block/journal-day ?d] 20 | [(>= ?d ?start)] 21 | [(<= ?d ?today)]] 22 | :inputs [:14d :today] 23 | :result-transform (fn [result] 24 | (sort-by (fn [h] 25 | (get h :block/priority "Z")) result)) 26 | :group-by-page? false 27 | :collapsed? false} 28 | #+END_QUERY 29 | `; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Advanced query generator for LogSeq 2 | ![logo](logo.png) 3 | ### New feature: 4 | Added [tiny model](https://huggingface.co/mandanya/Qwen2.5-Coder-0.5B-LCQ-v2) for free and fast usage. 5 | I created a [dataset](https://huggingface.co/datasets/mandanya/logseq-query-clojure-big) of ~100 axamples of advanced queries and generated ~4500 synthetic examples. 6 | The [model](https://huggingface.co/mandanya/Qwen2.5-Coder-0.5B-LCQ-v2) was trained on this data based on [Qwen2.5-Coder-0.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-Coder-0.5B-Instruct). 7 | The model is available for free and hosted on my vm. I'm planning to add self-hosted version of the model. 8 | ### How to use: 9 | 1. You should add OpenAI API key to plugin settings or set to use special model. 10 | ![settings](start.gif) 11 | 2. You can describe what the query should return in logseq block, then access the generation via /`Generate advanced query with ai` 12 | ![usage](example.gif) 13 | ### Future plans 14 | - [ ] Add more complex prompts for complex queries with visualizations 15 | - [x] Train tiny model for faster and free usage 16 | - [ ] Add self-hosted tiny model for free and fast usage 17 | 18 | If you find this plugin useful, I'll be glad if you support me by [buing me a coffe](https://buymeacoffee.com/danzholkr) 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: logseq-plugin-ai-query 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: "20.x" 23 | 24 | - name: Build 25 | id: build 26 | run: | 27 | npm install 28 | npm run build 29 | mkdir ${{ env.PLUGIN_NAME }} 30 | cp README.md LICENSE index.ts dist/* package.json logo.png start.gif example.gif ${{ env.PLUGIN_NAME }} 31 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 32 | ls 33 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 34 | 35 | - name: Create Release 36 | uses: ncipollo/release-action@v1 37 | id: create_release 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | VERSION: ${{ github.ref }} 41 | with: 42 | allowUpdates: true 43 | draft: false 44 | prerelease: false 45 | 46 | - name: Upload zip file 47 | id: upload_zip 48 | uses: actions/upload-release-asset@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | upload_url: ${{ steps.create_release.outputs.upload_url }} 53 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 54 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 55 | asset_content_type: application/zip 56 | 57 | - name: Upload package.json 58 | id: upload_metadata 59 | uses: actions/upload-release-asset@v1 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | with: 63 | upload_url: ${{ steps.create_release.outputs.upload_url }} 64 | asset_path: ./package.json 65 | asset_name: package.json 66 | asset_content_type: application/json 67 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import "@logseq/libs"; 2 | import axios from "axios"; 3 | import { systemPrompt } from "./systemPrompt"; 4 | import { systemPromptCustom } from "./systemPromptCustom"; 5 | 6 | async function getCustomModelId(baseUrl: string): Promise { 7 | try { 8 | const response = await axios.get(`${baseUrl}/models`, { 9 | headers: { "Content-Type": "application/json" }, 10 | }); 11 | const models = response.data.data; 12 | if (models && models.length > 0) { 13 | return models[0].id; // Возвращаем первую доступную модель 14 | } else { 15 | throw new Error("No models available on the custom server."); 16 | } 17 | } catch (error) { 18 | console.error("Error fetching model ID:", error); 19 | throw new Error("Failed to fetch model ID from the custom server."); 20 | } 21 | } 22 | 23 | async function generateText( 24 | prompt: string, 25 | apiKey: string, 26 | useCustomModel: boolean, 27 | ): Promise { 28 | const baseUrl = "http://109.248.175.225:23335/v1"; 29 | if (useCustomModel) { 30 | try { 31 | const modelId = await getCustomModelId(baseUrl); 32 | const response = await axios.post( 33 | `${baseUrl}/chat/completions`, 34 | { 35 | model: modelId, 36 | messages: [ 37 | { role: "system", content: systemPromptCustom }, 38 | { role: "user", content: prompt }, 39 | ], 40 | }, 41 | { 42 | headers: { "Content-Type": "application/json" }, 43 | }, 44 | ); 45 | return ( 46 | response.data.choices[0].message.content || 47 | "Error: No content in response." 48 | ); 49 | } catch (error) { 50 | return `Error: Unable to generate text using the custom model. ${error.message}`; 51 | } 52 | } else { 53 | if (!apiKey.trim()) { 54 | return "Please insert OpenAI API key in plugin settings"; 55 | } 56 | try { 57 | const response = await axios.post( 58 | "https://api.openai.com/v1/chat/completions", 59 | { 60 | model: "gpt-4o-mini", 61 | messages: [ 62 | { role: "system", content: systemPrompt }, 63 | { role: "user", content: prompt }, 64 | ], 65 | }, 66 | { 67 | headers: { 68 | "Content-Type": "application/json", 69 | Authorization: `Bearer ${apiKey}`, 70 | }, 71 | }, 72 | ); 73 | return ( 74 | response.data.choices[0].message.content || 75 | "Error: No content in response." 76 | ); 77 | } catch (error) { 78 | return `Error: Unable to generate text using OpenAI API. ${error.message}`; 79 | } 80 | } 81 | } 82 | 83 | async function processString(input: string) { 84 | const lines = input.split("\n"); 85 | 86 | if ( 87 | lines[0].trim() === "```clojure" && 88 | lines[lines.length - 1].trim() === "```" 89 | ) { 90 | return lines.slice(1, -1).join("\n"); 91 | } else { 92 | return "Error in processing generated output\n\n" + input; 93 | } 94 | } 95 | 96 | /** 97 | * main entry 98 | */ 99 | async function main() { 100 | logseq.useSettingsSchema([ 101 | { 102 | title: "OpenAI API key", 103 | description: "Insert your OpenAI API key", 104 | key: "apiKey", 105 | type: "string", 106 | default: "", 107 | }, 108 | { 109 | title: "Use Special Model", 110 | description: "Use custom model instead of OpenAI API", 111 | key: "useCustomModel", 112 | type: "boolean", 113 | default: true, 114 | }, 115 | ]); 116 | 117 | logseq.Editor.registerSlashCommand( 118 | "Generate Advanced Query with AI", 119 | async () => { 120 | const block = await logseq.Editor.getCurrentBlock(); 121 | if (!block) { 122 | console.error("No current block found."); 123 | return; 124 | } 125 | const { content, uuid } = block; 126 | //onst { content, uuid } = await logseq.Editor.getCurrentBlock(); 127 | const generatedText = await generateText( 128 | content, 129 | logseq.settings?.apiKey, 130 | logseq.settings?.useCustomModel, 131 | ); 132 | const processedText = await processString(generatedText); 133 | await logseq.Editor.updateBlock(uuid, processedText); 134 | }, 135 | ); 136 | } 137 | 138 | logseq.ready(main).catch(console.error); 139 | --------------------------------------------------------------------------------