├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── pre-release-build.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── README.zh-CN.md ├── build.mjs ├── package.json ├── packages ├── Glarity-chromium-alpha.zip ├── Glarity-chromium-beta.zip ├── Glarity-firefox-alpha.zip ├── Glarity-firefox-beta.zip └── chromium-chrome.zip ├── pnpm-lock.yaml ├── safari └── Glarity │ ├── Glarity.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── givebest.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── givebest.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── Shared (App) │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── 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 │ │ │ └── universal-icon-1024@1x.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 │ ├── _locales │ │ ├── en │ │ │ └── messages.json │ │ ├── ja-JP │ │ │ └── messages.json │ │ ├── zh_Hans │ │ │ └── messages.json │ │ └── zh_Hant │ │ │ └── messages.json │ └── manifest.json │ ├── 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 │ ├── Glarity.entitlements │ └── Info.plist │ └── macOS (Extension) │ ├── Glarity.entitlements │ └── Info.plist ├── screenshots ├── PMC-cn.png ├── PMC-en.png ├── bilibili-cn.webp ├── bing-cn.png ├── bing-en.png ├── brave.png ├── extension-google-zh-CN.png ├── extension-google.png ├── extension-youtube-zh-CN.jpeg ├── extension-youtube.jpeg ├── github-cn.png ├── github-en.png ├── google-patents-en.png ├── google-patents-zh.png ├── google-vs-chatgpt.png ├── iOS-Safari-en.webp ├── macOS-Safari-en.webp ├── newspicks-jp.jpg ├── newspicks-zh.jpg ├── nikkei-jp.png ├── pubmed-en.png ├── pubmed-zh.png ├── pubmed.jpg ├── yahoo-japan.jpg ├── yahoo-news-en.png ├── yahoo-news-zh.jpg ├── youtube-clickbait-high-cn.jpeg ├── youtube-clickbait-high-en.jpeg └── youtube-clickbait-low-en.jpeg ├── src ├── _locales │ ├── am │ │ └── messages.json │ ├── ar │ │ └── messages.json │ ├── bg │ │ └── messages.json │ ├── bn │ │ └── messages.json │ ├── ca │ │ └── messages.json │ ├── cs │ │ └── messages.json │ ├── da │ │ └── messages.json │ ├── de │ │ └── messages.json │ ├── el │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── en_GB │ │ └── messages.json │ ├── en_US │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── es_419 │ │ └── messages.json │ ├── et │ │ └── messages.json │ ├── fa │ │ └── messages.json │ ├── fi │ │ └── messages.json │ ├── fil │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── gu │ │ └── messages.json │ ├── he │ │ └── messages.json │ ├── hi │ │ └── messages.json │ ├── hr │ │ └── messages.json │ ├── hu │ │ └── messages.json │ ├── id │ │ └── messages.json │ ├── it │ │ └── messages.json │ ├── iw │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── ko │ │ └── messages.json │ ├── lv │ │ └── messages.json │ ├── mr │ │ └── messages.json │ ├── ms │ │ └── messages.json │ ├── nl │ │ └── messages.json │ ├── no │ │ └── messages.json │ ├── pl │ │ └── messages.json │ ├── pt_BR │ │ └── messages.json │ ├── pt_PT │ │ └── messages.json │ ├── ro │ │ └── messages.json │ ├── ru │ │ └── messages.json │ ├── sk │ │ └── messages.json │ ├── sl │ │ └── messages.json │ ├── sr │ │ └── messages.json │ ├── sv │ │ └── messages.json │ ├── sw │ │ └── messages.json │ ├── te │ │ └── messages.json │ ├── th │ │ └── messages.json │ ├── tr │ │ └── messages.json │ ├── uk │ │ └── messages.json │ ├── vi │ │ └── messages.json │ ├── zh_CN │ │ └── messages.json │ └── zh_TW │ │ └── messages.json ├── assets │ ├── img │ │ ├── logo-128.png │ │ ├── logo-16.png │ │ ├── logo-32.png │ │ ├── logo-48.png │ │ ├── logo-white.png │ │ ├── logo-white.svg │ │ └── logo.png │ └── styles │ │ └── base.scss ├── background │ ├── fetch-sse.ts │ ├── index.ts │ ├── providers │ │ ├── chatgpt.ts │ │ └── openai.ts │ ├── stream-async-iterable.ts │ └── types.ts ├── config │ └── index.ts ├── content-script │ ├── compenents │ │ ├── ChatGPTCard.tsx │ │ ├── ChatGPTContainer.tsx │ │ ├── ChatGPTFeedback.tsx │ │ ├── ChatGPTQuery.tsx │ │ ├── ChatGPTTip.tsx │ │ ├── GetQuestion.tsx │ │ ├── Mount.tsx │ │ └── PageSummary.tsx │ ├── dark.scss │ ├── index.tsx │ ├── light.scss │ ├── prompt.ts │ ├── search-engine-configs.ts │ ├── styles.scss │ └── utils.ts ├── global.d.ts ├── manifest.json ├── manifest.v2.json ├── messaging.ts ├── options │ ├── App.tsx │ ├── ProviderSelect.tsx │ ├── components │ │ ├── CustomizePrompt.tsx │ │ ├── EnableGlarity.tsx │ │ ├── Header.tsx │ │ └── PageSummary.tsx │ ├── index.html │ ├── index.tsx │ └── styles.scss ├── popup │ ├── App.tsx │ ├── index.html │ ├── index.tsx │ └── styles.scss ├── typings.d.ts └── utils │ ├── article-extractor │ └── cjs │ │ ├── article-extractor.esm.js │ │ ├── article-extractor.js │ │ ├── index.d.ts │ │ └── package.json │ ├── bilibili.ts │ ├── prompt.ts │ └── utils.ts ├── tailwind.config.cjs └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "env": { 4 | "browser": true 5 | }, 6 | "plugins": ["@typescript-eslint"], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:react/recommended", 11 | "plugin:react-hooks/recommended", 12 | "prettier" 13 | ], 14 | "overrides": [], 15 | "parserOptions": { 16 | "ecmaVersion": "latest", 17 | "sourceType": "module" 18 | }, 19 | "rules": { 20 | "react/react-in-jsx-scope": "off" 21 | }, 22 | "ignorePatterns": ["build/**"] 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **Desktop (please complete the following information):** 13 | 14 | - OS: [e.g. Windows] 15 | - Browser [e.g. chrome, brave] 16 | - Version [e.g. 1.15.1] 17 | -------------------------------------------------------------------------------- /.github/workflows/pre-release-build.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 18 15 | - run: npm install 16 | - run: npm run build 17 | 18 | - uses: josStorer/get-current-time@v2.0.2 19 | id: current-time 20 | with: 21 | format: YY_MMDD_HH_mm 22 | 23 | - uses: actions/upload-artifact@v3 24 | with: 25 | name: Chromium_ChatGPT_Extension_Build_${{ steps.current-time.outputs.formattedTime }} 26 | path: build/chromium/* 27 | 28 | - uses: actions/upload-artifact@v3 29 | with: 30 | name: Firefox_ChatGPT_Extension_Build_${{ steps.current-time.outputs.formattedTime }} 31 | path: build/firefox/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .DS_Store 4 | .env 5 | docs/ 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | *.xcuserstate 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | Carthage/Build 35 | 36 | fastlane/report.xml 37 | fastlane/Preview.html 38 | fastlane/screenshots/**/*.png 39 | fastlane/test_output 40 | 41 | iOSInjectionProject/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "overrides": [ 9 | { 10 | "files": ".prettierrc", 11 | "options": { 12 | "parser": "json" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glarity - Summary for Google/YouTube with ChatGPT 2 | 3 | Glarity Summary: an open-source ChatGPT Summary extension for YouTube, Google, Twitter, and any webpage. It provides cross-language summaries to effortlessly summarize videos, searches, PDFs, emails, and webpages. It supports free side-by-side translations, email writing assistance, Web Content Q&A, and much more 4 | Download the experience package ([https://github.com/sparticleinc/chatgpt-google-summary-extension/tree/main/packages](https://github.com/sparticleinc/chatgpt-google-summary-extension/tree/main/packages)) and adding it manually ([https://github.com/sparticleinc/chatgpt-google-summary-extension#chrome](https://github.com/sparticleinc/chatgpt-google-summary-extension#chrome)). 5 | 6 | ## Sponsors 7 | 8 | 9 |

10 | 11 | Felo AI
Felo AI 12 |
13 |

14 | 15 | ## Supported Websites 16 | 17 | - Google 18 | - YouTube 19 | - Yahoo! JAPAN ニュース 20 | - PubMed 21 | - PMC 22 | - NewsPicks 23 | - Github 24 | - Nikkei 25 | - Bing 26 | - Google Patents 27 | - Bilibili 28 | - Any website 29 | (summary list:https://blog.glarity.app/getting-started/user-guide/summary-list) 30 | 31 | ## Installation 32 | 33 | [Add from Chrome Web Store](https://chrome.google.com/webstore/detail/summary-for-google-with-c/cmnlolelipjlhfkhpohphpedmkfbobjc) 34 | [Add from Mozilla Add-on Store](https://addons.mozilla.org/zh-CN/firefox/addon/glarity/) 35 | 36 | ## Features 37 | 38 | - Side-by-Side Translation (mirror translation, immersive translate) 39 | - Gmail quick reply 40 | - Supports Google search 41 | - Supports YouTube (YouTube videos transcript, summary, key moment) 42 | - Supports Github 43 | - Supports Bing 44 | - Supports Yahoo! JAPAN ニュース 45 | - Supports PubMed 46 | - Supports PMC 47 | - Supports NewsPicks 48 | - Supports Nikkei 49 | - Supports Google Patents 50 | - Support bilibili 51 | - Support twitter summary 52 | - Support summary of any web page 53 | - Support for iOS Safari/ macOS Safari 54 | - Supports the official OpenAI API (GPT-3.5-turbo/text-davinci-003) 55 | - Supports ChatGPT Plus 56 | - Markdown rendering 57 | - Code highlights 58 | - Dark mode 59 | - Provide feedback to improve ChatGPT 60 | - Copy to clipboard 61 | - Switch languages 62 | - Glarity AI model 63 | 64 | ## Screenshot 65 | 66 | ### Google 67 | 68 | ![Screenshot](screenshots/google-vs-chatgpt.png?raw=true) 69 | ![Screenshot](screenshots/extension-google.png?raw=true) 70 | 71 | ### YouTube 72 | 73 | ![Screenshot](screenshots/extension-youtube.jpeg?raw=true) 74 | 75 | ### Bilibili 76 | 77 | ![Screenshot](screenshots/bilibili-cn.webp?raw=true) 78 | 79 | ### iOS Safari /macOS Safari 80 | 81 | ![Screenshot](screenshots/iOS-Safari-en.webp?raw=true) 82 | ![Screenshot](screenshots/macOS-Safari-en.webp?raw=true) 83 | 84 | ### Github 85 | 86 | ![Github](screenshots/github-en.png?raw=true) 87 | 88 | ### Bing 89 | 90 | ![Bing](screenshots/bing-en.png) 91 | 92 | ### Google Patents 93 | 94 | ![Google Patents](screenshots/google-patents-en.png) 95 | 96 | ### Yahoo! JAPAN ニュース 97 | 98 | ![Screenshot](screenshots/yahoo-japan.jpg?raw=true) 99 | 100 | ### PubMed 101 | 102 | ![Screenshot](screenshots/pubmed.jpg?raw=true) 103 | 104 | ### PMC 105 | 106 | ![Screenshot](screenshots/PMC-en.png?raw=true) 107 | 108 | ### NewsPicks 109 | 110 | ![Screenshot](screenshots/newspicks-jp.jpg?raw=true) 111 | 112 | ### Nikkei 113 | 114 | ![Nikkei](screenshots/nikkei-jp.png) 115 | 116 | ## Troubleshooting 117 | 118 | ### How to make it work in Brave 119 | 120 | ![Screenshot](screenshots/brave.png?raw=true) 121 | Disable "Prevent sites from fingerprinting me based on my language preferences" in `brave://settings/shields` 122 | 123 | ## Build from source 124 | 125 | 1. Clone the repo 126 | 2. Install dependencies with `npm` 127 | 3. `npm run build` 128 | 129 | ### Packages 130 | 131 | - [Chromium](packages/Glarity-chromium-beta.zip) 132 | - [Firefox](packages/Glarity-firefox-beta.zip) 133 | 134 | ### Chrome 135 | 136 | 1. Go to `chrome://extensions/`. 137 | 2. At the top right, turn on `Developer mode`. 138 | 3. Click `Load unpacked`. 139 | 4. Find and select extension folder(`build/chromium/`). 140 | 141 | ### Firefox 142 | 143 | 1. Go to `about:debugging#/runtime/this-firefox`. 144 | 2. Click `Load Temporary Add-on`. 145 | 3. Find and select the extension file(`build/firefox.zip`). 146 | 147 | ## Credit 148 | 149 | This project is a fork of [wong2/chatgpt-google-extension](https://github.com/wong2/chatgpt-google-extension), and borrows code from [qunash/chatgpt-advanced](https://github.com/qunash/chatgpt-advanced) & [YouTube Summary with ChatGPT](https://github.com/kazuki-sf/YouTube_Summary_with_ChatGPT) 150 | 151 | ## License 152 | 153 | [GPL-3.0 license](LICENSE). 154 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # # Glarity - Summary for Google/YouTube with ChatGPT 2 | 3 | Chrome 浏览器扩展实现在 Google 搜索结果和 YouTube 视频旁边展示 ChatGPT 摘要,同时支持 Yahoo! JAPAN ニュース、PubMed、PMC、NewsPicks、Github、Nikkei、Bing、Google Patents, 以及任意网页的总结。 4 | 5 | ## ⚠️ Tips: 6 | 7 | 如果遇到不能选择 OpenAI API 模型, 麻烦删除并重新安装。 8 | 9 | 或者尝试下载最新体验包 ([https://github.com/sparticleinc/chatgpt-google-summary-extension/tree/main/packages](https://github.com/sparticleinc/chatgpt-google-summary-extension/tree/main/packages)) 然后手动安装 ([https://github.com/sparticleinc/chatgpt-google-summary-extension#chrome](https://github.com/sparticleinc/chatgpt-google-summary-extension#chrome)). 10 | 11 | ## Sponsors 12 | 13 | 14 |

15 | 16 | Felo AI
Felo AI 17 |
18 |

19 | 20 | 21 | ## 支持网站 22 | 23 | - Google 24 | - YouTube 25 | - Yahoo! 日本新闻 26 | - PubMed 27 | - PMC 28 | - NewsPicks 29 | - Github 30 | - Nikkei 31 | - Bing 32 | - Google Patents 33 | - Bilibili 34 | - 任意网站 35 | 36 | ## 安装 37 | 38 | [Chrome 应用市场](https://chrome.google.com/webstore/detail/summary-for-google-with-c/cmnlolelipjlhfkhpohphpedmkfbobjc) 39 | 40 | [Firefox Add-ons 市场](https://addons.mozilla.org/zh-CN/firefox/addon/glarity/) 41 | 42 | ## 功能 43 | 44 | - 支持 Google 搜索 45 | - 支持 YouTube 视频总结 46 | - 支持 Bing 搜索 47 | - 支持 Github 项目总结 48 | - 支持 Google 专利总结 49 | - 支持 Yahoo! JAPAN 新闻总结 50 | - 支持 PubMed 总结 51 | - 支持 PMC 总结 52 | - 支持 NewsPicks 总结 53 | - 支持 Nikkei 总结 54 | - 支持 Bilibili 视频总结 55 | - 支持任意网页的总结 56 | - 支持 iOS Safari/ macOS Safari 57 | - 支持 OpenAI 官方 API(GPT-3.5-turbo/text-davinci-003) 58 | - 支持 ChatGPT Plus 59 | - Markdown 格式渲染 60 | - 代码高亮 61 | - 暗色模式 62 | - 支持 ChatGPT 反馈 63 | - 复制结果 64 | - 切换语言 65 | 66 | ## 截图 67 | 68 | ### Google 69 | 70 | ![Screenshot](screenshots/extension-google-zh-CN.png?raw=true) 71 | ![Screenshot](screenshots/google-vs-chatgpt.png?raw=true) 72 | 73 | ### YouTube 74 | 75 | ![Screenshot](screenshots/extension-youtube-zh-CN.jpeg?raw=true) 76 | 77 | ### Bilibili 78 | 79 | ![Screenshot](screenshots/bilibili-cn.webp?raw=true) 80 | 81 | ### iOS Safari /macOS Safari 82 | 83 | ![Screenshot](screenshots/iOS-Safari-en.webp?raw=true) 84 | ![Screenshot](screenshots/macOS-Safari-en.webp?raw=true) 85 | 86 | ### Github 87 | 88 | ![Github](screenshots/github-cn.png?raw=true) 89 | 90 | ### Bing 91 | 92 | ![Bing](screenshots/bing-cn.png) 93 | 94 | ### Google Patents 95 | 96 | ![Google Patents](screenshots/google-patents-zh.png) 97 | 98 | ### Yahoo! JAPAN 新闻 99 | 100 | ![Screenshot](screenshots/yahoo-japan.jpg?raw=true) 101 | 102 | ### PubMed 103 | 104 | ![Screenshot](screenshots/pubmed.jpg?raw=true) 105 | 106 | ### PMC 107 | 108 | ![Screenshot](screenshots/PMC-cn.png?raw=true) 109 | 110 | ### NewsPicks 111 | 112 | ![Screenshot](screenshots/newspicks-zh.jpg?raw=true) 113 | 114 | ### Nikkei 115 | 116 | ![Nikkei](screenshots/nikkei-jp.png) 117 | 118 | ## 常见问题 119 | 120 | ### 在 Brave 运行 121 | 122 | ![Screenshot](screenshots/brave.png?raw=true) 123 | 124 | Disable "Prevent sites from fingerprinting me based on my language preferences" in `brave://settings/shields` 125 | 126 | ## 手动安装 127 | 128 | 1. Clone 代码 129 | 2. `npm i` 130 | 3. `npm run build` 131 | 132 | ### 扩展包 133 | 134 | - [Chromium](packages/Glarity-chromium-beta.zip) 135 | - [Firefox](packages/Glarity-firefox-beta.zip) 136 | 137 | ### Chrome 138 | 139 | 1. 打开扩展管理窗口,chrome://extensions 140 | 2. 激活开发者模式 141 | 3. 载入 `build/chromium/` 142 | 143 | ### Firefox 144 | 145 | 1. 打开 `about:debugging#/runtime/this-firefox` 146 | 2. 临时载入附加组件 147 | 3. 载入 `build/firefox.zip` 148 | 149 | ## Credit 150 | 151 | This project is a fork of [wong2/chatgpt-google-extension](https://github.com/wong2/chatgpt-google-extension), and borrows code from [qunash/chatgpt-advanced](https://github.com/qunash/chatgpt-advanced) & [YouTube Summary with ChatGPT](https://github.com/kazuki-sf/YouTube_Summary_with_ChatGPT) 152 | 153 | ## License 154 | 155 | [GPL-3.0 license](LICENSE). 156 | -------------------------------------------------------------------------------- /build.mjs: -------------------------------------------------------------------------------- 1 | import archiver from 'archiver' 2 | import autoprefixer from 'autoprefixer' 3 | import * as dotenv from 'dotenv' 4 | import esbuild from 'esbuild' 5 | import postcssPlugin from 'esbuild-style-plugin' 6 | import fs from 'fs-extra' 7 | import process from 'node:process' 8 | import tailwindcss from 'tailwindcss' 9 | 10 | dotenv.config() 11 | 12 | const outdir = 'build' 13 | const packagesDir = 'packages' 14 | const appName = 'Glarity-' 15 | 16 | const isDev = process.env.NODE_ENV === 'dev' 17 | 18 | let buildConfig = { 19 | entryPoints: [ 20 | 'src/content-script/index.tsx', 21 | 'src/background/index.ts', 22 | 'src/options/index.tsx', 23 | 'src/popup/index.tsx', 24 | ], 25 | bundle: true, 26 | outdir: outdir, 27 | treeShaking: true, 28 | minify: true, 29 | drop: ['console', 'debugger'], 30 | legalComments: 'none', 31 | define: { 32 | 'process.env.NODE_ENV': '"production"', 33 | }, 34 | jsxFactory: 'h', 35 | jsxFragment: 'Fragment', 36 | jsx: 'automatic', 37 | loader: { 38 | '.png': 'dataurl', 39 | '.svg': 'dataurl', 40 | }, 41 | plugins: [ 42 | postcssPlugin({ 43 | postcss: { 44 | plugins: [tailwindcss, autoprefixer], 45 | }, 46 | }), 47 | ], 48 | } 49 | 50 | if (isDev) { 51 | buildConfig = { ...buildConfig, ...{ minify: false, drop: [] } } 52 | } 53 | 54 | async function deleteOldDir() { 55 | await fs.remove(outdir) 56 | } 57 | 58 | async function runEsbuild() { 59 | await esbuild.build(buildConfig) 60 | } 61 | 62 | async function zipFolder(dir) { 63 | const output = fs.createWriteStream(`${dir}.zip`) 64 | const archive = archiver('zip', { 65 | zlib: { level: 9 }, 66 | }) 67 | archive.pipe(output) 68 | archive.directory(dir, false) 69 | await archive.finalize() 70 | } 71 | 72 | async function copyFiles(entryPoints, targetDir) { 73 | await fs.ensureDir(targetDir) 74 | await Promise.all( 75 | entryPoints.map(async (entryPoint) => { 76 | await fs.copy(entryPoint.src, `${targetDir}/${entryPoint.dst}`) 77 | }), 78 | ) 79 | } 80 | 81 | async function build() { 82 | await deleteOldDir() 83 | await runEsbuild() 84 | 85 | const commonFiles = [ 86 | { src: 'build/content-script/index.js', dst: 'content-script.js' }, 87 | { src: 'build/content-script/index.css', dst: 'content-script.css' }, 88 | { src: 'build/background/index.js', dst: 'background.js' }, 89 | { src: 'build/options/index.js', dst: 'options.js' }, 90 | { src: 'build/options/index.css', dst: 'options.css' }, 91 | { src: 'src/options/index.html', dst: 'options.html' }, 92 | { src: 'build/popup/index.js', dst: 'popup.js' }, 93 | { src: 'build/popup/index.css', dst: 'popup.css' }, 94 | { src: 'src/popup/index.html', dst: 'popup.html' }, 95 | { src: 'src/assets/img/logo-16.png', dst: 'logo-16.png' }, 96 | { src: 'src/assets/img/logo-32.png', dst: 'logo-32.png' }, 97 | { src: 'src/assets/img/logo-48.png', dst: 'logo-48.png' }, 98 | { src: 'src/assets/img/logo-128.png', dst: 'logo-128.png' }, 99 | { src: 'src/assets/img/logo.png', dst: 'logo.png' }, 100 | { src: 'src/_locales', dst: '_locales' }, 101 | ] 102 | 103 | // chromium 104 | await copyFiles( 105 | [...commonFiles, { src: 'src/manifest.json', dst: 'manifest.json' }], 106 | `./${outdir}/chromium`, 107 | ) 108 | 109 | await zipFolder(`./${outdir}/chromium`) 110 | await copyFiles( 111 | [ 112 | { 113 | src: `${outdir}/chromium.zip`, 114 | dst: `${appName}chromium.zip`, 115 | }, 116 | ], 117 | `./${packagesDir}`, 118 | ) 119 | 120 | await copyFiles( 121 | [ 122 | { 123 | src: `${outdir}/chromium`, 124 | dst: `./chromium`, 125 | }, 126 | ], 127 | `./${packagesDir}`, 128 | ) 129 | 130 | // firefox 131 | await copyFiles( 132 | [...commonFiles, { src: 'src/manifest.v2.json', dst: 'manifest.json' }], 133 | `./${outdir}/firefox`, 134 | ) 135 | 136 | await zipFolder(`./${outdir}/firefox`) 137 | await copyFiles( 138 | [ 139 | { 140 | src: `${outdir}/firefox.zip`, 141 | dst: `${appName}firefox.zip`, 142 | }, 143 | ], 144 | `./${packagesDir}`, 145 | ) 146 | 147 | console.log('Build success.') 148 | } 149 | 150 | build() 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-google-summary-extension", 3 | "author": "givebest", 4 | "scripts": { 5 | "dev": "cross-env NODE_ENV='dev' node build.mjs", 6 | "build": "cross-env NODE_ENV='production' node build.mjs", 7 | "lint": "eslint --ext .js,.mjs,.jsx .", 8 | "lint:fix": "eslint --ext .js,.mjs,.jsx . --fix", 9 | "prepare": "husky install", 10 | "watch": "chokidar src -c 'npm run dev'" 11 | }, 12 | "dependencies": { 13 | "@geist-ui/core": "^2.3.8", 14 | "@primer/octicons-react": "^17.9.0", 15 | "antd": "^5.3.0", 16 | "classnames": "^2.3.2", 17 | "copy-to-clipboard": "^3.3.3", 18 | "eventsource-parser": "^0.0.5", 19 | "expiry-map": "^2.0.0", 20 | "gb-url": "^1.1.6", 21 | "github-markdown-css": "^5.1.0", 22 | "gpt3-tokenizer": "^1.1.5", 23 | "inter-ui": "^3.19.3", 24 | "jquery": "^3.6.3", 25 | "lodash-es": "^4.17.21", 26 | "preact": "^10.13.1", 27 | "prop-types": "^15.8.1", 28 | "punycode": "^2.1.1", 29 | "react": "npm:@preact/compat@^17.1.2", 30 | "react-dom": "npm:@preact/compat@^17.1.2", 31 | "react-markdown": "^8.0.4", 32 | "rehype-highlight": "^6.0.0", 33 | "swr": "^2.0.0", 34 | "uuid": "^9.0.0", 35 | "xss": "^1.0.14" 36 | }, 37 | "devDependencies": { 38 | "@types/fs-extra": "^9.0.13", 39 | "@types/lodash-es": "^4.17.6", 40 | "@types/react": "17.0.44", 41 | "@types/uuid": "^9.0.0", 42 | "@types/webextension-polyfill": "^0.9.2", 43 | "@typescript-eslint/eslint-plugin": "^5.47.0", 44 | "@typescript-eslint/parser": "^5.47.0", 45 | "archiver": "^5.3.1", 46 | "autoprefixer": "^10.4.13", 47 | "chokidar-cli": "^3.0.0", 48 | "cross-env": "^7.0.3", 49 | "dotenv": "^16.0.3", 50 | "esbuild": "^0.17.4", 51 | "esbuild-style-plugin": "^1.6.1", 52 | "eslint": "^8.30.0", 53 | "eslint-config-prettier": "^8.5.0", 54 | "eslint-plugin-react": "^7.31.11", 55 | "eslint-plugin-react-hooks": "^4.6.0", 56 | "fs-extra": "^11.1.0", 57 | "husky": "^8.0.0", 58 | "lint-staged": "^13.1.0", 59 | "npm-force-resolutions": "^0.0.10", 60 | "postcss": "^8.4.31", 61 | "postcss-scss": "^4.0.6", 62 | "prettier": "^2.8.0", 63 | "prettier-plugin-organize-imports": "^3.2.1", 64 | "sass": "^1.57.1", 65 | "tailwindcss": "^3.2.4", 66 | "typescript": "^4.9.4", 67 | "webextension-polyfill": "^0.10.0" 68 | }, 69 | "lint-staged": { 70 | "src/*.{js,jsx,ts,tsx,mjs}": [ 71 | "npx prettier --write", 72 | "npx eslint --fix" 73 | ] 74 | }, 75 | "resolutions": { 76 | "@types/react": "17.0.44" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/Glarity-chromium-alpha.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/packages/Glarity-chromium-alpha.zip -------------------------------------------------------------------------------- /packages/Glarity-chromium-beta.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/packages/Glarity-chromium-beta.zip -------------------------------------------------------------------------------- /packages/Glarity-firefox-alpha.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/packages/Glarity-firefox-alpha.zip -------------------------------------------------------------------------------- /packages/Glarity-firefox-beta.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/packages/Glarity-firefox-beta.zip -------------------------------------------------------------------------------- /packages/chromium-chrome.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/packages/chromium-chrome.zip -------------------------------------------------------------------------------- /safari/Glarity/Glarity.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /safari/Glarity/Glarity.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /safari/Glarity/Glarity.xcodeproj/project.xcworkspace/xcuserdata/givebest.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Glarity.xcodeproj/project.xcworkspace/xcuserdata/givebest.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /safari/Glarity/Glarity.xcodeproj/xcuserdata/givebest.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Glarity (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | Glarity (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /safari/Glarity/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/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "1024x1024", 5 | "idiom" : "universal", 6 | "filename" : "universal-icon-1024@1x.png", 7 | "platform" : "ios" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "mac-icon-16@1x.png", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "size" : "16x16", 17 | "idiom" : "mac", 18 | "filename" : "mac-icon-16@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "mac-icon-32@1x.png", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "size" : "32x32", 29 | "idiom" : "mac", 30 | "filename" : "mac-icon-32@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "mac-icon-128@1x.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "128x128", 41 | "idiom" : "mac", 42 | "filename" : "mac-icon-128@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "mac-icon-256@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "256x256", 53 | "idiom" : "mac", 54 | "filename" : "mac-icon-256@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "mac-icon-512@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "512x512", 65 | "idiom" : "mac", 66 | "filename" : "mac-icon-512@2x.png", 67 | "scale" : "2x" 68 | } 69 | ], 70 | "info" : { 71 | "version" : 1, 72 | "author" : "xcode" 73 | } 74 | } -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /safari/Glarity/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/Glarity/Shared (App)/Assets.xcassets/LargeIcon.imageset/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Assets.xcassets/LargeIcon.imageset/logo-128.png -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Base.lproj/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Glarity Icon 14 |

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

15 |

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

16 |

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

17 |

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

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /safari/Glarity/Shared (App)/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/safari/Glarity/Shared (App)/Resources/Icon.png -------------------------------------------------------------------------------- /safari/Glarity/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 = "Glarity’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 = "Glarity’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 Glarity’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/Glarity/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/Glarity/Shared (App)/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Shared (App) 4 | // 5 | // Created by Pell Wong on 2023/3/17. 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.yourCompany.Glarity.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/Glarity/Shared (Extension)/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebExtensionHandler.swift 3 | // Shared (Extension) 4 | // 5 | // Created by Pell Wong on 2023/3/17. 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/Glarity/Shared (Extension)/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity - ChatGPT for YouTube/Google" 4 | }, 5 | "appDesc": { 6 | "message": "Display ChatGPT summaries alongside Google search results and YouTube videos" 7 | } 8 | } -------------------------------------------------------------------------------- /safari/Glarity/Shared (Extension)/_locales/ja-JP/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity - ChatGPT for YouTube/Google" 4 | }, 5 | "appDesc": { 6 | "message": "Google検索結果やYouTube動画と一緒にChatGPTの要約を表示" 7 | } 8 | } -------------------------------------------------------------------------------- /safari/Glarity/Shared (Extension)/_locales/zh_Hans/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity - 使用ChatGPT为谷歌搜索结果及YouTube视频生成摘要" 4 | }, 5 | "appDesc": { 6 | "message": "Google 搜索结果和 YouTube 视频旁边展示 ChatGPT 摘要(支持自动翻译成中文)" 7 | } 8 | } -------------------------------------------------------------------------------- /safari/Glarity/Shared (Extension)/_locales/zh_Hant/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-用ChatGPT為谷歌搜索結果及YouTube視頻生成摘要" 4 | }, 5 | "appDesc": { 6 | "message": "Google 搜索結果和 YouTube 視頻旁邊展示 ChatGPT 摘要(支持自動翻譯)" 7 | } 8 | } -------------------------------------------------------------------------------- /safari/Glarity/Shared (Extension)/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_appName__", 3 | "description": "__MSG_appDesc__", 4 | "default_locale": "en", 5 | "version": "1.7.0", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "logo-16.png", 9 | "32": "logo-32.png", 10 | "48": "logo-48.png", 11 | "128": "logo-128.png" 12 | }, 13 | "host_permissions": [ 14 | "https://*.openai.com/" 15 | ], 16 | "permissions": [ 17 | "storage", 18 | "activeTab", 19 | "tabs" 20 | ], 21 | "background": { 22 | "scripts": [ 23 | "background.js" 24 | ] 25 | }, 26 | "action": {}, 27 | "options_ui": { 28 | "page": "options.html", 29 | "open_in_tab": true 30 | }, 31 | "content_scripts": [ 32 | { 33 | "matches": [ 34 | "", 35 | "*://*/*" 36 | ], 37 | "js": [ 38 | "content-script.js" 39 | ], 40 | "css": [ 41 | "content-script.css" 42 | ] 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /safari/Glarity/iOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by Pell Wong on 2023/3/17. 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/Glarity/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/Glarity/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/Glarity/iOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SFSafariWebExtensionConverterVersion 6 | 14.2 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/Glarity/iOS (App)/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by Pell Wong on 2023/3/17. 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/Glarity/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/Glarity/macOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS (App) 4 | // 5 | // Created by Pell Wong on 2023/3/17. 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/Glarity/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/Glarity/macOS (App)/Glarity.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/Glarity/macOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SFSafariWebExtensionConverterVersion 6 | 14.2 7 | 8 | 9 | -------------------------------------------------------------------------------- /safari/Glarity/macOS (Extension)/Glarity.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 | -------------------------------------------------------------------------------- /safari/Glarity/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 | -------------------------------------------------------------------------------- /screenshots/PMC-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/PMC-cn.png -------------------------------------------------------------------------------- /screenshots/PMC-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/PMC-en.png -------------------------------------------------------------------------------- /screenshots/bilibili-cn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/bilibili-cn.webp -------------------------------------------------------------------------------- /screenshots/bing-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/bing-cn.png -------------------------------------------------------------------------------- /screenshots/bing-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/bing-en.png -------------------------------------------------------------------------------- /screenshots/brave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/brave.png -------------------------------------------------------------------------------- /screenshots/extension-google-zh-CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/extension-google-zh-CN.png -------------------------------------------------------------------------------- /screenshots/extension-google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/extension-google.png -------------------------------------------------------------------------------- /screenshots/extension-youtube-zh-CN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/extension-youtube-zh-CN.jpeg -------------------------------------------------------------------------------- /screenshots/extension-youtube.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/extension-youtube.jpeg -------------------------------------------------------------------------------- /screenshots/github-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/github-cn.png -------------------------------------------------------------------------------- /screenshots/github-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/github-en.png -------------------------------------------------------------------------------- /screenshots/google-patents-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/google-patents-en.png -------------------------------------------------------------------------------- /screenshots/google-patents-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/google-patents-zh.png -------------------------------------------------------------------------------- /screenshots/google-vs-chatgpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/google-vs-chatgpt.png -------------------------------------------------------------------------------- /screenshots/iOS-Safari-en.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/iOS-Safari-en.webp -------------------------------------------------------------------------------- /screenshots/macOS-Safari-en.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/macOS-Safari-en.webp -------------------------------------------------------------------------------- /screenshots/newspicks-jp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/newspicks-jp.jpg -------------------------------------------------------------------------------- /screenshots/newspicks-zh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/newspicks-zh.jpg -------------------------------------------------------------------------------- /screenshots/nikkei-jp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/nikkei-jp.png -------------------------------------------------------------------------------- /screenshots/pubmed-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/pubmed-en.png -------------------------------------------------------------------------------- /screenshots/pubmed-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/pubmed-zh.png -------------------------------------------------------------------------------- /screenshots/pubmed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/pubmed.jpg -------------------------------------------------------------------------------- /screenshots/yahoo-japan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/yahoo-japan.jpg -------------------------------------------------------------------------------- /screenshots/yahoo-news-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/yahoo-news-en.png -------------------------------------------------------------------------------- /screenshots/yahoo-news-zh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/yahoo-news-zh.jpg -------------------------------------------------------------------------------- /screenshots/youtube-clickbait-high-cn.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/youtube-clickbait-high-cn.jpeg -------------------------------------------------------------------------------- /screenshots/youtube-clickbait-high-en.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/youtube-clickbait-high-en.jpeg -------------------------------------------------------------------------------- /screenshots/youtube-clickbait-low-en.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/screenshots/youtube-clickbait-low-en.jpeg -------------------------------------------------------------------------------- /src/_locales/am/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-ամփոփում Google/YouTube-ի համար (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Ցուցադրեք ChatGPT ամփոփագրերը Google-ի որոնման արդյունքների և YouTube տեսանյութերի հետ մեկտեղ" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ar/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google / YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "اعرض ملخصات ChatGPT بجانب نتائج بحث Google ومقاطع فيديو YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/bg/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Резюме за Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Показвайте резюмета на ChatGPT заедно с резултатите от търсенето с Google и видеоклиповете в YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/bn/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-গুগল/ইউটিউবের জন্য সারাংশ (চ্যাটজিপিটি)" 4 | }, 5 | "appDesc": { 6 | "message": "Google অনুসন্ধান ফলাফল এবং YouTube ভিডিওর পাশাপাশি ChatGPT সারাংশ প্রদর্শন করুন" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ca/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Resum per a Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Mostra resums de ChatGPT juntament amb els resultats de la cerca de Google i els vídeos de YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/cs/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary pro Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Zobrazte souhrny ChatGPT vedle výsledků vyhledávání Google a videí na YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/da/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Vis ChatGPT-oversigter sammen med Google-søgeresultater og YouTube-videoer" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Zusammenfassung für Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Zeigen Sie ChatGPT-Zusammenfassungen neben Google-Suchergebnissen und YouTube-Videos an" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/el/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Προβάλετε τις περιλήψεις ChatGPT μαζί με τα αποτελέσματα αναζήτησης Google και τα βίντεο YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Display ChatGPT summaries alongside Google search results and YouTube videos" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/en_GB/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Display ChatGPT summaries alongside Google search results and YouTube videos" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/en_US/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Display ChatGPT summaries alongside Google search results and YouTube videos" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Mostrar resúmenes de ChatGPT junto a los resultados de búsqueda de Google y los vídeos de YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/es_419/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Προβάλετε τις περιλήψεις ChatGPT μαζί με τα αποτελέσματα αναζήτησης Google και τα βίντεο YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/et/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-kokkuvõte Google'i/YouTube'i jaoks (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Kuvage Google'i otsingutulemuste ja YouTube'i videote kõrval ChatGPT kokkuvõtteid" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/fa/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "خلاصه‌های ChatGPT را در کنار نتایج جستجوی Google و ویدیوهای YouTube نمایش دهید" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/fi/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-yhteenveto Googlelle/YouTubelle (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Näytä ChatGPT-yhteenvedot Googlen hakutulosten ja YouTube-videoiden rinnalla" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/fil/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary para sa Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Ipakita ang mga buod ng ChatGPT kasama ng mga resulta ng paghahanap sa Google at mga video sa YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Afficher les résumés de ChatGPT à côté des résultats de recherche Google et des vidéos YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/gu/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Google/YouTube (ChatGPT) માટે ગ્લેરિટી-સારાંશ" 4 | }, 5 | "appDesc": { 6 | "message": "Google શોધ પરિણામો અને YouTube વિડિઓની સાથે ChatGPT સારાંશ પ્રદર્શિત કરો" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/he/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary עבור Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "הצג סיכומים של ChatGPT לצד תוצאות החיפוש של Google וסרטוני YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/hi/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Google/YouTube के लिए ग्लैरिटी-सारांश (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Google खोज परिणामों और YouTube वीडियो के साथ चैटजीपीटी सारांश प्रदर्शित करें" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/hr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-sažetak za Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Prikažite ChatGPT sažetke uz rezultate Google pretraživanja i YouTube videozapise" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/hu/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Jelenítse meg a ChatGPT-összefoglalókat a Google keresési eredményei és a YouTube-videók mellett" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/id/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Ringkasan untuk Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Tampilkan ringkasan ChatGPT bersama hasil penelusuran Google dan video YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Riepilogo per Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Visualizza i riepiloghi di ChatGPT accanto ai risultati di ricerca di Google e ai video di YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/iw/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary עבור Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "הצג סיכומים של ChatGPT לצד תוצאות החיפוש של Google וסרטוני YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Google検索結果やYouTube動画と一緒にChatGPTの要約を表示" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Google 검색 결과 및 YouTube 동영상과 함께 ChatGPT 요약 표시" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/lv/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-kopsavilkums Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Parādiet ChatGPT kopsavilkumus kopā ar Google meklēšanas rezultātiem un YouTube videoklipiem" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/mr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Google/YouTube (ChatGPT) साठी सारांश" 4 | }, 5 | "appDesc": { 6 | "message": "Google शोध परिणाम आणि YouTube व्हिडिओंसोबत ChatGPT सारांश प्रदर्शित करा" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ms/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Ringkasan untuk Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Paparkan ringkasan ChatGPT bersama hasil carian Google dan video YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Samenvatting voor Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Toon ChatGPT-samenvattingen naast Google-zoekresultaten en YouTube-video's" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/no/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Sammendrag for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Vis ChatGPT-sammendrag sammen med Googles søkeresultater og YouTube-videoer" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Podsumowanie dla Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Wyświetlaj podsumowania ChatGPT obok wyników wyszukiwania Google i filmów z YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Resumo para Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Exibir resumos do ChatGPT ao lado dos resultados de pesquisa do Google e vídeos do YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/pt_PT/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Resumo para Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Exibir resumos do ChatGPT ao lado dos resultados de pesquisa do Google e vídeos do YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ro/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Afișați rezumate ChatGPT alături de rezultatele căutării Google și videoclipurile YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Отображать сводки ChatGPT вместе с результатами поиска Google и видео на YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/sk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- Súhrn pre Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Zobrazte súhrny ChatGPT vedľa výsledkov vyhľadávania Google a videí YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/sl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- Povzetek za Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Prikažite povzetke ChatGPT poleg Googlovih rezultatov iskanja in videoposnetkov YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/sr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- резиме за Гоогле/ИоуТубе (ЦхатГПТ)" 4 | }, 5 | "appDesc": { 6 | "message": "Прикажите ЦхатГПТ резимее поред резултата Гоогле претраге и ИоуТубе видео снимака" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/sv/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- Sammanfattning för Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Visa ChatGPT-sammanfattningar tillsammans med Googles sökresultat och YouTube-videor" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/sw/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- Muhtasari wa Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Onyesha muhtasari wa ChatGPT pamoja na matokeo ya utafutaji wa Google na video za YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/te/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- Google/YouTube க்கான சுருக்கம் (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "கூகுள் தேடல் முடிவுகள் மற்றும் யூடியூப் வீடியோக்களுடன் ChatGPT சுருக்கங்களை காட்சிப்படுத்தவும்" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/th/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- ข้อมูลสรุปสำหรับ Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "แสดงสรุป ChatGPT ควบคู่ไปกับผลการค้นหาของ Google และวิดีโอ YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/tr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity- Google/YouTube için Özet (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Google arama sonuçlarının ve YouTube videolarının yanında ChatGPT özetlerini görüntüleyin" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/uk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Summary for Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Відображати підсумки ChatGPT разом із результатами пошуку Google і відео YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/vi/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-Tóm tắt cho Google/YouTube (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Hiển thị tóm tắt ChatGPT cùng với kết quả tìm kiếm của Google và video trên YouTube" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-自动为谷歌搜索结果及YouTube视频生成摘要 (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Google 搜索结果和 YouTube 视频旁边展示 ChatGPT 摘要(支持自动翻译成中文)" 7 | } 8 | } -------------------------------------------------------------------------------- /src/_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Glarity-自動為谷歌搜索結果及YouTube視頻生成摘要 (ChatGPT)" 4 | }, 5 | "appDesc": { 6 | "message": "Google 搜索結果和 YouTube 視頻旁邊展示 ChatGPT 摘要(支持自動翻譯)" 7 | } 8 | } -------------------------------------------------------------------------------- /src/assets/img/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/src/assets/img/logo-128.png -------------------------------------------------------------------------------- /src/assets/img/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/src/assets/img/logo-16.png -------------------------------------------------------------------------------- /src/assets/img/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/src/assets/img/logo-32.png -------------------------------------------------------------------------------- /src/assets/img/logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/src/assets/img/logo-48.png -------------------------------------------------------------------------------- /src/assets/img/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/src/assets/img/logo-white.png -------------------------------------------------------------------------------- /src/assets/img/logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparticleinc/chatgpt-google-summary-extension/bff54a73d98d082067feedffa3be9ad91c96c6c7/src/assets/img/logo.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/fetch-sse.ts: -------------------------------------------------------------------------------- 1 | import { createParser } from 'eventsource-parser' 2 | import { isEmpty } from 'lodash-es' 3 | import { streamAsyncIterable } from './stream-async-iterable.js' 4 | 5 | export async function fetchSSE( 6 | resource: string, 7 | options: RequestInit & { onMessage: (message: string) => void }, 8 | ) { 9 | const { onMessage, ...fetchOptions } = options 10 | const resp = await fetch(resource, fetchOptions) 11 | if (!resp.ok) { 12 | const error = await resp.json().catch(() => ({})) 13 | throw new Error(!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`) 14 | } 15 | const parser = createParser((event) => { 16 | if (event.type === 'event') { 17 | onMessage(event.data) 18 | } 19 | }) 20 | for await (const chunk of streamAsyncIterable(resp.body!)) { 21 | const str = new TextDecoder().decode(chunk) 22 | parser.feed(str) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/background/index.ts: -------------------------------------------------------------------------------- 1 | import Browser from 'webextension-polyfill' 2 | import { getProviderConfigs, ProviderType, BASE_URL } from '@/config' 3 | import { ChatGPTProvider, getChatGPTAccessToken, sendMessageFeedback } from './providers/chatgpt' 4 | import { OpenAIProvider } from './providers/openai' 5 | import { Provider } from './types' 6 | import { isFirefox, tabSendMsg } from '@/utils/utils' 7 | 8 | async function generateAnswers(port: Browser.Runtime.Port, question: string) { 9 | const providerConfigs = await getProviderConfigs() 10 | 11 | let provider: Provider 12 | if (providerConfigs.provider === ProviderType.ChatGPT) { 13 | const token = await getChatGPTAccessToken() 14 | provider = new ChatGPTProvider(token) 15 | } else if (providerConfigs.provider === ProviderType.GPT3) { 16 | const { apiKey, model } = providerConfigs.configs[ProviderType.GPT3]! 17 | provider = new OpenAIProvider(apiKey, model) 18 | } else { 19 | throw new Error(`Unknown provider ${providerConfigs.provider}`) 20 | } 21 | 22 | const controller = new AbortController() 23 | port.onDisconnect.addListener(() => { 24 | controller.abort() 25 | cleanup?.() 26 | }) 27 | 28 | const { cleanup } = await provider.generateAnswer({ 29 | prompt: question, 30 | signal: controller.signal, 31 | onEvent(event) { 32 | if (event.type === 'done') { 33 | port.postMessage({ event: 'DONE' }) 34 | return 35 | } 36 | port.postMessage(event.data) 37 | }, 38 | }) 39 | } 40 | 41 | async function createTab(url) { 42 | Browser.tabs.query({ currentWindow: true, active: true }).then((tabs) => { 43 | console.log('getCurrent', tabs) 44 | const tab = tabs[0] 45 | 46 | if (tab.id) { 47 | Browser.storage.local.set({ glarityTabId: tab.id }) 48 | } 49 | }) 50 | 51 | const oldTabId = await Browser.storage.local.get('pinnedTabId') 52 | let tab 53 | if (oldTabId.pinnedTabId) { 54 | try { 55 | tab = await Browser.tabs.get(oldTabId.pinnedTabId) 56 | Browser.tabs.update(tab.id, { active: true, pinned: true }) 57 | } catch (error) { 58 | console.error(error) 59 | } 60 | } 61 | if (!tab) { 62 | tab = await Browser.tabs.create({ 63 | url, 64 | pinned: true, 65 | active: true, 66 | }) 67 | } 68 | Browser.storage.local.set({ pinnedTabId: tab.id }) 69 | return { pinnedTabId: tab.id } 70 | } 71 | 72 | Browser.runtime.onConnect.addListener(async (port) => { 73 | port.onMessage.addListener(async (msg) => { 74 | console.debug('received msg', msg) 75 | try { 76 | await generateAnswers(port, msg.question) 77 | } catch (err: any) { 78 | // console.error(err) 79 | port.postMessage({ error: err.message }) 80 | } 81 | }) 82 | }) 83 | 84 | Browser.runtime.onMessage.addListener(async (message) => { 85 | if (message.type === 'FEEDBACK') { 86 | const token = await getChatGPTAccessToken() 87 | await sendMessageFeedback(token, message.data) 88 | } else if (message.type === 'OPEN_OPTIONS_PAGE') { 89 | Browser.runtime.openOptionsPage() 90 | } else if (message.type === 'GET_ACCESS_TOKEN') { 91 | return getChatGPTAccessToken() 92 | } else if (message.type === 'NEW_TAB') { 93 | return createTab(message.data.url) 94 | } else if (message.type === 'GO_BACK') { 95 | const tab = await Browser.storage.local.get('glarityTabId') 96 | 97 | if (tab.glarityTabId) { 98 | Browser.tabs.update(tab.glarityTabId, { active: true }).catch(() => { 99 | Browser.tabs.create({ url: 'about:newtab', active: true }) 100 | }) 101 | } else { 102 | Browser.tabs.create({ url: 'about:newtab', active: true }) 103 | } 104 | } 105 | }) 106 | 107 | Browser.runtime.onInstalled.addListener(async (details) => { 108 | if (details.reason === 'install') { 109 | Browser.runtime.openOptionsPage() 110 | } 111 | }) 112 | 113 | Browser.tabs.onUpdated.addListener(async (tabId, changeInfo) => { 114 | const oldTabId = await Browser.storage.local.get('pinnedTabId') 115 | 116 | Browser.tabs.get(tabId).then((tab) => { 117 | console.log('tabId', tabId, tab, changeInfo) 118 | 119 | // Browser.tabs.query({}).then((tabs) => { 120 | // tabs.forEach((tab) => { 121 | // if ( 122 | // changeInfo.status === 'complete' && 123 | // tab.id && 124 | // tab.id && 125 | // oldTabId.pinnedTabId === tab.id 126 | // ) { 127 | // Browser.runtime.sendMessage(tab.id, { type: 'CHATGPT_TAB_CURRENT_' }).catch(() => {}) 128 | // } 129 | // }) 130 | // }) 131 | 132 | if ( 133 | tab.url?.includes(BASE_URL) && 134 | changeInfo.status === 'complete' && 135 | tab.id && 136 | oldTabId.pinnedTabId === tab.id 137 | ) { 138 | console.log('onUpdated', oldTabId, tab) 139 | tabSendMsg(tab) 140 | } 141 | }) 142 | }) 143 | 144 | async function openPageSummary(tab) { 145 | const { id } = tab 146 | 147 | if (!id) { 148 | return 149 | } 150 | 151 | Browser.tabs.sendMessage(id, { type: 'OPEN_WEB_SUMMARY', data: {} }).catch(() => {}) 152 | } 153 | 154 | if (isFirefox) { 155 | Browser.browserAction.onClicked.addListener(async (tab) => { 156 | await openPageSummary(tab) 157 | }) 158 | } else { 159 | Browser.action.onClicked.addListener(async (tab) => { 160 | await openPageSummary(tab) 161 | }) 162 | } 163 | -------------------------------------------------------------------------------- /src/background/providers/chatgpt.ts: -------------------------------------------------------------------------------- 1 | import ExpiryMap from 'expiry-map' 2 | import { v4 as uuidv4 } from 'uuid' 3 | import { fetchSSE } from '../fetch-sse' 4 | import { GenerateAnswerParams, Provider } from '../types' 5 | import { BASE_URL } from '@/config' 6 | 7 | async function request(token: string, method: string, path: string, data?: unknown) { 8 | return fetch(`${BASE_URL}/backend-api${path}`, { 9 | method, 10 | headers: { 11 | 'Content-Type': 'application/json', 12 | Authorization: `Bearer ${token}`, 13 | }, 14 | body: data === undefined ? undefined : JSON.stringify(data), 15 | }) 16 | } 17 | 18 | export async function sendMessageFeedback(token: string, data: unknown) { 19 | await request(token, 'POST', '/conversation/message_feedback', data) 20 | } 21 | 22 | export async function setConversationProperty( 23 | token: string, 24 | conversationId: string, 25 | propertyObject: object, 26 | ) { 27 | await request(token, 'PATCH', `/conversation/${conversationId}`, propertyObject) 28 | } 29 | 30 | const KEY_ACCESS_TOKEN = 'accessToken' 31 | 32 | const cache = new ExpiryMap(10 * 1000) 33 | 34 | export async function getChatGPTAccessToken(): Promise { 35 | if (cache.get(KEY_ACCESS_TOKEN)) { 36 | return cache.get(KEY_ACCESS_TOKEN) 37 | } 38 | const resp = await fetch(`${BASE_URL}/api/auth/session`) 39 | if (resp.status === 403) { 40 | throw new Error('CLOUDFLARE') 41 | } 42 | const data = await resp.json().catch(() => ({})) 43 | if (!data.accessToken) { 44 | throw new Error('UNAUTHORIZED') 45 | } 46 | cache.set(KEY_ACCESS_TOKEN, data.accessToken) 47 | return data.accessToken 48 | } 49 | 50 | export class ChatGPTProvider implements Provider { 51 | constructor(private token: string) { 52 | this.token = token 53 | } 54 | 55 | private async fetchModels(): Promise< 56 | { slug: string; title: string; description: string; max_tokens: number }[] 57 | > { 58 | const resp = await request(this.token, 'GET', '/models').then((r) => r.json()) 59 | return resp.models 60 | } 61 | 62 | private async getModelName(): Promise { 63 | try { 64 | const models = await this.fetchModels() 65 | return models[0].slug 66 | } catch (err) { 67 | console.error(err) 68 | return 'text-davinci-002-render-sha' 69 | } 70 | } 71 | 72 | async generateAnswer(params: GenerateAnswerParams) { 73 | let conversationId: string | undefined 74 | 75 | const cleanup = () => { 76 | if (conversationId) { 77 | setConversationProperty(this.token, conversationId, { is_visible: true }) 78 | } 79 | } 80 | 81 | const modelName = await this.getModelName() 82 | 83 | await fetchSSE(`${BASE_URL}/backend-api/conversation`, { 84 | method: 'POST', 85 | signal: params.signal, 86 | headers: { 87 | 'Content-Type': 'application/json', 88 | Authorization: `Bearer ${this.token}`, 89 | }, 90 | body: JSON.stringify({ 91 | action: 'next', 92 | messages: [ 93 | { 94 | id: uuidv4(), 95 | role: 'user', 96 | content: { 97 | content_type: 'text', 98 | parts: [params.prompt], 99 | }, 100 | }, 101 | ], 102 | model: modelName, 103 | parent_message_id: uuidv4(), 104 | }), 105 | onMessage(message: string) { 106 | console.debug('sse message', message) 107 | if (message === '[DONE]') { 108 | params.onEvent({ type: 'done' }) 109 | cleanup() 110 | return 111 | } 112 | let data 113 | try { 114 | data = JSON.parse(message) 115 | } catch (err) { 116 | console.error(err) 117 | return 118 | } 119 | const text = data.message?.content?.parts?.[0] 120 | if (text) { 121 | conversationId = data.conversation_id 122 | params.onEvent({ 123 | type: 'answer', 124 | data: { 125 | text, 126 | messageId: data.message.id, 127 | conversationId: data.conversation_id, 128 | }, 129 | }) 130 | } 131 | }, 132 | }) 133 | return { cleanup } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/background/providers/openai.ts: -------------------------------------------------------------------------------- 1 | import { fetchSSE } from '../fetch-sse' 2 | import { GenerateAnswerParams, Provider } from '../types' 3 | import { getProviderConfigs, ProviderType, DEFAULT_MODEL, DEFAULT_API_HOST } from '@/config' 4 | 5 | export class OpenAIProvider implements Provider { 6 | constructor(private token: string, private model: string) { 7 | this.token = token 8 | this.model = model 9 | } 10 | 11 | private buildPrompt(prompt: string): string { 12 | if (this.model.startsWith('text-chat-davinci')) { 13 | return `Respond conversationally.<|im_end|>\n\nUser: ${prompt}<|im_sep|>\nChatGPT:` 14 | } 15 | return prompt 16 | } 17 | 18 | private buildMessages(prompt: string) { 19 | return [{ role: 'user', content: prompt }] 20 | } 21 | 22 | async generateAnswer(params: GenerateAnswerParams) { 23 | const [config] = await Promise.all([getProviderConfigs()]) 24 | 25 | const gptModel = config.configs[ProviderType.GPT3]?.model ?? DEFAULT_MODEL 26 | const apiHost = config.configs[ProviderType.GPT3]?.apiHost || DEFAULT_API_HOST 27 | const apiPath = config.configs[ProviderType.GPT3]?.apiPath 28 | 29 | let url = '' 30 | let reqParams = { 31 | model: this.model, 32 | // prompt: this.buildPrompt(params.prompt), 33 | // messages: this.buildMessages(params.prompt), 34 | stream: true, 35 | max_tokens: 800, 36 | // temperature: 0.5, 37 | } 38 | if (gptModel === 'text-davinci-003') { 39 | url = `https://${apiHost}${apiPath || '/v1/completions'}` 40 | reqParams = { ...reqParams, ...{ prompt: this.buildPrompt(params.prompt) } } 41 | } else { 42 | url = `https://${apiHost}${apiPath || '/v1/chat/completions'}` 43 | reqParams = { ...reqParams, ...{ messages: this.buildMessages(params.prompt) } } 44 | } 45 | 46 | let result = '' 47 | await fetchSSE(url, { 48 | method: 'POST', 49 | signal: params.signal, 50 | headers: { 51 | 'Content-Type': 'application/json', 52 | Authorization: `Bearer ${this.token}`, 53 | }, 54 | body: JSON.stringify(reqParams), 55 | onMessage(message) { 56 | console.debug('sse message', message) 57 | if (message === '[DONE]') { 58 | params.onEvent({ type: 'done' }) 59 | return 60 | } 61 | let data 62 | try { 63 | data = JSON.parse(message) 64 | const text = 65 | gptModel === 'text-davinci-003' ? data.choices[0].text : data.choices[0].delta.content 66 | 67 | if (text === undefined || text === '<|im_end|>' || text === '<|im_sep|>') { 68 | return 69 | } 70 | result += text 71 | params.onEvent({ 72 | type: 'answer', 73 | data: { 74 | text: result, 75 | messageId: data.id, 76 | conversationId: data.id, 77 | }, 78 | }) 79 | } catch (err) { 80 | // console.error(err) 81 | return 82 | } 83 | }, 84 | }) 85 | return {} 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/background/stream-async-iterable.ts: -------------------------------------------------------------------------------- 1 | export async function* streamAsyncIterable(stream: ReadableStream) { 2 | const reader = stream.getReader() 3 | try { 4 | while (true) { 5 | const { done, value } = await reader.read() 6 | if (done) { 7 | return 8 | } 9 | yield value 10 | } 11 | } finally { 12 | reader.releaseLock() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/background/types.ts: -------------------------------------------------------------------------------- 1 | import { Answer } from '../messaging' 2 | 3 | export type Event = 4 | | { 5 | type: 'answer' 6 | data: Answer 7 | } 8 | | { 9 | type: 'done' 10 | } 11 | 12 | export interface GenerateAnswerParams { 13 | prompt: string 14 | onEvent: (event: Event) => void 15 | signal?: AbortSignal 16 | } 17 | 18 | export interface Provider { 19 | generateAnswer(params: GenerateAnswerParams): Promise<{ cleanup?: () => void }> 20 | } 21 | -------------------------------------------------------------------------------- /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 | apiPath: string | undefined 88 | } 89 | 90 | export interface ProviderConfigs { 91 | provider: ProviderType 92 | configs: { 93 | [ProviderType.GPT3]: GPT3ProviderConfig | undefined 94 | } 95 | } 96 | 97 | export async function getProviderConfigs(): Promise { 98 | const { provider = ProviderType.ChatGPT } = await Browser.storage.local.get('provider') 99 | const configKey = `provider:${ProviderType.GPT3}` 100 | const result = await Browser.storage.local.get(configKey) 101 | const configKeys = result[configKey]?.apiKey?.split(',').map(v => v.trim()) ?? [] 102 | const randomIndex = configKeys.length > 0 ? Math.floor(Math.random() * configKeys.length) : 0; 103 | const apiKey = configKeys[randomIndex] ?? '' 104 | result[configKey].apiKey = apiKey 105 | if (!result[configKey]) { 106 | result[configKey] = {} 107 | } 108 | result[configKey].apiKey = apiKey 109 | 110 | return { 111 | provider, 112 | configs: { 113 | [ProviderType.GPT3]: result[configKey], 114 | }, 115 | } 116 | } 117 | 118 | export async function saveProviderConfigs( 119 | provider: ProviderType, 120 | configs: ProviderConfigs['configs'], 121 | ) { 122 | return Browser.storage.local.set({ 123 | provider, 124 | [`provider:${ProviderType.GPT3}`]: configs[ProviderType.GPT3], 125 | }) 126 | } 127 | 128 | export const BASE_URL = 'https://chat.openai.com' 129 | 130 | export const DEFAULT_PAGE_SUMMARY_BLACKLIST = `https://translate.google.com 131 | https://www.deepl.com 132 | https://www.youtube.com 133 | https://youku.com 134 | https://v.qq.com 135 | https://www.iqiyi.com 136 | https://www.bilibili.com 137 | https://www.tudou.com 138 | https://www.tiktok.com 139 | https://vimeo.com 140 | https://www.dailymotion.com 141 | https://www.twitch.tv 142 | https://www.hulu.com 143 | https://www.netflix.com 144 | https://www.hbomax.com 145 | https://www.disneyplus.com 146 | https://www.peacocktv.com 147 | https://www.crunchyroll.com 148 | https://www.funimation.com 149 | https://www.viki.com 150 | https://map.baidu.com 151 | ` 152 | export const APP_TITLE = `Glarity Summary` 153 | 154 | export const DEFAULT_MODEL = 'gpt-3.5-turbo' 155 | export const DEFAULT_API_HOST = 'api.openai.com' 156 | -------------------------------------------------------------------------------- /src/content-script/compenents/ChatGPTCard.tsx: -------------------------------------------------------------------------------- 1 | import { LightBulbIcon, SearchIcon } from '@primer/octicons-react' 2 | import { useState } from 'preact/hooks' 3 | import { TriggerMode } from '@/config' 4 | import ChatGPTQuery, { QueryStatus } from './ChatGPTQuery' 5 | import { endsWithQuestionMark } from '@/content-script/utils' 6 | 7 | interface Props { 8 | question: string 9 | triggerMode: TriggerMode 10 | onStatusChange?: (status: QueryStatus) => void 11 | currentTime?: number 12 | } 13 | 14 | function ChatGPTCard(props: Props) { 15 | const { triggerMode, question, onStatusChange, currentTime: propCurrentTime } = props 16 | 17 | const [triggered, setTriggered] = useState(false) 18 | 19 | if (triggerMode === TriggerMode.Always || propCurrentTime) { 20 | return ( 21 | 26 | ) 27 | } 28 | if (triggerMode === TriggerMode.QuestionMark) { 29 | if (endsWithQuestionMark(question.trim())) { 30 | return 31 | } 32 | return ( 33 |

34 | Trigger ChatGPT by appending a question mark after your query 35 |

36 | ) 37 | } 38 | if (triggered) { 39 | return ( 40 | <> 41 | 46 | 47 | ) 48 | } 49 | return ( 50 | { 53 | setTriggered(true) 54 | }} 55 | > 56 | Ask ChatGPT to summarize 57 | 58 | ) 59 | } 60 | 61 | export default ChatGPTCard 62 | -------------------------------------------------------------------------------- /src/content-script/compenents/ChatGPTFeedback.tsx: -------------------------------------------------------------------------------- 1 | import { ThumbsdownIcon, ThumbsupIcon, CopyIcon, CheckIcon } from '@primer/octicons-react' 2 | import { memo } from 'react' 3 | import { useEffect, useCallback, useState } from 'preact/hooks' 4 | import Browser from 'webextension-polyfill' 5 | 6 | interface Props { 7 | messageId: string 8 | conversationId: string 9 | answerText: string 10 | } 11 | 12 | function ChatGPTFeedback(props: Props) { 13 | const [copied, setCopied] = useState(false) 14 | const [action, setAction] = useState<'thumbsUp' | 'thumbsDown' | null>(null) 15 | 16 | const clickThumbsUp = useCallback(async () => { 17 | if (action) { 18 | return 19 | } 20 | setAction('thumbsUp') 21 | await Browser.runtime.sendMessage({ 22 | type: 'FEEDBACK', 23 | data: { 24 | conversation_id: props.conversationId, 25 | message_id: props.messageId, 26 | rating: 'thumbsUp', 27 | }, 28 | }) 29 | }, [action, props.conversationId, props.messageId]) 30 | 31 | const clickThumbsDown = useCallback(async () => { 32 | if (action) { 33 | return 34 | } 35 | setAction('thumbsDown') 36 | await Browser.runtime.sendMessage({ 37 | type: 'FEEDBACK', 38 | data: { 39 | conversation_id: props.conversationId, 40 | message_id: props.messageId, 41 | rating: 'thumbsDown', 42 | text: '', 43 | tags: [], 44 | }, 45 | }) 46 | }, [action, props.conversationId, props.messageId]) 47 | 48 | const clickCopyToClipboard = useCallback(async () => { 49 | await navigator.clipboard.writeText(props.answerText) 50 | setCopied(true) 51 | }, [props.answerText]) 52 | 53 | useEffect(() => { 54 | if (copied) { 55 | const timer = setTimeout(() => { 56 | setCopied(false) 57 | }, 500) 58 | return () => clearTimeout(timer) 59 | } 60 | }, [copied]) 61 | 62 | return ( 63 |
64 | 68 | 69 | 70 | 74 | 75 | 76 | 77 | {copied ? : } 78 | 79 |
80 | ) 81 | } 82 | 83 | export default memo(ChatGPTFeedback) 84 | -------------------------------------------------------------------------------- /src/content-script/compenents/ChatGPTQuery.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useCallback, useRef } from 'preact/hooks' 2 | import classNames from 'classnames' 3 | import { memo, useMemo } from 'react' 4 | import { Loading } from '@geist-ui/core' 5 | import ReactMarkdown from 'react-markdown' 6 | import rehypeHighlight from 'rehype-highlight' 7 | import Browser from 'webextension-polyfill' 8 | import { Answer } from '@/messaging' 9 | import ChatGPTFeedback from './ChatGPTFeedback' 10 | import { debounce } from 'lodash-es' 11 | import { isBraveBrowser, shouldShowRatingTip } from '@/content-script/utils' 12 | import { BASE_URL } from '@/config' 13 | import { isIOS, isSafari } from '@/utils/utils' 14 | import { getUserConfig } from '@/config' 15 | 16 | import '@/content-script/styles.scss' 17 | 18 | export type QueryStatus = 'success' | 'error' | 'done' | undefined 19 | 20 | interface Props { 21 | question: string 22 | onStatusChange?: (status: QueryStatus) => void 23 | currentTime?: number 24 | } 25 | 26 | function ChatGPTQuery(props: Props) { 27 | const { onStatusChange, currentTime, question } = props 28 | 29 | const [answer, setAnswer] = useState(null) 30 | const [error, setError] = useState('') 31 | const [retry, setRetry] = useState(0) 32 | const [done, setDone] = useState(false) 33 | const [showTip, setShowTip] = useState(false) 34 | const [continueConversation, setContinueConversation] = useState(false) 35 | const [status, setStatus] = useState() 36 | const wrapRef = useRef(null) 37 | 38 | const requestGpt = useMemo(() => { 39 | console.log('question', question) 40 | 41 | return debounce(() => { 42 | setStatus(undefined) 43 | // setError('error') 44 | // setStatus('error') 45 | // return 46 | 47 | const port = Browser.runtime.connect() 48 | const listener = (msg: any) => { 49 | if (msg.text) { 50 | let text = msg.text || '' 51 | text = text.replace(/^(\s|:\n\n)+|(:)+|(:\s)$/g, '') 52 | 53 | setAnswer({ ...msg, ...{ text } }) 54 | setStatus('success') 55 | } else if (msg.error) { 56 | setError(msg.error) 57 | setStatus('error') 58 | } else if (msg.event === 'DONE') { 59 | setDone(true) 60 | setStatus('done') 61 | } 62 | } 63 | port.onMessage.addListener(listener) 64 | port.postMessage({ question }) 65 | return () => { 66 | port.onMessage.removeListener(listener) 67 | port.disconnect() 68 | } 69 | }, 1000) 70 | }, [question]) 71 | 72 | const newTab = useCallback(() => { 73 | Browser.runtime.sendMessage({ 74 | type: 'NEW_TAB', 75 | data: { 76 | url: `${BASE_URL}/chat`, 77 | }, 78 | }) 79 | }, []) 80 | 81 | const openOptionsPage = useCallback(() => { 82 | Browser.runtime.sendMessage({ type: 'OPEN_OPTIONS_PAGE' }) 83 | }, []) 84 | 85 | useEffect(() => { 86 | onStatusChange?.(status) 87 | }, [onStatusChange, status]) 88 | 89 | useEffect(() => { 90 | requestGpt() 91 | }, [question, retry, currentTime, requestGpt]) 92 | 93 | // retry error on focus 94 | useEffect(() => { 95 | const onFocus = () => { 96 | if (error && (error == 'UNAUTHORIZED' || error === 'CLOUDFLARE')) { 97 | setError('') 98 | setRetry((r) => r + 1) 99 | } 100 | } 101 | window.addEventListener('focus', onFocus) 102 | return () => { 103 | window.removeEventListener('focus', onFocus) 104 | } 105 | }, [error]) 106 | 107 | useEffect(() => { 108 | shouldShowRatingTip().then((show) => setShowTip(show)) 109 | }, []) 110 | 111 | useEffect(() => { 112 | getUserConfig().then((settings) => setContinueConversation(settings.continueConversation)) 113 | }, []) 114 | 115 | useEffect(() => { 116 | const wrap: HTMLDivElement | null = wrapRef.current 117 | if (!wrap) { 118 | return 119 | } 120 | 121 | if (answer) { 122 | wrap.scrollTo({ 123 | top: 10000, 124 | behavior: 'smooth', 125 | }) 126 | } 127 | }, [answer]) 128 | 129 | if (answer) { 130 | return ( 131 |
132 |
133 | 138 |
139 |
140 | 141 | {answer.text} 142 | 143 |
144 | {(continueConversation && answer.conversationId && done) && ( 145 | 150 | )} 151 | 152 | {/* {done && showTip && ( 153 |

154 | Enjoy this extension? Give us a 5-star rating at{' '} 155 | 160 | Chrome Web Store 161 | 162 |

163 | )} */} 164 |
165 | ) 166 | } 167 | 168 | if (error === 'UNAUTHORIZED' || error === 'CLOUDFLARE') { 169 | return ( 170 |

171 | {isSafari ? ( 172 | <> 173 | Please set OpenAI API Key in the{' '} 174 | 180 | . 181 | 182 | ) : ( 183 | <> 184 | {' '} 185 | Please login and pass Cloudflare check at{' '} 186 | 192 | . 193 | 194 | )} 195 | 196 | {retry > 0 && 197 | !isIOS && 198 | (() => { 199 | if (isBraveBrowser()) { 200 | return ( 201 | 202 | Still not working? Follow{' '} 203 | 204 | Brave Troubleshooting 205 | 206 | 207 | ) 208 | } else { 209 | return ( 210 | 211 | OpenAI requires passing a security check every once in a while. If this keeps 212 | happening, change AI provider to OpenAI API in the{' '} 213 | 223 | . 224 | 225 | ) 226 | } 227 | })()} 228 |

229 | ) 230 | } 231 | if (error) { 232 | return ( 233 |

234 | Failed to load response from ChatGPT: 235 | {error} 236 | { 239 | setError('') 240 | setRetry((r) => r + 1) 241 | }} 242 | > 243 | Retry 244 | 245 |
246 | If this keeps happening, change AI provider to OpenAI API in the{' '} 247 | 253 | . 254 |

255 | ) 256 | } 257 | 258 | return 259 | } 260 | 261 | export default memo(ChatGPTQuery) 262 | -------------------------------------------------------------------------------- /src/content-script/compenents/ChatGPTTip.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'preact/hooks' 2 | import { Note, Description, Button, Divider } from '@geist-ui/core' 3 | import { XCircleFillIcon } from '@primer/octicons-react' 4 | import Browser from 'webextension-polyfill' 5 | import { APP_TITLE } from '@/config' 6 | 7 | interface Props { 8 | isLogin: boolean 9 | } 10 | 11 | function ChatGPTTip(props: Props) { 12 | const isLogin = props?.isLogin 13 | const [showTip, setShowTip] = useState(true) 14 | 15 | const onClose = () => { 16 | setShowTip(false) 17 | } 18 | 19 | const onBack = () => { 20 | Browser.runtime.sendMessage({ 21 | type: 'GO_BACK', 22 | }) 23 | } 24 | 25 | return ( 26 | <> 27 | {showTip && ( 28 | <> 29 | 30 | 34 | {isLogin ? ( 35 | <> 36 | 37 | I am Ready!
38 | Keep this tab to make ChatGPT more stable. 39 | 40 | 43 | 44 | ) : ( 45 | <> 46 | 47 | Login to the ChatGPT web application to use the Glarity Summary. 48 | 49 | )} 50 | 51 | } 52 | /> 53 |
54 | 55 | 142 | 143 | 144 |
145 | 151 |
152 | 153 | 154 |
155 | {question ? ( 156 |
157 |
158 | 159 |
160 |
161 | ) : ( 162 |
163 | {!supportSummary ? ( 164 | 'Sorry, the summary of this page is not supported.' 165 | ) : ( 166 | 178 | )} 179 |
180 | )} 181 |
182 | 183 | ) : ( 184 | show && ( 185 | 195 | ) 196 | )} 197 | 198 | ) 199 | } 200 | 201 | export default PageSummary 202 | -------------------------------------------------------------------------------- /src/content-script/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'preact' 2 | import '@/assets/styles/base.scss' 3 | import { getUserConfig } from '@/config' 4 | import ChatGPTTip from '@/content-script/compenents/ChatGPTTip' 5 | import { config } from '@/content-script/search-engine-configs' 6 | import Browser from 'webextension-polyfill' 7 | import PageSummary from '@/content-script/compenents/PageSummary' 8 | import mount from '@/content-script/compenents/Mount' 9 | import getQuestion from './compenents/GetQuestion' 10 | import { siteConfig as sietConfigFn } from './utils' 11 | import '@/content-script/styles.scss' 12 | 13 | const siteConfig = sietConfigFn() 14 | 15 | async function Run() { 16 | const userConfig = await getUserConfig() 17 | const siteRegex = new RegExp( 18 | Object.values(config) 19 | .map((v) => { 20 | return v.regex 21 | }) 22 | .join('|'), 23 | ) 24 | const container = document.createElement('section') 25 | container.className = 'glarity--summary' 26 | document.body.prepend(container) 27 | render( 28 | , 34 | container, 35 | ) 36 | 37 | const questionData = await getQuestion() 38 | if (questionData) { 39 | mount(questionData) 40 | } 41 | 42 | Browser.runtime.onMessage.addListener((message, _, sendResponse) => { 43 | const { type, data } = message 44 | switch (type) { 45 | case 'CHATGPT_TAB_CURRENT': { 46 | const container = document.createElement('section') 47 | container.className = 'glarity--chatgpt--tips' 48 | container.id = 'glarity--chatgpt--tips' 49 | document.body.prepend(container) 50 | render(, container) 51 | break 52 | } 53 | case 'GET_DOM': { 54 | sendResponse({ html: document.querySelector('html')?.outerHTML }) 55 | break 56 | } 57 | } 58 | }) 59 | } 60 | 61 | Run() 62 | 63 | if (siteConfig?.watchRouteChange) { 64 | siteConfig.watchRouteChange(Run) 65 | } 66 | -------------------------------------------------------------------------------- /src/content-script/prompt.ts: -------------------------------------------------------------------------------- 1 | import { ProviderType } from '@/config' 2 | import GPT3Tokenizer from 'gpt3-tokenizer' 3 | const tokenizer = new GPT3Tokenizer({ type: 'gpt3' }) 4 | 5 | export function getSummaryPrompt(transcript = '', providerConfigs?: ProviderType) { 6 | const text = transcript 7 | ? transcript 8 | .replace(/'/g, "'") 9 | .replace(/(\r\n)+/g, '\r\n') 10 | .replace(/(\s{2,})/g, ' ') 11 | .replace(/^(\s)+|(\s)$/g, '') 12 | : '' 13 | 14 | return truncateTranscript(text, providerConfigs) 15 | } 16 | 17 | // Seems like 15,000 bytes is the limit for the prompt 18 | const textLimit = 14000 19 | const limit = 1100 // 1000 is a buffer 20 | const apiLimit = 2000 21 | 22 | export function getChunckedTranscripts(textData, textDataOriginal) { 23 | // [Thought Process] 24 | // (1) If text is longer than limit, then split it into chunks (even numbered chunks) 25 | // (2) Repeat until it's under limit 26 | // (3) Then, try to fill the remaining space with some text 27 | // (eg. 15,000 => 7,500 is too much chuncked, so fill the rest with some text) 28 | 29 | let result = '' 30 | const text = textData 31 | .sort((a, b) => a.index - b.index) 32 | .map((t) => t.text) 33 | .join(' ') 34 | const bytes = textToBinaryString(text).length 35 | 36 | if (bytes > limit) { 37 | // Get only even numbered chunks from textArr 38 | const evenTextData = textData.filter((t, i) => i % 2 === 0) 39 | result = getChunckedTranscripts(evenTextData, textDataOriginal) 40 | } else { 41 | // Check if any array items can be added to result to make it under limit but really close to it 42 | if (textDataOriginal.length !== textData.length) { 43 | textDataOriginal.forEach((obj, i) => { 44 | if (textData.some((t) => t.text === obj.text)) { 45 | return 46 | } 47 | 48 | textData.push(obj) 49 | 50 | const newText = textData 51 | .sort((a, b) => a.index - b.index) 52 | .map((t) => t.text) 53 | .join(' ') 54 | const newBytes = textToBinaryString(newText).length 55 | 56 | if (newBytes < limit) { 57 | const nextText = textDataOriginal[i + 1] 58 | const nextTextBytes = textToBinaryString(nextText.text).length 59 | 60 | if (newBytes + nextTextBytes > limit) { 61 | const overRate = (newBytes + nextTextBytes - limit) / nextTextBytes 62 | const chunkedText = nextText.text.substring( 63 | 0, 64 | Math.floor(nextText.text.length * overRate), 65 | ) 66 | textData.push({ text: chunkedText, index: nextText.index }) 67 | result = textData 68 | .sort((a, b) => a.index - b.index) 69 | .map((t) => t.text) 70 | .join(' ') 71 | } else { 72 | result = newText 73 | } 74 | } 75 | }) 76 | } else { 77 | result = text 78 | } 79 | } 80 | 81 | const originalText = textDataOriginal 82 | .sort((a, b) => a.index - b.index) 83 | .map((t) => t.text) 84 | .join(' ') 85 | return result == '' ? originalText : result // Just in case the result is empty 86 | } 87 | 88 | function truncateTranscript(str, providerConfigs) { 89 | let textStr = str 90 | 91 | const textBytes = textToBinaryString(str).length 92 | if (textBytes > textLimit) { 93 | const ratio = textLimit / textBytes 94 | const newStr = str.substring(0, str.length * ratio) 95 | textStr = newStr 96 | } 97 | 98 | const tokenLimit = providerConfigs === ProviderType.GPT3 ? apiLimit : limit 99 | 100 | // if (providerConfigs === ProviderType.GPT3) { 101 | const encoded: { bpe: number[]; text: string[] } = tokenizer.encode(textStr) 102 | const bytes = encoded.bpe.length 103 | 104 | if (bytes > tokenLimit) { 105 | const ratio = tokenLimit / bytes 106 | const newStr = textStr.substring(0, textStr.length * ratio) 107 | 108 | return newStr 109 | } 110 | 111 | return textStr 112 | // } else { 113 | // const bytes = textToBinaryString(str).length 114 | // if (bytes > tokenLimit) { 115 | // const ratio = tokenLimit / bytes 116 | // const newStr = str.substring(0, str.length * ratio) 117 | // return newStr 118 | // } 119 | // return str 120 | // } 121 | } 122 | 123 | function truncateTranscriptByToken(str, providerConfigs) { 124 | const tokenLimit = providerConfigs === ProviderType.GPT3 ? apiLimit : limit 125 | 126 | // if (providerConfigs === ProviderType.GPT3) { 127 | const encoded: { bpe: number[]; text: string[] } = tokenizer.encode(str) 128 | const bytes = encoded.bpe.length 129 | 130 | if (bytes > tokenLimit) { 131 | const ratio = tokenLimit / bytes 132 | const newStr = str.substring(0, str.length * ratio) 133 | 134 | return newStr 135 | } 136 | 137 | return str 138 | } 139 | 140 | export function textToBinaryString(str) { 141 | const escstr = decodeURIComponent(encodeURIComponent(escape(str))) 142 | const binstr = escstr.replace(/%([0-9A-F]{2})/gi, function (match, hex) { 143 | const i = parseInt(hex, 16) 144 | return String.fromCharCode(i) 145 | }) 146 | return binstr 147 | } 148 | -------------------------------------------------------------------------------- /src/content-script/search-engine-configs.ts: -------------------------------------------------------------------------------- 1 | import { waitForElm } from './utils' 2 | import { queryParam } from 'gb-url' 3 | import { getBiliVideoId } from '../utils/bilibili' 4 | export interface SearchEngine { 5 | inputQuery: string[] 6 | sidebarContainerQuery: string[] 7 | appendContainerQuery: string[] 8 | extabarContainerQuery?: string[] 9 | contentContainerQuery: string[] 10 | watchRouteChange?: (callback: () => void) => void 11 | name?: string 12 | siteName: string 13 | siteValue: string 14 | regex: string 15 | searchRegExp?: string 16 | } 17 | 18 | export const config: Record = { 19 | google: { 20 | inputQuery: ["input[name='q']"], 21 | sidebarContainerQuery: ['#rhs'], 22 | appendContainerQuery: ['#rcnt'], 23 | extabarContainerQuery: ['#extabar'], 24 | contentContainerQuery: [], 25 | name: 'gogole', 26 | siteName: 'Google', 27 | siteValue: 'google', 28 | regex: '(^(www.)?google.)', 29 | }, 30 | bing: { 31 | inputQuery: ["[name='q']"], 32 | sidebarContainerQuery: ['ol#b_context'], 33 | appendContainerQuery: ['#b_content'], 34 | contentContainerQuery: [], 35 | siteName: 'Bing', 36 | siteValue: 'bing', 37 | regex: '(^(www|cn).?bing.com)', 38 | }, 39 | yahoo: { 40 | inputQuery: ["input[name='p']"], 41 | sidebarContainerQuery: ['#right', '.Contents__inner.Contents__inner--sub'], 42 | appendContainerQuery: ['#cols', '#contents__wrap'], 43 | contentContainerQuery: [], 44 | siteName: 'Yahoo!', 45 | siteValue: 'yahoo', 46 | regex: '(^(search.)?yahoo.)', 47 | }, 48 | duckduckgo: { 49 | inputQuery: ["input[name='q']"], 50 | sidebarContainerQuery: ['.results--sidebar.js-results-sidebar'], 51 | appendContainerQuery: ['#links_wrapper'], 52 | contentContainerQuery: [], 53 | siteName: 'DuckDuckGo', 54 | siteValue: 'duckduckgo', 55 | regex: '(^(www.)?duckduckgo.com)', 56 | }, 57 | baidu: { 58 | inputQuery: ["input[name='wd']"], 59 | sidebarContainerQuery: ['#content_right'], 60 | appendContainerQuery: ['#container'], 61 | contentContainerQuery: [], 62 | watchRouteChange(callback) { 63 | const targetNode = document.getElementById('wrapper_wrapper')! 64 | const observer = new MutationObserver(function (records) { 65 | for (const record of records) { 66 | if (record.type === 'childList') { 67 | for (const node of record.addedNodes) { 68 | if ('id' in node && node.id === 'container') { 69 | callback() 70 | return 71 | } 72 | } 73 | } 74 | } 75 | }) 76 | observer.observe(targetNode, { childList: true }) 77 | }, 78 | siteName: 'Baidu', 79 | siteValue: 'baidu', 80 | regex: '(^(www.)?baidu.com)', 81 | }, 82 | kagi: { 83 | inputQuery: ["input[name='q']"], 84 | sidebarContainerQuery: ['.right-content-box > ._0_right_sidebar'], 85 | appendContainerQuery: ['#_0_app_content'], 86 | contentContainerQuery: [], 87 | siteName: 'kagi', 88 | siteValue: 'kagi', 89 | regex: '(^(www.)?kagi.com)', 90 | }, 91 | yandex: { 92 | inputQuery: ["input[name='text']"], 93 | sidebarContainerQuery: ['#search-result-aside'], 94 | appendContainerQuery: [], 95 | contentContainerQuery: [], 96 | siteName: 'Yandex', 97 | siteValue: 'yandex', 98 | regex: '(^(w+.)?yandex.)', 99 | }, 100 | naver: { 101 | inputQuery: ["input[name='query']"], 102 | sidebarContainerQuery: ['#sub_pack'], 103 | appendContainerQuery: ['#content'], 104 | contentContainerQuery: [], 105 | siteName: 'NAVER', 106 | siteValue: 'naver', 107 | regex: '(^(search.)?naver.com)', 108 | }, 109 | brave: { 110 | inputQuery: ["input[name='q']"], 111 | sidebarContainerQuery: ['#side-right'], 112 | appendContainerQuery: [], 113 | contentContainerQuery: [], 114 | siteName: 'Brave', 115 | siteValue: 'brave', 116 | regex: `(^(search.)?brave.com)`, 117 | }, 118 | searx: { 119 | inputQuery: ["input[name='q']"], 120 | sidebarContainerQuery: ['#sidebar_results'], 121 | appendContainerQuery: [], 122 | contentContainerQuery: [], 123 | siteName: 'searX', 124 | siteValue: 'searx', 125 | regex: '(^(www.)?searx.be)', 126 | }, 127 | youtube: { 128 | inputQuery: ["input[name='q']"], 129 | sidebarContainerQuery: ['#rhs'], 130 | appendContainerQuery: ['#rcnt'], 131 | extabarContainerQuery: ['#extabar'], 132 | contentContainerQuery: [], 133 | name: 'youtube', 134 | watchRouteChange(callback) { 135 | let currentUrl = window.location.href 136 | 137 | setInterval(() => { 138 | const videoId = queryParam('v', window.location.href) 139 | if (window.location.href !== currentUrl && videoId) { 140 | waitForElm('#secondary.style-scope.ytd-watch-flexy').then(() => { 141 | if (document.querySelector('section.glarity--container')) { 142 | document.querySelector('section.glarity--container')?.remove() 143 | } 144 | }) 145 | 146 | callback() 147 | currentUrl = window.location.href 148 | } 149 | }, 1000) 150 | }, 151 | siteName: 'YouTube', 152 | siteValue: 'youtube', 153 | regex: '(^(www.)?youtube.com)', 154 | }, 155 | yahooJpNews: { 156 | inputQuery: ["input[name='q']"], 157 | sidebarContainerQuery: ['#rhs'], 158 | appendContainerQuery: ['#rcnt'], 159 | extabarContainerQuery: ['#yjnFixableArea.sc-feJyhm'], 160 | contentContainerQuery: ['div.article_body'], 161 | name: 'yahooJpNews', 162 | siteName: 'Yahoo! JAPAN ニュース', 163 | siteValue: 'yahooJpNews', 164 | regex: '(^news.yahoo.co.jp)', 165 | }, 166 | pubmed: { 167 | inputQuery: ["input[name='q']"], 168 | sidebarContainerQuery: ['#rhs'], 169 | appendContainerQuery: ['#rcnt'], 170 | // extabarContainerQuery: ['aside.page-sidebar > div.inner-wrap'], 171 | extabarContainerQuery: ['aside.page-sidebar', 'aside.pmc-sidebar'], 172 | contentContainerQuery: ['div#abstract'], 173 | name: 'pubmed', 174 | siteName: 'PubMed', 175 | siteValue: 'pubmed', 176 | regex: '((w+.)?ncbi.nlm.nih.gov)', 177 | }, 178 | newspicks: { 179 | inputQuery: ["input[name='q']"], 180 | sidebarContainerQuery: ['#rhs'], 181 | appendContainerQuery: ['#rcnt'], 182 | extabarContainerQuery: ['div.right-container'], 183 | contentContainerQuery: ['div#body div.article-body'], 184 | name: 'newspicks', 185 | siteName: 'NewsPicks', 186 | siteValue: 'newspicks', 187 | regex: '(^(www.)?newspicks.com)', 188 | }, 189 | nikkei: { 190 | inputQuery: ["input[name='q']"], 191 | sidebarContainerQuery: ['#rhs'], 192 | appendContainerQuery: ['#rcnt'], 193 | extabarContainerQuery: ['aside.aside_au9xyxw'], 194 | contentContainerQuery: ['section.container_c1suc6un'], 195 | name: 'nikkei', 196 | siteName: 'Nikkei', 197 | siteValue: 'nikkei', 198 | regex: '(^(www.)?nikkei.com)', 199 | }, 200 | github: { 201 | inputQuery: ["input[name='q']"], 202 | sidebarContainerQuery: ['#rhs'], 203 | appendContainerQuery: ['#rcnt'], 204 | extabarContainerQuery: ['div.Layout-sidebar'], 205 | contentContainerQuery: ['div.Box-body'], 206 | name: 'github', 207 | siteName: 'GitHub', 208 | siteValue: 'github', 209 | regex: '(^(www.)?github.com)', 210 | }, 211 | googlePatents: { 212 | inputQuery: ["input[name='q']"], 213 | sidebarContainerQuery: ['#rhs'], 214 | appendContainerQuery: ['#rcnt'], 215 | extabarContainerQuery: ['section.knowledge-card.patent-result'], 216 | contentContainerQuery: ['#descriptionText'], 217 | name: 'googlePatents', 218 | siteName: 'Google Patents', 219 | siteValue: 'googlePatents', 220 | regex: '(^(patents).google.com)', 221 | watchRouteChange(callback) { 222 | let currentUrl = window.location.href 223 | 224 | setInterval(() => { 225 | if (window.location.href !== currentUrl) { 226 | if (/patents.google.com\/patent\/\w+/g.test(location.href)) { 227 | waitForElm(config.googlePatents.extabarContainerQuery?.[0]).then(() => { 228 | if (document.querySelector('section.glarity--container')) { 229 | document.querySelector('section.glarity--container')?.remove() 230 | } 231 | }) 232 | 233 | callback() 234 | } 235 | 236 | currentUrl = window.location.href 237 | } 238 | }, 1000) 239 | }, 240 | }, 241 | bilibili: { 242 | inputQuery: [], 243 | sidebarContainerQuery: [], 244 | appendContainerQuery: [], 245 | extabarContainerQuery: ['div.bpx-player-auxiliary'], 246 | contentContainerQuery: [], 247 | name: 'bilibili', 248 | siteName: 'Bilibili', 249 | siteValue: 'bilibili', 250 | regex: '(^(www.)?bilibili.com)', 251 | watchRouteChange(callback) { 252 | let currentUrl = window.location.href 253 | 254 | setInterval(() => { 255 | if (window.location.href !== currentUrl) { 256 | if (getBiliVideoId(location.href)) { 257 | waitForElm(config.bilibili.extabarContainerQuery?.[0]).then(() => { 258 | if (document.querySelector('section.glarity--container')) { 259 | document.querySelector('section.glarity--container')?.remove() 260 | } 261 | }) 262 | 263 | callback() 264 | } 265 | 266 | currentUrl = window.location.href 267 | } 268 | }, 1000) 269 | }, 270 | }, 271 | } 272 | -------------------------------------------------------------------------------- /src/content-script/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'light.scss'; 2 | @import 'dark.scss'; 3 | 4 | .glarity--container { 5 | position: relative; 6 | z-index: 1; 7 | margin-bottom: 30px; 8 | flex-basis: 0; 9 | flex-grow: 1; 10 | z-index: 1; 11 | box-sizing: border-box; 12 | width: var(--rhs-width); 13 | min-width: var(--rhs-width); 14 | max-width: 100%; 15 | pointer-events: all; 16 | 17 | &.glarity--chatgpt--youtube { 18 | width: var(--ytd-watch-flexy-sidebar-width); 19 | min-width: var(--ytd-watch-flexy-sidebar-min-width); 20 | } 21 | 22 | &.glarity--full-container { 23 | min-width: calc(100% - var(--rhs-margin)); 24 | margin-right: var(--rhs-margin); 25 | margin-bottom: 10px; 26 | } 27 | &.glarity--chatgpt--pubmed { 28 | width: calc(100% + 80px); 29 | max-width: calc(100% + 80px); 30 | } 31 | 32 | .glarity--link { 33 | cursor: pointer; 34 | color: #065fd4; 35 | 36 | a:hover { 37 | text-decoration: underline; 38 | } 39 | } 40 | 41 | .glarity--chatgpt { 42 | border: 1px solid; 43 | border-radius: 8px; 44 | padding: 6px 10px 0; 45 | line-height: 1.5; 46 | word-wrap: break-word; 47 | // word-break:break-all; 48 | } 49 | 50 | &.sidebar--free { 51 | margin-left: 30px; 52 | height: fit-content; 53 | } 54 | 55 | p { 56 | margin: 0; 57 | } 58 | 59 | a.gpt-promotion-link { 60 | &:hover { 61 | text-decoration: none; 62 | } 63 | a:visited { 64 | color: inherit; 65 | } 66 | } 67 | 68 | .icon-and-text { 69 | display: flex; 70 | align-items: center; 71 | gap: 6px; 72 | } 73 | 74 | #gpt-answer.markdown-body.gpt-markdown { 75 | font-size: 14px; 76 | line-height: 1.5; 77 | 78 | pre { 79 | margin-top: 10px; 80 | } 81 | 82 | & > p:not(:last-child) { 83 | margin-bottom: 10px; 84 | } 85 | 86 | pre code { 87 | white-space: pre-wrap; 88 | word-break: break-all; 89 | } 90 | 91 | pre code.hljs { 92 | padding: 0; 93 | background-color: transparent; 94 | } 95 | 96 | ol li { 97 | list-style: disc; 98 | } 99 | 100 | img { 101 | width: 100%; 102 | } 103 | 104 | a { 105 | text-decoration: underline; 106 | &:visited { 107 | color: unset; 108 | } 109 | } 110 | 111 | .glarity--chatgpt--header { 112 | margin-top: -42px !important; 113 | } 114 | } 115 | 116 | .glarity--chatgpt--header { 117 | margin-top: -42px !important; 118 | margin-left: calc(100% - 58px); 119 | margin-bottom: 10px; 120 | padding-bottom: 6px; 121 | display: flex; 122 | flex-direction: row; 123 | justify-content: flex-start; 124 | align-items: center; 125 | 126 | gap: 5px; 127 | 128 | .gpt--feedback { 129 | margin-left: auto; 130 | display: flex; 131 | gap: 6px; 132 | cursor: pointer; 133 | } 134 | 135 | .gpt--feedback--selected { 136 | color: #ff6347; 137 | } 138 | } 139 | } 140 | 141 | .glarity--summary--highlight { 142 | color: red; 143 | } 144 | .glarity--header { 145 | // position: relative; 146 | // z-index: 10; 147 | display: flex; 148 | padding: 2px 0 10px; 149 | justify-content: space-between; 150 | align-items: center; 151 | font-size: 16px; 152 | user-select: none; 153 | cursor: pointer; 154 | } 155 | 156 | a.glarity--header__logo, 157 | span.glarity--header__logo { 158 | margin-right: 6px; 159 | height: 20px; 160 | color: #000; 161 | text-decoration: none; 162 | line-height: 20px; 163 | 164 | &:visited { 165 | color: #000; 166 | } 167 | 168 | img { 169 | margin-right: 5px; 170 | width: 20px; 171 | height: 20px; 172 | vertical-align: middle; 173 | } 174 | } 175 | 176 | span.glarity--header__logo { 177 | cursor: default; 178 | } 179 | 180 | .glarity--main { 181 | position: relative; 182 | margin-left: -10px; 183 | margin-right: -10px; 184 | padding: 0 12px; 185 | border-top: 1px solid rgb(221, 221, 221); 186 | } 187 | .glarity--main__header { 188 | position: relative; 189 | z-index: 20; 190 | display: flex; 191 | justify-content: space-between; 192 | align-items: center; 193 | padding: 6px 0; 194 | text-align: left; 195 | font-size: 14px; 196 | border-bottom: 1px solid rgb(221, 221, 223); 197 | 198 | a { 199 | color: #333; 200 | } 201 | } 202 | .glarity--main__header--title { 203 | font-weight: bold; 204 | cursor: pointer; 205 | } 206 | 207 | .glarity--main__header--icon { 208 | cursor: pointer; 209 | } 210 | 211 | .glarity--main__header--action { 212 | a { 213 | display: inline-block; 214 | margin-left: 5px; 215 | } 216 | } 217 | 218 | .glarity--main__container { 219 | position: relative; 220 | z-index: 30; 221 | padding: 10px 0 10px; 222 | font-size: 14px; 223 | color: #444; 224 | 225 | &.glarity--main__container--subtitle { 226 | margin-right: -10px; 227 | padding-right: 10px; 228 | height: 200px; 229 | max-height: 200px; 230 | overflow: hidden; 231 | overflow-y: auto; 232 | } 233 | 234 | .glarity--chatgpt--content { 235 | margin-right: -10px; 236 | padding-right: 10px; 237 | max-height: 260px; 238 | height: 260px; 239 | overflow: hidden; 240 | overflow-y: auto; 241 | } 242 | } 243 | .glarity--main__loading { 244 | position: absolute; 245 | z-index: 99; 246 | width: 100%; 247 | height: calc(100% - 16px); 248 | background-color: rgb(249, 249, 249); 249 | } 250 | 251 | .glarity--subtitle { 252 | display: flex; 253 | justify-content: left; 254 | align-items: baseline; 255 | grid-template-columns: auto auto; 256 | gap: 0px; 257 | margin: 0px 0px 16px; 258 | 259 | .subtitle--time { 260 | padding-right: 10px; 261 | color: var(--yt-spec-call-to-action); 262 | background-color: var(--yt-spec-suggested-action); 263 | padding: 0 4px; 264 | font-size: 1.3rem; 265 | font-weight: 500; 266 | line-height: 1.8rem; 267 | cursor: pointer; 268 | } 269 | .subtitle--text { 270 | padding-left: 10px; 271 | padding-right: 10px; 272 | } 273 | } 274 | 275 | .glarity--select { 276 | width: 70px; 277 | border: 1px solid #bbb; 278 | border-radius: 4px; 279 | } 280 | 281 | .glarity--icon--loading { 282 | display: inline-block !important; 283 | vertical-align: middle; 284 | transform: scale(0.7); 285 | } 286 | 287 | .glarity--text-xs { 288 | font-size: 12px; 289 | line-height: 1.6; 290 | } 291 | .glarity--mt-2 { 292 | margin-top: 10px; 293 | } 294 | 295 | // glarity--chatgpt--tips 296 | .glarity--chatgpt--tips { 297 | position: fixed; 298 | z-index: 100; 299 | right: 20px; 300 | top: 20px; 301 | width: 300px; 302 | 303 | button.btn.glarity--chatgpt--tips--close { 304 | position: absolute !important; 305 | z-index: 100; 306 | right: 5px; 307 | top: 5px; 308 | padding: 0; 309 | height: auto; 310 | border: none; 311 | background-color: transparent; 312 | } 313 | } 314 | 315 | // newspicks 316 | .glarity--chatgpt--newspicks { 317 | margin-bottom: 32px; 318 | background-color: #fff; 319 | border: none; 320 | border-radius: 0; 321 | box-shadow: 0 1px 4px #ddd; 322 | 323 | .glarity--chatgpt { 324 | border: none; 325 | background: #fff; 326 | border-radius: 0; 327 | 328 | .glarity--main__container { 329 | a { 330 | color: #337ab7; 331 | 332 | &:hover { 333 | text-decoration: underline; 334 | } 335 | } 336 | } 337 | } 338 | } 339 | 340 | .glarity--geist--select { 341 | position: relative; 342 | } 343 | 344 | // nikkei 345 | .glarity--chatgpt--nikkei { 346 | flex-grow: inherit; 347 | } 348 | 349 | // google partents 350 | .glarity--chatgpt--googlePatents { 351 | margin-bottom: 0; 352 | 353 | .glarity--chatgpt { 354 | border: none; 355 | border-radius: 0; 356 | } 357 | } 358 | 359 | // bing 360 | .glarity--chatgpt--bing { 361 | margin-left: -20px; 362 | margin-right: -20px; 363 | } 364 | 365 | // Web Summary 366 | .glarity--summary { 367 | position: fixed; 368 | top: 160px; 369 | right: 15px; 370 | width: auto; 371 | z-index: 99999999999999; 372 | 373 | .glarity--card { 374 | width: 360px; 375 | } 376 | } 377 | 378 | // Bilibili 379 | .glarity--chatgpt--bilibili { 380 | margin-top: 20px; 381 | margin-bottom: 20px; 382 | 383 | .glarity--chatgpt { 384 | border-radius: 6px; 385 | border: none; 386 | 387 | .glarity--main { 388 | border-top-color: var(--line_regular); 389 | } 390 | } 391 | } 392 | 393 | @media screen and (max-width: 600px) { 394 | .glarity--summary { 395 | .glarity--card { 396 | width: calc(100vw - 30px); 397 | } 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const src: string 3 | export default src 4 | } 5 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_appName__", 3 | "description": "__MSG_appDesc__", 4 | "default_locale": "en", 5 | "version": "1.7.0", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "logo-16.png", 9 | "32": "logo-32.png", 10 | "48": "logo-48.png", 11 | "128": "logo-128.png" 12 | }, 13 | "host_permissions": [ 14 | "https://*.openai.com/" 15 | ], 16 | "permissions": [ 17 | "storage", 18 | "activeTab", 19 | "tabs" 20 | ], 21 | "background": { 22 | "service_worker": "background.js" 23 | }, 24 | "action": {}, 25 | "options_ui": { 26 | "page": "options.html", 27 | "open_in_tab": true 28 | }, 29 | "content_scripts": [ 30 | { 31 | "matches": [ 32 | "", 33 | "*://*/*" 34 | ], 35 | "js": [ 36 | "content-script.js" 37 | ], 38 | "css": [ 39 | "content-script.css" 40 | ] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/manifest.v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_appName__", 3 | "description": "__MSG_appDesc__", 4 | "default_locale": "en", 5 | "version": "1.7.0", 6 | "manifest_version": 2, 7 | "icons": { 8 | "16": "logo-16.png", 9 | "32": "logo-32.png", 10 | "48": "logo-48.png", 11 | "128": "logo-128.png" 12 | }, 13 | "permissions": [ 14 | "storage", 15 | "https://*.openai.com/", 16 | "activeTab", 17 | "tabs" 18 | ], 19 | "background": { 20 | "scripts": [ 21 | "background.js" 22 | ] 23 | }, 24 | "browser_action": {}, 25 | "options_ui": { 26 | "page": "options.html", 27 | "open_in_tab": true 28 | }, 29 | "content_scripts": [ 30 | { 31 | "matches": [ 32 | "", 33 | "*://*/*" 34 | ], 35 | "js": [ 36 | "content-script.js" 37 | ], 38 | "css": [ 39 | "content-script.css" 40 | ] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/messaging.ts: -------------------------------------------------------------------------------- 1 | export interface Answer { 2 | text: string 3 | messageId: string 4 | conversationId: string 5 | } 6 | -------------------------------------------------------------------------------- /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 { config as supportSites } from '@/content-script/search-engine-configs' 25 | import { isIOS } from '@/utils/utils' 26 | import Header from './components/Header' 27 | import CustomizePrompt from './components/CustomizePrompt' 28 | import PageSummaryComponent from './components/PageSummary' 29 | import EnableGlarity from './components/EnableGlarity' 30 | import { detectSystemColorScheme } from '@/utils/utils' 31 | import { 32 | videoSummaryPromptHightligt, 33 | searchPromptHighlight, 34 | pageSummaryPromptHighlight, 35 | commentSummaryPromptHightligt, 36 | } from '@/utils/prompt' 37 | 38 | import './styles.scss' 39 | 40 | function OptionsPage( 41 | props: { 42 | theme: Theme 43 | onThemeChange: (theme: Theme) => void 44 | } & PageSummaryProps, 45 | ) { 46 | const { 47 | setPageSummaryEnable, 48 | pageSummaryEnable, 49 | pageSummaryWhitelist, 50 | pageSummaryBlacklist, 51 | setPageSummaryWhitelist, 52 | setPageSummaryBlacklist, 53 | } = props 54 | const [triggerMode, setTriggerMode] = useState(TriggerMode.Always) 55 | const [language, setLanguage] = useState(Language.Auto) 56 | const { setToast } = useToasts() 57 | const [allSites, setAllSites] = useState([]) 58 | const [enableSites, setEnableSites] = useState([]) 59 | const [prompt, setPrompt] = useState('') 60 | const [promptSearch, setPromptSearch] = useState('') 61 | const [promptPage, setPromptPage] = useState('') 62 | const [promptComment, setPromptComment] = useState('') 63 | 64 | const onTriggerModeChange = useCallback( 65 | (mode: TriggerMode) => { 66 | setTriggerMode(mode) 67 | updateUserConfig({ triggerMode: mode }) 68 | setToast({ text: 'Changes saved', type: 'success' }) 69 | }, 70 | [setToast], 71 | ) 72 | 73 | const onThemeChange = useCallback( 74 | (theme: Theme) => { 75 | updateUserConfig({ theme }) 76 | props.onThemeChange(theme) 77 | setToast({ text: 'Changes saved', type: 'success' }) 78 | }, 79 | [props, setToast], 80 | ) 81 | 82 | const onLanguageChange = useCallback( 83 | (language: Language) => { 84 | updateUserConfig({ language }) 85 | setToast({ text: 'Changes saved', type: 'success' }) 86 | }, 87 | [setToast], 88 | ) 89 | 90 | const getSplitString = (str: string) => { 91 | if (str && str.includes('Chinese')) { 92 | return `Chinese (${str.split('Chinese')[1] || ''})` 93 | } 94 | 95 | return str ?? '' 96 | } 97 | 98 | useEffect(() => { 99 | getUserConfig().then((config) => { 100 | setTriggerMode(config.triggerMode) 101 | setLanguage(config.language) 102 | 103 | setPrompt(config.prompt ? config.prompt : videoSummaryPromptHightligt) 104 | setPromptSearch(config.promptSearch ? config.promptSearch : searchPromptHighlight) 105 | setPromptPage(config.promptPage ? config.promptPage : pageSummaryPromptHighlight) 106 | setPromptComment(config.promptComment ? config.promptComment : commentSummaryPromptHightligt) 107 | 108 | const sites = 109 | Object.values(supportSites).map((site) => { 110 | return site.siteValue 111 | }) || [] 112 | 113 | setAllSites(sites) 114 | const enableSites = config.enableSites 115 | setEnableSites(enableSites ? enableSites : sites) 116 | }) 117 | }, []) 118 | 119 | return ( 120 |
121 |
122 | 123 |
124 | Options 125 | 126 | {/* Trigger Mode */} 127 | {!isIOS && ( 128 | <> 129 | 130 | Trigger Mode 131 | 132 | onTriggerModeChange(val as TriggerMode)} 135 | > 136 | {Object.entries(TRIGGER_MODE_TEXT).map(([value, texts]) => { 137 | return ( 138 | 139 | {texts.title} 140 | {texts.desc} 141 | 142 | ) 143 | })} 144 | 145 | 146 | )} 147 | 148 | {/* Theme */} 149 | 150 | Theme 151 | 152 | onThemeChange(val as Theme)} useRow> 153 | {Object.entries(Theme).map(([k, v]) => { 154 | return ( 155 | 156 | {k} 157 | 158 | ) 159 | })} 160 | 161 | 162 | {/* Language */} 163 | 164 | Language 165 | 166 | 167 | The language used in ChatGPT response. Auto is 168 | recommended. 169 | 170 | 181 | 182 | {/* AI Provider */} 183 | 184 | AI Provider 185 | 186 | 187 | 188 | 198 | 199 | {/* Enable/Disable Glarity */} 200 | 206 | 207 | {/* Misc */} 208 | {/* 209 | Misc 210 | 211 |
212 | 213 | 214 | Auto delete conversations generated by search 215 | 216 |
*/} 217 | 218 | {/* */} 219 | 220 | {/* Page Summary */} 221 | 229 |
230 |
231 | ) 232 | } 233 | 234 | function App() { 235 | const [theme, setTheme] = useState(Theme.Auto) 236 | const [pageSummaryEnable, setPageSummaryEnable] = useState(true) 237 | const [pageSummaryWhitelist, setPageSummaryWhitelist] = useState('') 238 | const [pageSummaryBlacklist, setPageSummaryBlacklist] = useState('') 239 | 240 | const themeType = useMemo(() => { 241 | if (theme === Theme.Auto) { 242 | return detectSystemColorScheme() 243 | } 244 | return theme 245 | }, [theme]) 246 | 247 | useEffect(() => { 248 | getUserConfig().then((config) => { 249 | setTheme(config.theme) 250 | setPageSummaryEnable(config.pageSummaryEnable) 251 | setPageSummaryWhitelist(config.pageSummaryWhitelist) 252 | setPageSummaryBlacklist( 253 | config.pageSummaryBlacklist ? config.pageSummaryBlacklist : DEFAULT_PAGE_SUMMARY_BLACKLIST, 254 | ) 255 | }) 256 | }, []) 257 | 258 | return ( 259 | 260 | 261 | 271 | 272 | ) 273 | } 274 | 275 | export default App 276 | -------------------------------------------------------------------------------- /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 | const { Option } = Aselect 7 | import { isSafari } from '@/utils/utils' 8 | 9 | interface ConfigProps { 10 | config: ProviderConfigs 11 | models: string[] 12 | } 13 | 14 | const ConfigPanel: FC = ({ config, models }) => { 15 | const [tab, setTab] = useState(isSafari ? ProviderType.GPT3 : config.provider) 16 | const { bindings: apiKeyBindings } = useInput(config.configs[ProviderType.GPT3]?.apiKey ?? '') 17 | const { bindings: apiHostBindings } = useInput(config.configs[ProviderType.GPT3]?.apiHost ?? '') 18 | const { bindings: apiPathBindings } = useInput(config.configs[ProviderType.GPT3]?.apiPath ?? '') 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 | const apiPath = apiPathBindings.value || '' 39 | 40 | await saveProviderConfigs(tab, { 41 | [ProviderType.GPT3]: { 42 | model, 43 | apiKey: apiKeyBindings.value, 44 | apiHost: apiHost, 45 | apiPath: apiPath, 46 | }, 47 | }) 48 | setToast({ text: 'Changes saved', type: 'success' }) 49 | }, [ 50 | apiHostBindings.value, 51 | apiKeyBindings.value, 52 | apiPathBindings.value, 53 | model, 54 | models, 55 | setToast, 56 | tab, 57 | ]) 58 | 59 | useEffect(() => { 60 | console.log('config', config) 61 | console.log('models', models) 62 | }, [config, models]) 63 | 64 | return ( 65 | <> 66 | 67 |
68 | setTab(v as ProviderType)}> 69 | {!isSafari && ( 70 | <> 71 | 72 | ChatGPT webapp 73 | 74 | {' '} 75 | The API that powers ChatGPT webapp, free, but sometimes unstable 76 | 77 | 78 | 79 | )} 80 | 81 | 82 | OpenAI API 83 | 84 |
85 | 86 | OpenAI official API, more stable,{' '} 87 | charge by usage 88 | 89 |
90 | 98 | 106 | setModel(v as string)} 109 | placeholder="model" 110 | optionLabelProp="label" 111 | style={{ width: '170px' }} 112 | > 113 | {models.map((m) => ( 114 | 117 | ))} 118 | 119 | 127 |
128 | 129 | You can find or create your API key{' '} 130 | 135 | here 136 | 137 | 138 |
139 |
140 |
141 |
142 | 143 | 146 | 147 |
148 |
149 | 150 | ) 151 | } 152 | 153 | function ProviderSelect() { 154 | const query = useSWR('provider-configs', async () => { 155 | const [config] = await Promise.all([getProviderConfigs()]) 156 | 157 | return { config } 158 | }) 159 | 160 | const models = [ 161 | 'gpt-3.5-turbo', 162 | 'gpt-3.5-turbo-0301', 163 | 'gpt-3.5-turbo-0613', 164 | 'gpt-3.5-turbo-16k', 165 | 'text-davinci-003', 166 | // 'text-curie-001', 167 | // 'text-babbage-001', 168 | // 'text-ada-001', 169 | // 'text-chat-davinci-002-20221122', 170 | ] 171 | 172 | if (query.isLoading) { 173 | return 174 | } 175 | 176 | if (query.error) { 177 | return
Error loading provider configurations.
178 | } 179 | 180 | if (!query.data || !query.data.config) { 181 | return
No provider configurations found.
182 | } 183 | 184 | return 185 | } 186 | 187 | export default ProviderSelect 188 | -------------------------------------------------------------------------------- /src/options/components/EnableGlarity.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useCallback, useEffect, useState } from 'preact/hooks' 3 | import { SearchEngine } from '@/content-script/search-engine-configs' 4 | import { Text, Card, Button, Spacer, useToasts, Checkbox } from '@geist-ui/core' 5 | import { updateUserConfig } from '@/config' 6 | import { changeToast, isIOS } from '@/utils/utils' 7 | 8 | interface Props { 9 | enableSites: string[] 10 | setEnableSites: (site: string[]) => void 11 | allSites: string[] 12 | supportSites: Record 13 | } 14 | 15 | function EnableGlarity(props: Props) { 16 | const { enableSites, setEnableSites, allSites, supportSites } = props 17 | const { setToast } = useToasts() 18 | const [allSelect, setAllSelect] = useState(true) 19 | 20 | const onSaveSelect = useCallback(() => { 21 | updateUserConfig({ enableSites }) 22 | setToast(changeToast) 23 | }, [setToast, enableSites]) 24 | 25 | const onChangeSites = (value) => { 26 | setEnableSites(value) 27 | } 28 | 29 | const onChangeSelectAll = (e) => { 30 | if (e.target.checked) { 31 | setEnableSites(allSites) 32 | } else { 33 | setEnableSites([]) 34 | } 35 | } 36 | 37 | useEffect(() => { 38 | if (enableSites.length === allSites.length) { 39 | setAllSelect(true) 40 | } else { 41 | setAllSelect(false) 42 | } 43 | }, [enableSites, allSites]) 44 | 45 | return ( 46 | <> 47 | {!isIOS && ( 48 | <> 49 | 50 | Enable/Disable Glarity 51 | 52 | You can enable/disable the Glarity Summary on the following website. 53 | 54 | 55 | 56 | 57 | 58 | 63 | {Object.entries(supportSites).map(([k, v]) => { 64 | return ( 65 | 66 | {v.siteName} 67 | 68 | ) 69 | })} 70 | 71 | 72 | 73 | 74 | Select All / Reverse 75 | 76 | 77 | 80 | 81 | 82 | 83 | )} 84 | 85 | ) 86 | } 87 | 88 | export default EnableGlarity 89 | -------------------------------------------------------------------------------- /src/options/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { getExtensionVersion, AppName } from '@/utils/utils' 2 | import logo from '@/assets/img/logo.png' 3 | 4 | function Header() { 5 | return ( 6 | <> 7 | 44 | 45 | ) 46 | } 47 | 48 | export default Header 49 | -------------------------------------------------------------------------------- /src/options/components/PageSummary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useCallback } from 'preact/hooks' 3 | import { 4 | Text, 5 | Textarea, 6 | Card, 7 | Button, 8 | useToasts, 9 | Divider, 10 | Toggle, 11 | Spacer, 12 | Description, 13 | } from '@geist-ui/core' 14 | import { updateUserConfig } from '@/config' 15 | import { changeToast } from '@/utils/utils' 16 | 17 | export interface PageSummaryProps { 18 | pageSummaryEnable: boolean 19 | setPageSummaryEnable: (state: boolean) => void 20 | pageSummaryWhitelist: string 21 | setPageSummaryWhitelist: (whitelist: string) => void 22 | pageSummaryBlacklist: string 23 | setPageSummaryBlacklist: (blacklist: string) => void 24 | } 25 | 26 | function PageSummaryComponent(props: PageSummaryProps) { 27 | const { 28 | pageSummaryEnable, 29 | setPageSummaryEnable, 30 | pageSummaryWhitelist, 31 | pageSummaryBlacklist, 32 | setPageSummaryWhitelist, 33 | setPageSummaryBlacklist, 34 | } = props 35 | const { setToast } = useToasts() 36 | 37 | const onPageSummarySave = useCallback(() => { 38 | updateUserConfig({ pageSummaryWhitelist, pageSummaryBlacklist }) 39 | setPageSummaryWhitelist(pageSummaryWhitelist) 40 | setPageSummaryBlacklist(pageSummaryBlacklist) 41 | setToast(changeToast) 42 | }, [ 43 | pageSummaryBlacklist, 44 | pageSummaryWhitelist, 45 | setPageSummaryBlacklist, 46 | setPageSummaryWhitelist, 47 | setToast, 48 | ]) 49 | 50 | const onPageSummaryEnableChange = useCallback( 51 | (e: React.ChangeEvent) => { 52 | const value = e.target.checked 53 | setPageSummaryEnable(value) 54 | updateUserConfig({ pageSummaryEnable: value }) 55 | setToast(changeToast) 56 | }, 57 | [setPageSummaryEnable, setToast], 58 | ) 59 | 60 | const onPageSummaryWhitelistChange = useCallback( 61 | (e: React.ChangeEvent) => { 62 | const value = e.target.value || '' 63 | setPageSummaryWhitelist(value) 64 | }, 65 | [setPageSummaryWhitelist], 66 | ) 67 | 68 | const onPageSummaryBlacklistChange = useCallback( 69 | (e: React.ChangeEvent) => { 70 | const value = e.target.value || '' 71 | setPageSummaryBlacklist(value) 72 | }, 73 | [setPageSummaryBlacklist], 74 | ) 75 | 76 | return ( 77 | <> 78 | 79 | Page Summary 80 | 81 | 82 | 83 | 84 | 88 | {' '} 93 | Show Glarity Icon 94 | 95 | 96 | Once hidden, the Glarity icon will no longer appear on the page. However, you can open 97 | the page summary by clicking on the browser extension icon. 98 | 99 | 100 | 101 | 102 | 103 | Whitelist Sites 104 | 105 | 106 | Only display the Glarity icon on these sites (one URL per line). 107 | 108 | 109 |