From ed5b71de1fea4fb35a95bd780d22b4e33ef0585c Mon Sep 17 00:00:00 2001
From: jaibuu <jaibuu@gmail.com>
Date: Sun, 2 Feb 2025 12:36:26 -0500
Subject: Better support for reasoning models, subdivision of tasks supports
 nesting and ignores empty lines, typo fix

---
 src/ollama.tsx | 117 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 98 insertions(+), 19 deletions(-)

(limited to 'src/ollama.tsx')

diff --git a/src/ollama.tsx b/src/ollama.tsx
index ecc7565..4451beb 100644
--- a/src/ollama.tsx
+++ b/src/ollama.tsx
@@ -119,17 +119,21 @@ async function promptLLM(prompt: string) {
         prompt: prompt,
         stream: false,
       }),
-    })
+    });
     if (!response.ok) {
-      logseq.App.showMsg("Coudln't fulfill request make sure that ollama service is running and make sure there is no typo in host or model name")
-      throw new Error("Error in Ollama request: " + response.statusText)
+      logseq.App.showMsg("Couldn't fulfill request. Make sure Ollama service is running and there are no typos in host or model name.");
+      throw new Error("Error in Ollama request: " + response.statusText);
     }
+
     const data = await response.json();
 
-    return data.response;
+    // Remove any <think>…</think> blocks in the response
+    const filteredResponse = data.response.replace(/<think>[\s\S]*?<\/think>/g, '');
+
+    return filteredResponse;
   } catch (e: any) {
-    console.error("ERROR: ", e)
-    logseq.App.showMsg("Coudln't fulfill request make sure that ollama service is running and make sure there is no typo in host or model name")
+    console.error("ERROR: ", e);
+    logseq.App.showMsg("Couldn't fulfill request. Make sure Ollama service is running and there are no typos in host or model name.");
   }
 }
 
@@ -277,8 +281,8 @@ export async function askAI(prompt: string, context: string) {
 
 export async function convertToFlashCard(uuid: string, blockContent: string) {
   try {
-    const questionBlock = await logseq.Editor.insertBlock(uuid, "⌛Genearting question....", { before: false })
-    const answerBlock = await logseq.Editor.insertBlock(questionBlock!.uuid, "⌛Genearting answer....", { before: false })
+    const questionBlock = await logseq.Editor.insertBlock(uuid, "⌛Generating question....", { before: false })
+    const answerBlock = await logseq.Editor.insertBlock(questionBlock!.uuid, "⌛Generating answer....", { before: false })
     const question = await promptLLM(`Create a question for a flashcard. Provide the question only. Here is the knowledge to check:\n ${blockContent}`)
     const answer = await promptLLM(`Given the question ${question} and the context of ${blockContent} What is the answer? be as brief as possible and provide the answer only.`)
     await logseq.Editor.updateBlock(questionBlock!.uuid, `${question} #card`)
@@ -302,20 +306,95 @@ export async function convertToFlashCardCurrentBlock() {
 
 export async function DivideTaskIntoSubTasks(uuid: string, content: string) {
   try {
-    const block = await logseq.Editor.insertBlock(uuid, "✅ Genearting todos ...", { before: false })
-    let i = 0;
-    const response = await promptLLM(`Divide this task into subtasks with numbers: ${content} `)
-    for (const todo of response.split("\n")) {
-      if (i == 0) {
-        await logseq.Editor.updateBlock(block!.uuid, `TODO ${todo.slice(3)} `)
-      } else {
-        await logseq.Editor.insertBlock(uuid, `TODO ${todo.slice(3)} `, { before: false })
+    // 1) Insert initial placeholder block.
+    const placeholderBlock = await logseq.Editor.insertBlock(
+      uuid,
+      "✅ Generating todos ...",
+      { before: false }
+    );
+    if (!placeholderBlock) {
+      throw new Error("Could not insert the placeholder block.");
+    }
+
+    // 2) Fetch LLM response
+    const response = await promptLLM(
+      `Divide this task into subtasks with numbers respond with multilevel nested markdown format, no dot notation, one subtask per line, plain text only.: ${content}`
+    );
+
+    // 3) Split on newlines, ignoring empty lines
+    const lines = response
+      .split("\n")
+      .map((line: string) => line.replace(/\r$/, ""))  // remove trailing \r if present
+      .filter((line: string) => line.trim().length > 0);
+
+    // If there's nothing, do nothing
+    if (!lines.length) return;
+
+    // 4) Update the placeholder block with the very first line
+    //    preserving its entire text (no slicing or removal).
+    //    Prepend “TODO ” as in your original code structure.
+    await logseq.Editor.updateBlock(
+      placeholderBlock.uuid,
+      `TODO ${lines[0]}`
+    );
+
+    // If there was only one line, we’re done
+    if (lines.length === 1) return;
+
+    // 5) Now set up stack-based nesting for the rest:
+    //    stack top is always the most recent block at a particular level.
+    //    We'll treat the placeholder block as level=0
+    const stack = [{ uuid: placeholderBlock.uuid, level: 0 }];
+
+    // Helper: Determine indent-based nesting level.
+    // Adjust baseIndent if your model uses different spacing for sub-levels.
+    function getIndentLevel(line: string): number {
+      const firstCharIndex = line.search(/\S/);
+      // no non-whitespace => treat as top-level
+      if (firstCharIndex < 0) {
+        return 0;
+      }
+      // e.g. baseIndent=2 → each 2 leading spaces => +1 nesting level
+      const baseIndent = 3;
+      return Math.floor(firstCharIndex / baseIndent);
+    }
+
+    // 6) Process remaining lines to handle nesting
+    for (let i = 1; i < lines.length; i++) {
+      const rawLine = lines[i];
+
+      // figure out “level” from indentation
+      const level = getIndentLevel(rawLine);
+
+      // pop the stack until we find a block whose level is < current level
+      while (stack.length > 0 && stack[stack.length - 1].level >= level) {
+        stack.pop();
+      }
+
+      // if the stack is empty (all popped), fallback to the placeholder:
+      if (stack.length === 0) {
+        stack.push({ uuid: placeholderBlock.uuid, level: 0 });
+      }
+
+      // top of stack is the parent block where we create a child
+      const parent = stack[stack.length - 1];
+
+      // Insert a child block. Trim only leading spaces from the line’s text so we keep the numbering/content
+      const trimmedText = rawLine.replace(/^\s+/, "");
+      const newBlock = await logseq.Editor.insertBlock(
+        parent.uuid,
+        `TODO ${trimmedText}`,
+        { sibling: false }
+      );
+
+      if (newBlock) {
+        // push newly inserted block at the correct level
+        stack.push({ uuid: newBlock.uuid, level });
       }
-      i++;
     }
   } catch (e: any) {
-    logseq.App.showMsg(e.toString(), 'warning')
-    console.error(e)
+    logseq.App.showMsg(e.toString(), "warning");
+    console.error(e);
   }
 }
 
-- 
cgit v1.2.3

