├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── build.mjs ├── package-lock.json ├── package.json ├── packages └── chromium │ ├── background.js │ ├── content.js │ ├── copy.png │ ├── logo-128.png │ ├── logo-16.png │ ├── logo-32.png │ ├── logo-48.png │ ├── logo.png │ ├── manifest.json │ ├── options.css │ ├── options.html │ ├── options.js │ ├── popup.css │ ├── popup.html │ ├── popup.js │ └── setting.png ├── safari └── SummarAI │ ├── Shared (App) │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── logo-1024.png │ │ │ ├── mac-icon-128@1x.png │ │ │ ├── mac-icon-128@2x.png │ │ │ ├── mac-icon-16@1x.png │ │ │ ├── mac-icon-16@2x.png │ │ │ ├── mac-icon-256@1x.png │ │ │ ├── mac-icon-256@2x.png │ │ │ ├── mac-icon-32@1x.png │ │ │ ├── mac-icon-32@2x.png │ │ │ ├── mac-icon-512@1x.png │ │ │ └── mac-icon-512@2x.png │ │ ├── Contents.json │ │ └── LargeIcon.imageset │ │ │ ├── Contents.json │ │ │ └── logo-128.png │ ├── Base.lproj │ │ └── Main.html │ ├── Resources │ │ ├── Icon.png │ │ ├── Script.js │ │ └── Style.css │ └── ViewController.swift │ ├── Shared (Extension) │ └── SafariWebExtensionHandler.swift │ ├── SummarAI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── zhangferry.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── zhangferry.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── iOS (App) │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── SceneDelegate.swift │ ├── iOS (Extension) │ └── Info.plist │ ├── macOS (App) │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── SummarAI.entitlements │ └── macOS (Extension) │ ├── Info.plist │ └── SummarAI.entitlements ├── src ├── assets │ ├── img │ │ ├── copy.png │ │ ├── idea.png │ │ ├── logo-128.png │ │ ├── logo-16.png │ │ ├── logo-32.png │ │ ├── logo-48.png │ │ ├── logo.png │ │ └── setting.png │ └── styles │ │ └── base.scss ├── background │ └── index.ts ├── config │ └── index.ts ├── content │ └── content.ts ├── global.d.ts ├── manifest.json ├── options │ ├── App.tsx │ ├── ProviderSelect.tsx │ ├── components │ │ ├── CustomizePrompt.tsx │ │ ├── EnableGlarity.tsx │ │ ├── Header.tsx │ │ └── PageSummary.tsx │ ├── index.html │ ├── index.tsx │ └── styles.scss ├── popup │ ├── ChatGPTProvider.ts │ ├── OpenAIProvider.ts │ ├── popup.html │ ├── popup.ts │ ├── prompt.ts │ ├── styles.css │ └── types.ts └── utils │ └── utils.ts ├── tailwind.config.cjs ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es2021": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "overrides": [ 13 | ], 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaVersion": "latest" 17 | }, 18 | "plugins": [ 19 | "react", 20 | "@typescript-eslint" 21 | ], 22 | "rules": { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .vscode/ 4 | .DS_Store 5 | .env 6 | *.log 7 | *.zip -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 zhangferry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Project Logo](src/assets/img/logo.png) 2 | 3 | # SummarAI Browser Extension 4 | 5 | This browser extension utilizes AI technology to analyze web pages and extract important information. 6 | 7 | There are two ways to summarize: 8 | 9 | 1. Click the `Analyze` button, use the title and summary to summarize the page content. This is the default method. 10 | ![](https://cdn.zhangferry.com/Images/202306052334674.png) 11 | 2. click the `Zettelkasten` button, use the [zettelkasten/卡片盒笔记](https://zettelkasten.de/introduction/zh/) method to summarize the page content 12 | ![](https://cdn.zhangferry.com/Images/202306070718893.png) 13 | 14 | ## Installation 15 | 16 | 1. Clone the repository to your local machine or download the zip package in [Releases](https://github.com/zhangferry/SummarAI/releases). 17 | 2. Open Chrome and navigate to `chrome://extensions`. 18 | 3. Enable Developer mode by toggling the switch in the top right corner. 19 | 4. Click on "Load unpacked", select the `packages/chromium` folder if you are using clone or the unzip folder if you are using Release package. 20 | 21 | ## Usage 22 | 23 | ### Setting 24 | 25 | ![](https://cdn.zhangferry.com/Images/202305312325405.png) 26 | 27 | There are two AI modes available for you to choose from: 28 | 29 | 1. Through the ChatGPT web app, which requires you to have successfully logged in to the ChatGPT web version to preserve the session. 30 | 31 | 2. Through OpenAI's API, which requires you to provide the corresponding API Key. 32 | 33 | - The default host is https://api.openai.com, you can customize this value. 34 | - API key is required, you can find it in [OpenAI API Key](https://platform.openai.com/account/api-keys) 35 | 36 | If you don't have access to OpenAI, you can purchase API Key at [API2D](https://api2d.com/r/187046). Update the host and API Key to use it. 37 | 38 | ### Summary 39 | 40 | 1. Pin the SummarAI button to the extension toolbar. 41 | 2. Navigate to the web page you want to summarize and click on the SummerAI icon. 42 | 3. The extension will analyze the content of the page and display a summary in the popup. 43 | 44 | ## Safari 45 | 46 | Safari plug-ins are typically used for macOS/iOS systems, you should compile SummarAI.xcodeproj with Xcode. 47 | 48 | ### macOS 49 | 50 | - Check the Allow unsigned extensions option in Safari's developer options. 51 | 52 | ![](https://cdn.zhangferry.com/Images/202309092301123.png) 53 | 54 | ### iOS 55 | 56 | - Change the developer certificate to yours. 57 | 58 | ## Build from source(Optional) 59 | 60 | If you want to change the code logic, you need to recompile the TypeScript file: 61 | 62 | 1. install dependencies with `yarn install` 63 | 2. build source code with `yarn build` 64 | 65 | The development of the web side of safari extension is still carried out through typescript code, and the js code update will be completed in `yarn build`. The native interface development should use Swift in the xcode project. 66 | 67 | ## Support 68 | 69 | - For any issues or questions, please contact me. 70 | 71 | ## QA 72 | 73 | 1. ErrCode: `CLOUDFLARE` 74 | 75 | The openai server rejected the request, possible reasons: 76 | 77 | - No permission to access the API 78 | - The authentication credentials provided are invalid or incorrect 79 | - Requests are too frequent, triggering traffic limiting 80 | 81 | 2. ErrCode: `UNAUTHORIZED` 82 | 83 | Failed to obtain `chat.openai.com` accesstoken, possible reasons: 84 | 85 | - Your login credentials have expired, so you'll need to log back into `chat.openai.com` and try again 86 | -------------------------------------------------------------------------------- /build.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild" 2 | import fs from "fs-extra" 3 | import archiver from "archiver" 4 | import postcssPlugin from "esbuild-style-plugin" 5 | import tailwindcss from "tailwindcss" 6 | 7 | const outdir = "build" 8 | const packagesDir = "packages" 9 | const appName = "SummarAI-" 10 | 11 | let buildConfig = { 12 | entryPoints: [ 13 | "src/background/index.ts", 14 | "src/content/content.ts", 15 | "src/options/index.tsx", 16 | "src/popup/popup.ts", 17 | ], 18 | bundle: true, 19 | treeShaking: true, 20 | outdir: outdir, 21 | drop: ["debugger"], 22 | legalComments: "none", 23 | loader: { 24 | ".png": "dataurl", 25 | ".svg": "dataurl", 26 | }, 27 | minify: false, 28 | jsxFactory: "h", 29 | jsxFragment: "Fragment", 30 | jsx: "automatic", 31 | plugins: [ 32 | postcssPlugin({ 33 | postcss: { 34 | plugins: [tailwindcss], 35 | }, 36 | }), 37 | ], 38 | } 39 | 40 | async function deleteOldDir() { 41 | await fs.remove(outdir) 42 | } 43 | 44 | async function runEsbuild() { 45 | await esbuild.build(buildConfig) 46 | } 47 | 48 | async function zipFolder(dir) { 49 | const output = fs.createWriteStream(`${dir}.zip`) 50 | const archive = archiver("zip", { 51 | zlib: { level: 9 }, 52 | }) 53 | archive.pipe(output) 54 | archive.directory(dir, false) 55 | await archive.finalize() 56 | } 57 | 58 | async function copyFiles(entryPoints, targetDir) { 59 | await fs.ensureDir(targetDir) 60 | await Promise.all( 61 | entryPoints.map(async (entryPoint) => { 62 | await fs.copy(entryPoint.src, `${targetDir}/${entryPoint.dst}`) 63 | }) 64 | ) 65 | } 66 | 67 | async function build() { 68 | await deleteOldDir() 69 | await runEsbuild() 70 | 71 | const commonFiles = [ 72 | { src: "build/background/index.js", dst: "background.js" }, 73 | { src: "build/content/content.js", dst: "content.js" }, 74 | { src: "build/options/index.js", dst: "options.js" }, 75 | { src: "build/options/index.css", dst: "options.css" }, 76 | { src: "build/popup/popup.js", dst: "popup.js" }, 77 | { src: "src/popup/styles.css", dst: "popup.css" }, 78 | { src: "src/popup/popup.html", dst: "popup.html" }, 79 | { src: "src/options/index.html", dst: "options.html" }, 80 | { src: "src/assets/img/logo-16.png", dst: "logo-16.png" }, 81 | { src: "src/assets/img/logo-32.png", dst: "logo-32.png" }, 82 | { src: "src/assets/img/logo-48.png", dst: "logo-48.png" }, 83 | { src: "src/assets/img/logo-128.png", dst: "logo-128.png" }, 84 | { src: "src/assets/img/logo.png", dst: "logo.png" }, 85 | { src: "src/assets/img/copy.png", dst: "copy.png" }, 86 | { src: "src/assets/img/setting.png", dst: "setting.png" }, 87 | ] 88 | 89 | // chromium 90 | await copyFiles( 91 | [...commonFiles, { src: "src/manifest.json", dst: "manifest.json" }], 92 | `./${outdir}/chromium` 93 | ) 94 | 95 | await zipFolder(`./${outdir}/chromium`) 96 | await copyFiles( 97 | [ 98 | { 99 | src: `${outdir}/chromium.zip`, 100 | dst: `${appName}chromium.zip`, 101 | }, 102 | ], 103 | `./${packagesDir}` 104 | ) 105 | 106 | await copyFiles( 107 | [ 108 | { 109 | src: `${outdir}/chromium`, 110 | dst: `./chromium`, 111 | }, 112 | ], 113 | `./${packagesDir}` 114 | ) 115 | 116 | console.log("Build success.") 117 | } 118 | 119 | build() 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "summarai", 3 | "scripts": { 4 | "build": "node build.mjs", 5 | "lint": "eslint --ext .js,.mjs,.jsx ." 6 | }, 7 | "author": "zhangferry", 8 | "dependencies": { 9 | "@extractus/article-extractor": "^7.2.16", 10 | "@geist-ui/core": "^2.3.8", 11 | "@postlight/parser": "^2.2.3", 12 | "@primer/octicons-react": "^17.9.0", 13 | "antd": "^5.3.0", 14 | "eventsource-parser": "^0.0.5", 15 | "expiry-map": "^2.0.0", 16 | "gpt3-tokenizer": "^1.1.5", 17 | "lodash-es": "^4.17.21", 18 | "preact": "^10.13.1", 19 | "react": "npm:@preact/compat@^17.1.2", 20 | "react-dom": "npm:@preact/compat@^17.1.2", 21 | "react-markdown": "^8.0.4", 22 | "swr": "^2.0.0", 23 | "url": "^0.11.0", 24 | "uuid": "^9.0.0" 25 | }, 26 | "devDependencies": { 27 | "@types/fs-extra": "^9.0.13", 28 | "@types/lodash-es": "^4.17.6", 29 | "@types/react": "17.0.44", 30 | "@types/webextension-polyfill": "^0.9.2", 31 | "@typescript-eslint/eslint-plugin": "^5.59.6", 32 | "@typescript-eslint/parser": "^5.59.6", 33 | "archiver": "^5.3.1", 34 | "cross-env": "^7.0.3", 35 | "dotenv": "^16.0.3", 36 | "esbuild": "^0.17.4", 37 | "esbuild-style-plugin": "^1.6.1", 38 | "eslint": "^8.30.0", 39 | "eslint-plugin-react": "^7.32.2", 40 | "fs-extra": "^11.1.0", 41 | "husky": "^8.0.0", 42 | "lint-staged": "^13.1.0", 43 | "prettier": "^2.8.0", 44 | "sass": "^1.57.1", 45 | "tailwindcss": "^3.2.4", 46 | "typescript": "^4.9.4", 47 | "webextension-polyfill": "^0.10.0" 48 | }, 49 | "lint-staged": { 50 | "src/*.{js,jsx,ts,tsx,mjs}": [ 51 | "npx prettier --write", 52 | "npx eslint --fix" 53 | ] 54 | }, 55 | "husky": { 56 | "hooks": { 57 | "pre-commit": "lint-staged" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/chromium/background.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | var __create = Object.create; 3 | var __defProp = Object.defineProperty; 4 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 5 | var __getOwnPropNames = Object.getOwnPropertyNames; 6 | var __getProtoOf = Object.getPrototypeOf; 7 | var __hasOwnProp = Object.prototype.hasOwnProperty; 8 | var __commonJS = (cb, mod) => function __require() { 9 | return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 10 | }; 11 | var __copyProps = (to, from, except, desc) => { 12 | if (from && typeof from === "object" || typeof from === "function") { 13 | for (let key of __getOwnPropNames(from)) 14 | if (!__hasOwnProp.call(to, key) && key !== except) 15 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 16 | } 17 | return to; 18 | }; 19 | var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( 20 | // If the importer is in node compatibility mode or this is not an ESM 21 | // file that has been converted to a CommonJS file using a Babel- 22 | // compatible transform (i.e. "__esModule" has not been set), then set 23 | // "default" to the CommonJS "module.exports" for node compatibility. 24 | isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, 25 | mod 26 | )); 27 | 28 | // node_modules/webextension-polyfill/dist/browser-polyfill.js 29 | var require_browser_polyfill = __commonJS({ 30 | "node_modules/webextension-polyfill/dist/browser-polyfill.js"(exports, module) { 31 | (function(global, factory) { 32 | if (typeof define === "function" && define.amd) { 33 | define("webextension-polyfill", ["module"], factory); 34 | } else if (typeof exports !== "undefined") { 35 | factory(module); 36 | } else { 37 | var mod = { 38 | exports: {} 39 | }; 40 | factory(mod); 41 | global.browser = mod.exports; 42 | } 43 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : exports, function(module2) { 44 | "use strict"; 45 | if (!globalThis.chrome?.runtime?.id) { 46 | throw new Error("This script should only be loaded in a browser extension."); 47 | } 48 | if (typeof globalThis.browser === "undefined" || Object.getPrototypeOf(globalThis.browser) !== Object.prototype) { 49 | const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received."; 50 | const wrapAPIs = (extensionAPIs) => { 51 | const apiMetadata = { 52 | "alarms": { 53 | "clear": { 54 | "minArgs": 0, 55 | "maxArgs": 1 56 | }, 57 | "clearAll": { 58 | "minArgs": 0, 59 | "maxArgs": 0 60 | }, 61 | "get": { 62 | "minArgs": 0, 63 | "maxArgs": 1 64 | }, 65 | "getAll": { 66 | "minArgs": 0, 67 | "maxArgs": 0 68 | } 69 | }, 70 | "bookmarks": { 71 | "create": { 72 | "minArgs": 1, 73 | "maxArgs": 1 74 | }, 75 | "get": { 76 | "minArgs": 1, 77 | "maxArgs": 1 78 | }, 79 | "getChildren": { 80 | "minArgs": 1, 81 | "maxArgs": 1 82 | }, 83 | "getRecent": { 84 | "minArgs": 1, 85 | "maxArgs": 1 86 | }, 87 | "getSubTree": { 88 | "minArgs": 1, 89 | "maxArgs": 1 90 | }, 91 | "getTree": { 92 | "minArgs": 0, 93 | "maxArgs": 0 94 | }, 95 | "move": { 96 | "minArgs": 2, 97 | "maxArgs": 2 98 | }, 99 | "remove": { 100 | "minArgs": 1, 101 | "maxArgs": 1 102 | }, 103 | "removeTree": { 104 | "minArgs": 1, 105 | "maxArgs": 1 106 | }, 107 | "search": { 108 | "minArgs": 1, 109 | "maxArgs": 1 110 | }, 111 | "update": { 112 | "minArgs": 2, 113 | "maxArgs": 2 114 | } 115 | }, 116 | "browserAction": { 117 | "disable": { 118 | "minArgs": 0, 119 | "maxArgs": 1, 120 | "fallbackToNoCallback": true 121 | }, 122 | "enable": { 123 | "minArgs": 0, 124 | "maxArgs": 1, 125 | "fallbackToNoCallback": true 126 | }, 127 | "getBadgeBackgroundColor": { 128 | "minArgs": 1, 129 | "maxArgs": 1 130 | }, 131 | "getBadgeText": { 132 | "minArgs": 1, 133 | "maxArgs": 1 134 | }, 135 | "getPopup": { 136 | "minArgs": 1, 137 | "maxArgs": 1 138 | }, 139 | "getTitle": { 140 | "minArgs": 1, 141 | "maxArgs": 1 142 | }, 143 | "openPopup": { 144 | "minArgs": 0, 145 | "maxArgs": 0 146 | }, 147 | "setBadgeBackgroundColor": { 148 | "minArgs": 1, 149 | "maxArgs": 1, 150 | "fallbackToNoCallback": true 151 | }, 152 | "setBadgeText": { 153 | "minArgs": 1, 154 | "maxArgs": 1, 155 | "fallbackToNoCallback": true 156 | }, 157 | "setIcon": { 158 | "minArgs": 1, 159 | "maxArgs": 1 160 | }, 161 | "setPopup": { 162 | "minArgs": 1, 163 | "maxArgs": 1, 164 | "fallbackToNoCallback": true 165 | }, 166 | "setTitle": { 167 | "minArgs": 1, 168 | "maxArgs": 1, 169 | "fallbackToNoCallback": true 170 | } 171 | }, 172 | "browsingData": { 173 | "remove": { 174 | "minArgs": 2, 175 | "maxArgs": 2 176 | }, 177 | "removeCache": { 178 | "minArgs": 1, 179 | "maxArgs": 1 180 | }, 181 | "removeCookies": { 182 | "minArgs": 1, 183 | "maxArgs": 1 184 | }, 185 | "removeDownloads": { 186 | "minArgs": 1, 187 | "maxArgs": 1 188 | }, 189 | "removeFormData": { 190 | "minArgs": 1, 191 | "maxArgs": 1 192 | }, 193 | "removeHistory": { 194 | "minArgs": 1, 195 | "maxArgs": 1 196 | }, 197 | "removeLocalStorage": { 198 | "minArgs": 1, 199 | "maxArgs": 1 200 | }, 201 | "removePasswords": { 202 | "minArgs": 1, 203 | "maxArgs": 1 204 | }, 205 | "removePluginData": { 206 | "minArgs": 1, 207 | "maxArgs": 1 208 | }, 209 | "settings": { 210 | "minArgs": 0, 211 | "maxArgs": 0 212 | } 213 | }, 214 | "commands": { 215 | "getAll": { 216 | "minArgs": 0, 217 | "maxArgs": 0 218 | } 219 | }, 220 | "contextMenus": { 221 | "remove": { 222 | "minArgs": 1, 223 | "maxArgs": 1 224 | }, 225 | "removeAll": { 226 | "minArgs": 0, 227 | "maxArgs": 0 228 | }, 229 | "update": { 230 | "minArgs": 2, 231 | "maxArgs": 2 232 | } 233 | }, 234 | "cookies": { 235 | "get": { 236 | "minArgs": 1, 237 | "maxArgs": 1 238 | }, 239 | "getAll": { 240 | "minArgs": 1, 241 | "maxArgs": 1 242 | }, 243 | "getAllCookieStores": { 244 | "minArgs": 0, 245 | "maxArgs": 0 246 | }, 247 | "remove": { 248 | "minArgs": 1, 249 | "maxArgs": 1 250 | }, 251 | "set": { 252 | "minArgs": 1, 253 | "maxArgs": 1 254 | } 255 | }, 256 | "devtools": { 257 | "inspectedWindow": { 258 | "eval": { 259 | "minArgs": 1, 260 | "maxArgs": 2, 261 | "singleCallbackArg": false 262 | } 263 | }, 264 | "panels": { 265 | "create": { 266 | "minArgs": 3, 267 | "maxArgs": 3, 268 | "singleCallbackArg": true 269 | }, 270 | "elements": { 271 | "createSidebarPane": { 272 | "minArgs": 1, 273 | "maxArgs": 1 274 | } 275 | } 276 | } 277 | }, 278 | "downloads": { 279 | "cancel": { 280 | "minArgs": 1, 281 | "maxArgs": 1 282 | }, 283 | "download": { 284 | "minArgs": 1, 285 | "maxArgs": 1 286 | }, 287 | "erase": { 288 | "minArgs": 1, 289 | "maxArgs": 1 290 | }, 291 | "getFileIcon": { 292 | "minArgs": 1, 293 | "maxArgs": 2 294 | }, 295 | "open": { 296 | "minArgs": 1, 297 | "maxArgs": 1, 298 | "fallbackToNoCallback": true 299 | }, 300 | "pause": { 301 | "minArgs": 1, 302 | "maxArgs": 1 303 | }, 304 | "removeFile": { 305 | "minArgs": 1, 306 | "maxArgs": 1 307 | }, 308 | "resume": { 309 | "minArgs": 1, 310 | "maxArgs": 1 311 | }, 312 | "search": { 313 | "minArgs": 1, 314 | "maxArgs": 1 315 | }, 316 | "show": { 317 | "minArgs": 1, 318 | "maxArgs": 1, 319 | "fallbackToNoCallback": true 320 | } 321 | }, 322 | "extension": { 323 | "isAllowedFileSchemeAccess": { 324 | "minArgs": 0, 325 | "maxArgs": 0 326 | }, 327 | "isAllowedIncognitoAccess": { 328 | "minArgs": 0, 329 | "maxArgs": 0 330 | } 331 | }, 332 | "history": { 333 | "addUrl": { 334 | "minArgs": 1, 335 | "maxArgs": 1 336 | }, 337 | "deleteAll": { 338 | "minArgs": 0, 339 | "maxArgs": 0 340 | }, 341 | "deleteRange": { 342 | "minArgs": 1, 343 | "maxArgs": 1 344 | }, 345 | "deleteUrl": { 346 | "minArgs": 1, 347 | "maxArgs": 1 348 | }, 349 | "getVisits": { 350 | "minArgs": 1, 351 | "maxArgs": 1 352 | }, 353 | "search": { 354 | "minArgs": 1, 355 | "maxArgs": 1 356 | } 357 | }, 358 | "i18n": { 359 | "detectLanguage": { 360 | "minArgs": 1, 361 | "maxArgs": 1 362 | }, 363 | "getAcceptLanguages": { 364 | "minArgs": 0, 365 | "maxArgs": 0 366 | } 367 | }, 368 | "identity": { 369 | "launchWebAuthFlow": { 370 | "minArgs": 1, 371 | "maxArgs": 1 372 | } 373 | }, 374 | "idle": { 375 | "queryState": { 376 | "minArgs": 1, 377 | "maxArgs": 1 378 | } 379 | }, 380 | "management": { 381 | "get": { 382 | "minArgs": 1, 383 | "maxArgs": 1 384 | }, 385 | "getAll": { 386 | "minArgs": 0, 387 | "maxArgs": 0 388 | }, 389 | "getSelf": { 390 | "minArgs": 0, 391 | "maxArgs": 0 392 | }, 393 | "setEnabled": { 394 | "minArgs": 2, 395 | "maxArgs": 2 396 | }, 397 | "uninstallSelf": { 398 | "minArgs": 0, 399 | "maxArgs": 1 400 | } 401 | }, 402 | "notifications": { 403 | "clear": { 404 | "minArgs": 1, 405 | "maxArgs": 1 406 | }, 407 | "create": { 408 | "minArgs": 1, 409 | "maxArgs": 2 410 | }, 411 | "getAll": { 412 | "minArgs": 0, 413 | "maxArgs": 0 414 | }, 415 | "getPermissionLevel": { 416 | "minArgs": 0, 417 | "maxArgs": 0 418 | }, 419 | "update": { 420 | "minArgs": 2, 421 | "maxArgs": 2 422 | } 423 | }, 424 | "pageAction": { 425 | "getPopup": { 426 | "minArgs": 1, 427 | "maxArgs": 1 428 | }, 429 | "getTitle": { 430 | "minArgs": 1, 431 | "maxArgs": 1 432 | }, 433 | "hide": { 434 | "minArgs": 1, 435 | "maxArgs": 1, 436 | "fallbackToNoCallback": true 437 | }, 438 | "setIcon": { 439 | "minArgs": 1, 440 | "maxArgs": 1 441 | }, 442 | "setPopup": { 443 | "minArgs": 1, 444 | "maxArgs": 1, 445 | "fallbackToNoCallback": true 446 | }, 447 | "setTitle": { 448 | "minArgs": 1, 449 | "maxArgs": 1, 450 | "fallbackToNoCallback": true 451 | }, 452 | "show": { 453 | "minArgs": 1, 454 | "maxArgs": 1, 455 | "fallbackToNoCallback": true 456 | } 457 | }, 458 | "permissions": { 459 | "contains": { 460 | "minArgs": 1, 461 | "maxArgs": 1 462 | }, 463 | "getAll": { 464 | "minArgs": 0, 465 | "maxArgs": 0 466 | }, 467 | "remove": { 468 | "minArgs": 1, 469 | "maxArgs": 1 470 | }, 471 | "request": { 472 | "minArgs": 1, 473 | "maxArgs": 1 474 | } 475 | }, 476 | "runtime": { 477 | "getBackgroundPage": { 478 | "minArgs": 0, 479 | "maxArgs": 0 480 | }, 481 | "getPlatformInfo": { 482 | "minArgs": 0, 483 | "maxArgs": 0 484 | }, 485 | "openOptionsPage": { 486 | "minArgs": 0, 487 | "maxArgs": 0 488 | }, 489 | "requestUpdateCheck": { 490 | "minArgs": 0, 491 | "maxArgs": 0 492 | }, 493 | "sendMessage": { 494 | "minArgs": 1, 495 | "maxArgs": 3 496 | }, 497 | "sendNativeMessage": { 498 | "minArgs": 2, 499 | "maxArgs": 2 500 | }, 501 | "setUninstallURL": { 502 | "minArgs": 1, 503 | "maxArgs": 1 504 | } 505 | }, 506 | "sessions": { 507 | "getDevices": { 508 | "minArgs": 0, 509 | "maxArgs": 1 510 | }, 511 | "getRecentlyClosed": { 512 | "minArgs": 0, 513 | "maxArgs": 1 514 | }, 515 | "restore": { 516 | "minArgs": 0, 517 | "maxArgs": 1 518 | } 519 | }, 520 | "storage": { 521 | "local": { 522 | "clear": { 523 | "minArgs": 0, 524 | "maxArgs": 0 525 | }, 526 | "get": { 527 | "minArgs": 0, 528 | "maxArgs": 1 529 | }, 530 | "getBytesInUse": { 531 | "minArgs": 0, 532 | "maxArgs": 1 533 | }, 534 | "remove": { 535 | "minArgs": 1, 536 | "maxArgs": 1 537 | }, 538 | "set": { 539 | "minArgs": 1, 540 | "maxArgs": 1 541 | } 542 | }, 543 | "managed": { 544 | "get": { 545 | "minArgs": 0, 546 | "maxArgs": 1 547 | }, 548 | "getBytesInUse": { 549 | "minArgs": 0, 550 | "maxArgs": 1 551 | } 552 | }, 553 | "sync": { 554 | "clear": { 555 | "minArgs": 0, 556 | "maxArgs": 0 557 | }, 558 | "get": { 559 | "minArgs": 0, 560 | "maxArgs": 1 561 | }, 562 | "getBytesInUse": { 563 | "minArgs": 0, 564 | "maxArgs": 1 565 | }, 566 | "remove": { 567 | "minArgs": 1, 568 | "maxArgs": 1 569 | }, 570 | "set": { 571 | "minArgs": 1, 572 | "maxArgs": 1 573 | } 574 | } 575 | }, 576 | "tabs": { 577 | "captureVisibleTab": { 578 | "minArgs": 0, 579 | "maxArgs": 2 580 | }, 581 | "create": { 582 | "minArgs": 1, 583 | "maxArgs": 1 584 | }, 585 | "detectLanguage": { 586 | "minArgs": 0, 587 | "maxArgs": 1 588 | }, 589 | "discard": { 590 | "minArgs": 0, 591 | "maxArgs": 1 592 | }, 593 | "duplicate": { 594 | "minArgs": 1, 595 | "maxArgs": 1 596 | }, 597 | "executeScript": { 598 | "minArgs": 1, 599 | "maxArgs": 2 600 | }, 601 | "get": { 602 | "minArgs": 1, 603 | "maxArgs": 1 604 | }, 605 | "getCurrent": { 606 | "minArgs": 0, 607 | "maxArgs": 0 608 | }, 609 | "getZoom": { 610 | "minArgs": 0, 611 | "maxArgs": 1 612 | }, 613 | "getZoomSettings": { 614 | "minArgs": 0, 615 | "maxArgs": 1 616 | }, 617 | "goBack": { 618 | "minArgs": 0, 619 | "maxArgs": 1 620 | }, 621 | "goForward": { 622 | "minArgs": 0, 623 | "maxArgs": 1 624 | }, 625 | "highlight": { 626 | "minArgs": 1, 627 | "maxArgs": 1 628 | }, 629 | "insertCSS": { 630 | "minArgs": 1, 631 | "maxArgs": 2 632 | }, 633 | "move": { 634 | "minArgs": 2, 635 | "maxArgs": 2 636 | }, 637 | "query": { 638 | "minArgs": 1, 639 | "maxArgs": 1 640 | }, 641 | "reload": { 642 | "minArgs": 0, 643 | "maxArgs": 2 644 | }, 645 | "remove": { 646 | "minArgs": 1, 647 | "maxArgs": 1 648 | }, 649 | "removeCSS": { 650 | "minArgs": 1, 651 | "maxArgs": 2 652 | }, 653 | "sendMessage": { 654 | "minArgs": 2, 655 | "maxArgs": 3 656 | }, 657 | "setZoom": { 658 | "minArgs": 1, 659 | "maxArgs": 2 660 | }, 661 | "setZoomSettings": { 662 | "minArgs": 1, 663 | "maxArgs": 2 664 | }, 665 | "update": { 666 | "minArgs": 1, 667 | "maxArgs": 2 668 | } 669 | }, 670 | "topSites": { 671 | "get": { 672 | "minArgs": 0, 673 | "maxArgs": 0 674 | } 675 | }, 676 | "webNavigation": { 677 | "getAllFrames": { 678 | "minArgs": 1, 679 | "maxArgs": 1 680 | }, 681 | "getFrame": { 682 | "minArgs": 1, 683 | "maxArgs": 1 684 | } 685 | }, 686 | "webRequest": { 687 | "handlerBehaviorChanged": { 688 | "minArgs": 0, 689 | "maxArgs": 0 690 | } 691 | }, 692 | "windows": { 693 | "create": { 694 | "minArgs": 0, 695 | "maxArgs": 1 696 | }, 697 | "get": { 698 | "minArgs": 1, 699 | "maxArgs": 2 700 | }, 701 | "getAll": { 702 | "minArgs": 0, 703 | "maxArgs": 1 704 | }, 705 | "getCurrent": { 706 | "minArgs": 0, 707 | "maxArgs": 1 708 | }, 709 | "getLastFocused": { 710 | "minArgs": 0, 711 | "maxArgs": 1 712 | }, 713 | "remove": { 714 | "minArgs": 1, 715 | "maxArgs": 1 716 | }, 717 | "update": { 718 | "minArgs": 2, 719 | "maxArgs": 2 720 | } 721 | } 722 | }; 723 | if (Object.keys(apiMetadata).length === 0) { 724 | throw new Error("api-metadata.json has not been included in browser-polyfill"); 725 | } 726 | class DefaultWeakMap extends WeakMap { 727 | constructor(createItem, items = void 0) { 728 | super(items); 729 | this.createItem = createItem; 730 | } 731 | get(key) { 732 | if (!this.has(key)) { 733 | this.set(key, this.createItem(key)); 734 | } 735 | return super.get(key); 736 | } 737 | } 738 | const isThenable = (value) => { 739 | return value && typeof value === "object" && typeof value.then === "function"; 740 | }; 741 | const makeCallback = (promise, metadata) => { 742 | return (...callbackArgs) => { 743 | if (extensionAPIs.runtime.lastError) { 744 | promise.reject(new Error(extensionAPIs.runtime.lastError.message)); 745 | } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) { 746 | promise.resolve(callbackArgs[0]); 747 | } else { 748 | promise.resolve(callbackArgs); 749 | } 750 | }; 751 | }; 752 | const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments"; 753 | const wrapAsyncFunction = (name, metadata) => { 754 | return function asyncFunctionWrapper(target, ...args) { 755 | if (args.length < metadata.minArgs) { 756 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); 757 | } 758 | if (args.length > metadata.maxArgs) { 759 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); 760 | } 761 | return new Promise((resolve, reject) => { 762 | if (metadata.fallbackToNoCallback) { 763 | try { 764 | target[name](...args, makeCallback({ 765 | resolve, 766 | reject 767 | }, metadata)); 768 | } catch (cbError) { 769 | console.warn(`${name} API method doesn't seem to support the callback parameter, falling back to call it without a callback: `, cbError); 770 | target[name](...args); 771 | metadata.fallbackToNoCallback = false; 772 | metadata.noCallback = true; 773 | resolve(); 774 | } 775 | } else if (metadata.noCallback) { 776 | target[name](...args); 777 | resolve(); 778 | } else { 779 | target[name](...args, makeCallback({ 780 | resolve, 781 | reject 782 | }, metadata)); 783 | } 784 | }); 785 | }; 786 | }; 787 | const wrapMethod = (target, method, wrapper) => { 788 | return new Proxy(method, { 789 | apply(targetMethod, thisObj, args) { 790 | return wrapper.call(thisObj, target, ...args); 791 | } 792 | }); 793 | }; 794 | let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); 795 | const wrapObject = (target, wrappers = {}, metadata = {}) => { 796 | let cache = /* @__PURE__ */ Object.create(null); 797 | let handlers = { 798 | has(proxyTarget2, prop) { 799 | return prop in target || prop in cache; 800 | }, 801 | get(proxyTarget2, prop, receiver) { 802 | if (prop in cache) { 803 | return cache[prop]; 804 | } 805 | if (!(prop in target)) { 806 | return void 0; 807 | } 808 | let value = target[prop]; 809 | if (typeof value === "function") { 810 | if (typeof wrappers[prop] === "function") { 811 | value = wrapMethod(target, target[prop], wrappers[prop]); 812 | } else if (hasOwnProperty(metadata, prop)) { 813 | let wrapper = wrapAsyncFunction(prop, metadata[prop]); 814 | value = wrapMethod(target, target[prop], wrapper); 815 | } else { 816 | value = value.bind(target); 817 | } 818 | } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) { 819 | value = wrapObject(value, wrappers[prop], metadata[prop]); 820 | } else if (hasOwnProperty(metadata, "*")) { 821 | value = wrapObject(value, wrappers[prop], metadata["*"]); 822 | } else { 823 | Object.defineProperty(cache, prop, { 824 | configurable: true, 825 | enumerable: true, 826 | get() { 827 | return target[prop]; 828 | }, 829 | set(value2) { 830 | target[prop] = value2; 831 | } 832 | }); 833 | return value; 834 | } 835 | cache[prop] = value; 836 | return value; 837 | }, 838 | set(proxyTarget2, prop, value, receiver) { 839 | if (prop in cache) { 840 | cache[prop] = value; 841 | } else { 842 | target[prop] = value; 843 | } 844 | return true; 845 | }, 846 | defineProperty(proxyTarget2, prop, desc) { 847 | return Reflect.defineProperty(cache, prop, desc); 848 | }, 849 | deleteProperty(proxyTarget2, prop) { 850 | return Reflect.deleteProperty(cache, prop); 851 | } 852 | }; 853 | let proxyTarget = Object.create(target); 854 | return new Proxy(proxyTarget, handlers); 855 | }; 856 | const wrapEvent = (wrapperMap) => ({ 857 | addListener(target, listener, ...args) { 858 | target.addListener(wrapperMap.get(listener), ...args); 859 | }, 860 | hasListener(target, listener) { 861 | return target.hasListener(wrapperMap.get(listener)); 862 | }, 863 | removeListener(target, listener) { 864 | target.removeListener(wrapperMap.get(listener)); 865 | } 866 | }); 867 | const onRequestFinishedWrappers = new DefaultWeakMap((listener) => { 868 | if (typeof listener !== "function") { 869 | return listener; 870 | } 871 | return function onRequestFinished(req) { 872 | const wrappedReq = wrapObject( 873 | req, 874 | {}, 875 | { 876 | getContent: { 877 | minArgs: 0, 878 | maxArgs: 0 879 | } 880 | } 881 | ); 882 | listener(wrappedReq); 883 | }; 884 | }); 885 | const onMessageWrappers = new DefaultWeakMap((listener) => { 886 | if (typeof listener !== "function") { 887 | return listener; 888 | } 889 | return function onMessage(message, sender, sendResponse) { 890 | let didCallSendResponse = false; 891 | let wrappedSendResponse; 892 | let sendResponsePromise = new Promise((resolve) => { 893 | wrappedSendResponse = function(response) { 894 | didCallSendResponse = true; 895 | resolve(response); 896 | }; 897 | }); 898 | let result; 899 | try { 900 | result = listener(message, sender, wrappedSendResponse); 901 | } catch (err) { 902 | result = Promise.reject(err); 903 | } 904 | const isResultThenable = result !== true && isThenable(result); 905 | if (result !== true && !isResultThenable && !didCallSendResponse) { 906 | return false; 907 | } 908 | const sendPromisedResult = (promise) => { 909 | promise.then((msg) => { 910 | sendResponse(msg); 911 | }, (error) => { 912 | let message2; 913 | if (error && (error instanceof Error || typeof error.message === "string")) { 914 | message2 = error.message; 915 | } else { 916 | message2 = "An unexpected error occurred"; 917 | } 918 | sendResponse({ 919 | __mozWebExtensionPolyfillReject__: true, 920 | message: message2 921 | }); 922 | }).catch((err) => { 923 | console.error("Failed to send onMessage rejected reply", err); 924 | }); 925 | }; 926 | if (isResultThenable) { 927 | sendPromisedResult(result); 928 | } else { 929 | sendPromisedResult(sendResponsePromise); 930 | } 931 | return true; 932 | }; 933 | }); 934 | const wrappedSendMessageCallback = ({ 935 | reject, 936 | resolve 937 | }, reply) => { 938 | if (extensionAPIs.runtime.lastError) { 939 | if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { 940 | resolve(); 941 | } else { 942 | reject(new Error(extensionAPIs.runtime.lastError.message)); 943 | } 944 | } else if (reply && reply.__mozWebExtensionPolyfillReject__) { 945 | reject(new Error(reply.message)); 946 | } else { 947 | resolve(reply); 948 | } 949 | }; 950 | const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => { 951 | if (args.length < metadata.minArgs) { 952 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); 953 | } 954 | if (args.length > metadata.maxArgs) { 955 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); 956 | } 957 | return new Promise((resolve, reject) => { 958 | const wrappedCb = wrappedSendMessageCallback.bind(null, { 959 | resolve, 960 | reject 961 | }); 962 | args.push(wrappedCb); 963 | apiNamespaceObj.sendMessage(...args); 964 | }); 965 | }; 966 | const staticWrappers = { 967 | devtools: { 968 | network: { 969 | onRequestFinished: wrapEvent(onRequestFinishedWrappers) 970 | } 971 | }, 972 | runtime: { 973 | onMessage: wrapEvent(onMessageWrappers), 974 | onMessageExternal: wrapEvent(onMessageWrappers), 975 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", { 976 | minArgs: 1, 977 | maxArgs: 3 978 | }) 979 | }, 980 | tabs: { 981 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", { 982 | minArgs: 2, 983 | maxArgs: 3 984 | }) 985 | } 986 | }; 987 | const settingMetadata = { 988 | clear: { 989 | minArgs: 1, 990 | maxArgs: 1 991 | }, 992 | get: { 993 | minArgs: 1, 994 | maxArgs: 1 995 | }, 996 | set: { 997 | minArgs: 1, 998 | maxArgs: 1 999 | } 1000 | }; 1001 | apiMetadata.privacy = { 1002 | network: { 1003 | "*": settingMetadata 1004 | }, 1005 | services: { 1006 | "*": settingMetadata 1007 | }, 1008 | websites: { 1009 | "*": settingMetadata 1010 | } 1011 | }; 1012 | return wrapObject(extensionAPIs, staticWrappers, apiMetadata); 1013 | }; 1014 | module2.exports = wrapAPIs(chrome); 1015 | } else { 1016 | module2.exports = globalThis.browser; 1017 | } 1018 | }); 1019 | } 1020 | }); 1021 | 1022 | // src/background/index.ts 1023 | var import_webextension_polyfill = __toESM(require_browser_polyfill()); 1024 | async function Run() { 1025 | import_webextension_polyfill.default.runtime.onInstalled.addListener(async (details) => { 1026 | if (details.reason === "install") { 1027 | import_webextension_polyfill.default.runtime.openOptionsPage(); 1028 | } 1029 | }); 1030 | } 1031 | Run(); 1032 | })(); 1033 | -------------------------------------------------------------------------------- /packages/chromium/content.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | var __create = Object.create; 3 | var __defProp = Object.defineProperty; 4 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 5 | var __getOwnPropNames = Object.getOwnPropertyNames; 6 | var __getProtoOf = Object.getPrototypeOf; 7 | var __hasOwnProp = Object.prototype.hasOwnProperty; 8 | var __commonJS = (cb, mod) => function __require() { 9 | return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 10 | }; 11 | var __copyProps = (to, from, except, desc) => { 12 | if (from && typeof from === "object" || typeof from === "function") { 13 | for (let key of __getOwnPropNames(from)) 14 | if (!__hasOwnProp.call(to, key) && key !== except) 15 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 16 | } 17 | return to; 18 | }; 19 | var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( 20 | // If the importer is in node compatibility mode or this is not an ESM 21 | // file that has been converted to a CommonJS file using a Babel- 22 | // compatible transform (i.e. "__esModule" has not been set), then set 23 | // "default" to the CommonJS "module.exports" for node compatibility. 24 | isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, 25 | mod 26 | )); 27 | 28 | // node_modules/webextension-polyfill/dist/browser-polyfill.js 29 | var require_browser_polyfill = __commonJS({ 30 | "node_modules/webextension-polyfill/dist/browser-polyfill.js"(exports, module) { 31 | (function(global, factory) { 32 | if (typeof define === "function" && define.amd) { 33 | define("webextension-polyfill", ["module"], factory); 34 | } else if (typeof exports !== "undefined") { 35 | factory(module); 36 | } else { 37 | var mod = { 38 | exports: {} 39 | }; 40 | factory(mod); 41 | global.browser = mod.exports; 42 | } 43 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : exports, function(module2) { 44 | "use strict"; 45 | if (!globalThis.chrome?.runtime?.id) { 46 | throw new Error("This script should only be loaded in a browser extension."); 47 | } 48 | if (typeof globalThis.browser === "undefined" || Object.getPrototypeOf(globalThis.browser) !== Object.prototype) { 49 | const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received."; 50 | const wrapAPIs = (extensionAPIs) => { 51 | const apiMetadata = { 52 | "alarms": { 53 | "clear": { 54 | "minArgs": 0, 55 | "maxArgs": 1 56 | }, 57 | "clearAll": { 58 | "minArgs": 0, 59 | "maxArgs": 0 60 | }, 61 | "get": { 62 | "minArgs": 0, 63 | "maxArgs": 1 64 | }, 65 | "getAll": { 66 | "minArgs": 0, 67 | "maxArgs": 0 68 | } 69 | }, 70 | "bookmarks": { 71 | "create": { 72 | "minArgs": 1, 73 | "maxArgs": 1 74 | }, 75 | "get": { 76 | "minArgs": 1, 77 | "maxArgs": 1 78 | }, 79 | "getChildren": { 80 | "minArgs": 1, 81 | "maxArgs": 1 82 | }, 83 | "getRecent": { 84 | "minArgs": 1, 85 | "maxArgs": 1 86 | }, 87 | "getSubTree": { 88 | "minArgs": 1, 89 | "maxArgs": 1 90 | }, 91 | "getTree": { 92 | "minArgs": 0, 93 | "maxArgs": 0 94 | }, 95 | "move": { 96 | "minArgs": 2, 97 | "maxArgs": 2 98 | }, 99 | "remove": { 100 | "minArgs": 1, 101 | "maxArgs": 1 102 | }, 103 | "removeTree": { 104 | "minArgs": 1, 105 | "maxArgs": 1 106 | }, 107 | "search": { 108 | "minArgs": 1, 109 | "maxArgs": 1 110 | }, 111 | "update": { 112 | "minArgs": 2, 113 | "maxArgs": 2 114 | } 115 | }, 116 | "browserAction": { 117 | "disable": { 118 | "minArgs": 0, 119 | "maxArgs": 1, 120 | "fallbackToNoCallback": true 121 | }, 122 | "enable": { 123 | "minArgs": 0, 124 | "maxArgs": 1, 125 | "fallbackToNoCallback": true 126 | }, 127 | "getBadgeBackgroundColor": { 128 | "minArgs": 1, 129 | "maxArgs": 1 130 | }, 131 | "getBadgeText": { 132 | "minArgs": 1, 133 | "maxArgs": 1 134 | }, 135 | "getPopup": { 136 | "minArgs": 1, 137 | "maxArgs": 1 138 | }, 139 | "getTitle": { 140 | "minArgs": 1, 141 | "maxArgs": 1 142 | }, 143 | "openPopup": { 144 | "minArgs": 0, 145 | "maxArgs": 0 146 | }, 147 | "setBadgeBackgroundColor": { 148 | "minArgs": 1, 149 | "maxArgs": 1, 150 | "fallbackToNoCallback": true 151 | }, 152 | "setBadgeText": { 153 | "minArgs": 1, 154 | "maxArgs": 1, 155 | "fallbackToNoCallback": true 156 | }, 157 | "setIcon": { 158 | "minArgs": 1, 159 | "maxArgs": 1 160 | }, 161 | "setPopup": { 162 | "minArgs": 1, 163 | "maxArgs": 1, 164 | "fallbackToNoCallback": true 165 | }, 166 | "setTitle": { 167 | "minArgs": 1, 168 | "maxArgs": 1, 169 | "fallbackToNoCallback": true 170 | } 171 | }, 172 | "browsingData": { 173 | "remove": { 174 | "minArgs": 2, 175 | "maxArgs": 2 176 | }, 177 | "removeCache": { 178 | "minArgs": 1, 179 | "maxArgs": 1 180 | }, 181 | "removeCookies": { 182 | "minArgs": 1, 183 | "maxArgs": 1 184 | }, 185 | "removeDownloads": { 186 | "minArgs": 1, 187 | "maxArgs": 1 188 | }, 189 | "removeFormData": { 190 | "minArgs": 1, 191 | "maxArgs": 1 192 | }, 193 | "removeHistory": { 194 | "minArgs": 1, 195 | "maxArgs": 1 196 | }, 197 | "removeLocalStorage": { 198 | "minArgs": 1, 199 | "maxArgs": 1 200 | }, 201 | "removePasswords": { 202 | "minArgs": 1, 203 | "maxArgs": 1 204 | }, 205 | "removePluginData": { 206 | "minArgs": 1, 207 | "maxArgs": 1 208 | }, 209 | "settings": { 210 | "minArgs": 0, 211 | "maxArgs": 0 212 | } 213 | }, 214 | "commands": { 215 | "getAll": { 216 | "minArgs": 0, 217 | "maxArgs": 0 218 | } 219 | }, 220 | "contextMenus": { 221 | "remove": { 222 | "minArgs": 1, 223 | "maxArgs": 1 224 | }, 225 | "removeAll": { 226 | "minArgs": 0, 227 | "maxArgs": 0 228 | }, 229 | "update": { 230 | "minArgs": 2, 231 | "maxArgs": 2 232 | } 233 | }, 234 | "cookies": { 235 | "get": { 236 | "minArgs": 1, 237 | "maxArgs": 1 238 | }, 239 | "getAll": { 240 | "minArgs": 1, 241 | "maxArgs": 1 242 | }, 243 | "getAllCookieStores": { 244 | "minArgs": 0, 245 | "maxArgs": 0 246 | }, 247 | "remove": { 248 | "minArgs": 1, 249 | "maxArgs": 1 250 | }, 251 | "set": { 252 | "minArgs": 1, 253 | "maxArgs": 1 254 | } 255 | }, 256 | "devtools": { 257 | "inspectedWindow": { 258 | "eval": { 259 | "minArgs": 1, 260 | "maxArgs": 2, 261 | "singleCallbackArg": false 262 | } 263 | }, 264 | "panels": { 265 | "create": { 266 | "minArgs": 3, 267 | "maxArgs": 3, 268 | "singleCallbackArg": true 269 | }, 270 | "elements": { 271 | "createSidebarPane": { 272 | "minArgs": 1, 273 | "maxArgs": 1 274 | } 275 | } 276 | } 277 | }, 278 | "downloads": { 279 | "cancel": { 280 | "minArgs": 1, 281 | "maxArgs": 1 282 | }, 283 | "download": { 284 | "minArgs": 1, 285 | "maxArgs": 1 286 | }, 287 | "erase": { 288 | "minArgs": 1, 289 | "maxArgs": 1 290 | }, 291 | "getFileIcon": { 292 | "minArgs": 1, 293 | "maxArgs": 2 294 | }, 295 | "open": { 296 | "minArgs": 1, 297 | "maxArgs": 1, 298 | "fallbackToNoCallback": true 299 | }, 300 | "pause": { 301 | "minArgs": 1, 302 | "maxArgs": 1 303 | }, 304 | "removeFile": { 305 | "minArgs": 1, 306 | "maxArgs": 1 307 | }, 308 | "resume": { 309 | "minArgs": 1, 310 | "maxArgs": 1 311 | }, 312 | "search": { 313 | "minArgs": 1, 314 | "maxArgs": 1 315 | }, 316 | "show": { 317 | "minArgs": 1, 318 | "maxArgs": 1, 319 | "fallbackToNoCallback": true 320 | } 321 | }, 322 | "extension": { 323 | "isAllowedFileSchemeAccess": { 324 | "minArgs": 0, 325 | "maxArgs": 0 326 | }, 327 | "isAllowedIncognitoAccess": { 328 | "minArgs": 0, 329 | "maxArgs": 0 330 | } 331 | }, 332 | "history": { 333 | "addUrl": { 334 | "minArgs": 1, 335 | "maxArgs": 1 336 | }, 337 | "deleteAll": { 338 | "minArgs": 0, 339 | "maxArgs": 0 340 | }, 341 | "deleteRange": { 342 | "minArgs": 1, 343 | "maxArgs": 1 344 | }, 345 | "deleteUrl": { 346 | "minArgs": 1, 347 | "maxArgs": 1 348 | }, 349 | "getVisits": { 350 | "minArgs": 1, 351 | "maxArgs": 1 352 | }, 353 | "search": { 354 | "minArgs": 1, 355 | "maxArgs": 1 356 | } 357 | }, 358 | "i18n": { 359 | "detectLanguage": { 360 | "minArgs": 1, 361 | "maxArgs": 1 362 | }, 363 | "getAcceptLanguages": { 364 | "minArgs": 0, 365 | "maxArgs": 0 366 | } 367 | }, 368 | "identity": { 369 | "launchWebAuthFlow": { 370 | "minArgs": 1, 371 | "maxArgs": 1 372 | } 373 | }, 374 | "idle": { 375 | "queryState": { 376 | "minArgs": 1, 377 | "maxArgs": 1 378 | } 379 | }, 380 | "management": { 381 | "get": { 382 | "minArgs": 1, 383 | "maxArgs": 1 384 | }, 385 | "getAll": { 386 | "minArgs": 0, 387 | "maxArgs": 0 388 | }, 389 | "getSelf": { 390 | "minArgs": 0, 391 | "maxArgs": 0 392 | }, 393 | "setEnabled": { 394 | "minArgs": 2, 395 | "maxArgs": 2 396 | }, 397 | "uninstallSelf": { 398 | "minArgs": 0, 399 | "maxArgs": 1 400 | } 401 | }, 402 | "notifications": { 403 | "clear": { 404 | "minArgs": 1, 405 | "maxArgs": 1 406 | }, 407 | "create": { 408 | "minArgs": 1, 409 | "maxArgs": 2 410 | }, 411 | "getAll": { 412 | "minArgs": 0, 413 | "maxArgs": 0 414 | }, 415 | "getPermissionLevel": { 416 | "minArgs": 0, 417 | "maxArgs": 0 418 | }, 419 | "update": { 420 | "minArgs": 2, 421 | "maxArgs": 2 422 | } 423 | }, 424 | "pageAction": { 425 | "getPopup": { 426 | "minArgs": 1, 427 | "maxArgs": 1 428 | }, 429 | "getTitle": { 430 | "minArgs": 1, 431 | "maxArgs": 1 432 | }, 433 | "hide": { 434 | "minArgs": 1, 435 | "maxArgs": 1, 436 | "fallbackToNoCallback": true 437 | }, 438 | "setIcon": { 439 | "minArgs": 1, 440 | "maxArgs": 1 441 | }, 442 | "setPopup": { 443 | "minArgs": 1, 444 | "maxArgs": 1, 445 | "fallbackToNoCallback": true 446 | }, 447 | "setTitle": { 448 | "minArgs": 1, 449 | "maxArgs": 1, 450 | "fallbackToNoCallback": true 451 | }, 452 | "show": { 453 | "minArgs": 1, 454 | "maxArgs": 1, 455 | "fallbackToNoCallback": true 456 | } 457 | }, 458 | "permissions": { 459 | "contains": { 460 | "minArgs": 1, 461 | "maxArgs": 1 462 | }, 463 | "getAll": { 464 | "minArgs": 0, 465 | "maxArgs": 0 466 | }, 467 | "remove": { 468 | "minArgs": 1, 469 | "maxArgs": 1 470 | }, 471 | "request": { 472 | "minArgs": 1, 473 | "maxArgs": 1 474 | } 475 | }, 476 | "runtime": { 477 | "getBackgroundPage": { 478 | "minArgs": 0, 479 | "maxArgs": 0 480 | }, 481 | "getPlatformInfo": { 482 | "minArgs": 0, 483 | "maxArgs": 0 484 | }, 485 | "openOptionsPage": { 486 | "minArgs": 0, 487 | "maxArgs": 0 488 | }, 489 | "requestUpdateCheck": { 490 | "minArgs": 0, 491 | "maxArgs": 0 492 | }, 493 | "sendMessage": { 494 | "minArgs": 1, 495 | "maxArgs": 3 496 | }, 497 | "sendNativeMessage": { 498 | "minArgs": 2, 499 | "maxArgs": 2 500 | }, 501 | "setUninstallURL": { 502 | "minArgs": 1, 503 | "maxArgs": 1 504 | } 505 | }, 506 | "sessions": { 507 | "getDevices": { 508 | "minArgs": 0, 509 | "maxArgs": 1 510 | }, 511 | "getRecentlyClosed": { 512 | "minArgs": 0, 513 | "maxArgs": 1 514 | }, 515 | "restore": { 516 | "minArgs": 0, 517 | "maxArgs": 1 518 | } 519 | }, 520 | "storage": { 521 | "local": { 522 | "clear": { 523 | "minArgs": 0, 524 | "maxArgs": 0 525 | }, 526 | "get": { 527 | "minArgs": 0, 528 | "maxArgs": 1 529 | }, 530 | "getBytesInUse": { 531 | "minArgs": 0, 532 | "maxArgs": 1 533 | }, 534 | "remove": { 535 | "minArgs": 1, 536 | "maxArgs": 1 537 | }, 538 | "set": { 539 | "minArgs": 1, 540 | "maxArgs": 1 541 | } 542 | }, 543 | "managed": { 544 | "get": { 545 | "minArgs": 0, 546 | "maxArgs": 1 547 | }, 548 | "getBytesInUse": { 549 | "minArgs": 0, 550 | "maxArgs": 1 551 | } 552 | }, 553 | "sync": { 554 | "clear": { 555 | "minArgs": 0, 556 | "maxArgs": 0 557 | }, 558 | "get": { 559 | "minArgs": 0, 560 | "maxArgs": 1 561 | }, 562 | "getBytesInUse": { 563 | "minArgs": 0, 564 | "maxArgs": 1 565 | }, 566 | "remove": { 567 | "minArgs": 1, 568 | "maxArgs": 1 569 | }, 570 | "set": { 571 | "minArgs": 1, 572 | "maxArgs": 1 573 | } 574 | } 575 | }, 576 | "tabs": { 577 | "captureVisibleTab": { 578 | "minArgs": 0, 579 | "maxArgs": 2 580 | }, 581 | "create": { 582 | "minArgs": 1, 583 | "maxArgs": 1 584 | }, 585 | "detectLanguage": { 586 | "minArgs": 0, 587 | "maxArgs": 1 588 | }, 589 | "discard": { 590 | "minArgs": 0, 591 | "maxArgs": 1 592 | }, 593 | "duplicate": { 594 | "minArgs": 1, 595 | "maxArgs": 1 596 | }, 597 | "executeScript": { 598 | "minArgs": 1, 599 | "maxArgs": 2 600 | }, 601 | "get": { 602 | "minArgs": 1, 603 | "maxArgs": 1 604 | }, 605 | "getCurrent": { 606 | "minArgs": 0, 607 | "maxArgs": 0 608 | }, 609 | "getZoom": { 610 | "minArgs": 0, 611 | "maxArgs": 1 612 | }, 613 | "getZoomSettings": { 614 | "minArgs": 0, 615 | "maxArgs": 1 616 | }, 617 | "goBack": { 618 | "minArgs": 0, 619 | "maxArgs": 1 620 | }, 621 | "goForward": { 622 | "minArgs": 0, 623 | "maxArgs": 1 624 | }, 625 | "highlight": { 626 | "minArgs": 1, 627 | "maxArgs": 1 628 | }, 629 | "insertCSS": { 630 | "minArgs": 1, 631 | "maxArgs": 2 632 | }, 633 | "move": { 634 | "minArgs": 2, 635 | "maxArgs": 2 636 | }, 637 | "query": { 638 | "minArgs": 1, 639 | "maxArgs": 1 640 | }, 641 | "reload": { 642 | "minArgs": 0, 643 | "maxArgs": 2 644 | }, 645 | "remove": { 646 | "minArgs": 1, 647 | "maxArgs": 1 648 | }, 649 | "removeCSS": { 650 | "minArgs": 1, 651 | "maxArgs": 2 652 | }, 653 | "sendMessage": { 654 | "minArgs": 2, 655 | "maxArgs": 3 656 | }, 657 | "setZoom": { 658 | "minArgs": 1, 659 | "maxArgs": 2 660 | }, 661 | "setZoomSettings": { 662 | "minArgs": 1, 663 | "maxArgs": 2 664 | }, 665 | "update": { 666 | "minArgs": 1, 667 | "maxArgs": 2 668 | } 669 | }, 670 | "topSites": { 671 | "get": { 672 | "minArgs": 0, 673 | "maxArgs": 0 674 | } 675 | }, 676 | "webNavigation": { 677 | "getAllFrames": { 678 | "minArgs": 1, 679 | "maxArgs": 1 680 | }, 681 | "getFrame": { 682 | "minArgs": 1, 683 | "maxArgs": 1 684 | } 685 | }, 686 | "webRequest": { 687 | "handlerBehaviorChanged": { 688 | "minArgs": 0, 689 | "maxArgs": 0 690 | } 691 | }, 692 | "windows": { 693 | "create": { 694 | "minArgs": 0, 695 | "maxArgs": 1 696 | }, 697 | "get": { 698 | "minArgs": 1, 699 | "maxArgs": 2 700 | }, 701 | "getAll": { 702 | "minArgs": 0, 703 | "maxArgs": 1 704 | }, 705 | "getCurrent": { 706 | "minArgs": 0, 707 | "maxArgs": 1 708 | }, 709 | "getLastFocused": { 710 | "minArgs": 0, 711 | "maxArgs": 1 712 | }, 713 | "remove": { 714 | "minArgs": 1, 715 | "maxArgs": 1 716 | }, 717 | "update": { 718 | "minArgs": 2, 719 | "maxArgs": 2 720 | } 721 | } 722 | }; 723 | if (Object.keys(apiMetadata).length === 0) { 724 | throw new Error("api-metadata.json has not been included in browser-polyfill"); 725 | } 726 | class DefaultWeakMap extends WeakMap { 727 | constructor(createItem, items = void 0) { 728 | super(items); 729 | this.createItem = createItem; 730 | } 731 | get(key) { 732 | if (!this.has(key)) { 733 | this.set(key, this.createItem(key)); 734 | } 735 | return super.get(key); 736 | } 737 | } 738 | const isThenable = (value) => { 739 | return value && typeof value === "object" && typeof value.then === "function"; 740 | }; 741 | const makeCallback = (promise, metadata) => { 742 | return (...callbackArgs) => { 743 | if (extensionAPIs.runtime.lastError) { 744 | promise.reject(new Error(extensionAPIs.runtime.lastError.message)); 745 | } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) { 746 | promise.resolve(callbackArgs[0]); 747 | } else { 748 | promise.resolve(callbackArgs); 749 | } 750 | }; 751 | }; 752 | const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments"; 753 | const wrapAsyncFunction = (name, metadata) => { 754 | return function asyncFunctionWrapper(target, ...args) { 755 | if (args.length < metadata.minArgs) { 756 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); 757 | } 758 | if (args.length > metadata.maxArgs) { 759 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); 760 | } 761 | return new Promise((resolve, reject) => { 762 | if (metadata.fallbackToNoCallback) { 763 | try { 764 | target[name](...args, makeCallback({ 765 | resolve, 766 | reject 767 | }, metadata)); 768 | } catch (cbError) { 769 | console.warn(`${name} API method doesn't seem to support the callback parameter, falling back to call it without a callback: `, cbError); 770 | target[name](...args); 771 | metadata.fallbackToNoCallback = false; 772 | metadata.noCallback = true; 773 | resolve(); 774 | } 775 | } else if (metadata.noCallback) { 776 | target[name](...args); 777 | resolve(); 778 | } else { 779 | target[name](...args, makeCallback({ 780 | resolve, 781 | reject 782 | }, metadata)); 783 | } 784 | }); 785 | }; 786 | }; 787 | const wrapMethod = (target, method, wrapper) => { 788 | return new Proxy(method, { 789 | apply(targetMethod, thisObj, args) { 790 | return wrapper.call(thisObj, target, ...args); 791 | } 792 | }); 793 | }; 794 | let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); 795 | const wrapObject = (target, wrappers = {}, metadata = {}) => { 796 | let cache = /* @__PURE__ */ Object.create(null); 797 | let handlers = { 798 | has(proxyTarget2, prop) { 799 | return prop in target || prop in cache; 800 | }, 801 | get(proxyTarget2, prop, receiver) { 802 | if (prop in cache) { 803 | return cache[prop]; 804 | } 805 | if (!(prop in target)) { 806 | return void 0; 807 | } 808 | let value = target[prop]; 809 | if (typeof value === "function") { 810 | if (typeof wrappers[prop] === "function") { 811 | value = wrapMethod(target, target[prop], wrappers[prop]); 812 | } else if (hasOwnProperty(metadata, prop)) { 813 | let wrapper = wrapAsyncFunction(prop, metadata[prop]); 814 | value = wrapMethod(target, target[prop], wrapper); 815 | } else { 816 | value = value.bind(target); 817 | } 818 | } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) { 819 | value = wrapObject(value, wrappers[prop], metadata[prop]); 820 | } else if (hasOwnProperty(metadata, "*")) { 821 | value = wrapObject(value, wrappers[prop], metadata["*"]); 822 | } else { 823 | Object.defineProperty(cache, prop, { 824 | configurable: true, 825 | enumerable: true, 826 | get() { 827 | return target[prop]; 828 | }, 829 | set(value2) { 830 | target[prop] = value2; 831 | } 832 | }); 833 | return value; 834 | } 835 | cache[prop] = value; 836 | return value; 837 | }, 838 | set(proxyTarget2, prop, value, receiver) { 839 | if (prop in cache) { 840 | cache[prop] = value; 841 | } else { 842 | target[prop] = value; 843 | } 844 | return true; 845 | }, 846 | defineProperty(proxyTarget2, prop, desc) { 847 | return Reflect.defineProperty(cache, prop, desc); 848 | }, 849 | deleteProperty(proxyTarget2, prop) { 850 | return Reflect.deleteProperty(cache, prop); 851 | } 852 | }; 853 | let proxyTarget = Object.create(target); 854 | return new Proxy(proxyTarget, handlers); 855 | }; 856 | const wrapEvent = (wrapperMap) => ({ 857 | addListener(target, listener, ...args) { 858 | target.addListener(wrapperMap.get(listener), ...args); 859 | }, 860 | hasListener(target, listener) { 861 | return target.hasListener(wrapperMap.get(listener)); 862 | }, 863 | removeListener(target, listener) { 864 | target.removeListener(wrapperMap.get(listener)); 865 | } 866 | }); 867 | const onRequestFinishedWrappers = new DefaultWeakMap((listener) => { 868 | if (typeof listener !== "function") { 869 | return listener; 870 | } 871 | return function onRequestFinished(req) { 872 | const wrappedReq = wrapObject( 873 | req, 874 | {}, 875 | { 876 | getContent: { 877 | minArgs: 0, 878 | maxArgs: 0 879 | } 880 | } 881 | ); 882 | listener(wrappedReq); 883 | }; 884 | }); 885 | const onMessageWrappers = new DefaultWeakMap((listener) => { 886 | if (typeof listener !== "function") { 887 | return listener; 888 | } 889 | return function onMessage(message, sender, sendResponse) { 890 | let didCallSendResponse = false; 891 | let wrappedSendResponse; 892 | let sendResponsePromise = new Promise((resolve) => { 893 | wrappedSendResponse = function(response) { 894 | didCallSendResponse = true; 895 | resolve(response); 896 | }; 897 | }); 898 | let result; 899 | try { 900 | result = listener(message, sender, wrappedSendResponse); 901 | } catch (err) { 902 | result = Promise.reject(err); 903 | } 904 | const isResultThenable = result !== true && isThenable(result); 905 | if (result !== true && !isResultThenable && !didCallSendResponse) { 906 | return false; 907 | } 908 | const sendPromisedResult = (promise) => { 909 | promise.then((msg) => { 910 | sendResponse(msg); 911 | }, (error) => { 912 | let message2; 913 | if (error && (error instanceof Error || typeof error.message === "string")) { 914 | message2 = error.message; 915 | } else { 916 | message2 = "An unexpected error occurred"; 917 | } 918 | sendResponse({ 919 | __mozWebExtensionPolyfillReject__: true, 920 | message: message2 921 | }); 922 | }).catch((err) => { 923 | console.error("Failed to send onMessage rejected reply", err); 924 | }); 925 | }; 926 | if (isResultThenable) { 927 | sendPromisedResult(result); 928 | } else { 929 | sendPromisedResult(sendResponsePromise); 930 | } 931 | return true; 932 | }; 933 | }); 934 | const wrappedSendMessageCallback = ({ 935 | reject, 936 | resolve 937 | }, reply) => { 938 | if (extensionAPIs.runtime.lastError) { 939 | if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { 940 | resolve(); 941 | } else { 942 | reject(new Error(extensionAPIs.runtime.lastError.message)); 943 | } 944 | } else if (reply && reply.__mozWebExtensionPolyfillReject__) { 945 | reject(new Error(reply.message)); 946 | } else { 947 | resolve(reply); 948 | } 949 | }; 950 | const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => { 951 | if (args.length < metadata.minArgs) { 952 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); 953 | } 954 | if (args.length > metadata.maxArgs) { 955 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); 956 | } 957 | return new Promise((resolve, reject) => { 958 | const wrappedCb = wrappedSendMessageCallback.bind(null, { 959 | resolve, 960 | reject 961 | }); 962 | args.push(wrappedCb); 963 | apiNamespaceObj.sendMessage(...args); 964 | }); 965 | }; 966 | const staticWrappers = { 967 | devtools: { 968 | network: { 969 | onRequestFinished: wrapEvent(onRequestFinishedWrappers) 970 | } 971 | }, 972 | runtime: { 973 | onMessage: wrapEvent(onMessageWrappers), 974 | onMessageExternal: wrapEvent(onMessageWrappers), 975 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", { 976 | minArgs: 1, 977 | maxArgs: 3 978 | }) 979 | }, 980 | tabs: { 981 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", { 982 | minArgs: 2, 983 | maxArgs: 3 984 | }) 985 | } 986 | }; 987 | const settingMetadata = { 988 | clear: { 989 | minArgs: 1, 990 | maxArgs: 1 991 | }, 992 | get: { 993 | minArgs: 1, 994 | maxArgs: 1 995 | }, 996 | set: { 997 | minArgs: 1, 998 | maxArgs: 1 999 | } 1000 | }; 1001 | apiMetadata.privacy = { 1002 | network: { 1003 | "*": settingMetadata 1004 | }, 1005 | services: { 1006 | "*": settingMetadata 1007 | }, 1008 | websites: { 1009 | "*": settingMetadata 1010 | } 1011 | }; 1012 | return wrapObject(extensionAPIs, staticWrappers, apiMetadata); 1013 | }; 1014 | module2.exports = wrapAPIs(chrome); 1015 | } else { 1016 | module2.exports = globalThis.browser; 1017 | } 1018 | }); 1019 | } 1020 | }); 1021 | 1022 | // src/content/content.ts 1023 | var import_webextension_polyfill = __toESM(require_browser_polyfill()); 1024 | import_webextension_polyfill.default.runtime.onMessage.addListener((message, _, sendResponse) => { 1025 | var _a; 1026 | switch (message.action) { 1027 | case "getTextContent": { 1028 | const url = window.location.href; 1029 | let pageContent = document.body.innerText; 1030 | console.log(url); 1031 | sendResponse({ textContent: { url, innerText: pageContent } }); 1032 | break; 1033 | } 1034 | case "GET_DOM": { 1035 | sendResponse({ html: (_a = document.querySelector("html")) == null ? void 0 : _a.outerHTML }); 1036 | break; 1037 | } 1038 | } 1039 | }); 1040 | })(); 1041 | -------------------------------------------------------------------------------- /packages/chromium/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/copy.png -------------------------------------------------------------------------------- /packages/chromium/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/logo-128.png -------------------------------------------------------------------------------- /packages/chromium/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/logo-16.png -------------------------------------------------------------------------------- /packages/chromium/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/logo-32.png -------------------------------------------------------------------------------- /packages/chromium/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/logo-48.png -------------------------------------------------------------------------------- /packages/chromium/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/logo.png -------------------------------------------------------------------------------- /packages/chromium/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "SummarAI", 4 | "version": "1.2.5", 5 | "homepage_url": "https://zhangferry.com", 6 | "description": "This browser extension utilizes AI technology to analyze web pages and extract important information.", 7 | "icons": { 8 | "16": "logo-16.png", 9 | "48": "logo-48.png", 10 | "128": "logo-128.png" 11 | }, 12 | "action": { 13 | "default_popup": "popup.html", 14 | "default_icon": "logo-128.png" 15 | }, 16 | "options_ui": { 17 | "page": "options.html", 18 | "open_in_tab": true 19 | }, 20 | "permissions": [ 21 | "activeTab", 22 | "scripting", 23 | "storage", 24 | "tabs" 25 | ], 26 | "background": { 27 | "service_worker": "background.js" 28 | }, 29 | "host_permissions": [ 30 | "https://*.openai.com/" 31 | ], 32 | "content_scripts": [ 33 | { 34 | "matches": [ 35 | "", 36 | "*://*/*" 37 | ], 38 | "js": [ 39 | "content.js" 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /packages/chromium/options.css: -------------------------------------------------------------------------------- 1 | /* temp_stylePlugin:ni:sha-256;atELJwC38yS4qnHZpFoiDblJ-m11-6Y-ylgQ8gHuROE */ 2 | *, 3 | ::before, 4 | ::after { 5 | --tw-border-spacing-x: 0; 6 | --tw-border-spacing-y: 0; 7 | --tw-translate-x: 0; 8 | --tw-translate-y: 0; 9 | --tw-rotate: 0; 10 | --tw-skew-x: 0; 11 | --tw-skew-y: 0; 12 | --tw-scale-x: 1; 13 | --tw-scale-y: 1; 14 | --tw-pan-x: ; 15 | --tw-pan-y: ; 16 | --tw-pinch-zoom: ; 17 | --tw-scroll-snap-strictness: proximity; 18 | --tw-gradient-from-position: ; 19 | --tw-gradient-via-position: ; 20 | --tw-gradient-to-position: ; 21 | --tw-ordinal: ; 22 | --tw-slashed-zero: ; 23 | --tw-numeric-figure: ; 24 | --tw-numeric-spacing: ; 25 | --tw-numeric-fraction: ; 26 | --tw-ring-inset: ; 27 | --tw-ring-offset-width: 0px; 28 | --tw-ring-offset-color: #fff; 29 | --tw-ring-color: rgb(59 130 246 / 0.5); 30 | --tw-ring-offset-shadow: 0 0 #0000; 31 | --tw-ring-shadow: 0 0 #0000; 32 | --tw-shadow: 0 0 #0000; 33 | --tw-shadow-colored: 0 0 #0000; 34 | --tw-blur: ; 35 | --tw-brightness: ; 36 | --tw-contrast: ; 37 | --tw-grayscale: ; 38 | --tw-hue-rotate: ; 39 | --tw-invert: ; 40 | --tw-saturate: ; 41 | --tw-sepia: ; 42 | --tw-drop-shadow: ; 43 | --tw-backdrop-blur: ; 44 | --tw-backdrop-brightness: ; 45 | --tw-backdrop-contrast: ; 46 | --tw-backdrop-grayscale: ; 47 | --tw-backdrop-hue-rotate: ; 48 | --tw-backdrop-invert: ; 49 | --tw-backdrop-opacity: ; 50 | --tw-backdrop-saturate: ; 51 | --tw-backdrop-sepia: ; 52 | } 53 | ::backdrop { 54 | --tw-border-spacing-x: 0; 55 | --tw-border-spacing-y: 0; 56 | --tw-translate-x: 0; 57 | --tw-translate-y: 0; 58 | --tw-rotate: 0; 59 | --tw-skew-x: 0; 60 | --tw-skew-y: 0; 61 | --tw-scale-x: 1; 62 | --tw-scale-y: 1; 63 | --tw-pan-x: ; 64 | --tw-pan-y: ; 65 | --tw-pinch-zoom: ; 66 | --tw-scroll-snap-strictness: proximity; 67 | --tw-gradient-from-position: ; 68 | --tw-gradient-via-position: ; 69 | --tw-gradient-to-position: ; 70 | --tw-ordinal: ; 71 | --tw-slashed-zero: ; 72 | --tw-numeric-figure: ; 73 | --tw-numeric-spacing: ; 74 | --tw-numeric-fraction: ; 75 | --tw-ring-inset: ; 76 | --tw-ring-offset-width: 0px; 77 | --tw-ring-offset-color: #fff; 78 | --tw-ring-color: rgb(59 130 246 / 0.5); 79 | --tw-ring-offset-shadow: 0 0 #0000; 80 | --tw-ring-shadow: 0 0 #0000; 81 | --tw-shadow: 0 0 #0000; 82 | --tw-shadow-colored: 0 0 #0000; 83 | --tw-blur: ; 84 | --tw-brightness: ; 85 | --tw-contrast: ; 86 | --tw-grayscale: ; 87 | --tw-hue-rotate: ; 88 | --tw-invert: ; 89 | --tw-saturate: ; 90 | --tw-sepia: ; 91 | --tw-drop-shadow: ; 92 | --tw-backdrop-blur: ; 93 | --tw-backdrop-brightness: ; 94 | --tw-backdrop-contrast: ; 95 | --tw-backdrop-grayscale: ; 96 | --tw-backdrop-hue-rotate: ; 97 | --tw-backdrop-invert: ; 98 | --tw-backdrop-opacity: ; 99 | --tw-backdrop-saturate: ; 100 | --tw-backdrop-sepia: ; 101 | } 102 | .glarity--container { 103 | width: 100%; 104 | } 105 | @media (min-width: 640px) { 106 | .glarity--container { 107 | max-width: 640px; 108 | } 109 | } 110 | @media (min-width: 768px) { 111 | .glarity--container { 112 | max-width: 768px; 113 | } 114 | } 115 | @media (min-width: 1024px) { 116 | .glarity--container { 117 | max-width: 1024px; 118 | } 119 | } 120 | @media (min-width: 1280px) { 121 | .glarity--container { 122 | max-width: 1280px; 123 | } 124 | } 125 | @media (min-width: 1536px) { 126 | .glarity--container { 127 | max-width: 1536px; 128 | } 129 | } 130 | .glarity--mx-auto { 131 | margin-left: auto; 132 | margin-right: auto; 133 | } 134 | .glarity--my-1 { 135 | margin-top: 0.25rem; 136 | margin-bottom: 0.25rem; 137 | } 138 | .glarity--mb-0 { 139 | margin-bottom: 0px; 140 | } 141 | .glarity--mt-0 { 142 | margin-top: 0px; 143 | } 144 | .glarity--mt-14 { 145 | margin-top: 3.5rem; 146 | } 147 | .glarity--mt-5 { 148 | margin-top: 1.25rem; 149 | } 150 | .glarity--flex { 151 | display: flex; 152 | } 153 | .glarity--h-10 { 154 | height: 2.5rem; 155 | } 156 | .glarity--w-10 { 157 | width: 2.5rem; 158 | } 159 | .glarity--w-\[900px\] { 160 | width: 900px; 161 | } 162 | .glarity--flex-row { 163 | flex-direction: row; 164 | } 165 | .glarity--flex-col { 166 | flex-direction: column; 167 | } 168 | .glarity--items-center { 169 | align-items: center; 170 | } 171 | .glarity--justify-between { 172 | justify-content: space-between; 173 | } 174 | .glarity--gap-1 { 175 | gap: 0.25rem; 176 | } 177 | .glarity--gap-2 { 178 | gap: 0.5rem; 179 | } 180 | .glarity--gap-3 { 181 | gap: 0.75rem; 182 | } 183 | .glarity--rounded-lg { 184 | border-radius: 0.5rem; 185 | } 186 | .glarity--px-2 { 187 | padding-left: 0.5rem; 188 | padding-right: 0.5rem; 189 | } 190 | .glarity--text-xs { 191 | font-size: 0.75rem; 192 | line-height: 1rem; 193 | } 194 | .glarity--font-semibold { 195 | font-weight: 600; 196 | } 197 | .glarity--italic { 198 | font-style: italic; 199 | } 200 | .glarity--card { 201 | font-size: 14px; 202 | border: 1px solid #ddd; 203 | box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35); 204 | border-radius: 6px !important; 205 | background: #fff; 206 | } 207 | .glarity--card .glarity--card__head { 208 | display: flex; 209 | justify-content: space-between; 210 | justify-items: center; 211 | padding: 10px 12px; 212 | border-bottom: 1px solid #eee; 213 | font-size: 16px; 214 | } 215 | .glarity--card .glarity--card__head .glarity--card__head--title { 216 | display: inline-flex; 217 | align-items: center; 218 | font-weight: bold; 219 | } 220 | .glarity--card .glarity--card__head .glarity--card__head--title .glarity--btn { 221 | margin-left: 4px; 222 | } 223 | .glarity--card .glarity--card__head .glarity--card__head--title a { 224 | display: inline-flex; 225 | text-decoration: none; 226 | color: #000; 227 | align-items: center; 228 | } 229 | .glarity--card .glarity--card__head .glarity--card__head--title img { 230 | margin: 0 4px 0 0; 231 | width: 20px; 232 | height: 20px; 233 | vertical-align: middle; 234 | } 235 | .glarity--card .glarity--card__content { 236 | position: relative; 237 | padding: 15px; 238 | color: #333; 239 | line-height: 1.6; 240 | max-height: 500px; 241 | overflow-y: auto; 242 | overflow-wrap: break-word; 243 | min-height: 110px; 244 | } 245 | .glarity--card .glarity--card__content .glarity--container { 246 | margin-bottom: 0; 247 | } 248 | .glarity--card .glarity--card__content .glarity--container .glarity--chatgpt { 249 | border: none; 250 | } 251 | .glarity--card .glarity--card__content .glarity--card__empty { 252 | position: absolute; 253 | left: 50%; 254 | top: 50%; 255 | transform: translate(-50%, -50%); 256 | width: 80%; 257 | display: flex; 258 | padding: 6px 16px; 259 | text-align: center; 260 | justify-content: center; 261 | align-items: center; 262 | min-height: 70px; 263 | } 264 | .glarity--card .glarity--card__content .glarity--container #gpt-answer.markdown-body.gpt-markdown .glarity--chatgpt--header { 265 | margin: -13px -12px 0 0 !important; 266 | } 267 | .glarity--btn { 268 | margin: 0; 269 | padding: 8px 10px; 270 | line-height: 1; 271 | font-size: 14px; 272 | border-radius: 8px; 273 | border: 1px solid #fff; 274 | cursor: pointer; 275 | text-align: center; 276 | } 277 | .glarity--btn:disabled { 278 | cursor: default; 279 | opacity: 0.5; 280 | } 281 | .glarity--btn:disabled:hover { 282 | opacity: 0.5; 283 | } 284 | .glarity--btn:hover { 285 | opacity: 0.9; 286 | } 287 | .glarity--btn.glarity--btn__icon { 288 | padding: 0; 289 | border: none; 290 | cursor: pointer; 291 | color: #444; 292 | background: transparent; 293 | } 294 | .glarity--btn.glarity--btn__launch { 295 | display: flex; 296 | justify-content: center; 297 | align-items: center; 298 | padding: 0; 299 | width: 24px; 300 | height: 24px; 301 | text-align: center; 302 | vertical-align: middle; 303 | border: 1px solid #144a7f; 304 | background: #fff; 305 | } 306 | .glarity--btn.glarity--btn__launch img { 307 | margin: 0; 308 | width: 20px; 309 | height: 20px; 310 | vertical-align: middle; 311 | filter: blur(0); 312 | } 313 | .glarity--btn.glarity--btn__primary { 314 | color: #fff; 315 | background: #0070f3; 316 | } 317 | .glarity--btn.glarity--btn__primary--ghost { 318 | color: #0070f3; 319 | background: #fff; 320 | border: 1px solid #0070f3; 321 | } 322 | .glarity--btn.glarity--btn__block { 323 | width: 100%; 324 | } 325 | .glarity--btn.glarity--btn__large { 326 | font-size: 16px; 327 | padding: 8px 16px; 328 | } 329 | .glarity--btn.glarity--btn__small { 330 | font-size: 12px; 331 | padding: 4px 8px; 332 | } 333 | .glarity--subtitle { 334 | font-size: 12px; 335 | color: #999 !important; 336 | font-weight: normal; 337 | } 338 | @media (prefers-color-scheme: dark) { 339 | .glarity--card { 340 | background: rgb(35, 36, 40); 341 | border-color: #444950; 342 | box-shadow: 0 3px 9px rgba(255, 255, 255, 0.3); 343 | } 344 | .glarity--card .glarity--card__head { 345 | border-bottom-color: #444950; 346 | } 347 | .glarity--card .glarity--card__content, 348 | .glarity--card .glarity--card__head { 349 | color: #ccc; 350 | } 351 | .glarity--card .glarity--card__content .glarity--card__head--title a, 352 | .glarity--card .glarity--card__head .glarity--card__head--title a { 353 | color: #ccc; 354 | } 355 | .glarity--btn { 356 | border-color: #444950; 357 | } 358 | .glarity--btn.glarity--btn__primary { 359 | border-color: #999; 360 | } 361 | .glarity--btn.glarity--btn__icon { 362 | color: #999; 363 | } 364 | } 365 | 366 | /* temp_stylePlugin:ni:sha-256;bvAddJE5IBOAtzXKplfsM0AX7VwNA3a8ek_1FpFyUcI */ 367 | .glarity--card .wrapper { 368 | padding: 0 10px; 369 | width: 100% !important; 370 | max-width: 100% !important; 371 | } 372 | .glarity--support__sites { 373 | display: flex; 374 | flex-wrap: wrap; 375 | justify-content: start; 376 | max-width: 100vw; 377 | } 378 | .glarity--support__sites--item { 379 | display: inline-flex; 380 | margin: 0 0 10px !important; 381 | width: 200px; 382 | min-width: 200px; 383 | justify-content: start !important; 384 | } 385 | .glarity--options { 386 | padding-bottom: 30px; 387 | } 388 | .glarity--container { 389 | width: 100%; 390 | padding: 10px 15px; 391 | max-width: 100vw; 392 | box-sizing: border-box; 393 | word-wrap: break-word; 394 | } 395 | @media screen and (max-width: 900px) { 396 | .glarity--w-\[900px\] { 397 | max-width: 100%; 398 | } 399 | textarea { 400 | max-width: 100%; 401 | width: 100% !important; 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /packages/chromium/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | SummarAI 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/chromium/popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Montserrat', sans-serif; 3 | line-height: 1.5; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | .root { 9 | padding: 24px; 10 | } 11 | 12 | .content-input { 13 | margin-bottom: 15px; 14 | } 15 | 16 | .container, .setting-container { 17 | position: relative; 18 | width: 350px; 19 | margin: 0 auto; 20 | background-color: white; 21 | border-radius: 10px; 22 | box-shadow: 0 2px 4px rgba(0,0,0,0.2); 23 | padding: 24px; 24 | } 25 | 26 | .logo { 27 | position: absolute; 28 | top: 16px; 29 | right: 16px; 30 | height: 70px; 31 | width: 70px; 32 | } 33 | 34 | /* .header { 35 | margin-right: 40px; 36 | } */ 37 | 38 | h2 { 39 | font-size: 24px; 40 | font-family: 'Montserrat', sans-serif; 41 | margin-bottom: 16px; 42 | color: #333; 43 | } 44 | 45 | button { 46 | background-color: #0078FF; 47 | color: #FFF; 48 | border: none; 49 | font-family: 'Montserrat', sans-serif; 50 | border-radius: 4px; 51 | padding: 8px 8px; 52 | font-size: 12px; 53 | cursor: pointer; 54 | transition: background-color 0.3s ease; 55 | } 56 | 57 | button:hover { 58 | background-color: #0066CC; 59 | } 60 | 61 | button:disabled { 62 | background-color: #DDD; 63 | cursor: not-allowed; 64 | } 65 | 66 | .setting-btn { 67 | background-image: url(setting.png); 68 | background-size: contain; 69 | background-repeat: no-repeat; 70 | background-position: center; 71 | background-color: white; 72 | width: 25px; 73 | height: 25px; 74 | } 75 | .copy-btn { 76 | background-image: url(copy.png); 77 | background-size: contain; 78 | background-repeat: no-repeat; 79 | background-position: center; 80 | background-color: white; 81 | width: 25px; 82 | height: 25px; 83 | } 84 | .setting-btn:hover, .copy-btn:hover { 85 | background-color: white; 86 | } 87 | .setting-btn:disabled, .copy-btn:disabled { 88 | background-color: white; 89 | } 90 | 91 | .button-container { 92 | display: flex; 93 | justify-content: space-between; 94 | align-items: center; 95 | } 96 | .edit-container { 97 | display: flex; 98 | justify-content: flex-end; 99 | gap: 5px; 100 | } 101 | 102 | pre { 103 | white-space: pre-wrap; 104 | word-wrap: break-word; 105 | font-family: 'Montserrat', sans-serif; 106 | overflow-x: hidden; 107 | overflow-y: auto; 108 | max-height: 300px; 109 | background-color: #F5F5F5; 110 | padding: 16px; 111 | border-radius: 4px; 112 | font-size: 14px; 113 | line-height: 1.5; 114 | margin-top: 16px; 115 | } 116 | 117 | .error { 118 | color: #FF0000; 119 | } 120 | 121 | .powered-by { 122 | font-size: 12px; 123 | text-align: center; 124 | font-family: 'Montserrat', sans-serif; 125 | margin-top: 8px; 126 | color: #666; 127 | } 128 | 129 | .loading { 130 | display: none; 131 | text-align: center; 132 | } 133 | 134 | .spinner { 135 | border: 3px solid rgba(0, 0, 0, 0.2); 136 | border-top-color: #0078FF; 137 | border-radius: 50%; 138 | width: 15px; 139 | height: 15px; 140 | animation: spin 0.8s linear infinite; 141 | margin: 0 auto; 142 | } 143 | 144 | @keyframes spin { 145 | to { 146 | transform: rotate(360deg); 147 | } 148 | } -------------------------------------------------------------------------------- /packages/chromium/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SummarAI 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 |

SummarAI

17 |

This browser extension utilizes AI technology to analyze web pages and extract important information.

18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |

34 |       
35 |       

36 |
37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/chromium/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/packages/chromium/setting.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "mac-icon-16@1x.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "mac-icon-16@2x.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "mac-icon-32@1x.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "mac-icon-32@2x.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "mac-icon-128@1x.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "mac-icon-128@2x.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "mac-icon-256@1x.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "mac-icon-256@2x.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "mac-icon-512@1x.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "mac-icon-512@2x.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/logo-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/logo-1024.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "logo-128.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Assets.xcassets/LargeIcon.imageset/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Assets.xcassets/LargeIcon.imageset/logo-128.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Base.lproj/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | SummarAI Icon 14 |

You can turn on SummarAI’s Safari extension in Settings.

15 |

You can turn on SummarAI’s extension in Safari Extensions preferences.

16 |

SummarAI’s extension is currently on. You can turn it off in Safari Extensions preferences.

17 |

SummarAI’s extension is currently off. You can turn it on in Safari Extensions preferences.

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/Shared (App)/Resources/Icon.png -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Resources/Script.js: -------------------------------------------------------------------------------- 1 | function show(platform, enabled, useSettingsInsteadOfPreferences) { 2 | document.body.classList.add(`platform-${platform}`); 3 | 4 | if (useSettingsInsteadOfPreferences) { 5 | document.getElementsByClassName('platform-mac state-on')[0].innerText = "SummarAI’s extension is currently on. You can turn it off in the Extensions section of Safari Settings."; 6 | document.getElementsByClassName('platform-mac state-off')[0].innerText = "SummarAI’s extension is currently off. You can turn it on in the Extensions section of Safari Settings."; 7 | document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on SummarAI’s extension in the Extensions section of Safari Settings."; 8 | document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…"; 9 | } 10 | 11 | if (typeof enabled === "boolean") { 12 | document.body.classList.toggle(`state-on`, enabled); 13 | document.body.classList.toggle(`state-off`, !enabled); 14 | } else { 15 | document.body.classList.remove(`state-on`); 16 | document.body.classList.remove(`state-off`); 17 | } 18 | } 19 | 20 | function openPreferences() { 21 | webkit.messageHandlers.controller.postMessage("open-preferences"); 22 | } 23 | 24 | document.querySelector("button.open-preferences").addEventListener("click", openPreferences); 25 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/Resources/Style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-user-select: none; 3 | -webkit-user-drag: none; 4 | cursor: default; 5 | } 6 | 7 | :root { 8 | color-scheme: light dark; 9 | 10 | --spacing: 20px; 11 | } 12 | 13 | html { 14 | height: 100%; 15 | } 16 | 17 | body { 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | flex-direction: column; 22 | 23 | gap: var(--spacing); 24 | margin: 0 calc(var(--spacing) * 2); 25 | height: 100%; 26 | 27 | font: -apple-system-short-body; 28 | text-align: center; 29 | } 30 | 31 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) { 32 | display: none; 33 | } 34 | 35 | body.platform-ios .platform-mac { 36 | display: none; 37 | } 38 | 39 | body.platform-mac .platform-ios { 40 | display: none; 41 | } 42 | 43 | body.platform-ios .platform-mac { 44 | display: none; 45 | } 46 | 47 | body:not(.state-on, .state-off) :is(.state-on, .state-off) { 48 | display: none; 49 | } 50 | 51 | body.state-on :is(.state-off, .state-unknown) { 52 | display: none; 53 | } 54 | 55 | body.state-off :is(.state-on, .state-unknown) { 56 | display: none; 57 | } 58 | 59 | button { 60 | font-size: 1em; 61 | } 62 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (App)/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Shared (App) 4 | // 5 | // Created by zhangferry on 2023/7/15. 6 | // 7 | 8 | import WebKit 9 | 10 | #if os(iOS) 11 | import UIKit 12 | typealias PlatformViewController = UIViewController 13 | #elseif os(macOS) 14 | import Cocoa 15 | import SafariServices 16 | typealias PlatformViewController = NSViewController 17 | #endif 18 | 19 | let extensionBundleIdentifier = "com.zhangferry.SummarAI.Extension" 20 | 21 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler { 22 | 23 | @IBOutlet var webView: WKWebView! 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | self.webView.navigationDelegate = self 29 | 30 | #if os(iOS) 31 | self.webView.scrollView.isScrollEnabled = false 32 | #endif 33 | 34 | self.webView.configuration.userContentController.add(self, name: "controller") 35 | 36 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) 37 | } 38 | 39 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 40 | #if os(iOS) 41 | webView.evaluateJavaScript("show('ios')") 42 | #elseif os(macOS) 43 | webView.evaluateJavaScript("show('mac')") 44 | 45 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in 46 | guard let state = state, error == nil else { 47 | // Insert code to inform the user that something went wrong. 48 | return 49 | } 50 | 51 | DispatchQueue.main.async { 52 | if #available(macOS 13, *) { 53 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)") 54 | } else { 55 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)") 56 | } 57 | } 58 | } 59 | #endif 60 | } 61 | 62 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 63 | #if os(macOS) 64 | if (message.body as! String != "open-preferences") { 65 | return; 66 | } 67 | 68 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in 69 | guard error == nil else { 70 | // Insert code to inform the user that something went wrong. 71 | return 72 | } 73 | 74 | DispatchQueue.main.async { 75 | NSApplication.shared.terminate(nil) 76 | } 77 | } 78 | #endif 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /safari/SummarAI/Shared (Extension)/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebExtensionHandler.swift 3 | // Shared (Extension) 4 | // 5 | // Created by zhangferry on 2023/7/15. 6 | // 7 | 8 | import SafariServices 9 | import os.log 10 | 11 | let SFExtensionMessageKey = "message" 12 | 13 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { 14 | 15 | func beginRequest(with context: NSExtensionContext) { 16 | let item = context.inputItems[0] as! NSExtensionItem 17 | let message = item.userInfo?[SFExtensionMessageKey] 18 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg) 19 | 20 | let response = NSExtensionItem() 21 | response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ] 22 | 23 | context.completeRequest(returningItems: [response], completionHandler: nil) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /safari/SummarAI/SummarAI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /safari/SummarAI/SummarAI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /safari/SummarAI/SummarAI.xcodeproj/project.xcworkspace/xcuserdata/zhangferry.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/safari/SummarAI/SummarAI.xcodeproj/project.xcworkspace/xcuserdata/zhangferry.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /safari/SummarAI/SummarAI.xcodeproj/xcuserdata/zhangferry.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SummarAI (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | SummarAI (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /safari/SummarAI/iOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by zhangferry on 2023/7/15. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /safari/SummarAI/iOS (App)/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /safari/SummarAI/iOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /safari/SummarAI/iOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SFSafariWebExtensionConverterVersion 6 | 14.0.1 7 | UIApplicationSceneManifest 8 | 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | UIWindowSceneSessionRoleApplication 14 | 15 | 16 | UISceneConfigurationName 17 | Default Configuration 18 | UISceneDelegateClassName 19 | $(PRODUCT_MODULE_NAME).SceneDelegate 20 | UISceneStoryboardFile 21 | Main 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /safari/SummarAI/iOS (App)/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by zhangferry on 2023/7/15. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | guard let _ = (scene as? UIWindowScene) else { return } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /safari/SummarAI/iOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /safari/SummarAI/macOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS (App) 4 | // 5 | // Created by zhangferry on 2023/7/15. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | func applicationDidFinishLaunching(_ notification: Notification) { 14 | // Override point for customization after application launch. 15 | } 16 | 17 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 18 | return true 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /safari/SummarAI/macOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /safari/SummarAI/macOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SFSafariWebExtensionConverterVersion 6 | 14.0.1 7 | 8 | 9 | -------------------------------------------------------------------------------- /safari/SummarAI/macOS (App)/SummarAI.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /safari/SummarAI/macOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /safari/SummarAI/macOS (Extension)/SummarAI.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/img/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/copy.png -------------------------------------------------------------------------------- /src/assets/img/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/idea.png -------------------------------------------------------------------------------- /src/assets/img/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/logo-128.png -------------------------------------------------------------------------------- /src/assets/img/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/logo-16.png -------------------------------------------------------------------------------- /src/assets/img/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/logo-32.png -------------------------------------------------------------------------------- /src/assets/img/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/logo-48.png -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangferry/SummarAI/0c0f51fad9fd69dbf058b1b010986891e23dd0c9/src/assets/img/setting.png -------------------------------------------------------------------------------- /src/assets/styles/base.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | $primary-color: #0070f3; 6 | $secondary-color: #000; 7 | $gray-color: #444; 8 | $font-size-large: 16px; 9 | $font-size-normal: 14px; 10 | $font-size-small: 12px; 11 | 12 | // card 13 | .glarity--card { 14 | font-size: $font-size-normal; 15 | border: 1px solid #ddd; 16 | box-shadow: 0 3px 8px rgb(0 0 0 / 35%); 17 | border-radius: 6px !important; 18 | background: #fff; 19 | 20 | .glarity--card__head { 21 | display: flex; 22 | justify-content: space-between; 23 | justify-items: center; 24 | padding: 10px 12px; 25 | border-bottom: 1px solid #eee; 26 | font-size: $font-size-large; 27 | 28 | .glarity--card__head--title { 29 | display: inline-flex; 30 | align-items: center; 31 | font-weight: bold; 32 | 33 | .glarity--btn { 34 | margin-left: 4px; 35 | } 36 | 37 | a { 38 | display: inline-flex; 39 | text-decoration: none; 40 | color: #000; 41 | align-items: center; 42 | } 43 | 44 | img { 45 | margin: 0 4px 0 0; 46 | width: 20px; 47 | height: 20px; 48 | vertical-align: middle; 49 | } 50 | } 51 | } 52 | 53 | .glarity--card__content { 54 | position: relative; 55 | padding: 15px; 56 | color: #333; 57 | line-height: 1.6; 58 | max-height: 500px; 59 | overflow-y: auto; 60 | overflow-wrap: break-word; 61 | min-height: 110px; 62 | 63 | .glarity--container { 64 | margin-bottom: 0; 65 | 66 | .glarity--chatgpt { 67 | border: none; 68 | } 69 | } 70 | 71 | .glarity--card__empty { 72 | position: absolute; 73 | left: 50%; 74 | top: 50%; 75 | transform: translate(-50%, -50%); 76 | width: 80%; 77 | display: flex; 78 | padding: 6px 16px; 79 | text-align: center; 80 | justify-content: center; 81 | align-items: center; 82 | min-height: 70px; 83 | } 84 | 85 | .glarity--container #gpt-answer.markdown-body.gpt-markdown .glarity--chatgpt--header { 86 | margin: -13px -12px 0 0 !important; 87 | } 88 | } 89 | } 90 | 91 | // button 92 | .glarity--btn { 93 | margin: 0; 94 | padding: 8px 10px; 95 | line-height: 1; 96 | font-size: $font-size-normal; 97 | border-radius: 8px; 98 | border: 1px solid #fff; 99 | cursor: pointer; 100 | text-align: center; 101 | 102 | &:disabled { 103 | cursor: default; 104 | opacity: 0.5; 105 | 106 | &:hover { 107 | opacity: 0.5; 108 | } 109 | } 110 | 111 | &:hover { 112 | opacity: 0.9; 113 | } 114 | 115 | &.glarity--btn__icon { 116 | color: $secondary-color; 117 | padding: 0; 118 | border: none; 119 | cursor: pointer; 120 | color: $gray-color; 121 | background: transparent; 122 | 123 | // &:hover { 124 | // color: $primary-color; 125 | // } 126 | } 127 | 128 | &.glarity--btn__launch { 129 | display: flex; 130 | justify-content: center; 131 | align-items: center; 132 | padding: 0; 133 | width: 24px; 134 | height: 24px; 135 | text-align: center; 136 | vertical-align: middle; 137 | border: 1px solid #144a7f; 138 | background: #fff; 139 | 140 | img { 141 | margin: 0; 142 | width: 20px; 143 | height: 20px; 144 | vertical-align: middle; 145 | filter: blur(0); 146 | } 147 | } 148 | 149 | &.glarity--btn__primary { 150 | color: #fff; 151 | background: $primary-color; 152 | } 153 | 154 | &.glarity--btn__primary--ghost { 155 | color: $primary-color; 156 | background: #fff; 157 | border: 1px solid $primary-color; 158 | } 159 | 160 | &.glarity--btn__block { 161 | width: 100%; 162 | } 163 | 164 | &.glarity--btn__large { 165 | font-size: $font-size-large; 166 | padding: 8px 16px; 167 | } 168 | 169 | &.glarity--btn__small { 170 | font-size: $font-size-small; 171 | padding: 4px 8px; 172 | } 173 | } 174 | 175 | // text 176 | .glarity--subtitle { 177 | font-size: $font-size-small; 178 | color: #999 !important; 179 | font-weight: normal; 180 | } 181 | 182 | // Dark 183 | @media (prefers-color-scheme: dark) { 184 | .glarity--card { 185 | background: rgb(35, 36, 40); 186 | border-color: #444950; 187 | box-shadow: 0 3px 9px rgb(255 255 255 / 30%); 188 | 189 | .glarity--card__head { 190 | border-bottom-color: #444950; 191 | } 192 | 193 | .glarity--card__content, 194 | .glarity--card__head { 195 | color: #ccc; 196 | 197 | .glarity--card__head--title { 198 | a { 199 | color: #ccc; 200 | } 201 | } 202 | } 203 | } 204 | 205 | .glarity--btn { 206 | border-color: #444950; 207 | 208 | &.glarity--btn__primary { 209 | border-color: #999; 210 | } 211 | 212 | &.glarity--btn__icon { 213 | color: #999; 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/background/index.ts: -------------------------------------------------------------------------------- 1 | import Browser from 'webextension-polyfill' 2 | 3 | async function Run() { 4 | 5 | Browser.runtime.onInstalled.addListener(async (details) => { 6 | if (details.reason === 'install') { 7 | Browser.runtime.openOptionsPage() 8 | } 9 | }) 10 | } 11 | 12 | Run() -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { defaults } from 'lodash-es' 2 | import Browser from 'webextension-polyfill' 3 | 4 | export enum TriggerMode { 5 | Always = 'always', 6 | QuestionMark = 'questionMark', 7 | Manually = 'manually', 8 | } 9 | 10 | export const TRIGGER_MODE_TEXT = { 11 | [TriggerMode.Always]: { title: 'Always', desc: 'ChatGPT is queried on every search' }, 12 | [TriggerMode.Manually]: { 13 | title: 'Manually', 14 | desc: 'ChatGPT is queried when you manually click a button', 15 | }, 16 | } 17 | 18 | export enum Theme { 19 | Auto = 'auto', 20 | Light = 'light', 21 | Dark = 'dark', 22 | } 23 | 24 | export enum Language { 25 | Auto = 'auto', 26 | English = 'en-US', 27 | ChineseSimplified = 'zh-Hans', 28 | ChineseTraditional = 'zh-Hant', 29 | Spanish = 'es-ES', 30 | French = 'fr-FR', 31 | Korean = 'ko-KR', 32 | Japanese = 'ja-JP', 33 | German = 'de-DE', 34 | Portuguese = 'pt-PT', 35 | Russian = 'ru-RU', 36 | } 37 | 38 | const userConfigWithDefaultValue: { 39 | triggerMode: TriggerMode 40 | theme: Theme 41 | language: Language 42 | prompt: string 43 | promptSearch: string 44 | promptPage: string 45 | promptComment: string 46 | enableSites: string[] | null 47 | pageSummaryEnable: boolean 48 | pageSummaryWhitelist: string 49 | pageSummaryBlacklist: string 50 | continueConversation: boolean 51 | } = { 52 | triggerMode: TriggerMode.Always, 53 | theme: Theme.Auto, 54 | language: Language.Auto, 55 | prompt: '', 56 | promptSearch: '', 57 | promptPage: '', 58 | promptComment: '', 59 | enableSites: null, 60 | pageSummaryEnable: true, 61 | pageSummaryWhitelist: '', 62 | pageSummaryBlacklist: '', 63 | continueConversation: true 64 | } 65 | 66 | export type UserConfig = typeof userConfigWithDefaultValue 67 | 68 | export async function getUserConfig(): Promise { 69 | const result = await Browser.storage.local.get(Object.keys(userConfigWithDefaultValue)) 70 | return defaults(result, userConfigWithDefaultValue) 71 | } 72 | 73 | export async function updateUserConfig(updates: Partial) { 74 | console.debug('update configs', updates) 75 | return Browser.storage.local.set(updates) 76 | } 77 | 78 | export enum ProviderType { 79 | ChatGPT = 'chatgpt', 80 | GPT3 = 'gpt3', 81 | } 82 | 83 | interface GPT3ProviderConfig { 84 | model: string 85 | apiKey: string 86 | apiHost: string 87 | } 88 | 89 | export interface ProviderConfigs { 90 | provider: ProviderType 91 | configs: { 92 | [ProviderType.GPT3]: GPT3ProviderConfig | undefined 93 | } 94 | } 95 | 96 | export async function getProviderConfigs(): Promise { 97 | const { provider = ProviderType.ChatGPT } = await Browser.storage.local.get('provider') 98 | const configKey = `provider:${ProviderType.GPT3}` 99 | const result = await Browser.storage.local.get(configKey) 100 | return { 101 | provider, 102 | configs: { 103 | [ProviderType.GPT3]: result[configKey], 104 | }, 105 | } 106 | } 107 | 108 | export async function saveProviderConfigs( 109 | provider: ProviderType, 110 | configs: ProviderConfigs['configs'], 111 | ) { 112 | console.log(`saveProviderConfigs: ${provider}, type: ${ProviderType.GPT3}, configs: ${configs[ProviderType.GPT3]}`) 113 | return Browser.storage.local.set({ 114 | provider, 115 | [`provider:${ProviderType.GPT3}`]: configs[ProviderType.GPT3], 116 | }) 117 | } 118 | 119 | export const BASE_URL = 'https://chat.openai.com' 120 | 121 | export const DEFAULT_PAGE_SUMMARY_BLACKLIST = `https://translate.google.com 122 | https://www.deepl.com 123 | https://www.youtube.com 124 | https://youku.com 125 | https://v.qq.com 126 | https://www.iqiyi.com 127 | https://www.bilibili.com 128 | https://www.tudou.com 129 | https://www.tiktok.com 130 | https://vimeo.com 131 | https://www.dailymotion.com 132 | https://www.twitch.tv 133 | https://www.hulu.com 134 | https://www.netflix.com 135 | https://www.hbomax.com 136 | https://www.disneyplus.com 137 | https://www.peacocktv.com 138 | https://www.crunchyroll.com 139 | https://www.funimation.com 140 | https://www.viki.com 141 | ` 142 | export const APP_TITLE = `SummarAI` 143 | 144 | export const DEFAULT_MODEL = 'gpt-3.5-turbo' 145 | export const DEFAULT_API_HOST = 'api.openai.com' 146 | -------------------------------------------------------------------------------- /src/content/content.ts: -------------------------------------------------------------------------------- 1 | import Browser from "webextension-polyfill" 2 | 3 | Browser.runtime.onMessage.addListener((message, _, sendResponse) => { 4 | switch (message.action) { 5 | case "getTextContent": { 6 | const url = window.location.href // add this line to get the URL 7 | let pageContent = document.body.innerText 8 | console.log(url) 9 | sendResponse({ textContent: {url: url, innerText: pageContent}}) 10 | break 11 | } 12 | case "GET_DOM": { 13 | sendResponse({ html: document.querySelector("html")?.outerHTML }) 14 | break 15 | } 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const src: string 3 | export default src 4 | } -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "SummarAI", 4 | "version": "1.2.5", 5 | "homepage_url": "https://zhangferry.com", 6 | "description": "This browser extension utilizes AI technology to analyze web pages and extract important information.", 7 | "icons": { 8 | "16": "logo-16.png", 9 | "48": "logo-48.png", 10 | "128": "logo-128.png" 11 | }, 12 | "action": { 13 | "default_popup": "popup.html", 14 | "default_icon": "logo-128.png" 15 | }, 16 | "options_ui": { 17 | "page": "options.html", 18 | "open_in_tab": true 19 | }, 20 | "permissions": [ 21 | "activeTab", 22 | "scripting", 23 | "storage", 24 | "tabs" 25 | ], 26 | "background": { 27 | "service_worker": "background.js" 28 | }, 29 | "host_permissions": [ 30 | "https://*.openai.com/" 31 | ], 32 | "content_scripts": [ 33 | { 34 | "matches": [ 35 | "", 36 | "*://*/*" 37 | ], 38 | "js": [ 39 | "content.js" 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/options/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CssBaseline, 3 | GeistProvider, 4 | Radio, 5 | Select, 6 | Text, 7 | Toggle, 8 | useToasts, 9 | Divider, 10 | } from '@geist-ui/core' 11 | import { useCallback, useEffect, useMemo, useState } from 'preact/hooks' 12 | import '@/assets/styles/base.scss' 13 | import { 14 | getUserConfig, 15 | Language, 16 | Theme, 17 | TriggerMode, 18 | TRIGGER_MODE_TEXT, 19 | updateUserConfig, 20 | DEFAULT_PAGE_SUMMARY_BLACKLIST, 21 | } from '@/config' 22 | import { PageSummaryProps } from './components/PageSummary' 23 | import ProviderSelect from './ProviderSelect' 24 | import { isIOS } from '@/utils/utils' 25 | import Header from './components/Header' 26 | import { detectSystemColorScheme } from '@/utils/utils' 27 | 28 | import './styles.scss' 29 | 30 | function OptionsPage( 31 | props: { 32 | theme: Theme 33 | onThemeChange: (theme: Theme) => void 34 | } & PageSummaryProps, 35 | ) { 36 | const { 37 | setPageSummaryEnable, 38 | pageSummaryEnable, 39 | pageSummaryWhitelist, 40 | pageSummaryBlacklist, 41 | setPageSummaryWhitelist, 42 | setPageSummaryBlacklist, 43 | } = props 44 | const [triggerMode, setTriggerMode] = useState(TriggerMode.Always) 45 | const [language, setLanguage] = useState(Language.Auto) 46 | const { setToast } = useToasts() 47 | // const [prompt, setPrompt] = useState('') 48 | // const [promptSearch, setPromptSearch] = useState('') 49 | // const [promptPage, setPromptPage] = useState('') 50 | // const [promptComment, setPromptComment] = useState('') 51 | 52 | const onTriggerModeChange = useCallback( 53 | (mode: TriggerMode) => { 54 | setTriggerMode(mode) 55 | updateUserConfig({ triggerMode: mode }) 56 | setToast({ text: 'Changes saved', type: 'success' }) 57 | }, 58 | [setToast], 59 | ) 60 | 61 | const onThemeChange = useCallback( 62 | (theme: Theme) => { 63 | updateUserConfig({ theme }) 64 | props.onThemeChange(theme) 65 | setToast({ text: 'Changes saved', type: 'success' }) 66 | }, 67 | [props, setToast], 68 | ) 69 | 70 | useEffect(() => { 71 | getUserConfig().then((config) => { 72 | setTriggerMode(config.triggerMode) 73 | setLanguage(config.language) 74 | }) 75 | }, []) 76 | 77 | return ( 78 |
79 |
80 | 81 |
82 | Options 83 | 84 | {/* Trigger Mode */} 85 | {!isIOS && ( 86 | <> 87 | 88 | Trigger Mode 89 | 90 | onTriggerModeChange(val as TriggerMode)} 93 | > 94 | {Object.entries(TRIGGER_MODE_TEXT).map(([value, texts]) => { 95 | return ( 96 | 97 | {texts.title} 98 | {texts.desc} 99 | 100 | ) 101 | })} 102 | 103 | 104 | )} 105 | 106 | {/* Theme */} 107 | 108 | Theme 109 | 110 | onThemeChange(val as Theme)} useRow> 111 | {Object.entries(Theme).map(([k, v]) => { 112 | return ( 113 | 114 | {k} 115 | 116 | ) 117 | })} 118 | 119 | 120 | {/* AI Provider */} 121 | 122 | AI Provider 123 | 124 | 125 | 126 | {/* */} 136 | 137 | {/* */} 138 |
139 |
140 | ) 141 | } 142 | 143 | function App() { 144 | const [theme, setTheme] = useState(Theme.Auto) 145 | const [pageSummaryEnable, setPageSummaryEnable] = useState(true) 146 | const [pageSummaryWhitelist, setPageSummaryWhitelist] = useState('') 147 | const [pageSummaryBlacklist, setPageSummaryBlacklist] = useState('') 148 | 149 | const themeType = useMemo(() => { 150 | if (theme === Theme.Auto) { 151 | return detectSystemColorScheme() 152 | } 153 | return theme 154 | }, [theme]) 155 | 156 | useEffect(() => { 157 | getUserConfig().then((config) => { 158 | setTheme(config.theme) 159 | setPageSummaryEnable(config.pageSummaryEnable) 160 | setPageSummaryWhitelist(config.pageSummaryWhitelist) 161 | setPageSummaryBlacklist( 162 | config.pageSummaryBlacklist ? config.pageSummaryBlacklist : DEFAULT_PAGE_SUMMARY_BLACKLIST, 163 | ) 164 | }) 165 | }, []) 166 | 167 | return ( 168 | 169 | 170 | 180 | 181 | ) 182 | } 183 | 184 | export default App 185 | -------------------------------------------------------------------------------- /src/options/ProviderSelect.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Spinner, useInput, useToasts, Radio, Card } from '@geist-ui/core' 2 | import { FC, useCallback, useState, useEffect } from 'react' 3 | import useSWR from 'swr' 4 | import { getProviderConfigs, ProviderConfigs, ProviderType, saveProviderConfigs } from '@/config' 5 | import { Select as Aselect } from 'antd' 6 | import cn from 'classnames'; 7 | const { Option } = Aselect 8 | import { isSafari, availableModels } from '@/utils/utils' 9 | 10 | interface ConfigProps { 11 | config: ProviderConfigs 12 | models: string[] 13 | } 14 | 15 | const ConfigPanel: FC = ({ config, models }) => { 16 | const [tab, setTab] = useState(isSafari ? ProviderType.GPT3 : config.provider) 17 | const { bindings: apiKeyBindings } = useInput(config.configs[ProviderType.GPT3]?.apiKey ?? '') 18 | const { bindings: apiHostBindings } = useInput(config.configs[ProviderType.GPT3]?.apiHost ?? '') 19 | const [model, setModel] = useState(config.configs[ProviderType.GPT3]?.model ?? models[0]) 20 | const { setToast } = useToasts() 21 | 22 | const save = useCallback(async () => { 23 | if (tab === ProviderType.GPT3) { 24 | if (!apiKeyBindings.value) { 25 | alert('Please enter your OpenAI API key') 26 | return 27 | } 28 | 29 | if (!model || !models.includes(model)) { 30 | alert('Please select a valid model') 31 | return 32 | } 33 | } 34 | 35 | let apiHost = apiHostBindings.value || '' 36 | apiHost = apiHost.replace(/^http(s)?:\/\//, '') 37 | 38 | await saveProviderConfigs(tab, { 39 | [ProviderType.GPT3]: { 40 | model, 41 | apiKey: apiKeyBindings.value, 42 | apiHost: apiHost, 43 | }, 44 | }) 45 | setToast({ text: 'Changes saved', type: 'success' }) 46 | }, [apiHostBindings.value, apiKeyBindings.value, model, models, setToast, tab]) 47 | 48 | useEffect(() => { 49 | console.log('config', config) 50 | console.log('models', models) 51 | }, [config, models]) 52 | 53 | const isMobile = window.innerWidth < 768; // 假设小于768px为移动端 54 | const flexClass = isMobile ? 'glarity--flex-col' : 'glarity--flex-row'; 55 | 56 | return ( 57 | <> 58 | 59 |
60 | setTab(v as ProviderType)}> 61 | { !isSafari && ( 62 | <> 63 | 64 | ChatGPT webapp 65 | 66 | {' '} 67 | The API that powers ChatGPT webapp, free, but sometimes unstable 68 | 69 | 70 | 71 | ) } 72 | 73 | 74 | OpenAI API 75 | 76 |
77 | 78 | OpenAI official API, more stable,{' '} 79 | charge by usage 80 | 81 |
82 | 90 | setModel(v as string)} 93 | placeholder="model" 94 | optionLabelProp="label" 95 | style={{ width: '170px' }} 96 | > 97 | {models.map((m) => ( 98 | 101 | ))} 102 | 103 | 111 |
112 | 113 | You can find or create your API key{' '} 114 | 119 | here 120 | 121 | 122 |
123 |
124 |
125 |
126 | 127 | 130 | 131 |
132 |
133 | 134 | ) 135 | } 136 | 137 | function ProviderSelect() { 138 | const query = useSWR('provider-configs', async () => { 139 | const [config] = await Promise.all([getProviderConfigs()]) 140 | 141 | return { config } 142 | }) 143 | 144 | const models = availableModels.map(model => model.name) 145 | 146 | if (query.isLoading) { 147 | return 148 | } 149 | return 150 | } 151 | 152 | export default ProviderSelect 153 | -------------------------------------------------------------------------------- /src/options/components/CustomizePrompt.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useCallback } from 'preact/hooks' 3 | import { Text, Code, Textarea, Card, Button, Snippet, Collapse, useToasts } from '@geist-ui/core' 4 | import { Space } from 'antd' 5 | import { updateUserConfig } from '@/config' 6 | import { isIOS, changeToast } from '@/utils/utils' 7 | import { 8 | videoSummaryPromptHightligt, 9 | searchPromptHighlight, 10 | pageSummaryPromptHighlight, 11 | commentSummaryPromptHightligt, 12 | customizePrompt, 13 | customizePromptSearch, 14 | customizePrompt1, 15 | } from '@/utils/prompt' 16 | 17 | interface Props { 18 | prompt: string 19 | setPrompt: (prompt: string) => void 20 | promptSearch: string 21 | setPromptSearch: (promptSearch: string) => void 22 | promptPage: string 23 | setPromptPage: (promptPage: string) => void 24 | promptComment: string 25 | setPromptComment: (promptComment: string) => void 26 | } 27 | 28 | function CustomizePrompt(props: Props) { 29 | const { 30 | prompt, 31 | setPrompt, 32 | promptSearch, 33 | setPromptSearch, 34 | promptPage, 35 | setPromptPage, 36 | promptComment, 37 | setPromptComment, 38 | } = props 39 | const { setToast } = useToasts() 40 | 41 | const onPromptChange = useCallback( 42 | (e: React.ChangeEvent, type?: string | undefined) => { 43 | const prompt = e.target.value || '' 44 | switch (type) { 45 | case 'search': { 46 | setPromptSearch(prompt) 47 | break 48 | } 49 | 50 | case 'page': { 51 | setPromptPage(prompt) 52 | break 53 | } 54 | 55 | case 'comment': { 56 | setPromptComment(prompt) 57 | break 58 | } 59 | 60 | default: { 61 | setPrompt(prompt) 62 | break 63 | } 64 | } 65 | }, 66 | [setPrompt, setPromptSearch, setPromptPage, setPromptComment], 67 | ) 68 | 69 | const onSetPrompt = useCallback( 70 | (type?: string) => { 71 | switch (type) { 72 | case 'search': { 73 | setPromptSearch(searchPromptHighlight) 74 | updateUserConfig({ promptSearch: searchPromptHighlight }) 75 | break 76 | } 77 | 78 | case 'page': { 79 | setPromptPage(pageSummaryPromptHighlight) 80 | updateUserConfig({ promptPage: pageSummaryPromptHighlight }) 81 | break 82 | } 83 | 84 | case 'comment': { 85 | setPromptComment(commentSummaryPromptHightligt) 86 | updateUserConfig({ promptComment: commentSummaryPromptHightligt }) 87 | break 88 | } 89 | 90 | default: { 91 | setPrompt(videoSummaryPromptHightligt) 92 | updateUserConfig({ prompt: videoSummaryPromptHightligt }) 93 | break 94 | } 95 | } 96 | 97 | setToast(changeToast) 98 | }, 99 | [setPrompt, setPromptComment, setPromptPage, setPromptSearch, setToast], 100 | ) 101 | 102 | const onSavePrompt = useCallback( 103 | (type?: string) => { 104 | switch (type) { 105 | case 'search': { 106 | setPromptSearch(promptSearch) 107 | updateUserConfig({ promptSearch: promptSearch }) 108 | break 109 | } 110 | 111 | case 'page': { 112 | setPromptPage(promptPage) 113 | updateUserConfig({ promptPage: promptPage }) 114 | break 115 | } 116 | 117 | case 'comment': { 118 | setPromptPage(promptComment) 119 | updateUserConfig({ promptComment: promptComment }) 120 | break 121 | } 122 | 123 | default: { 124 | setPrompt(prompt) 125 | updateUserConfig({ prompt }) 126 | break 127 | } 128 | } 129 | 130 | updateUserConfig({ prompt }) 131 | setToast(changeToast) 132 | }, 133 | [ 134 | prompt, 135 | setToast, 136 | setPromptSearch, 137 | promptSearch, 138 | setPromptPage, 139 | promptPage, 140 | promptComment, 141 | setPrompt, 142 | ], 143 | ) 144 | 145 | return ( 146 | <> 147 | {!isIOS && ( 148 | <> 149 | 150 | Customize Prompt for Summary 151 | 152 | 153 | {/* YouTube */} 154 | 157 | YouTube / Bilibili{' '} 158 | 159 | } 160 | > 161 | 162 | 163 | 164 | {customizePrompt} 165 | 166 | 167 | 168 |