diff --git a/chrome-extension/package.json b/chrome-extension/package.json index 126e4c6..ff03b28 100644 --- a/chrome-extension/package.json +++ b/chrome-extension/package.json @@ -14,6 +14,7 @@ "author": "", "license": "ISC", "dependencies": { + "html-to-markdown-wasm": "^2.5.6", "ollama": "^0.5.16" }, "devDependencies": { diff --git a/chrome-extension/pnpm-lock.yaml b/chrome-extension/pnpm-lock.yaml index 68bbda6..feb0466 100644 --- a/chrome-extension/pnpm-lock.yaml +++ b/chrome-extension/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + html-to-markdown-wasm: + specifier: ^2.5.6 + version: 2.5.6 ollama: specifier: ^0.5.16 version: 0.5.16 @@ -265,6 +268,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html-to-markdown-wasm@2.5.6: + resolution: {integrity: sha512-YQxvcN5KjrZAinKxI26p66f3sP+j6mG6SCF3JhwzvnicVPfC6v7rYMMlAa7BuyhemIywKO2IlAyuzWwCS2VOZA==} + import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -450,6 +456,9 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + up@1.0.2: + resolution: {integrity: sha512-PKVGUj7i1qMWSR83H3PZpw1h+21wAr1qxqF/8zgTVHS+sHTsu1zvepQDoDJN0Kv49quNJwWgAKDAK2xPy7jocg==} + update-browserslist-db@1.1.2: resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -744,6 +753,10 @@ snapshots: dependencies: function-bind: 1.1.2 + html-to-markdown-wasm@2.5.6: + dependencies: + up: 1.0.2 + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -897,6 +910,8 @@ snapshots: undici-types@6.20.0: {} + up@1.0.2: {} + update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 diff --git a/chrome-extension/src/content.js b/chrome-extension/src/content.js index 13caa93..ba6c7ab 100644 --- a/chrome-extension/src/content.js +++ b/chrome-extension/src/content.js @@ -1,22 +1,52 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request.action === "getPageInfo") { - const pageInfo = { - url: window.location.href, - title: document.title, - description: getDescription(), - }; - sendResponse(pageInfo); - } + if (request.action === "getPageInfo") { + const pageInfo = { + url: window.location.href, + title: document.title, + description: getDescription(), + }; + sendResponse(pageInfo); + } + if (request.action === "getBodyContent") { + sendResponse(getContent()); + } }); function getDescription() { - // Try meta description first - const metaDesc = - document.querySelector('meta[property="og:description"]')?.content || - document.querySelector('meta[name="description"]')?.content; - if (metaDesc) return metaDesc; + // Try meta description first + const metaDesc = + document.querySelector('meta[property="og:description"]')?.content || + document.querySelector('meta[name="description"]')?.content; + if (metaDesc) return metaDesc; - // Fallback to first paragraph - const firstPara = document.querySelector("p")?.textContent; - return firstPara ? firstPara.substring(0, 200) + "..." : ""; + // Fallback to first paragraph + const firstPara = document.querySelector("p")?.textContent; + return firstPara ? firstPara.substring(0, 200) + "..." : ""; +} + +function getContent() { + // Highest priority query to lowest priority + let queries = ["article", "#content", "main", ".main", "body"]; + for (let i = 0; i < queries.length; i++) { + let root = document.querySelector(queries[i]); + if (root != null) { + return getTextContent(root); + } + } + return "" +} + + +function getTextContent(root) { + let walker = document.createTreeWalker(root, + NodeFilter.SHOW_TEXT, + null + ); + + let node; + let textContent = "" + while (node = walker.nextNode()) { + textContent += node.nodeValue + " "; + } + return textContent; } diff --git a/chrome-extension/src/default_prompts.js b/chrome-extension/src/default_prompts.js new file mode 100644 index 0000000..131e118 --- /dev/null +++ b/chrome-extension/src/default_prompts.js @@ -0,0 +1,13 @@ +export let tagging_prompt = `Generate 3-5 relevant tags for this content. Return only the tags as a JSON array of strings. No additional explanation needed. + +Title: {{title}} +URL: {{url}} +Description: {{description}}` +export let description_prompt = `URL: {{url}} +Content: +{{content}} + +Title: +{{title}} + +Please write a short google search style description for the following webpage. Avoid describing the overall website and focus on the content on the page. Avoid phrases like 'news article about'. Return only the description as in the json object as a string. No additional explanation needed.`; diff --git a/chrome-extension/src/description_generation.js b/chrome-extension/src/description_generation.js new file mode 100644 index 0000000..d0f2177 --- /dev/null +++ b/chrome-extension/src/description_generation.js @@ -0,0 +1,60 @@ +import { Ollama } from "ollama/browser"; +import { description_schema } from "./schema" + +async function generateDescription(title, url, content) { + try { + // Get Ollama settings + const { + ollamaEndpoint, + ollamaSummaryModel: ollamaModel, + ollamaSummaryPrompt: ollamaPrompt, + ollamaSummaryTemperature: temperature, + } = await chrome.storage.sync.get([ + "ollamaEndpoint", + "ollamaSummaryModel", + "ollamaSummaryPrompt", + "ollamaSummaryTemperature", + ]); + console.log({ ollamaEndpoint, ollamaModel, ollamaPrompt, temperature }) + + const endpoint = ollamaEndpoint || "http://localhost:11434"; + const model = ollamaModel || "gemma3:4b"; + + // Use custom prompt if available, otherwise use default + let promptTemplate = ollamaPrompt || + `Please generate a short and simple description of the the following document in the style of a website description. +Document: {{content}}`; + + // Replace placeholders with actual content + const prompt = promptTemplate + .replace(/{{title}}/g, title) + .replace(/{{url}}/g, url) + .replace(/{{content}}/g, content); + + // Initialize Ollama client + const ollamaClient = new Ollama({ + host: endpoint, + }); + + const response = await ollamaClient.generate({ + model: model, + prompt: prompt, + options: { + temperature: temperature || 0.3, + }, + format: description_schema, + }); + + let desc = JSON.parse(response.response).description; + + return desc; + } catch (error) { + console.error("Error generating description with Ollama:", error); + updateStatus(`Error generating description: ${error.message}`); + return []; + } +} + +export { + generateDescription, +} diff --git a/chrome-extension/src/options.html b/chrome-extension/src/options.html index bf74fa2..d47f93d 100644 --- a/chrome-extension/src/options.html +++ b/chrome-extension/src/options.html @@ -1,139 +1,154 @@ - + - - Bookmark Sender Settings - - - -
-

Bookmark Sender Settings

-
- - -
+ + mark-browser Settings + + + +
+

mark-browser Settings

+
+ + +
-
-

Ollama Auto-Tagging Settings

-
- - -
-
- - -
-
- - -
-
- - +

+ You can use {{title}}, {{url}} and {{content}} as placeholders that + will be replaced with content from the current page. +

+
+ +

Tagging Model

+ +
+ + +
+
+ + +
+
+ + -

- Use {{title}} and {{description}} as placeholders that - will be replaced with the actual content. -

-
-
- -
-
- -
+ You can use {{title}}, {{url}} and {{description}} as placeholders that + will be replaced with content from the current page. +

+ +
+ +
+
+ + -
-
+ > +

Generated Tags:

+
+
+
+
- -
-
- - + +
+ + + diff --git a/chrome-extension/src/options.js b/chrome-extension/src/options.js index 7b5dfe9..147e5b1 100644 --- a/chrome-extension/src/options.js +++ b/chrome-extension/src/options.js @@ -1,5 +1,6 @@ import { Ollama } from "ollama/browser"; import { ollama_schema } from "./schema"; +import { description_prompt, tagging_prompt } from "./default_prompts"; document.addEventListener("DOMContentLoaded", function() { // Load saved settings @@ -10,6 +11,9 @@ document.addEventListener("DOMContentLoaded", function() { "ollamaModel", "ollamaPrompt", "ollamaTemperature", + "ollamaSummaryModel", + "ollamaSummaryPrompt", + "ollamaSummaryTemperature", ], function(result) { console.log(result); @@ -38,11 +42,27 @@ document.addEventListener("DOMContentLoaded", function() { } else { // Set default prompt if not already set document.getElementById("ollamaPrompt").value = - `Generate 3-5 relevant tags for this content. Return only the tags as a JSON array of strings. No additional explanation needed. + tagging_prompt; + } + + + if (result.ollamaSummaryModel) { + document.getElementById("ollamaSummaryModel").value = + result.ollamaSummaryModel; + } -Title: {{title}} -URL: {{url}} -Description: {{description}}`; + if (result.ollamaSummaryTemperature) { + document.getElementById("ollamaSummaryTemperature").value = + result.ollamaSummaryTemperature; + } + + if (result.ollamaSummaryPrompt) { + document.getElementById("ollamaSummaryPrompt").value = + result.ollamaSummaryPrompt; + } else { + // Set default prompt if not already set + document.getElementById("ollamaSummaryPrompt").value = + description_prompt; } }, ); @@ -58,16 +78,22 @@ Description: {{description}}`; const ollamaModel = document.getElementById("ollamaModel").value || "lukasmwerner/mark-tagger:1b"; const ollamaPrompt = - document.getElementById("ollamaPrompt").value || - `Generate 3-5 relevant tags for this content. Return only the tags as a JSON array of strings. No additional explanation needed. - -Title: {{title}} -URL: {{url}} -Description: {{description}}`; + document.getElementById("ollamaPrompt").value || tagging_prompt; const ollamaTemperature = new Number( document.getElementById("ollamaTemperature").value, ) || 0.3; + + + const ollamaSummaryModel = document.getElementById("ollamaSummaryModel").value || + "gemma3:4b"; + const ollamaSummaryPrompt = + document.getElementById("ollamaSummaryPrompt").value || + description_prompt; + const ollamaSummaryTemperature = new Number( + document.getElementById("ollamaSummaryTemperature").value, + ) || 0.3; + chrome.storage.sync.set( { apiToken: token, @@ -75,6 +101,9 @@ Description: {{description}}`; ollamaModel: ollamaModel, ollamaPrompt: ollamaPrompt, ollamaTemperature: ollamaTemperature.valueOf(), + ollamaSummaryModel: ollamaSummaryModel, + ollamaSummaryPrompt: ollamaSummaryPrompt, + ollamaSummaryTemperature: ollamaSummaryTemperature.valueOf(), }, function() { updateStatus("Settings saved successfully!"); diff --git a/chrome-extension/src/popup.html b/chrome-extension/src/popup.html index b24e3ce..463008a 100644 --- a/chrome-extension/src/popup.html +++ b/chrome-extension/src/popup.html @@ -1,140 +1,141 @@ - + - - Save with Tags - - - -
-
- - -
-
- - -
-
-
+
+ + +
+
+
- - -
-
-
- Generating tags... -
-
- -
-
- -
-
- - + "> + + +
+ +
+
+
+ + +
+
+
+ Generating tags... +
+
+ +
+
+ +
+ + + diff --git a/chrome-extension/src/popup.js b/chrome-extension/src/popup.js index b983355..ddc8009 100644 --- a/chrome-extension/src/popup.js +++ b/chrome-extension/src/popup.js @@ -1,3 +1,4 @@ +import { generateDescription } from "./description_generation"; import { generateTagsWithOllama } from "./tag_generation"; function createTagElement(tag) { @@ -63,7 +64,7 @@ async function initializeDetailedSave() { } }); - // Handle manual tag generation + // Handle AI tag generation document .getElementById("generateTags") .addEventListener("click", async () => { @@ -87,6 +88,27 @@ async function initializeDetailedSave() { } }); + // Handle AI description generation + document.getElementById("summarizeContent").addEventListener("click", async () => { + if (!pageInfo) { + updateStatus("Error: Page information not available"); + return; + } + try { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + if (!tabs[0]) return; + const response = await chrome.tabs.sendMessage(tabs[0].id, { + action: "getBodyContent", + }); + let desc = await generateDescription(pageInfo.title, pageInfo.url, response) + document.getElementById("description").value = desc; + } catch (error) { + updateStatus("Error: Could not load page data"); + console.error(error); + } + + }) + // Handle save document .getElementById("saveBookmark") diff --git a/chrome-extension/src/schema.js b/chrome-extension/src/schema.js index 64d639f..aa4bd28 100644 --- a/chrome-extension/src/schema.js +++ b/chrome-extension/src/schema.js @@ -1,10 +1,20 @@ export const ollama_schema = { - type: "object", - properties: { - tags: { - type: "array", - items: { type: "string" }, - }, - }, - required: ["tags"], + type: "object", + properties: { + tags: { + type: "array", + items: { type: "string" }, + }, + }, + required: ["tags"], +}; + +export const description_schema = { + type: "object", + properties: { + description: { + type: "string", + } + }, + required: ["description"] };