diff --git a/chrome-extension/src/background.js b/chrome-extension/src/background.js index 0a0926a..cb109d1 100644 --- a/chrome-extension/src/background.js +++ b/chrome-extension/src/background.js @@ -101,7 +101,7 @@ async function initOllamaClient() { // Default to local Ollama instance and a general model if not configured const endpoint = ollamaEndpoint || "http://localhost:11434"; - const model = ollamaModel || "gemma:7b"; + const model = ollamaModel || "lukasmwerner/mark-tagger:1b"; ollamaClient = new Ollama({ host: endpoint, @@ -157,7 +157,7 @@ async function generateTagsWithOllama(url, title, description) { "ollamaModel", "ollamaPrompt", ]); - const model = ollamaModel || "gemma:7b"; + const model = ollamaModel || "lukasmwerner/mark-tagger:1b"; // Use custom prompt if available, otherwise use default let promptTemplate = ollamaPrompt || diff --git a/chrome-extension/src/options.html b/chrome-extension/src/options.html index eaa1ee8..9c4cc8d 100644 --- a/chrome-extension/src/options.html +++ b/chrome-extension/src/options.html @@ -89,7 +89,7 @@
diff --git a/chrome-extension/src/options.js b/chrome-extension/src/options.js index 067d53b..bb9bb7f 100644 --- a/chrome-extension/src/options.js +++ b/chrome-extension/src/options.js @@ -1,7 +1,7 @@ import { Ollama } from "ollama/browser"; import { ollama_schema } from "./schema"; -document.addEventListener("DOMContentLoaded", function () { +document.addEventListener("DOMContentLoaded", function() { // Load saved settings chrome.storage.sync.get( [ @@ -12,7 +12,7 @@ document.addEventListener("DOMContentLoaded", function () { "ollamaTemperature", "enableAutoTagging", ], - function (result) { + function(result) { console.log(result); if (result.apiToken) { document.getElementById("tokenInput").value = result.apiToken; @@ -55,13 +55,13 @@ Description: {{description}}`; // Save settings document .getElementById("saveSettings") - .addEventListener("click", function () { + .addEventListener("click", function() { const token = document.getElementById("tokenInput").value; const ollamaEndpoint = document.getElementById("ollamaEndpoint").value || "http://localhost:11434"; const ollamaModel = document.getElementById("ollamaModel").value || - "gemma:7b"; + "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. @@ -84,7 +84,7 @@ Description: {{description}}`; ollamaTemperature: ollamaTemperature.valueOf(), enableAutoTagging: enableAutoTagging, }, - function () { + function() { updateStatus("Settings saved successfully!"); }, ); @@ -93,7 +93,7 @@ Description: {{description}}`; // Test Ollama connection document .getElementById("testOllama") - .addEventListener("click", async function () { + .addEventListener("click", async function() { try { updateStatus("Testing Ollama connection..."); @@ -101,7 +101,7 @@ Description: {{description}}`; document.getElementById("ollamaEndpoint").value || "http://localhost:11434"; const model = document.getElementById("ollamaModel").value || - "gemma:7b"; + "lukasmwerner/mark-tagger:1b"; const ollama = new Ollama({ host: endpoint, @@ -119,8 +119,7 @@ Description: {{description}}`; ); } else { updateStatus( - `Connection successful, but model "${model}" was not found. Available models: ${ - models.models.map((m) => m.name).join(", ") + `Connection successful, but model "${model}" was not found. Available models: ${models.models.map((m) => m.name).join(", ") }`, ); } @@ -133,7 +132,7 @@ Description: {{description}}`; // Test prompt for tag generation document .getElementById("testPrompt") - .addEventListener("click", async function () { + .addEventListener("click", async function() { try { updateStatus("Testing tag generation..."); diff --git a/chrome-extension/src/popup.js b/chrome-extension/src/popup.js index 821b01f..58e5dc5 100644 --- a/chrome-extension/src/popup.js +++ b/chrome-extension/src/popup.js @@ -54,7 +54,7 @@ async function generateTagsWithOllama(url, title, description) { ]); const endpoint = ollamaEndpoint || "http://localhost:11434"; - const model = ollamaModel || "gemma:7b"; + const model = ollamaModel || "lukasmwerner/mark-tagger:1b"; // Use custom prompt if available, otherwise use default let promptTemplate = ollamaPrompt || @@ -91,6 +91,7 @@ async function generateTagsWithOllama(url, title, description) { console.log(response); let tags = JSON.parse(response.response).tags; + tags = tags.map(v => v.toLowerCase()) return tags; } catch (error) { // Hide loading indicator diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..3427c88 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,72 @@ +/* +Copyright © 2025 Lukas Werner +*/ +package cmd + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/huh" + "github.com/lukasmwerner/mark/store" + "github.com/spf13/cobra" +) + +// deleteCmd represents the delete command +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "deletes bookmarks based on given search query", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + + db, err := store.Open() + if err != nil { + fmt.Println("unable to open database", err.Error()) + return + } + defer db.Close() + + searchQuery := strings.Join(args, " ") + + bookmarks, err := store.SearchBookmarks(db, searchQuery) + if err != nil { + fmt.Println("unable to search bookmarks", err.Error()) + return + } + for _, bm := range bookmarks { + var affirmative bool + err := huh.NewConfirm().Title("Delete?"). + Description(prettyOutput(bm)). + Affirmative("Delete!"). + Negative("No, Keep"). + Value(&affirmative).Run() + if err != nil { + return + } + if !affirmative { + fmt.Println("Skipped.") + continue + } + err = store.DeleteBookmark(db, bm) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Deleted.") + } + }, +} + +func init() { + rootCmd.AddCommand(deleteCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // deleteCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/dump.go b/cmd/dump.go new file mode 100644 index 0000000..e692248 --- /dev/null +++ b/cmd/dump.go @@ -0,0 +1,81 @@ +/* +Copyright © 2025 Lukas Werner +*/ +package cmd + +import ( + "database/sql" + "fmt" + + "github.com/lukasmwerner/mark/store" + "github.com/spf13/cobra" +) + +// dumpCmd represents the dump command +var dumpCmd = &cobra.Command{ + Use: "dump", + Short: "dumps the database to a sqlite3 file", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + db, err := store.Open() + if err != nil { + fmt.Println(err.Error()) + return + } + defer db.Close() + + dumpDB, err := sql.Open("sqlite3", args[0]) + if err != nil { + fmt.Println(err.Error()) + return + } + defer dumpDB.Close() + + dumpDB.Exec(`CREATE TABLE bookmarks ( + id INTEGER PRIMARY KEY NOT NULL, + url TEXT, + title TEXT, + description TEXT, + tags TEXT +);`) + + rows, err := db.QueryContext(cmd.Context(), "SELECT url, title, description, tags FROM Bookmarks;") + if err != nil { + fmt.Println(err.Error()) + return + } + + defer rows.Close() + + for rows.Next() { + var b store.Bookmark + var tags string + err := rows.Scan(&b.Url, &b.Title, &b.Description, &tags) + if err != nil { + fmt.Println(err.Error()) + return + } + _, err = dumpDB.Exec(`INSERT INTO bookmarks (url, title, description, tags) VALUES (?, ?, ?, ?)`, + b.Url, b.Title, b.Description, tags) + if err != nil { + fmt.Println(err.Error()) + return + } + } + + }, +} + +func init() { + rootCmd.AddCommand(dumpCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // dumpCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // dumpCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/show.go b/cmd/show.go index 90550a5..391bf02 100644 --- a/cmd/show.go +++ b/cmd/show.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" "github.com/lukasmwerner/mark/store" "github.com/spf13/cobra" ) @@ -77,7 +78,7 @@ func init() { // Cobra supports local flags which will only run when this command // is called directly, e.g.: // showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - showCmd.Flags().StringVarP(&outputMode, "mode", "m", "json", "Output mode: json,csv") + showCmd.Flags().StringVarP(&outputMode, "mode", "m", "pretty", "Output mode: json,csv,pretty") } func outputBookmark(mode string, bookmark store.Bookmark) { @@ -90,6 +91,18 @@ func outputBookmark(mode string, bookmark store.Bookmark) { w.Write([]string{"Title", "Description", "Tags", "URL"}) w.Write([]string{bookmark.Title, bookmark.Description, strings.Join(bookmark.Tags, ","), bookmark.Url}) w.Flush() - + case "pretty": + fmt.Print(prettyOutput(bookmark)) } } + +var categoryColorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#34A77C")) + +func prettyOutput(bm store.Bookmark) string { + out := "" + out += categoryColorStyle.Render("Title: ") + bm.Title + "\n" + out += categoryColorStyle.Render("URL: ") + bm.Url + "\n" + out += categoryColorStyle.Render("Tags: ") + strings.Join(bm.Tags, ", ") + "\n" + out += categoryColorStyle.Render("Description: ") + bm.Description + "\n" + return out +} diff --git a/store/database.go b/store/database.go index fc1d206..aa7535c 100644 --- a/store/database.go +++ b/store/database.go @@ -278,6 +278,21 @@ func UpdateBookmark(db *DB, original Bookmark, updated Bookmark) error { return err } +func DeleteBookmark(db *DB, bookmark Bookmark) error { + _, err := db.Exec(`DELETE FROM Bookmarks + WHERE + url = ? AND + title = ? AND + description = ? AND + tags = ?;`, + bookmark.Url, + bookmark.Title, + bookmark.Description, + strings.Join(bookmark.Tags, ", "), + ) + return err +} + func AddKey(db *DB, key string) error { _, err := db.Exec("INSERT INTO Server_Keys (key) VALUES (?)", key) if err != nil {