From e979c1e65325ec80f2f87665e63577cb103875a7 Mon Sep 17 00:00:00 2001 From: Mikheil Berishvili Date: Thu, 11 Dec 2025 16:53:54 +0400 Subject: [PATCH 1/7] feat: integrate better-sqlite3 for clipboard history management - Added DatabaseManager class for handling clipboard item storage and retrieval. - Implemented methods to add, get, delete, and clear clipboard items in the database. - Updated ClipboardManager to load history from the database on initialization. - Enhanced clipboard item saving with error handling for database operations. - Introduced loadMore functionality in ClipboardManager to fetch additional items. - Updated IPC communication to support loading more clipboard history. - Modified ClipboardHistory component to include a "Load More" button for fetching additional items. - Added styles for the "Load More" button in ClipboardHistory.css. - Updated ClipboardItem model to include an optional id field. - Added postinstall script for electron-rebuild in package.json. - Updated build scripts in package.json to include better-sqlite3 as an external dependency. --- bun.lock | 46 ++ package-lock.json | 532 +++++++++++++++++++++++- package.json | 19 +- src/main/clipboardManager.ts | 89 +++- src/main/database.ts | 117 ++++++ src/main/main.ts | 18 +- src/main/preload.ts | 1 + src/models/ClipboardItem.ts | 1 + src/renderer/pages/ClipboardHistory.css | 29 ++ src/renderer/pages/ClipboardHistory.tsx | 63 ++- src/renderer/types.d.ts | 1 + 11 files changed, 860 insertions(+), 56 deletions(-) create mode 100644 src/main/database.ts diff --git a/bun.lock b/bun.lock index 42bf279..875bcf3 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,9 @@ "": { "name": "clipai", "dependencies": { + "@types/better-sqlite3": "^7.6.13", "@vitejs/plugin-react": "^5.1.2", + "better-sqlite3": "^12.5.0", "concurrently": "^9.2.1", "electron-log": "^5.4.3", "highlight.js": "^11.11.1", @@ -241,6 +243,8 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], + "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], @@ -317,6 +321,10 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.5", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA=="], + "better-sqlite3": ["better-sqlite3@12.5.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], @@ -401,6 +409,8 @@ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], @@ -471,6 +481,8 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], @@ -485,6 +497,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], @@ -493,6 +507,8 @@ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], @@ -513,6 +529,8 @@ "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], @@ -565,6 +583,8 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], "is-ci": ["is-ci@3.0.1", "", { "dependencies": { "ci-info": "^3.2.0" }, "bin": { "is-ci": "bin.js" } }, "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ=="], @@ -651,10 +671,14 @@ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], @@ -705,6 +729,8 @@ "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "proc-log": ["proc-log@2.0.1", "", {}, "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw=="], "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], @@ -721,6 +747,8 @@ "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], @@ -775,6 +803,10 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], @@ -807,12 +839,18 @@ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], @@ -831,6 +869,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -917,6 +957,8 @@ "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "@types/better-sqlite3/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], + "@types/cacheable-request/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], "@types/fs-extra/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], @@ -997,6 +1039,8 @@ "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "temp/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], @@ -1017,6 +1061,8 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "@types/fs-extra/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], diff --git a/package-lock.json b/package-lock.json index 27fc6f8..8b6173c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,16 @@ { "name": "clipai", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "clipai", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { + "@types/better-sqlite3": "^7.6.13", "@vitejs/plugin-react": "^5.1.2", + "better-sqlite3": "^12.5.0", "concurrently": "^9.2.1", "electron-log": "^5.4.3", "highlight.js": "^11.11.1", @@ -22,7 +24,8 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "electron": "^39.2.6", - "electron-builder": "^26.0.12" + "electron-builder": "^26.0.12", + "electron-rebuild": "^3.2.9" }, "peerDependencies": { "typescript": "^5.9.3" @@ -1247,6 +1250,15 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/bun": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.4.tgz", @@ -1340,7 +1352,6 @@ }, "node_modules/@types/node": { "version": "22.19.2", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1664,6 +1675,28 @@ "node": ">= 10.0.0" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1744,7 +1777,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -1768,11 +1800,33 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/better-sqlite3": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz", + "integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -1832,7 +1886,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -2271,6 +2324,16 @@ "version": "1.1.4", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "license": "MIT", @@ -2398,6 +2461,13 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" + }, "node_modules/convert-source-map": { "version": "2.0.0", "license": "MIT" @@ -2469,7 +2539,6 @@ }, "node_modules/decompress-response": { "version": "6.0.0", - "dev": true, "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" @@ -2481,6 +2550,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -2543,11 +2621,17 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2888,6 +2972,97 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-rebuild": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-3.2.9.tgz", + "integrity": "sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw==", + "deprecated": "Please use @electron/rebuild moving forward. There is no API change, just a package name change", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "lzma-native": "^8.0.5", + "node-abi": "^3.0.0", + "node-api-version": "^0.1.4", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/src/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/electron-rebuild/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-rebuild/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-rebuild/node_modules/node-api-version": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.1.4.tgz", + "integrity": "sha512-KGXihXdUChwJAOHO53bv9/vXcLmdUsZ6jIptbvYvkpKfth+r7jw44JkVxQFA3kX5nQjzjmGu1uAu/xNNLNlI5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/electron-rebuild/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-rebuild/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "license": "ISC" @@ -2947,7 +3122,6 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", - "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -3069,6 +3243,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -3143,6 +3326,12 @@ } } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -3238,6 +3427,12 @@ "node": ">= 6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "8.1.0", "dev": true, @@ -3289,6 +3484,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "license": "MIT", @@ -3350,6 +3566,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3511,6 +3733,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -3653,7 +3882,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -3713,7 +3941,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, "node_modules/ip-address": { @@ -3960,6 +4193,32 @@ "yallist": "^3.0.2" } }, + "node_modules/lzma-native": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz", + "integrity": "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^3.1.0", + "node-gyp-build": "^4.2.1", + "readable-stream": "^3.6.0" + }, + "bin": { + "lzmajs": "bin/lzmajs" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/lzma-native/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "license": "MIT" + }, "node_modules/make-fetch-happen": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", @@ -4101,7 +4360,6 @@ }, "node_modules/mimic-response": { "version": "3.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4257,6 +4515,12 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "license": "MIT" @@ -4277,6 +4541,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -4291,7 +4561,6 @@ "version": "3.85.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", - "dev": true, "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -4304,7 +4573,6 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4344,6 +4612,57 @@ "node": ">=10" } }, + "node_modules/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-releases": { "version": "2.0.27", "license": "MIT" @@ -4375,6 +4694,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/object-keys": { "version": "1.1.1", "dev": true, @@ -4386,7 +4722,6 @@ }, "node_modules/once": { "version": "1.4.0", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4638,6 +4973,32 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proc-log": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", @@ -4683,7 +5044,6 @@ }, "node_modules/pump": { "version": "3.0.3", - "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -4711,6 +5071,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/react": { "version": "19.2.1", "license": "MIT", @@ -4752,7 +5127,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -4912,7 +5286,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -4985,6 +5358,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5025,6 +5405,51 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -5182,7 +5607,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -5240,6 +5664,15 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "dev": true, @@ -5282,6 +5715,40 @@ "node": ">=10" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -5467,6 +5934,18 @@ "version": "2.8.1", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-fest": { "version": "0.13.1", "dev": true, @@ -5492,7 +5971,6 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "devOptional": true, "license": "MIT" }, "node_modules/unique-filename": { @@ -5578,7 +6056,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/verror": { @@ -5712,6 +6189,16 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "license": "MIT", @@ -5748,7 +6235,6 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, "license": "ISC" }, "node_modules/xmlbuilder": { diff --git a/package.json b/package.json index 8c2cc98..edefe96 100644 --- a/package.json +++ b/package.json @@ -8,25 +8,31 @@ "type": "module", "private": true, "scripts": { + "postinstall": "electron-rebuild", "dev": "concurrently \"bun run dev:renderer\" \"bun run dev:electron\"", "dev:renderer": "vite", "dev:preload": "bun run scripts/build-preload.ts", - "dev:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron", + "dev:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron --external better-sqlite3 --external electron-liquid-glass", "dev:electron": "bun run dev:preload && bun run dev:main && wait-on http://localhost:5173 && electron .", "build": "bun run build:renderer && bun run build:main && bun run build:preload", "build:renderer": "vite build", - "build:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron", + "build:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron --external better-sqlite3 --external electron-liquid-glass", "build:preload": "bun build src/main/preload.ts --outdir dist/main --target node --format cjs --external electron", "package": "bun run build && electron-builder", "package:all": "bun run build && electron-builder --mac --win", - "package:dir": "bun run build && electron-builder --dir" + "package:dir": "bun run build && electron-builder --dir", + "rebuild": "electron-rebuild" }, "build": { "appId": "com.clipai.app", "productName": "clipai", "files": [ "dist/**/*", - "assets/**/*" + "assets/**/*", + "node_modules/better-sqlite3/**/*" + ], + "asarUnpack": [ + "node_modules/better-sqlite3/**/*" ], "directories": { "output": "release" @@ -75,13 +81,16 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "electron": "^39.2.6", - "electron-builder": "^26.0.12" + "electron-builder": "^26.0.12", + "electron-rebuild": "^3.2.9" }, "peerDependencies": { "typescript": "^5.9.3" }, "dependencies": { + "@types/better-sqlite3": "^7.6.13", "@vitejs/plugin-react": "^5.1.2", + "better-sqlite3": "^12.5.0", "concurrently": "^9.2.1", "electron-log": "^5.4.3", "highlight.js": "^11.11.1", diff --git a/src/main/clipboardManager.ts b/src/main/clipboardManager.ts index 79352ab..7b61d2e 100644 --- a/src/main/clipboardManager.ts +++ b/src/main/clipboardManager.ts @@ -1,5 +1,7 @@ import { clipboard, BrowserWindow, nativeImage } from "electron"; import type { ClipboardItem } from "../models/ClipboardItem.ts"; +import type { DatabaseManager } from "./database.ts"; +import log from "electron-log"; export class ClipboardManager { private history: ClipboardItem[] = []; @@ -7,9 +9,50 @@ export class ClipboardManager { private lastClipboardImage = ""; private intervalId: NodeJS.Timeout | null = null; private window: BrowserWindow | null = null; + private db: DatabaseManager; - constructor(window: BrowserWindow) { + constructor(window: BrowserWindow, database: DatabaseManager) { this.window = window; + this.db = database; + this.loadHistoryFromDB(); + } + + private loadHistoryFromDB() { + try { + const stats = this.db.getStats(); + log.info(`Database stats:`, stats); + + this.history = this.db.getItems(15); + log.info(`Loaded ${this.history.length} items from database (initial load)`); + + if (this.history.length > 0) { + const lastItem = this.history[0]; + if (lastItem && lastItem.type === "text" && lastItem.text) { + this.lastClipboardText = lastItem.text; + } else if (lastItem && lastItem.type === "image" && lastItem.image) { + this.lastClipboardImage = lastItem.image; + } + } + } catch (error) { + log.error("Failed to load history from database:", error); + } + } + + loadMore(limit: number = 20): ClipboardItem[] { + try { + const offset = this.history.length; + log.info(`loadMore called: offset=${offset}, limit=${limit}`); + + const moreItems = this.db.getItems(limit, offset); + this.history.push(...moreItems); + log.info( + `Loaded ${moreItems.length} more items (offset was ${offset}, total now: ${this.history.length})` + ); + return moreItems; + } catch (error) { + log.error("Failed to load more items from database:", error); + return []; + } } start() { @@ -22,14 +65,28 @@ export class ClipboardManager { if (imageDataURL !== this.lastClipboardImage) { this.lastClipboardImage = imageDataURL; const item: ClipboardItem = { - type: 'image', + type: "image", image: imageDataURL, timestamp: Date.now(), }; - this.history.unshift(item); - if (this.window) { - this.window.webContents.send("clipboard-update", item); + // Save to database + try { + const id = this.db.addItem(item); + item.id = id; + this.history.unshift(item); + + if (this.window) { + this.window.webContents.send("clipboard-update", item); + } + log.info(`Image clipboard item saved to DB with id: ${id}`); + } catch (error) { + log.error("Failed to save image to database:", error); + // Still add to memory even if DB fails + this.history.unshift(item); + if (this.window) { + this.window.webContents.send("clipboard-update", item); + } } } } else { @@ -39,14 +96,28 @@ export class ClipboardManager { if (trimmedText && text !== this.lastClipboardText) { this.lastClipboardText = text; const item: ClipboardItem = { - type: 'text', + type: "text", text, timestamp: Date.now(), }; - this.history.unshift(item); - if (this.window) { - this.window.webContents.send("clipboard-update", item); + // Save to database + try { + const id = this.db.addItem(item); + item.id = id; + this.history.unshift(item); + + if (this.window) { + this.window.webContents.send("clipboard-update", item); + } + log.info(`Text clipboard item saved to DB with id: ${id}`); + } catch (error) { + log.error("Failed to save text to database:", error); + // Still add to memory even if DB fails + this.history.unshift(item); + if (this.window) { + this.window.webContents.send("clipboard-update", item); + } } } } diff --git a/src/main/database.ts b/src/main/database.ts new file mode 100644 index 0000000..1d8ef8a --- /dev/null +++ b/src/main/database.ts @@ -0,0 +1,117 @@ +import Database from "better-sqlite3"; +import { app } from "electron"; +import { join } from "path"; +import log from "electron-log"; +import type { ClipboardItem } from "../models/ClipboardItem.ts"; + +export class DatabaseManager { + private db: Database.Database; + + constructor() { + const userDataPath = app.getPath("userData"); + const dbPath = join(userDataPath, "clipboard.db"); + log.info("Database path:", dbPath); + + this.db = new Database(dbPath); + this.db.pragma("journal_mode = WAL"); + this.initTables(); + } + + private initTables() { + this.db.exec(` + CREATE TABLE IF NOT EXISTS clipboard_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + text TEXT, + image TEXT, + timestamp INTEGER NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_timestamp ON clipboard_items(timestamp DESC); + CREATE INDEX IF NOT EXISTS idx_type ON clipboard_items(type); + `); + log.info("Database tables initialized"); + } + + addItem(item: Omit): number { + const stmt = this.db.prepare(` + INSERT INTO clipboard_items (type, text, image, timestamp) + VALUES (?, ?, ?, ?) + `); + + const result = stmt.run( + item.type, + item.text || null, + item.image || null, + item.timestamp + ); + + return result.lastInsertRowid as number; + } + + getItems(limit: number = 1000, offset: number = 0): ClipboardItem[] { + const stmt = this.db.prepare(` + SELECT id, type, text, image, timestamp + FROM clipboard_items + ORDER BY timestamp DESC + LIMIT ? OFFSET ? + `); + + return stmt.all(limit, offset) as ClipboardItem[]; + } + + getItemById(id: number): ClipboardItem | undefined { + const stmt = this.db.prepare(` + SELECT id, type, text, image, timestamp + FROM clipboard_items + WHERE id = ? + `); + + return stmt.get(id) as ClipboardItem | undefined; + } + + deleteItem(id: number): boolean { + const stmt = this.db.prepare("DELETE FROM clipboard_items WHERE id = ?"); + const result = stmt.run(id); + return result.changes > 0; + } + + clearAll(): void { + this.db.exec("DELETE FROM clipboard_items"); + log.info("All clipboard items cleared"); + } + + getStats(): { total: number; text: number; image: number } { + const result = this.db + .prepare( + ` + SELECT + COUNT(*) as total, + SUM(CASE WHEN type = 'text' THEN 1 ELSE 0 END) as text, + SUM(CASE WHEN type = 'image' THEN 1 ELSE 0 END) as image + FROM clipboard_items + ` + ) + .get() as { total: number; text: number; image: number }; + + return result; + } + + searchItems(query: string, limit: number = 100): ClipboardItem[] { + const stmt = this.db.prepare(` + SELECT id, type, text, image, timestamp + FROM clipboard_items + WHERE text LIKE ? + ORDER BY timestamp DESC + LIMIT ? + `); + + return stmt.all(`%${query}%`, limit) as ClipboardItem[]; + } + + close() { + this.db.close(); + log.info("Database closed"); + } +} diff --git a/src/main/main.ts b/src/main/main.ts index cc0634e..0347bc0 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -11,6 +11,7 @@ import { join, dirname } from "path"; import { fileURLToPath } from "url"; import { ClipboardManager } from "./clipboardManager.ts"; import { ConfigManager } from "./configManager.ts"; +import { DatabaseManager } from "./database.ts"; import log from "electron-log"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -22,6 +23,7 @@ let win: BrowserWindow | null = null; let tray: Tray | null = null; let clipboardManager: ClipboardManager | null = null; let configManager: ConfigManager | null = null; +let databaseManager: DatabaseManager | null = null; let isQuitting = false; async function createWindow() { @@ -83,7 +85,7 @@ async function createWindow() { log.error("Failed to load content:", error); } - clipboardManager = new ClipboardManager(win); + clipboardManager = new ClipboardManager(win, databaseManager!); clipboardManager.start(); log.info("ClipboardManager started"); } @@ -161,10 +163,12 @@ ipcMain.handle("get-clipboard-history", () => { return clipboardManager?.getHistory() || []; }); +ipcMain.handle("load-more-history", (_event, limit: number = 50) => { + return clipboardManager?.loadMore(limit) || []; +}); + ipcMain.handle("set-transparency", (_event, enabled: boolean) => { if (win) { - // Note: Changing transparency dynamically requires recreating the window - // For now, we'll just update the CSS class win.webContents.send("transparency-changed", enabled); } }); @@ -203,7 +207,6 @@ ipcMain.handle("set-global-shortcut", (_event, shortcut: string) => { }); function registerGlobalShortcut(shortcut: string = "CommandOrControl+Shift+V") { - // Unregister all shortcuts first globalShortcut.unregisterAll(); const registered = globalShortcut.register(shortcut, () => { @@ -232,6 +235,8 @@ app.whenReady().then(() => { log.info("isPackaged:", app.isPackaged); log.info("__dirname:", __dirname); + databaseManager = new DatabaseManager(); + configManager = new ConfigManager(); app.setLoginItemSettings({ @@ -257,11 +262,8 @@ app.whenReady().then(() => { log.info("Log file location:", log.transports.file.getFile().path); }); -app.on("window-all-closed", () => { - // Prevent app from quitting -}); +app.on("window-all-closed", () => {}); app.on("will-quit", () => { - // Unregister all shortcuts when app quits globalShortcut.unregisterAll(); }); diff --git a/src/main/preload.ts b/src/main/preload.ts index f00bfa1..2d4d444 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -3,6 +3,7 @@ import type { ClipboardItem } from "../models/ClipboardItem.ts"; contextBridge.exposeInMainWorld("electronAPI", { getClipboardHistory: () => ipcRenderer.invoke("get-clipboard-history"), + loadMoreHistory: (limit: number) => ipcRenderer.invoke("load-more-history", limit), onClipboardUpdate: (callback: (item: ClipboardItem) => void) => { ipcRenderer.on("clipboard-update", (_event, item) => callback(item)); }, diff --git a/src/models/ClipboardItem.ts b/src/models/ClipboardItem.ts index cfbdc8f..24586cc 100644 --- a/src/models/ClipboardItem.ts +++ b/src/models/ClipboardItem.ts @@ -1,4 +1,5 @@ export interface ClipboardItem { + id?: number; type: 'text' | 'image'; text?: string; image?: string; diff --git a/src/renderer/pages/ClipboardHistory.css b/src/renderer/pages/ClipboardHistory.css index 88a2faa..9253259 100644 --- a/src/renderer/pages/ClipboardHistory.css +++ b/src/renderer/pages/ClipboardHistory.css @@ -212,3 +212,32 @@ body.opaque .app { ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } + +.load-more-container { + padding: 2rem; + text-align: center; +} + +.load-more-btn { + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(0, 0, 0, 0.1); + color: #333; + padding: 0.75rem 2rem; + border-radius: 8px; + cursor: pointer; + font-size: 0.875rem; + font-weight: 600; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.load-more-btn:hover:not(:disabled) { + background: rgba(255, 255, 255, 1); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); +} + +.load-more-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} diff --git a/src/renderer/pages/ClipboardHistory.tsx b/src/renderer/pages/ClipboardHistory.tsx index 3cdd15c..ef4b9fb 100644 --- a/src/renderer/pages/ClipboardHistory.tsx +++ b/src/renderer/pages/ClipboardHistory.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import type { ClipboardItem as ClipboardItemType } from "../../models/ClipboardItem"; import HistoryItemCard from "../components/ClipboardItem"; import "./ClipboardHistory.css"; @@ -9,15 +9,43 @@ interface ClipboardHistoryProps { export default function ClipboardHistory({}: ClipboardHistoryProps) { const [history, setHistory] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [hasMore, setHasMore] = useState(true); useEffect(() => { - window.electronAPI.getClipboardHistory().then(setHistory); + window.electronAPI.getClipboardHistory().then((items) => { + console.log(`Initial history loaded: ${items.length} items`); + setHistory(items); + }); window.electronAPI.onClipboardUpdate((item) => { setHistory((prev) => [item, ...prev]); }); }, []); + const loadMore = async () => { + if (isLoading || !hasMore) { + console.log(`loadMore blocked: isLoading=${isLoading}, hasMore=${hasMore}`); + return; + } + + console.log('loadMore: Fetching more items...'); + setIsLoading(true); + try { + const moreItems = await window.electronAPI.loadMoreHistory(20); + console.log(`Loaded ${moreItems.length} more clipboard items`); + if (moreItems.length === 0) { + setHasMore(false); + } else { + setHistory((prev) => [...prev, ...moreItems]); + } + } catch (error) { + console.error("Failed to load more items:", error); + } finally { + setIsLoading(false); + } + }; + return ( <> @@ -44,15 +72,28 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {

Copy some text to get started

) : ( -
- {history.map((item, index) => ( - - ))} -
+ <> +
+ {history.map((item, index) => ( + + ))} +
+ {hasMore && ( +
+ +
+ )} + )} diff --git a/src/renderer/types.d.ts b/src/renderer/types.d.ts index d631d9c..5d7c248 100644 --- a/src/renderer/types.d.ts +++ b/src/renderer/types.d.ts @@ -7,6 +7,7 @@ export interface AppConfig { export interface ElectronAPI { getClipboardHistory: () => Promise; + loadMoreHistory: (limit: number) => Promise; onClipboardUpdate: (callback: (item: ClipboardItem) => void) => void; onNavigate: (callback: (page: string) => void) => void; setTransparency: (enabled: boolean) => Promise; From ec206e089ecd93fd9255340dc4163f0a24737fed Mon Sep 17 00:00:00 2001 From: Mikheil Berishvili Date: Thu, 11 Dec 2025 20:16:19 +0400 Subject: [PATCH 2/7] Update readme.md --- README.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 721ee6e..22435ef 100644 --- a/README.md +++ b/README.md @@ -121,11 +121,32 @@ Creates platform-specific installers in `release/`: - Click URLs to open in default browser - Click items to copy back to clipboard - Ignores empty or whitespace-only entries -- Clears when app is restarted +- Persistent storage using SQLite database +- Load more history with pagination -### Logs -- **macOS**: `~/Library/Logs/clipai/main.log` -- **Windows**: `%APPDATA%\clipai\logs\main.log` +## Logs + +**macOS:** +```bash +~/Library/Logs/clipai/main.log + +# Open in default editor +open ~/Library/Logs/clipai/main.log + +# Tail logs in real-time +tail -f ~/Library/Logs/clipai/main.log +``` + +**Windows:** +```bash +%USERPROFILE%\AppData\Roaming\clipai\logs\main.log + +# Open logs folder +explorer %APPDATA%\clipai\logs + +# View in terminal +type %APPDATA%\clipai\logs\main.log +``` ## Scripts From dcae35ff0fbd1a576f431a97d05b37b8dcac1556 Mon Sep 17 00:00:00 2001 From: Mikheil Berishvili Date: Thu, 11 Dec 2025 20:42:57 +0400 Subject: [PATCH 3/7] feat: Update readme, optimise loggin there was extra logging, that was for development purpose, we dont need to much logging anymore Also, readme was not correct. i fixed it --- README.md | 73 ++++++++++++++++++++++++++++-------- src/main/clipboardManager.ts | 8 +--- src/main/main.ts | 37 +++++------------- 3 files changed, 69 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 22435ef..4dacb1c 100644 --- a/README.md +++ b/README.md @@ -36,16 +36,22 @@ src/ ├── main/ │ ├── main.ts # Electron main process │ ├── preload.ts # Preload script (IPC bridge) -│ └── clipboardManager.ts # Clipboard monitoring logic -└── renderer/ - ├── pages/ - │ ├── ClipboardHistory.tsx # Main clipboard history view - │ ├── ClipboardHistory.css - │ ├── Settings.tsx # Settings page - │ └── Settings.css - ├── main.tsx # React entry point - ├── App.tsx # Main app component with routing - └── types.d.ts # TypeScript definitions +│ ├── clipboardManager.ts # Clipboard monitoring logic +│ ├── database.ts # SQLite database manager +│ └── configManager.ts # App configuration management +├── renderer/ +│ ├── pages/ +│ │ ├── ClipboardHistory.tsx # Main clipboard history view +│ │ ├── ClipboardHistory.css +│ │ ├── Settings.tsx # Settings page +│ │ └── Settings.css +│ ├── components/ +│ │ └── ClipboardItem.tsx # Individual clipboard item component +│ ├── main.tsx # React entry point +│ ├── App.tsx # Main app component with routing +│ └── types.d.ts # TypeScript definitions +└── models/ + └── ClipboardItem.ts # Data models and interfaces ``` ## Prerequisites @@ -55,9 +61,38 @@ src/ ## Installation +### From Release (Recommended) + +Download the latest release from the [Releases page](https://github.com/mberrishdev/clipai/releases). + +**macOS Users:** + +1. Download the `.dmg` file (choose `arm64` for Apple Silicon or `x64` for Intel Macs) +2. Open the `.dmg` file +3. Drag clipai to your Applications folder +4. On first launch, you may see "clipai cannot be opened because it is from an unidentified developer" + - Right-click (or Control-click) on clipai in Applications + - Click "Open" + - Click "Open" again in the dialog + - This only needs to be done once + +**Windows Users:** + +You may see a Windows SmartScreen warning because the app is not code-signed. This is normal for open-source apps. To install: + +1. Download the `.exe` installer +2. Run the installer +3. If you see a SmartScreen warning: + - Click "More info" + - Click "Run anyway" + +**Note:** These warnings appear because the app doesn't have a commercial code signing certificate ($300-400/year). The app is completely safe and open-source. + +### From Source + 1. Clone the repository: ```bash -git clone https://github.com/yourusername/clipai.git +git clone https://github.com/mberrishdev/clipai.git cd clipai ``` @@ -138,16 +173,24 @@ tail -f ~/Library/Logs/clipai/main.log ``` **Windows:** -```bash -%USERPROFILE%\AppData\Roaming\clipai\logs\main.log +``` +C:\Users\\AppData\Roaming\clipai\logs\main.log +``` -# Open logs folder +To open the logs folder: +```cmd +# Open in File Explorer explorer %APPDATA%\clipai\logs -# View in terminal +# Open log file directly in Notepad +notepad %APPDATA%\clipai\logs\main.log + +# View in Command Prompt type %APPDATA%\clipai\logs\main.log ``` +**Note:** If the logs folder doesn't exist, the app may not have been launched yet, or electron-log may not have initialized. Launch the app once and the log file will be created automatically. + ## Scripts - `bun run dev` - Start development mode diff --git a/src/main/clipboardManager.ts b/src/main/clipboardManager.ts index 7b61d2e..6ae70a5 100644 --- a/src/main/clipboardManager.ts +++ b/src/main/clipboardManager.ts @@ -41,13 +41,9 @@ export class ClipboardManager { loadMore(limit: number = 20): ClipboardItem[] { try { const offset = this.history.length; - log.info(`loadMore called: offset=${offset}, limit=${limit}`); - const moreItems = this.db.getItems(limit, offset); this.history.push(...moreItems); - log.info( - `Loaded ${moreItems.length} more items (offset was ${offset}, total now: ${this.history.length})` - ); + log.info(`Loaded ${moreItems.length} more items (total: ${this.history.length})`); return moreItems; } catch (error) { log.error("Failed to load more items from database:", error); @@ -79,7 +75,6 @@ export class ClipboardManager { if (this.window) { this.window.webContents.send("clipboard-update", item); } - log.info(`Image clipboard item saved to DB with id: ${id}`); } catch (error) { log.error("Failed to save image to database:", error); // Still add to memory even if DB fails @@ -110,7 +105,6 @@ export class ClipboardManager { if (this.window) { this.window.webContents.send("clipboard-update", item); } - log.info(`Text clipboard item saved to DB with id: ${id}`); } catch (error) { log.error("Failed to save text to database:", error); // Still add to memory even if DB fails diff --git a/src/main/main.ts b/src/main/main.ts index 0347bc0..bee79ff 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -16,8 +16,18 @@ import log from "electron-log"; const __dirname = dirname(fileURLToPath(import.meta.url)); +// Configure electron-log +log.initialize({ preload: true }); log.transports.file.level = "info"; +log.transports.console.level = "info"; +log.transports.file.format = "[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}"; + +log.info("=".repeat(80)); log.info("App starting..."); +log.info("Platform:", process.platform); +log.info("App version:", app.getVersion()); +log.info("Electron version:", process.versions.electron); +log.info("=".repeat(80)); let win: BrowserWindow | null = null; let tray: Tray | null = null; @@ -55,39 +65,31 @@ async function createWindow() { }); }); - log.info("Window created"); - win.on("close", (event) => { if (!isQuitting) { - log.info("Window close prevented, hiding instead"); event.preventDefault(); win?.hide(); } }); win.on("blur", () => { - log.info("Window lost focus, hiding"); win?.hide(); }); try { if (!app.isPackaged) { - log.info("Loading dev URL: http://localhost:5173"); await win.loadURL("http://localhost:5173"); //win.webContents.openDevTools(); } else { const htmlPath = join(__dirname, "../renderer/index.html"); - log.info("Loading file:", htmlPath); await win.loadFile(htmlPath); } - log.info("Content loaded successfully"); } catch (error) { log.error("Failed to load content:", error); } clipboardManager = new ClipboardManager(win, databaseManager!); clipboardManager.start(); - log.info("ClipboardManager started"); } function createTray() { @@ -95,23 +97,16 @@ function createTray() { const iconName = process.platform === "darwin" ? "trayIconTemplate.png" : "icon.png"; const iconPath = join(__dirname, "../../assets", iconName); - log.info("Creating tray with icon:", iconPath); tray = new Tray(iconPath); - log.info("Tray created successfully"); const contextMenu = Menu.buildFromTemplate([ { label: "Show Clipboard History", click: () => { - log.info("=== MENU: Show Clipboard History clicked ==="); - log.info("Window exists:", !!win); - log.info("Window visible before:", win?.isVisible()); try { win?.show(); win?.focus(); - log.info("Window visible after:", win?.isVisible()); win?.webContents.send("navigate", "history"); - log.info("Navigate event sent"); } catch (error) { log.error("Error in menu click:", error); } @@ -120,7 +115,6 @@ function createTray() { { label: "Settings", click: () => { - log.info("Menu: Settings clicked"); win?.show(); win?.webContents.send("navigate", "settings"); }, @@ -129,7 +123,6 @@ function createTray() { { label: "Quit", click: () => { - log.info("Menu: Quit clicked"); isQuitting = true; app.quit(); }, @@ -137,10 +130,8 @@ function createTray() { ]); tray.setToolTip("Clipboard Manager"); - log.info("Tray menu configured"); tray.on("click", () => { - log.info("Left-click: toggling window"); const isVisible = win?.isVisible(); if (isVisible) { win?.hide(); @@ -151,7 +142,6 @@ function createTray() { }); tray.on("right-click", () => { - log.info("Right-click: showing menu"); tray?.popUpContextMenu(contextMenu); }); } catch (error) { @@ -232,11 +222,8 @@ function registerGlobalShortcut(shortcut: string = "CommandOrControl+Shift+V") { app.whenReady().then(() => { log.info("App ready"); - log.info("isPackaged:", app.isPackaged); - log.info("__dirname:", __dirname); databaseManager = new DatabaseManager(); - configManager = new ConfigManager(); app.setLoginItemSettings({ @@ -244,7 +231,6 @@ app.whenReady().then(() => { }); if (app.dock) { - log.info("Hiding dock icon"); app.dock.hide(); } @@ -255,11 +241,8 @@ app.whenReady().then(() => { registerGlobalShortcut(config.globalShortcut); if (!app.isPackaged) { - log.info("Dev mode: showing window"); win?.show(); } - - log.info("Log file location:", log.transports.file.getFile().path); }); app.on("window-all-closed", () => {}); From 24d9f163e322d496bc6a345feeaade367d90037f Mon Sep 17 00:00:00 2001 From: Mikheil Berishvili Date: Thu, 11 Dec 2025 22:57:29 +0400 Subject: [PATCH 4/7] feat: add sqlite-verc, add openapi, integrate with openapi add semantic search Add sqlite-verc to save embedings integrate openapi to generate embedings todo: need improvement, current openai model is not working in good way. need improvement --- bun.lock | 92 +++++++++++++------- package.json | 7 +- scripts/build-preload.ts | 4 +- src/main/clipboardManager.ts | 42 ++++++++- src/main/configManager.ts | 11 +++ src/main/database.ts | 71 ++++++++++++--- src/main/embeddingService.ts | 55 ++++++++++++ src/main/main.ts | 39 ++++++++- src/main/preload.ts | 7 +- src/models/ClipboardItem.ts | 3 +- src/renderer/main.tsx | 3 + src/renderer/pages/ClipboardHistory.css | 111 ++++++++++++++++++++++++ src/renderer/pages/ClipboardHistory.tsx | 100 ++++++++++++++++++++- src/renderer/pages/Settings.css | 6 ++ src/renderer/pages/Settings.tsx | 85 ++++++++++++++++++ src/renderer/types.d.ts | 3 + src/renderer/utils/disableConsole.ts | 10 +++ 17 files changed, 593 insertions(+), 56 deletions(-) create mode 100644 src/main/embeddingService.ts create mode 100644 src/renderer/utils/disableConsole.ts diff --git a/bun.lock b/bun.lock index 875bcf3..958bc99 100644 --- a/bun.lock +++ b/bun.lock @@ -11,8 +11,11 @@ "concurrently": "^9.2.1", "electron-log": "^5.4.3", "highlight.js": "^11.11.1", + "openai": "^6.10.0", "react": "^19.2.1", "react-dom": "^19.2.1", + "sqlite-vec": "^0.1.7-alpha.2", + "sqlite-vec-darwin-arm64": "^0.1.7-alpha.2", "vite": "^7.2.7", "wait-on": "^9.0.3", }, @@ -22,6 +25,7 @@ "@types/react-dom": "^19.2.3", "electron": "^39.2.6", "electron-builder": "^26.0.12", + "electron-rebuild": "^3.2.9", }, "peerDependencies": { "typescript": "^5.9.3", @@ -299,6 +303,10 @@ "app-builder-lib": ["app-builder-lib@26.0.12", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.2.18", "@electron/fuses": "^1.8.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.1", "@electron/rebuild": "3.7.0", "@electron/universal": "2.0.1", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.0.11", "builder-util-runtime": "9.3.1", "chromium-pickle-js": "^0.2.0", "config-file-ts": "0.2.8-rc1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.0.11", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "is-ci": "^3.0.0", "isbinaryfile": "^5.0.0", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.0", "plist": "3.1.0", "resedit": "^1.7.0", "semver": "^7.3.8", "tar": "^6.1.12", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" }, "peerDependencies": { "dmg-builder": "26.0.12", "electron-builder-squirrel-windows": "26.0.12" } }, "sha512-+/CEPH1fVKf6HowBUs6LcAIoRcjeqgvAeoSE+cl7Y7LndyQ9ViGPYibNk7wmhMHzNgHIuIbw4nWADPO+4mjgWw=="], + "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="], + + "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], @@ -381,6 +389,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], @@ -393,6 +403,8 @@ "config-file-ts": ["config-file-ts@0.2.8-rc1", "", { "dependencies": { "glob": "^10.3.12", "typescript": "^5.4.3" } }, "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg=="], + "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], @@ -421,6 +433,8 @@ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], @@ -451,6 +465,8 @@ "electron-publish": ["electron-publish@26.0.11", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.0.11", "builder-util-runtime": "9.3.1", "chalk": "^4.1.2", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A=="], + "electron-rebuild": ["electron-rebuild@3.2.9", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "chalk": "^4.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "fs-extra": "^10.0.0", "got": "^11.7.0", "lzma-native": "^8.0.5", "node-abi": "^3.0.0", "node-api-version": "^0.1.4", "node-gyp": "^9.0.0", "ora": "^5.1.0", "semver": "^7.3.5", "tar": "^6.0.5", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/src/cli.js" } }, "sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw=="], + "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], @@ -519,6 +535,8 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], @@ -551,6 +569,8 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], @@ -635,6 +655,8 @@ "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "lzma-native": ["lzma-native@8.0.6", "", { "dependencies": { "node-addon-api": "^3.1.0", "node-gyp-build": "^4.2.1", "readable-stream": "^3.6.0" }, "bin": { "lzmajs": "bin/lzmajs" } }, "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA=="], + "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="], "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], @@ -683,9 +705,13 @@ "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], - "node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + "node-addon-api": ["node-addon-api@3.2.1", "", {}, "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="], + + "node-api-version": ["node-api-version@0.1.4", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-KGXihXdUChwJAOHO53bv9/vXcLmdUsZ6jIptbvYvkpKfth+r7jw44JkVxQFA3kX5nQjzjmGu1uAu/xNNLNlI5g=="], - "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], + "node-gyp": ["node-gyp@9.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.0.3", "nopt": "^6.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], @@ -693,12 +719,16 @@ "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "openai": ["openai@6.10.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], @@ -771,7 +801,7 @@ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], - "rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], @@ -789,19 +819,21 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], @@ -825,6 +857,18 @@ "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "sqlite-vec": ["sqlite-vec@0.1.7-alpha.2", "", { "optionalDependencies": { "sqlite-vec-darwin-arm64": "0.1.7-alpha.2", "sqlite-vec-darwin-x64": "0.1.7-alpha.2", "sqlite-vec-linux-arm64": "0.1.7-alpha.2", "sqlite-vec-linux-x64": "0.1.7-alpha.2", "sqlite-vec-windows-x64": "0.1.7-alpha.2" } }, "sha512-rNgRCv+4V4Ed3yc33Qr+nNmjhtrMnnHzXfLVPeGb28Dx5mmDL3Ngw/Wk8vhCGjj76+oC6gnkmMG8y73BZWGBwQ=="], + + "sqlite-vec-darwin-arm64": ["sqlite-vec-darwin-arm64@0.1.7-alpha.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-raIATOqFYkeCHhb/t3r7W7Cf2lVYdf4J3ogJ6GFc8PQEgHCPEsi+bYnm2JT84MzLfTlSTIdxr4/NKv+zF7oLPw=="], + + "sqlite-vec-darwin-x64": ["sqlite-vec-darwin-x64@0.1.7-alpha.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-jeZEELsQjjRsVojsvU5iKxOvkaVuE+JYC8Y4Ma8U45aAERrDYmqZoHvgSG7cg1PXL3bMlumFTAmHynf1y4pOzA=="], + + "sqlite-vec-linux-arm64": ["sqlite-vec-linux-arm64@0.1.7-alpha.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-6Spj4Nfi7tG13jsUG+W7jnT0bCTWbyPImu2M8nWp20fNrd1SZ4g3CSlDAK8GBdavX7wRlbBHCZ+BDa++rbDewA=="], + + "sqlite-vec-linux-x64": ["sqlite-vec-linux-x64@0.1.7-alpha.2", "", { "os": "linux", "cpu": "x64" }, "sha512-IcgrbHaDccTVhXDf8Orwdc2+hgDLAFORl6OBUhcvlmwswwBP1hqBTSEhovClG4NItwTOBNgpwOoQ7Qp3VDPWLg=="], + + "sqlite-vec-windows-x64": ["sqlite-vec-windows-x64@0.1.7-alpha.2", "", { "os": "win32", "cpu": "x64" }, "sha512-TRP6hTjAcwvQ6xpCZvjP00pdlda8J38ArFy1lMYhtQWXiIBmWnhMaMbq4kaeCYwvTTddfidatRS+TJrwIKB/oQ=="], + "ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="], "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], @@ -901,6 +945,8 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -921,23 +967,27 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@electron/asar/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "@electron/node-gyp/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="], + "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@electron/node-gyp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@electron/node-gyp/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="], "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], - "@electron/rebuild/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@electron/rebuild/node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], "@electron/universal/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], @@ -953,10 +1003,6 @@ "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "@npmcli/fs/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - "@types/better-sqlite3/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], "@types/cacheable-request/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], @@ -971,8 +1017,6 @@ "@types/yauzl/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], - "app-builder-lib/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "bun-types/@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="], "cacache/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="], @@ -981,8 +1025,6 @@ "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "cacache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], @@ -995,11 +1037,13 @@ "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "global-agent/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], @@ -1021,20 +1065,12 @@ "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "node-abi/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "node-api-version/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], - "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "simple-update-notifier/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -1043,6 +1079,8 @@ "temp/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], @@ -1079,8 +1117,6 @@ "cacache/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], - "cacache/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "config-file-ts/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "config-file-ts/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], @@ -1099,8 +1135,6 @@ "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "cacache/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "config-file-ts/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], } } diff --git a/package.json b/package.json index edefe96..0ff2d6e 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,11 @@ "dev": "concurrently \"bun run dev:renderer\" \"bun run dev:electron\"", "dev:renderer": "vite", "dev:preload": "bun run scripts/build-preload.ts", - "dev:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron --external better-sqlite3 --external electron-liquid-glass", + "dev:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron --external better-sqlite3 --external electron-liquid-glass --external sqlite-vec", "dev:electron": "bun run dev:preload && bun run dev:main && wait-on http://localhost:5173 && electron .", "build": "bun run build:renderer && bun run build:main && bun run build:preload", "build:renderer": "vite build", - "build:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron --external better-sqlite3 --external electron-liquid-glass", + "build:main": "bun build src/main/main.ts --outdir dist/main --target node --external electron --external better-sqlite3 --external electron-liquid-glass --external sqlite-vec", "build:preload": "bun build src/main/preload.ts --outdir dist/main --target node --format cjs --external electron", "package": "bun run build && electron-builder", "package:all": "bun run build && electron-builder --mac --win", @@ -94,8 +94,11 @@ "concurrently": "^9.2.1", "electron-log": "^5.4.3", "highlight.js": "^11.11.1", + "openai": "^6.10.0", "react": "^19.2.1", "react-dom": "^19.2.1", + "sqlite-vec": "^0.1.7-alpha.2", + "sqlite-vec-darwin-arm64": "^0.1.7-alpha.2", "vite": "^7.2.7", "wait-on": "^9.0.3" } diff --git a/scripts/build-preload.ts b/scripts/build-preload.ts index b4db607..9c55d30 100644 --- a/scripts/build-preload.ts +++ b/scripts/build-preload.ts @@ -2,7 +2,7 @@ import { build } from 'bun'; const result = await build({ entrypoints: ['./src/main/preload.ts'], - outdir: './src/main', + outdir: './dist/main', target: 'node', format: 'cjs', external: ['electron'], @@ -14,4 +14,4 @@ if (!result.success) { process.exit(1); } -console.log('Preload script built successfully'); +console.log('Preload script built successfully to dist/main'); diff --git a/src/main/clipboardManager.ts b/src/main/clipboardManager.ts index 6ae70a5..95d1050 100644 --- a/src/main/clipboardManager.ts +++ b/src/main/clipboardManager.ts @@ -2,6 +2,7 @@ import { clipboard, BrowserWindow, nativeImage } from "electron"; import type { ClipboardItem } from "../models/ClipboardItem.ts"; import type { DatabaseManager } from "./database.ts"; import log from "electron-log"; +import type { EmbeddingService } from "./embeddingService.ts"; export class ClipboardManager { private history: ClipboardItem[] = []; @@ -10,10 +11,16 @@ export class ClipboardManager { private intervalId: NodeJS.Timeout | null = null; private window: BrowserWindow | null = null; private db: DatabaseManager; + private embeddingService: EmbeddingService; - constructor(window: BrowserWindow, database: DatabaseManager) { + constructor( + window: BrowserWindow, + database: DatabaseManager, + embeddingService: EmbeddingService + ) { this.window = window; this.db = database; + this.embeddingService = embeddingService; this.loadHistoryFromDB(); } @@ -23,7 +30,9 @@ export class ClipboardManager { log.info(`Database stats:`, stats); this.history = this.db.getItems(15); - log.info(`Loaded ${this.history.length} items from database (initial load)`); + log.info( + `Loaded ${this.history.length} items from database (initial load)` + ); if (this.history.length > 0) { const lastItem = this.history[0]; @@ -43,7 +52,9 @@ export class ClipboardManager { const offset = this.history.length; const moreItems = this.db.getItems(limit, offset); this.history.push(...moreItems); - log.info(`Loaded ${moreItems.length} more items (total: ${this.history.length})`); + log.info( + `Loaded ${moreItems.length} more items (total: ${this.history.length})` + ); return moreItems; } catch (error) { log.error("Failed to load more items from database:", error); @@ -52,7 +63,7 @@ export class ClipboardManager { } start() { - this.intervalId = setInterval(() => { + this.intervalId = setInterval(async () => { const image = clipboard.readImage(); if (!image.isEmpty()) { @@ -64,6 +75,7 @@ export class ClipboardManager { type: "image", image: imageDataURL, timestamp: Date.now(), + embedding: [], }; // Save to database @@ -90,10 +102,16 @@ export class ClipboardManager { if (trimmedText && text !== this.lastClipboardText) { this.lastClipboardText = text; + + const embedding = await this.embeddingService.getEmbedding( + trimmedText + ); + const item: ClipboardItem = { type: "text", text, timestamp: Date.now(), + embedding, }; // Save to database @@ -129,6 +147,22 @@ export class ClipboardManager { return this.history; } + async semanticSearch(query: string, limit: number = 10): Promise { + try { + log.info(`Performing semantic search for: "${query}"`); + + const queryEmbedding = await this.embeddingService.getEmbedding(query); + + const results = this.db.semanticSearch(queryEmbedding, limit); + log.info(`Found ${results.length} results`); + + return results; + } catch (error) { + log.error("Failed to perform semantic search:", error); + throw error; + } + } + clear() { this.history = []; } diff --git a/src/main/configManager.ts b/src/main/configManager.ts index 9d6868a..cc7c8f5 100644 --- a/src/main/configManager.ts +++ b/src/main/configManager.ts @@ -6,11 +6,13 @@ import log from "electron-log"; export interface AppConfig { globalShortcut: string; transparency: boolean; + openaiApiKey?: string; } const DEFAULT_CONFIG: AppConfig = { globalShortcut: "CommandOrControl+Shift+V", transparency: true, + openaiApiKey: undefined, }; export class ConfigManager { @@ -65,4 +67,13 @@ export class ConfigManager { this.config.transparency = enabled; this.saveConfig(); } + + getOpenAIApiKey(): string | undefined { + return this.config.openaiApiKey; + } + + setOpenAIApiKey(apiKey: string): void { + this.config.openaiApiKey = apiKey.trim(); + this.saveConfig(); + } } diff --git a/src/main/database.ts b/src/main/database.ts index 1d8ef8a..9e026ab 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -2,6 +2,7 @@ import Database from "better-sqlite3"; import { app } from "electron"; import { join } from "path"; import log from "electron-log"; +import * as sqliteVec from "sqlite-vec"; import type { ClipboardItem } from "../models/ClipboardItem.ts"; export class DatabaseManager { @@ -14,6 +15,10 @@ export class DatabaseManager { this.db = new Database(dbPath); this.db.pragma("journal_mode = WAL"); + + sqliteVec.load(this.db); + log.info("sqlite-vec extension loaded"); + this.initTables(); } @@ -25,6 +30,7 @@ export class DatabaseManager { text TEXT, image TEXT, timestamp INTEGER NOT NULL, + embedding BLOB, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); @@ -35,19 +41,41 @@ export class DatabaseManager { } addItem(item: Omit): number { - const stmt = this.db.prepare(` - INSERT INTO clipboard_items (type, text, image, timestamp) - VALUES (?, ?, ?, ?) - `); + // If embedding exists, insert with vec_f32, otherwise insert without it + if (item.embedding && item.embedding.length > 0) { + const stmt = this.db.prepare(` + INSERT INTO clipboard_items (type, text, image, timestamp, embedding) + VALUES (?, ?, ?, ?, vec_f32(?)) + `); + + const embeddingBlob = Buffer.from( + new Float32Array(item.embedding).buffer + ); + + const result = stmt.run( + item.type, + item.text || null, + item.image || null, + item.timestamp, + embeddingBlob + ); - const result = stmt.run( - item.type, - item.text || null, - item.image || null, - item.timestamp - ); + return result.lastInsertRowid as number; + } else { + const stmt = this.db.prepare(` + INSERT INTO clipboard_items (type, text, image, timestamp) + VALUES (?, ?, ?, ?) + `); + + const result = stmt.run( + item.type, + item.text || null, + item.image || null, + item.timestamp + ); - return result.lastInsertRowid as number; + return result.lastInsertRowid as number; + } } getItems(limit: number = 1000, offset: number = 0): ClipboardItem[] { @@ -110,6 +138,27 @@ export class DatabaseManager { return stmt.all(`%${query}%`, limit) as ClipboardItem[]; } + semanticSearch( + queryEmbedding: number[], + limit: number = 10 + ): ClipboardItem[] { + const stmt = this.db.prepare(` + SELECT + id, + type, + text, + image, + timestamp, + vec_distance_cosine(embedding, vec_f32(?)) as distance + FROM clipboard_items + WHERE embedding IS NOT NULL + ORDER BY distance ASC + LIMIT ? + `); + + return stmt.all(JSON.stringify(queryEmbedding), limit) as ClipboardItem[]; + } + close() { this.db.close(); log.info("Database closed"); diff --git a/src/main/embeddingService.ts b/src/main/embeddingService.ts new file mode 100644 index 0000000..1923c09 --- /dev/null +++ b/src/main/embeddingService.ts @@ -0,0 +1,55 @@ +import OpenAI from "openai"; +import log from "electron-log"; +import type { ConfigManager } from "./configManager.ts"; + +export class EmbeddingService { + private openai: OpenAI | null = null; + private configManager: ConfigManager; + + constructor(configManager: ConfigManager) { + this.configManager = configManager; + this.initializeOpenAI(); + } + + private initializeOpenAI(): void { + const apiKey = this.configManager.getOpenAIApiKey(); + if (apiKey) { + this.openai = new OpenAI({ apiKey }); + log.info("OpenAI client initialized"); + } else { + this.openai = null; + log.warn("OpenAI API key not configured"); + } + } + + async getEmbedding(text: string): Promise { + if (!this.openai) { + const apiKey = this.configManager.getOpenAIApiKey(); + if (!apiKey) { + throw new Error("OpenAI API key not configured. Please add your API key in settings."); + } + this.initializeOpenAI(); + } + + try { + const response = await this.openai!.embeddings.create({ + model: "text-embedding-3-small", + input: text, + }); + + if (!response.data || response.data.length === 0 || !response.data[0]) { + throw new Error("No embedding data received from OpenAI"); + } + + return response.data[0].embedding; + } catch (error) { + log.error("Failed to get embedding:", error); + throw error; + } + } + + // Call this when API key is updated + refreshApiKey(): void { + this.initializeOpenAI(); + } +} diff --git a/src/main/main.ts b/src/main/main.ts index bee79ff..70c55fa 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -13,10 +13,10 @@ import { ClipboardManager } from "./clipboardManager.ts"; import { ConfigManager } from "./configManager.ts"; import { DatabaseManager } from "./database.ts"; import log from "electron-log"; +import { EmbeddingService } from "./embeddingService.ts"; const __dirname = dirname(fileURLToPath(import.meta.url)); -// Configure electron-log log.initialize({ preload: true }); log.transports.file.level = "info"; log.transports.console.level = "info"; @@ -34,6 +34,7 @@ let tray: Tray | null = null; let clipboardManager: ClipboardManager | null = null; let configManager: ConfigManager | null = null; let databaseManager: DatabaseManager | null = null; +let embeddingService: EmbeddingService | null = null; let isQuitting = false; async function createWindow() { @@ -88,7 +89,11 @@ async function createWindow() { log.error("Failed to load content:", error); } - clipboardManager = new ClipboardManager(win, databaseManager!); + clipboardManager = new ClipboardManager( + win, + databaseManager!, + embeddingService! + ); clipboardManager.start(); } @@ -157,6 +162,21 @@ ipcMain.handle("load-more-history", (_event, limit: number = 50) => { return clipboardManager?.loadMore(limit) || []; }); +ipcMain.handle( + "semantic-search", + async (_event, query: string, limit: number = 10) => { + try { + if (!clipboardManager) { + throw new Error("ClipboardManager not initialized"); + } + return await clipboardManager.semanticSearch(query, limit); + } catch (error) { + log.error("Failed to perform semantic search:", error); + throw error; + } + } +); + ipcMain.handle("set-transparency", (_event, enabled: boolean) => { if (win) { win.webContents.send("transparency-changed", enabled); @@ -196,6 +216,18 @@ ipcMain.handle("set-global-shortcut", (_event, shortcut: string) => { } }); +ipcMain.handle("set-openai-api-key", (_event, apiKey: string) => { + try { + configManager?.setOpenAIApiKey(apiKey); + embeddingService?.refreshApiKey(); + log.info("OpenAI API key updated"); + return { success: true }; + } catch (error) { + log.error("Failed to set OpenAI API key:", error); + return { success: false, error: String(error) }; + } +}); + function registerGlobalShortcut(shortcut: string = "CommandOrControl+Shift+V") { globalShortcut.unregisterAll(); @@ -223,8 +255,9 @@ function registerGlobalShortcut(shortcut: string = "CommandOrControl+Shift+V") { app.whenReady().then(() => { log.info("App ready"); - databaseManager = new DatabaseManager(); configManager = new ConfigManager(); + databaseManager = new DatabaseManager(); + embeddingService = new EmbeddingService(configManager); app.setLoginItemSettings({ openAtLogin: true, diff --git a/src/main/preload.ts b/src/main/preload.ts index 2d4d444..a652e49 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -3,7 +3,10 @@ import type { ClipboardItem } from "../models/ClipboardItem.ts"; contextBridge.exposeInMainWorld("electronAPI", { getClipboardHistory: () => ipcRenderer.invoke("get-clipboard-history"), - loadMoreHistory: (limit: number) => ipcRenderer.invoke("load-more-history", limit), + loadMoreHistory: (limit: number) => + ipcRenderer.invoke("load-more-history", limit), + semanticSearch: (query: string, limit?: number) => + ipcRenderer.invoke("semantic-search", query, limit), onClipboardUpdate: (callback: (item: ClipboardItem) => void) => { ipcRenderer.on("clipboard-update", (_event, item) => callback(item)); }, @@ -17,4 +20,6 @@ contextBridge.exposeInMainWorld("electronAPI", { getConfig: () => ipcRenderer.invoke("get-config"), setGlobalShortcut: (shortcut: string) => ipcRenderer.invoke("set-global-shortcut", shortcut), + setOpenAIApiKey: (apiKey: string) => + ipcRenderer.invoke("set-openai-api-key", apiKey), }); diff --git a/src/models/ClipboardItem.ts b/src/models/ClipboardItem.ts index 24586cc..2367902 100644 --- a/src/models/ClipboardItem.ts +++ b/src/models/ClipboardItem.ts @@ -1,7 +1,8 @@ export interface ClipboardItem { id?: number; - type: 'text' | 'image'; + type: "text" | "image"; text?: string; image?: string; timestamp: number; + embedding?: number[]; } diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index c22e77f..8804233 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -1,5 +1,8 @@ import { createRoot } from "react-dom/client"; import App from "./App"; +import { disableConsoleInProduction } from "./utils/disableConsole"; + +disableConsoleInProduction(); const rootElement = document.getElementById("root"); if (!rootElement) throw new Error("Root element not found"); diff --git a/src/renderer/pages/ClipboardHistory.css b/src/renderer/pages/ClipboardHistory.css index 9253259..e91ab4c 100644 --- a/src/renderer/pages/ClipboardHistory.css +++ b/src/renderer/pages/ClipboardHistory.css @@ -93,6 +93,117 @@ body.opaque .app { background: rgba(255, 255, 255, 0.3); } +.search-container { + padding: 1.5rem 2rem 1rem; + background: rgba(255, 255, 255, 0.03); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + position: relative; + z-index: 10; +} + +.search-wrapper { + position: relative; + display: flex; + align-items: center; + max-width: 600px; + margin: 0 auto; +} + +.search-hint { + text-align: center; + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.5); + margin-top: 0.5rem; + font-weight: 400; + letter-spacing: 0.02em; +} + +.search-warning { + text-align: center; + font-size: 0.75rem; + color: #ff6b6b; + margin-top: 0.5rem; + font-weight: 500; + letter-spacing: 0.02em; +} + +.search-input:disabled { + background: rgba(255, 255, 255, 0.5); + cursor: not-allowed; + opacity: 0.6; +} + +.search-input:disabled::placeholder { + color: #666; +} + +.search-icon { + position: absolute; + left: 1rem; + color: #666; + pointer-events: none; + z-index: 2; + transition: color 0.2s ease; +} + +.search-input { + width: 100%; + padding: 0.875rem 3rem 0.875rem 3rem; + background: rgba(255, 255, 255, 0.98); + border: 2px solid rgba(0, 0, 0, 0.06); + border-radius: 12px; + font-size: 0.9375rem; + color: #1a1a1a; + outline: none; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04), + 0 1px 2px rgba(0, 0, 0, 0.06); +} + +.search-input::placeholder { + color: #999; + font-weight: 400; +} + +.search-input:focus { + background: rgba(255, 255, 255, 1); + border-color: rgba(59, 130, 246, 0.5); + box-shadow: 0 4px 16px rgba(59, 130, 246, 0.15), + 0 2px 4px rgba(0, 0, 0, 0.06); + transform: translateY(-1px); +} + +.search-wrapper:focus-within .search-icon { + color: #3b82f6; +} + +.clear-btn { + position: absolute; + right: 0.75rem; + background: rgba(0, 0, 0, 0.05); + border: none; + border-radius: 6px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: #666; + transition: all 0.2s ease; + z-index: 2; +} + +.clear-btn:hover { + background: rgba(0, 0, 0, 0.1); + color: #333; + transform: scale(1.05); +} + +.clear-btn:active { + transform: scale(0.95); +} + .content { flex: 1; overflow-y: auto; diff --git a/src/renderer/pages/ClipboardHistory.tsx b/src/renderer/pages/ClipboardHistory.tsx index ef4b9fb..0fdb9a6 100644 --- a/src/renderer/pages/ClipboardHistory.tsx +++ b/src/renderer/pages/ClipboardHistory.tsx @@ -11,6 +11,39 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) { const [history, setHistory] = useState([]); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(true); + const [searchQuery, setSearchQuery] = useState(""); + const [hasApiKey, setHasApiKey] = useState(false); + + const performSearch = async () => { + console.log("Search triggered for:", searchQuery); + + if (searchQuery.trim()) { + setIsLoading(true); + try { + console.log("Performing semantic search for:", searchQuery); + const results = await window.electronAPI.semanticSearch( + searchQuery, + 10 + ); + console.log(`Found ${results.length} results:`, results); + setHistory(results); + } catch (error) { + console.error("Semantic search failed:", error); + } finally { + setIsLoading(false); + } + } else { + console.log("Empty query, reloading full history"); + const fullHistory = await window.electronAPI.getClipboardHistory(); + setHistory(fullHistory); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + performSearch(); + } + }; useEffect(() => { window.electronAPI.getClipboardHistory().then((items) => { @@ -18,6 +51,10 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) { setHistory(items); }); + window.electronAPI.getConfig().then((config) => { + setHasApiKey(!!config.openaiApiKey); + }); + window.electronAPI.onClipboardUpdate((item) => { setHistory((prev) => [item, ...prev]); }); @@ -25,11 +62,13 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) { const loadMore = async () => { if (isLoading || !hasMore) { - console.log(`loadMore blocked: isLoading=${isLoading}, hasMore=${hasMore}`); + console.log( + `loadMore blocked: isLoading=${isLoading}, hasMore=${hasMore}` + ); return; } - console.log('loadMore: Fetching more items...'); + console.log("loadMore: Fetching more items..."); setIsLoading(true); try { const moreItems = await window.electronAPI.loadMoreHistory(20); @@ -65,6 +104,61 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) {

Clipboard History

+
+
+ + + + setSearchQuery(e.target.value)} + onKeyDown={handleKeyDown} + disabled={!hasApiKey} + /> + {searchQuery && ( + + )} +
+ {hasApiKey ? ( +

Press Enter to search

+ ) : ( +

OpenAI API key required. Configure in Settings to enable semantic search.

+ )} +
{history.length === 0 ? (
@@ -89,7 +183,7 @@ export default function ClipboardHistory({}: ClipboardHistoryProps) { onClick={loadMore} disabled={isLoading} > - {isLoading ? 'Loading...' : 'Load More'} + {isLoading ? "Loading..." : "Load More"}
)} diff --git a/src/renderer/pages/Settings.css b/src/renderer/pages/Settings.css index f128ceb..d33fc4f 100644 --- a/src/renderer/pages/Settings.css +++ b/src/renderer/pages/Settings.css @@ -227,3 +227,9 @@ input:checked + .slider:before { font-size: 0.875rem; margin-top: 0.5rem; } + +.success-message { + color: #51cf66; + font-size: 0.875rem; + margin-top: 0.5rem; +} diff --git a/src/renderer/pages/Settings.tsx b/src/renderer/pages/Settings.tsx index 2f249e4..f205ce2 100644 --- a/src/renderer/pages/Settings.tsx +++ b/src/renderer/pages/Settings.tsx @@ -17,9 +17,15 @@ export default function Settings({ const [isEditingShortcut, setIsEditingShortcut] = useState(false); const [shortcutError, setShortcutError] = useState(""); + const [openaiApiKey, setOpenaiApiKey] = useState(""); + const [isEditingApiKey, setIsEditingApiKey] = useState(false); + const [apiKeyError, setApiKeyError] = useState(""); + const [apiKeySuccess, setApiKeySuccess] = useState(false); + useEffect(() => { window.electronAPI.getConfig().then((config) => { setGlobalShortcut(config.globalShortcut); + setOpenaiApiKey(config.openaiApiKey || ""); }); }, []); @@ -45,6 +51,32 @@ export default function Settings({ setGlobalShortcut(config.globalShortcut); }; + const handleApiKeySave = async () => { + setApiKeyError(""); + setApiKeySuccess(false); + + if (!openaiApiKey.trim()) { + setApiKeyError("API key cannot be empty"); + return; + } + + const result = await window.electronAPI.setOpenAIApiKey(openaiApiKey.trim()); + if (result.success) { + setIsEditingApiKey(false); + setApiKeySuccess(true); + setTimeout(() => setApiKeySuccess(false), 3000); + } else { + setApiKeyError(result.error || "Failed to save API key"); + } + }; + + const handleApiKeyCancel = async () => { + setIsEditingApiKey(false); + setApiKeyError(""); + const config = await window.electronAPI.getConfig(); + setOpenaiApiKey(config.openaiApiKey || ""); + }; + return ( <> @@ -130,6 +162,59 @@ export default function Settings({ + +
+

AI Features

+
+
+ +

Required for semantic search functionality

+ {apiKeyError && ( +

{apiKeyError}

+ )} + {apiKeySuccess && ( +

API key saved successfully!

+ )} +
+
+ {isEditingApiKey ? ( + <> + setOpenaiApiKey(e.target.value)} + placeholder="sk-..." + /> + + + + ) : ( + <> + + {openaiApiKey ? "••••••••••••••••" : "Not configured"} + + + + )} +
+
+
diff --git a/src/renderer/types.d.ts b/src/renderer/types.d.ts index 5d7c248..45d745d 100644 --- a/src/renderer/types.d.ts +++ b/src/renderer/types.d.ts @@ -3,17 +3,20 @@ import type { ClipboardItem } from "../models/ClipboardItem"; export interface AppConfig { globalShortcut: string; transparency: boolean; + openaiApiKey?: string; } export interface ElectronAPI { getClipboardHistory: () => Promise; loadMoreHistory: (limit: number) => Promise; + semanticSearch: (query: string, limit?: number) => Promise; onClipboardUpdate: (callback: (item: ClipboardItem) => void) => void; onNavigate: (callback: (page: string) => void) => void; setTransparency: (enabled: boolean) => Promise; openExternalURL: (url: string) => Promise; getConfig: () => Promise; setGlobalShortcut: (shortcut: string) => Promise<{ success: boolean; error?: string }>; + setOpenAIApiKey: (apiKey: string) => Promise<{ success: boolean; error?: string }>; } declare global { diff --git a/src/renderer/utils/disableConsole.ts b/src/renderer/utils/disableConsole.ts new file mode 100644 index 0000000..fe645dd --- /dev/null +++ b/src/renderer/utils/disableConsole.ts @@ -0,0 +1,10 @@ +// Disable console.log in production +export function disableConsoleInProduction() { + // Check if running in production (Vite sets this) + if (import.meta.env.PROD) { + console.log = () => {}; + console.info = () => {}; + console.debug = () => {}; + // Keep console.error and console.warn for critical issues + } +} From 733fcef352c47efd5856b2faf84e208d5fe012b2 Mon Sep 17 00:00:00 2001 From: Mikheil Berishvili Date: Fri, 12 Dec 2025 15:07:55 +0400 Subject: [PATCH 5/7] feat(settings): add keyboard shortcut recorder and UI improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace text input with interactive key capture for global shortcut - Display shortcuts with friendly symbols (⌘/Ctrl, ⇧, ⌥) - Add animated recording indicator with pulsing dot - Fix EPIPE error handling for stdout/stderr pipes - Improve button styles with gradient, hover effects, and disabled states - Add shortcut recorder CSS with glow animations - Update toggle switch with smooth gradient transitions - Enhance glass-morphism styling for settings cards - Add modern scrollbar styling --- src/main/main.ts | 9 + src/renderer/components/ClipboardItem.css | 215 ++++++---- src/renderer/pages/ClipboardHistory.css | 325 +++++++-------- src/renderer/pages/Settings.css | 480 ++++++++++++++++++---- src/renderer/pages/Settings.tsx | 140 +++++-- 5 files changed, 798 insertions(+), 371 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index 70c55fa..5039bc0 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -22,6 +22,15 @@ log.transports.file.level = "info"; log.transports.console.level = "info"; log.transports.file.format = "[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}"; +process.stdout?.on?.("error", (err) => { + if (err.code === "EPIPE") return; + throw err; +}); +process.stderr?.on?.("error", (err) => { + if (err.code === "EPIPE") return; + throw err; +}); + log.info("=".repeat(80)); log.info("App starting..."); log.info("Platform:", process.platform); diff --git a/src/renderer/components/ClipboardItem.css b/src/renderer/components/ClipboardItem.css index 6769ebc..9c62b80 100644 --- a/src/renderer/components/ClipboardItem.css +++ b/src/renderer/components/ClipboardItem.css @@ -1,207 +1,254 @@ +/* Modern Clipboard Item Card */ .history-item { display: flex; - gap: 0.75rem; - background: rgba(255, 255, 255, 0.95); + gap: 0.875rem; + background: rgba(255, 255, 255, 0.06); padding: 1rem 1.25rem; - border-radius: 10px; - border: 1px solid rgba(0, 0, 0, 0.05); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - transition: all 0.2s ease; - animation: slideIn 0.3s ease-out; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + animation: itemSlideIn 0.3s ease-out backwards; + cursor: pointer; outline: none; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } +.history-item:nth-child(1) { animation-delay: 0ms; } +.history-item:nth-child(2) { animation-delay: 30ms; } +.history-item:nth-child(3) { animation-delay: 60ms; } +.history-item:nth-child(4) { animation-delay: 90ms; } +.history-item:nth-child(5) { animation-delay: 120ms; } + .history-item:hover { - background: rgba(255, 255, 255, 1); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); - border-color: rgba(0, 0, 0, 0.08); + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.15); + transform: translateY(-2px) scale(1.005); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15), + 0 0 0 1px rgba(255, 255, 255, 0.1); } -@keyframes slideIn { +.history-item:focus-visible { + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); +} + +@keyframes itemSlideIn { from { opacity: 0; - transform: translateY(-10px); + transform: translateY(12px) scale(0.98); } to { opacity: 1; - transform: translateY(0); + transform: translateY(0) scale(1); } } +/* Item Number Badge */ .item-number { display: flex; - align-items: flex-start; + align-items: center; justify-content: center; - min-width: 24px; - height: 24px; - background: rgba(0, 0, 0, 0.05); - color: #666; - border-radius: 6px; - font-weight: 600; + min-width: 28px; + height: 28px; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: white; + border-radius: 8px; + font-weight: 700; font-size: 0.75rem; - padding-top: 3px; flex-shrink: 0; + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3); } +/* Item Content */ .item-content { flex: 1; display: flex; flex-direction: column; - gap: 0.375rem; + gap: 0.5rem; min-width: 0; } -.item-text { - color: #1a1a1a; - line-height: 1.5; - word-break: break-word; - white-space: pre-wrap; - font-size: 0.9375rem; - overflow: hidden; - text-overflow: ellipsis; -} - -.item-timestamp { - font-size: 0.6875rem; - color: #999; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.025em; -} - .item-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 0.5rem; } +/* Content Type Badges */ .content-badge { - display: inline-block; - padding: 0.125rem 0.5rem; - border-radius: 4px; + display: inline-flex; + align-items: center; + padding: 0.1875rem 0.5rem; + border-radius: 6px; font-size: 0.625rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; + backdrop-filter: blur(8px); } .content-badge--json { - background: #e3f2fd; - color: #1976d2; + background: rgba(59, 130, 246, 0.2); + color: #60a5fa; + border: 1px solid rgba(59, 130, 246, 0.3); } .content-badge--url { - background: #f3e5f5; - color: #7b1fa2; + background: rgba(168, 85, 247, 0.2); + color: #c084fc; + border: 1px solid rgba(168, 85, 247, 0.3); } .content-badge--email { - background: #e8f5e9; - color: #388e3c; + background: rgba(34, 197, 94, 0.2); + color: #4ade80; + border: 1px solid rgba(34, 197, 94, 0.3); } .content-badge--color { - background: #fff3e0; - color: #f57c00; + background: rgba(251, 146, 60, 0.2); + color: #fb923c; + border: 1px solid rgba(251, 146, 60, 0.3); } .content-badge--base64 { - background: #fce4ec; - color: #c2185b; + background: rgba(236, 72, 153, 0.2); + color: #f472b6; + border: 1px solid rgba(236, 72, 153, 0.3); } .content-badge--text { - background: #f5f5f5; - color: #616161; + background: rgba(148, 163, 184, 0.15); + color: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(148, 163, 184, 0.2); } .content-badge--image { - background: #e1f5fe; - color: #0277bd; + background: rgba(14, 165, 233, 0.2); + color: #38bdf8; + border: 1px solid rgba(14, 165, 233, 0.3); } +/* Text Content */ +.item-text { + color: rgba(255, 255, 255, 0.9); + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + font-size: 0.875rem; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Timestamp */ +.item-timestamp { + font-size: 0.6875rem; + color: rgba(255, 255, 255, 0.4); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.03em; +} + +/* Image Preview */ .item-image { max-width: 100%; - max-height: 300px; + max-height: 200px; border-radius: 8px; object-fit: contain; - border: 1px solid rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.1); } +/* Action Buttons */ .item-actions { display: flex; gap: 0.5rem; + margin-top: 0.25rem; } .action-btn { - background: rgba(0, 0, 0, 0.05); - border: none; - padding: 0.25rem 0.625rem; - border-radius: 4px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.375rem 0.75rem; + border-radius: 6px; font-size: 0.75rem; cursor: pointer; - transition: background 0.2s; - color: #666; + transition: all 0.2s ease; + color: rgba(255, 255, 255, 0.7); font-weight: 500; } .action-btn:hover { - background: rgba(0, 0, 0, 0.1); + background: rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.95); + border-color: rgba(255, 255, 255, 0.2); +} + +.action-btn--copied { + background: rgba(34, 197, 94, 0.2); + border-color: rgba(34, 197, 94, 0.3); + color: #4ade80; } +/* Code Block */ .item-code { - background: #0d1117; - padding: 0.75rem; - border-radius: 6px; + background: rgba(0, 0, 0, 0.4); + padding: 0.875rem; + border-radius: 8px; overflow-x: auto; margin: 0; font-size: 0.8125rem; - line-height: 1.5; - color: #c9d1d9; + line-height: 1.6; + border: 1px solid rgba(255, 255, 255, 0.08); } .item-code code { - font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; - color: #c9d1d9; + font-family: "SF Mono", "Monaco", "Menlo", "Ubuntu Mono", monospace; + color: #e2e8f0; } +/* Color Preview */ .item-color-preview { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.875rem; } .color-swatch { - width: 32px; - height: 32px; - border-radius: 6px; - border: 1px solid rgba(0, 0, 0, 0.1); + width: 36px; + height: 36px; + border-radius: 8px; + border: 2px solid rgba(255, 255, 255, 0.2); flex-shrink: 0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } +/* URL Link */ .item-link { - color: #1976d2; + color: #818cf8; text-decoration: none; word-break: break-all; + transition: color 0.2s ease; } .item-link:hover { + color: #a5b4fc; text-decoration: underline; } +/* Show More Button */ .show-more-btn { background: none; border: none; - color: #1976d2; + color: #818cf8; font-size: 0.8125rem; cursor: pointer; padding: 0.25rem 0; - margin-top: 0.25rem; + margin-top: 0.375rem; font-weight: 500; + transition: color 0.2s ease; } .show-more-btn:hover { - text-decoration: underline; + color: #a5b4fc; } diff --git a/src/renderer/pages/ClipboardHistory.css b/src/renderer/pages/ClipboardHistory.css index e91ab4c..ed73416 100644 --- a/src/renderer/pages/ClipboardHistory.css +++ b/src/renderer/pages/ClipboardHistory.css @@ -1,54 +1,79 @@ +/* Modern Clipboard History UI */ * { margin: 0; padding: 0; box-sizing: border-box; } +:root { + --accent-primary: #6366f1; + --accent-secondary: #8b5cf6; + --accent-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%); + --glass-bg: rgba(255, 255, 255, 0.08); + --glass-border: rgba(255, 255, 255, 0.12); + --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); + --text-primary: rgba(255, 255, 255, 0.95); + --text-secondary: rgba(255, 255, 255, 0.6); + --text-muted: rgba(255, 255, 255, 0.4); +} + body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", "Roboto", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background: transparent; } body.transparent { background: transparent; - background-color: rgba(0, 0, 0, 0); } body.transparent .app { position: relative; - background: transparent; + background: linear-gradient( + 180deg, + rgba(15, 15, 25, 0.85) 0%, + rgba(20, 20, 35, 0.9) 100% + ); } body.transparent .app::before { content: ''; position: absolute; inset: 0; - z-index: 0; - backdrop-filter: blur(40px); - filter: url(#lg-dist); + z-index: -1; + backdrop-filter: blur(80px) saturate(180%); + -webkit-backdrop-filter: blur(80px) saturate(180%); + border-radius: 12px; } body.transparent .app::after { content: ''; position: absolute; inset: 0; - z-index: 1; - background: rgba(255, 255, 255, 0.25); + z-index: 0; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.05) 50%, + rgba(255, 255, 255, 0.02) 100% + ); border-radius: 12px; - box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.75), - inset 0 0 5px rgba(255, 255, 255, 0.75); + border: 1px solid rgba(255, 255, 255, 0.1); + pointer-events: none; } body.opaque { - background: #1a1a1a; + background: #0f0f19; } body.opaque .app { - background: #2d2d2d; - backdrop-filter: none; + background: linear-gradient( + 180deg, + #1a1a2e 0%, + #16162a 100% + ); } .app { @@ -57,13 +82,16 @@ body.opaque .app { flex-direction: column; border-radius: 12px; overflow: hidden; + position: relative; } +/* Header */ .header { - padding: 0.5rem; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); + padding: 1rem 1.5rem; + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--glass-border); display: flex; justify-content: space-between; align-items: center; @@ -72,31 +100,38 @@ body.opaque .app { } .header h1 { - color: white; - font-size: 1rem; - font-weight: 600; + background: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-size: 1.125rem; + font-weight: 700; + letter-spacing: -0.02em; } .settings-btn { - background: rgba(255, 255, 255, 0.2); - border: none; - color: white; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + color: var(--text-primary); padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer; - font-size: 0.875rem; - transition: background 0.2s; + font-size: 0.8125rem; + transition: all 0.2s ease; font-weight: 500; + backdrop-filter: blur(10px); } .settings-btn:hover { - background: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); } +/* Search */ .search-container { - padding: 1.5rem 2rem 1rem; - background: rgba(255, 255, 255, 0.03); - border-bottom: 1px solid rgba(255, 255, 255, 0.08); + padding: 1.25rem 1.5rem 1rem; + background: transparent; position: relative; z-index: 10; } @@ -105,98 +140,96 @@ body.opaque .app { position: relative; display: flex; align-items: center; - max-width: 600px; - margin: 0 auto; + max-width: 100%; } .search-hint { text-align: center; - font-size: 0.75rem; - color: rgba(255, 255, 255, 0.5); - margin-top: 0.5rem; - font-weight: 400; - letter-spacing: 0.02em; + font-size: 0.6875rem; + color: var(--text-muted); + margin-top: 0.625rem; + font-weight: 500; + letter-spacing: 0.03em; + text-transform: uppercase; } .search-warning { text-align: center; - font-size: 0.75rem; - color: #ff6b6b; - margin-top: 0.5rem; + font-size: 0.6875rem; + color: #f87171; + margin-top: 0.625rem; font-weight: 500; - letter-spacing: 0.02em; -} - -.search-input:disabled { - background: rgba(255, 255, 255, 0.5); - cursor: not-allowed; - opacity: 0.6; -} - -.search-input:disabled::placeholder { - color: #666; + letter-spacing: 0.03em; } .search-icon { position: absolute; left: 1rem; - color: #666; + color: var(--text-muted); pointer-events: none; z-index: 2; - transition: color 0.2s ease; + transition: all 0.3s ease; } .search-input { width: 100%; - padding: 0.875rem 3rem 0.875rem 3rem; - background: rgba(255, 255, 255, 0.98); - border: 2px solid rgba(0, 0, 0, 0.06); + padding: 0.875rem 3rem; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; - font-size: 0.9375rem; - color: #1a1a1a; + font-size: 0.875rem; + color: var(--text-primary); outline: none; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04), - 0 1px 2px rgba(0, 0, 0, 0.06); + backdrop-filter: blur(10px); } .search-input::placeholder { - color: #999; + color: var(--text-muted); font-weight: 400; } .search-input:focus { - background: rgba(255, 255, 255, 1); - border-color: rgba(59, 130, 246, 0.5); - box-shadow: 0 4px 16px rgba(59, 130, 246, 0.15), - 0 2px 4px rgba(0, 0, 0, 0.06); - transform: translateY(-1px); + background: rgba(255, 255, 255, 0.1); + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15), + 0 4px 20px rgba(99, 102, 241, 0.1); } .search-wrapper:focus-within .search-icon { - color: #3b82f6; + color: var(--accent-primary); +} + +.search-input:disabled { + background: rgba(255, 255, 255, 0.03); + cursor: not-allowed; + opacity: 0.5; +} + +.search-input:disabled::placeholder { + color: var(--text-muted); } .clear-btn { position: absolute; right: 0.75rem; - background: rgba(0, 0, 0, 0.05); + background: rgba(255, 255, 255, 0.1); border: none; border-radius: 6px; - width: 28px; - height: 28px; + width: 26px; + height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; - color: #666; + color: var(--text-secondary); transition: all 0.2s ease; z-index: 2; } .clear-btn:hover { - background: rgba(0, 0, 0, 0.1); - color: #333; + background: rgba(255, 255, 255, 0.2); + color: var(--text-primary); transform: scale(1.05); } @@ -204,151 +237,107 @@ body.opaque .app { transform: scale(0.95); } +/* Content */ .content { flex: 1; overflow-y: auto; - padding: 2rem; + overflow-x: hidden; + padding: 1rem 1.5rem 2rem; position: relative; z-index: 10; } +/* Empty State */ .empty-state { text-align: center; padding: 4rem 2rem; - color: white; + color: var(--text-secondary); } .empty-state p { - font-size: 1.25rem; + font-size: 1rem; margin-bottom: 0.5rem; + color: var(--text-secondary); } .empty-state .hint { - font-size: 1rem; - opacity: 0.7; + font-size: 0.875rem; + color: var(--text-muted); } +/* History List */ .history-list { display: flex; flex-direction: column; - gap: 1rem; -} - -.history-item { - display: flex; - gap: 0.75rem; - background: rgba(255, 255, 255, 0.95); - padding: 1rem 1.25rem; - border-radius: 10px; - border: 1px solid rgba(0, 0, 0, 0.05); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - transition: all 0.2s ease; - animation: slideIn 0.3s ease-out; - cursor: pointer; - outline: none; -} - -.history-item:hover { - background: rgba(255, 255, 255, 1); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); - border-color: rgba(0, 0, 0, 0.08); -} - -.item-number { - display: flex; - align-items: flex-start; - justify-content: center; - min-width: 24px; - height: 24px; - background: rgba(0, 0, 0, 0.05); - color: #666; - border-radius: 6px; - font-weight: 600; - font-size: 0.75rem; - padding-top: 3px; - flex-shrink: 0; -} - -.item-content { - flex: 1; - display: flex; - flex-direction: column; - gap: 0.375rem; - min-width: 0; -} - -.item-text { - color: #1a1a1a; - line-height: 1.5; - word-break: break-word; - white-space: pre-wrap; - font-size: 0.9375rem; - overflow: hidden; - text-overflow: ellipsis; -} - -.item-timestamp { - font-size: 0.6875rem; - color: #999; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.025em; -} - -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } + gap: 0.625rem; } +/* Scrollbar */ ::-webkit-scrollbar { - width: 8px; + width: 6px; } ::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.1); + background: transparent; } ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 4px; + background: rgba(255, 255, 255, 0.15); + border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); + background: rgba(255, 255, 255, 0.25); } +/* Load More */ .load-more-container { - padding: 2rem; + padding: 1.5rem; text-align: center; } .load-more-btn { - background: rgba(255, 255, 255, 0.95); - border: 1px solid rgba(0, 0, 0, 0.1); - color: #333; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + color: var(--text-primary); padding: 0.75rem 2rem; - border-radius: 8px; + border-radius: 10px; cursor: pointer; - font-size: 0.875rem; + font-size: 0.8125rem; font-weight: 600; transition: all 0.2s ease; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + backdrop-filter: blur(10px); } .load-more-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 1); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + background: rgba(255, 255, 255, 0.15); + border-color: var(--accent-primary); + transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(99, 102, 241, 0.2); } .load-more-btn:disabled { - opacity: 0.6; + opacity: 0.5; cursor: not-allowed; } + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} diff --git a/src/renderer/pages/Settings.css b/src/renderer/pages/Settings.css index d33fc4f..3e5bc67 100644 --- a/src/renderer/pages/Settings.css +++ b/src/renderer/pages/Settings.css @@ -1,3 +1,4 @@ +/* Modern Settings Page */ .settings { height: 100vh; display: flex; @@ -9,39 +10,54 @@ body.transparent .settings { position: relative; - background: transparent; + background: linear-gradient( + 180deg, + rgba(15, 15, 25, 0.85) 0%, + rgba(20, 20, 35, 0.9) 100% + ); } body.transparent .settings::before { content: ''; position: absolute; inset: 0; - z-index: 0; - backdrop-filter: blur(40px); - filter: url(#lg-dist); + z-index: -1; + backdrop-filter: blur(80px) saturate(180%); + -webkit-backdrop-filter: blur(80px) saturate(180%); + border-radius: 12px; } body.transparent .settings::after { content: ''; position: absolute; inset: 0; - z-index: 1; - background: rgba(255, 255, 255, 0.25); + z-index: 0; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.05) 50%, + rgba(255, 255, 255, 0.02) 100% + ); border-radius: 12px; - box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.75), - inset 0 0 5px rgba(255, 255, 255, 0.75); + border: 1px solid rgba(255, 255, 255, 0.1); + pointer-events: none; } body.opaque .settings { - background: #2d2d2d; - backdrop-filter: none; + background: linear-gradient( + 180deg, + #1a1a2e 0%, + #16162a 100% + ); } +/* Header */ .settings-header { - padding: 0.5rem; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); + padding: 1rem 1.5rem; + background: rgba(255, 255, 255, 0.08); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid rgba(255, 255, 255, 0.12); display: flex; align-items: center; gap: 1rem; @@ -50,75 +66,98 @@ body.opaque .settings { } .settings-header h1 { - font-size: 1rem; - font-weight: 600; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-size: 1.125rem; + font-weight: 700; + letter-spacing: -0.02em; } .back-btn { - background: rgba(255, 255, 255, 0.2); - border: none; - color: white; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.9); padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer; - font-size: 1rem; - transition: background 0.2s; + font-size: 0.875rem; + transition: all 0.2s ease; + font-weight: 500; } .back-btn:hover { - background: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); } +/* Content */ .settings-content { flex: 1; overflow-y: auto; - padding: 2rem; + padding: 1.5rem; position: relative; z-index: 10; } +/* Setting Groups */ .setting-group { margin-bottom: 2rem; } .setting-group h2 { - font-size: 1.25rem; + font-size: 0.8125rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; margin-bottom: 1rem; - opacity: 0.9; + color: rgba(255, 255, 255, 0.5); } +/* Setting Items */ .setting-item { - background: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.06); padding: 1.25rem; border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.08); display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; + margin-bottom: 0.75rem; + transition: all 0.2s ease; +} + +.setting-item:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.12); } .setting-info { flex: 1; } -.setting-info label { - font-size: 1rem; - font-weight: 500; - display: block; +.setting-info h3 { + font-size: 0.9375rem; + font-weight: 600; margin-bottom: 0.25rem; + color: rgba(255, 255, 255, 0.95); } .setting-info p { - font-size: 0.875rem; - opacity: 0.7; - margin: 0; + font-size: 0.8125rem; + color: rgba(255, 255, 255, 0.5); + line-height: 1.5; } +/* Toggle Switch */ .switch { position: relative; display: inline-block; - width: 50px; + width: 52px; height: 28px; + flex-shrink: 0; } .switch input { @@ -130,106 +169,367 @@ body.opaque .settings { .slider { position: absolute; cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.3); - transition: 0.3s; - border-radius: 28px; + inset: 0; + background: rgba(255, 255, 255, 0.12); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); } -.slider:before { +.slider::before { position: absolute; content: ""; - height: 20px; - width: 20px; - left: 4px; - bottom: 4px; - background-color: white; - transition: 0.3s; + height: 22px; + width: 22px; + left: 2px; + bottom: 2px; + background: linear-gradient(180deg, #ffffff 0%, #f0f0f0 100%); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 50%; + box-shadow: + 0 2px 8px rgba(0, 0, 0, 0.2), + 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.switch input:checked + .slider { + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%); + border-color: transparent; + box-shadow: + 0 2px 8px rgba(99, 102, 241, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.switch input:checked + .slider::before { + transform: translateX(24px); + box-shadow: + 0 2px 8px rgba(0, 0, 0, 0.25), + 0 1px 2px rgba(0, 0, 0, 0.15); +} + +.switch:hover .slider { + background: rgba(255, 255, 255, 0.18); } -input:checked + .slider { - background-color: #4caf50; +.switch input:checked + .slider:hover { + background: linear-gradient(135deg, #7c7ff7 0%, #9d6df9 50%, #b366f9 100%); } -input:checked + .slider:before { - transform: translateX(22px); +/* Input Fields */ +.setting-input { + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 0.75rem 1rem; + color: rgba(255, 255, 255, 0.95); + font-size: 0.875rem; + width: 100%; + outline: none; + transition: all 0.2s ease; } +.setting-input:focus { + background: rgba(255, 255, 255, 0.1); + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); +} + +.setting-input::placeholder { + color: rgba(255, 255, 255, 0.3); +} + +/* Shortcut Control Container */ .shortcut-control { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.625rem; + flex-shrink: 0; } +/* Shortcut Display */ .shortcut-display { - font-family: 'Monaco', 'Menlo', monospace; - background: rgba(255, 255, 255, 0.2); - padding: 0.5rem 0.75rem; - border-radius: 6px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.625rem 1rem; + border-radius: 8px; font-size: 0.875rem; + font-weight: 500; + color: rgba(255, 255, 255, 0.85); + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif; + letter-spacing: 0.02em; } -.shortcut-input { - font-family: 'Monaco', 'Menlo', monospace; - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 0.5rem 0.75rem; - border-radius: 6px; +/* Shortcut Recorder */ +.shortcut-recorder { + min-width: 160px; + padding: 0.625rem 1rem; + border-radius: 10px; font-size: 0.875rem; - min-width: 250px; + font-weight: 500; + text-align: center; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + background: rgba(139, 92, 246, 0.1); + border: 2px solid rgba(139, 92, 246, 0.4); + box-shadow: + 0 0 20px rgba(139, 92, 246, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.05); + animation: recorderPulse 2s ease-in-out infinite; +} + +.shortcut-recorder.has-value { + background: rgba(139, 92, 246, 0.15); + border-color: rgba(139, 92, 246, 0.6); + animation: none; +} + +@keyframes recorderPulse { + 0%, 100% { + box-shadow: + 0 0 20px rgba(139, 92, 246, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.05); + border-color: rgba(139, 92, 246, 0.4); + } + 50% { + box-shadow: + 0 0 30px rgba(139, 92, 246, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.08); + border-color: rgba(139, 92, 246, 0.7); + } +} + +.recording-prompt { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + color: rgba(255, 255, 255, 0.6); + font-size: 0.8125rem; } -.shortcut-input::placeholder { - color: rgba(255, 255, 255, 0.5); +.pulse-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #a855f7; + animation: pulseDot 1s ease-in-out infinite; } -.shortcut-input:focus { +@keyframes pulseDot { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.5; + transform: scale(0.8); + } +} + +.recorded-keys { + color: rgba(255, 255, 255, 0.95); + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif; + font-weight: 600; + letter-spacing: 0.03em; +} + +/* Shortcut Input */ +.shortcut-input { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 8px; + padding: 0.625rem 1rem; + color: rgba(255, 255, 255, 0.95); + font-size: 0.8125rem; + font-family: "SF Mono", "Monaco", "Inconsolata", monospace; + width: 200px; outline: none; - border-color: rgba(255, 255, 255, 0.5); + transition: all 0.2s ease; } -.btn-primary, -.btn-secondary { - padding: 0.5rem 1rem; - border-radius: 6px; - border: none; - font-size: 0.875rem; - cursor: pointer; - transition: all 0.2s; - font-weight: 500; +.shortcut-input:focus { + background: rgba(255, 255, 255, 0.1); + border-color: #8b5cf6; + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.2); } +.shortcut-input::placeholder { + color: rgba(255, 255, 255, 0.3); +} + +/* Modern Buttons */ .btn-primary { - background: #4caf50; + position: relative; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%); + border: none; color: white; + padding: 0.625rem 1.25rem; + border-radius: 10px; + cursor: pointer; + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.02em; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: + 0 2px 8px rgba(99, 102, 241, 0.25), + 0 4px 16px rgba(139, 92, 246, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.2); + overflow: hidden; +} + +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.btn-primary:disabled:hover { + transform: none; + box-shadow: + 0 2px 8px rgba(99, 102, 241, 0.25), + 0 4px 16px rgba(139, 92, 246, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn-primary::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%); + opacity: 0; + transition: opacity 0.25s ease; } .btn-primary:hover { - background: #45a049; + transform: translateY(-2px); + box-shadow: + 0 4px 12px rgba(99, 102, 241, 0.35), + 0 8px 24px rgba(139, 92, 246, 0.25), + inset 0 1px 0 rgba(255, 255, 255, 0.25); +} + +.btn-primary:hover::before { + opacity: 1; +} + +.btn-primary:active { + transform: translateY(0); + box-shadow: + 0 2px 6px rgba(99, 102, 241, 0.3), + 0 4px 12px rgba(139, 92, 246, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.15); } .btn-secondary { - background: rgba(255, 255, 255, 0.2); - color: white; + position: relative; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.9); + padding: 0.625rem 1.25rem; + border-radius: 10px; + cursor: pointer; + font-size: 0.8125rem; + font-weight: 500; + letter-spacing: 0.01em; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + overflow: hidden; +} + +.btn-secondary::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.1) 0%, + transparent 50%, + rgba(255, 255, 255, 0.05) 100% + ); + opacity: 0; + transition: opacity 0.25s ease; + border-radius: 9px; } .btn-secondary:hover { - background: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.25); + transform: translateY(-2px); + box-shadow: + 0 4px 12px rgba(0, 0, 0, 0.15), + 0 2px 4px rgba(0, 0, 0, 0.1); } -.error-message { - color: #ff6b6b; - font-size: 0.875rem; +.btn-secondary:hover::before { + opacity: 1; +} + +.btn-secondary:active { + transform: translateY(0); + background: rgba(255, 255, 255, 0.08); +} + +/* Shortcut Keys (for display) */ +.shortcut-keys { + display: flex; + gap: 0.375rem; +} + +.shortcut-key { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.15); + padding: 0.375rem 0.625rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.9); + font-family: "SF Mono", monospace; +} + +/* Error/Success Messages */ +.error-message, +.setting-error { + color: #f87171; + font-size: 0.8125rem; margin-top: 0.5rem; + display: flex; + align-items: center; + gap: 0.375rem; } -.success-message { - color: #51cf66; - font-size: 0.875rem; +.error-message::before { + content: '⚠'; + font-size: 0.75rem; +} + +.success-message, +.setting-success { + color: #4ade80; + font-size: 0.8125rem; margin-top: 0.5rem; + display: flex; + align-items: center; + gap: 0.375rem; +} + +.success-message::before { + content: '✓'; + font-size: 0.75rem; +} + +/* Scrollbar */ +.settings-content::-webkit-scrollbar { + width: 6px; +} + +.settings-content::-webkit-scrollbar-track { + background: transparent; +} + +.settings-content::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 3px; +} + +.settings-content::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.25); } diff --git a/src/renderer/pages/Settings.tsx b/src/renderer/pages/Settings.tsx index f205ce2..dd5453c 100644 --- a/src/renderer/pages/Settings.tsx +++ b/src/renderer/pages/Settings.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import "./Settings.css"; interface SettingsProps { @@ -7,6 +7,46 @@ interface SettingsProps { onTransparencyChange: (value: boolean) => void; } +function keyEventToAccelerator(e: KeyboardEvent): string { + const parts: string[] = []; + + if (e.metaKey || e.ctrlKey) parts.push("CommandOrControl"); + if (e.altKey) parts.push("Alt"); + if (e.shiftKey) parts.push("Shift"); + + const key = e.key; + + if (["Control", "Shift", "Alt", "Meta"].includes(key)) { + return ""; + } + + const keyMap: Record = { + " ": "Space", + ArrowUp: "Up", + ArrowDown: "Down", + ArrowLeft: "Left", + ArrowRight: "Right", + Escape: "Escape", + Enter: "Enter", + Backspace: "Backspace", + Delete: "Delete", + Tab: "Tab", + }; + + const mappedKey = keyMap[key] || key.toUpperCase(); + parts.push(mappedKey); + + return parts.join("+"); +} + +function formatShortcutDisplay(shortcut: string): string { + return shortcut + .replace("CommandOrControl", "⌘/Ctrl") + .replace("Shift", "⇧") + .replace("Alt", "⌥") + .replace(/\+/g, " + "); +} + export default function Settings({ isTransparent, onTransparencyChange, @@ -14,7 +54,8 @@ export default function Settings({ const [globalShortcut, setGlobalShortcut] = useState( "CommandOrControl+Shift+V" ); - const [isEditingShortcut, setIsEditingShortcut] = useState(false); + const [isRecordingShortcut, setIsRecordingShortcut] = useState(false); + const [recordedShortcut, setRecordedShortcut] = useState(""); const [shortcutError, setShortcutError] = useState(""); const [openaiApiKey, setOpenaiApiKey] = useState(""); @@ -29,26 +70,61 @@ export default function Settings({ }); }, []); + // Handle keyboard capture for shortcut recording + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!isRecordingShortcut) return; + + e.preventDefault(); + e.stopPropagation(); + + const accelerator = keyEventToAccelerator(e); + if (accelerator) { + setRecordedShortcut(accelerator); + } + }, + [isRecordingShortcut] + ); + + useEffect(() => { + if (isRecordingShortcut) { + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + } + }, [isRecordingShortcut, handleKeyDown]); + const handleTransparencyChange = (value: boolean) => { onTransparencyChange(value); window.electronAPI.setTransparency(value); }; + const startRecording = () => { + setIsRecordingShortcut(true); + setRecordedShortcut(""); + setShortcutError(""); + }; + const handleShortcutSave = async () => { + if (!recordedShortcut) { + setShortcutError("Please press a key combination"); + return; + } + setShortcutError(""); - const result = await window.electronAPI.setGlobalShortcut(globalShortcut); + const result = await window.electronAPI.setGlobalShortcut(recordedShortcut); if (result.success) { - setIsEditingShortcut(false); + setGlobalShortcut(recordedShortcut); + setIsRecordingShortcut(false); + setRecordedShortcut(""); } else { setShortcutError(result.error || "Failed to register shortcut"); } }; - const handleShortcutCancel = async () => { - setIsEditingShortcut(false); + const handleShortcutCancel = () => { + setIsRecordingShortcut(false); + setRecordedShortcut(""); setShortcutError(""); - const config = await window.electronAPI.getConfig(); - setGlobalShortcut(config.globalShortcut); }; const handleApiKeySave = async () => { @@ -60,7 +136,9 @@ export default function Settings({ return; } - const result = await window.electronAPI.setOpenAIApiKey(openaiApiKey.trim()); + const result = await window.electronAPI.setOpenAIApiKey( + openaiApiKey.trim() + ); if (result.success) { setIsEditingApiKey(false); setApiKeySuccess(true); @@ -126,18 +204,28 @@ export default function Settings({ )}
- {isEditingShortcut ? ( + {isRecordingShortcut ? ( <> - setGlobalShortcut(e.target.value)} - placeholder="e.g., CommandOrControl+Shift+V" - /> +
+ {recordedShortcut ? ( + + {formatShortcutDisplay(recordedShortcut)} + + ) : ( + + + Press keys... + + )} +
@@ -150,11 +238,10 @@ export default function Settings({ ) : ( <> - {globalShortcut} - @@ -169,9 +256,7 @@ export default function Settings({

Required for semantic search functionality

- {apiKeyError && ( -

{apiKeyError}

- )} + {apiKeyError &&

{apiKeyError}

} {apiKeySuccess && (

API key saved successfully!

)} @@ -186,10 +271,7 @@ export default function Settings({ onChange={(e) => setOpenaiApiKey(e.target.value)} placeholder="sk-..." /> -
diff --git a/src/renderer/pages/Settings.tsx b/src/renderer/pages/Settings.tsx index dd5453c..8f5d381 100644 --- a/src/renderer/pages/Settings.tsx +++ b/src/renderer/pages/Settings.tsx @@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from "react"; import "./Settings.css"; interface SettingsProps { - onBack: () => void; isTransparent: boolean; onTransparencyChange: (value: boolean) => void; } From 4c089575e841422ba3ffbcc9f34ff6f6bd1e7897 Mon Sep 17 00:00:00 2001 From: Mikheil Berishvili Date: Fri, 12 Dec 2025 15:19:11 +0400 Subject: [PATCH 7/7] remove script from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 0ff2d6e..e61dda2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "type": "module", "private": true, "scripts": { - "postinstall": "electron-rebuild", "dev": "concurrently \"bun run dev:renderer\" \"bun run dev:electron\"", "dev:renderer": "vite", "dev:preload": "bun run scripts/build-preload.ts",