├── .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 | 
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 | 
11 | 2. You can describe what the query should return in logseq block, then access the generation via /`Generate advanced query with ai`
12 | 
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 |
--------------------------------------------------------------------------------