├── .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
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 | 
69 | 
70 |
71 | ### YouTube
72 |
73 | 
74 |
75 | ### Bilibili
76 |
77 | 
78 |
79 | ### iOS Safari /macOS Safari
80 |
81 | 
82 | 
83 |
84 | ### Github
85 |
86 | 
87 |
88 | ### Bing
89 |
90 | 
91 |
92 | ### Google Patents
93 |
94 | 
95 |
96 | ### Yahoo! JAPAN ニュース
97 |
98 | 
99 |
100 | ### PubMed
101 |
102 | 
103 |
104 | ### PMC
105 |
106 | 
107 |
108 | ### NewsPicks
109 |
110 | 
111 |
112 | ### Nikkei
113 |
114 | 
115 |
116 | ## Troubleshooting
117 |
118 | ### How to make it work in Brave
119 |
120 | 
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
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 | 
71 | 
72 |
73 | ### YouTube
74 |
75 | 
76 |
77 | ### Bilibili
78 |
79 | 
80 |
81 | ### iOS Safari /macOS Safari
82 |
83 | 
84 | 
85 |
86 | ### Github
87 |
88 | 
89 |
90 | ### Bing
91 |
92 | 
93 |
94 | ### Google Patents
95 |
96 | 
97 |
98 | ### Yahoo! JAPAN 新闻
99 |
100 | 
101 |
102 | ### PubMed
103 |
104 | 
105 |
106 | ### PMC
107 |
108 | 
109 |
110 | ### NewsPicks
111 |
112 | 
113 |
114 | ### Nikkei
115 |
116 | 
117 |
118 | ## 常见问题
119 |
120 | ### 在 Brave 运行
121 |
122 | 
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 |
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 |
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 |
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 | }
58 | auto
59 | onClick={onClose}
60 | />
61 | >
62 | )}
63 | >
64 | )
65 | }
66 |
67 | export default ChatGPTTip
68 |
--------------------------------------------------------------------------------
/src/content-script/compenents/Mount.tsx:
--------------------------------------------------------------------------------
1 | import { render } from 'preact'
2 | import { getUserConfig, Theme } from '@/config'
3 | import { config } from '@/content-script/search-engine-configs'
4 | import ChatGPTContainer from '@/content-script/compenents/ChatGPTContainer'
5 | import { detectSystemColorScheme } from '@/utils/utils'
6 | import { getPossibleElementByQuerySelector, waitForElm } from '@/content-script/utils'
7 | import {
8 | siteConfig as sietConfigFn,
9 | siteName as siteNameFn,
10 | hostname,
11 | } from '@/content-script/utils'
12 |
13 | interface MountProps {
14 | question: string | null
15 | transcript?: unknown
16 | langOptionsWithLink?: unknown
17 | }
18 |
19 | export default async function mount(props: MountProps) {
20 | const siteConfig = sietConfigFn()
21 | const siteName = siteNameFn()
22 |
23 | const { question, transcript, langOptionsWithLink } = props
24 | if (!siteConfig) {
25 | return
26 | }
27 | const userConfig = await getUserConfig()
28 |
29 | const sites = Object.values(config).map((site) => {
30 | return site.siteValue
31 | })
32 |
33 | const enableSites = userConfig.enableSites ? userConfig.enableSites : sites
34 | const regexList = []
35 | Object.values(enableSites).map((v) => {
36 | const item = config[v]
37 |
38 | if (item.regex) {
39 | regexList.push(item.regex)
40 | }
41 | })
42 |
43 | if (regexList.length <= 0) {
44 | return
45 | }
46 | const sitesRegex = new RegExp(regexList.join('|'))
47 |
48 | if (!sitesRegex.test(hostname)) {
49 | return
50 | }
51 |
52 | if (document.querySelector('section.glarity--container')) {
53 | document.querySelector('section.glarity--container')?.remove()
54 | }
55 |
56 | const container = document.createElement('section')
57 | container.className = 'b_glarity'
58 | container.classList.add('glarity--container')
59 | container.id = 'glarity--container'
60 |
61 | let theme: Theme
62 | if (userConfig.theme === Theme.Auto) {
63 | theme = detectSystemColorScheme()
64 | } else {
65 | theme = userConfig.theme
66 | }
67 | if (theme === Theme.Dark) {
68 | container.classList.add('gpt--dark')
69 | } else {
70 | container.classList.add('gpt--light')
71 | }
72 |
73 | switch (siteName) {
74 | case 'pubmed': {
75 | container.classList.add('glarity--chatgpt--pubmed')
76 | const appendContainer = getPossibleElementByQuerySelector(
77 | siteConfig.extabarContainerQuery || [],
78 | )
79 | appendContainer?.prepend(container)
80 | break
81 | }
82 | case 'newspicks': {
83 | container.classList.add('glarity--chatgpt--newspicks')
84 | const appendContainer = getPossibleElementByQuerySelector(
85 | siteConfig.extabarContainerQuery || [],
86 | )
87 | appendContainer?.prepend(container)
88 | break
89 | }
90 |
91 | case 'yahooJpNews': {
92 | container.classList.add('glarity--chatgpt--yahoonews')
93 |
94 | const appendContainer = getPossibleElementByQuerySelector(
95 | siteConfig.extabarContainerQuery || [],
96 | )
97 | appendContainer?.prepend(container)
98 | break
99 | }
100 | case 'nikkei': {
101 | container.classList.add('glarity--chatgpt--nikkei')
102 | const appendContainer = getPossibleElementByQuerySelector(
103 | siteConfig.extabarContainerQuery || [],
104 | )
105 | appendContainer?.prepend(container)
106 | break
107 | }
108 | case 'github': {
109 | container.classList.add('glarity--chatgpt--github')
110 | const appendContainer = getPossibleElementByQuerySelector(
111 | siteConfig.extabarContainerQuery || [],
112 | )
113 | appendContainer?.prepend(container)
114 | break
115 | }
116 |
117 | case 'googlePatents': {
118 | const extabarContainerQuery =
119 | siteConfig.extabarContainerQuery && siteConfig.extabarContainerQuery[0]
120 |
121 | if (!extabarContainerQuery) {
122 | return
123 | }
124 |
125 | waitForElm(extabarContainerQuery).then(() => {
126 | container.classList.add('glarity--chatgpt--googlePatents')
127 | const appendContainer = getPossibleElementByQuerySelector(
128 | siteConfig.extabarContainerQuery || [],
129 | )
130 | appendContainer?.prepend(container)
131 | })
132 |
133 | break
134 | }
135 | case 'youtube': {
136 | container.classList.add('glarity--chatgpt--youtube')
137 | waitForElm('#secondary.style-scope.ytd-watch-flexy').then(() => {
138 | document.querySelector('#secondary.style-scope.ytd-watch-flexy')?.prepend(container)
139 | })
140 | break
141 | }
142 | case 'bilibili': {
143 | container.classList.add('glarity--chatgpt--bilibili')
144 |
145 | waitForElm(siteConfig.extabarContainerQuery?.[0]).then(() => {
146 | container.classList.add('glarity--chatgpt--bilibili')
147 | const appendContainer = getPossibleElementByQuerySelector(
148 | siteConfig.extabarContainerQuery || [],
149 | )
150 | appendContainer?.insertAdjacentElement('beforebegin', container)
151 | })
152 | break
153 | }
154 | default: {
155 | if (siteName === 'bing') {
156 | if (!/bing.com\/search\?/g.test(location.href)) {
157 | return
158 | }
159 | container.classList.add('glarity--chatgpt--bing')
160 | }
161 |
162 | const siderbarContainer = getPossibleElementByQuerySelector(siteConfig.sidebarContainerQuery)
163 |
164 | if (siderbarContainer) {
165 | siderbarContainer.prepend(container)
166 | } else {
167 | if (
168 | siteConfig.extabarContainerQuery &&
169 | document.querySelector('#center_col')?.nextSibling
170 | ) {
171 | container.classList.add('glarity--full-container')
172 | const appendContainer = getPossibleElementByQuerySelector(
173 | siteConfig.extabarContainerQuery,
174 | )
175 | if (appendContainer) {
176 | appendContainer.appendChild(container)
177 | appendContainer.appendChild(container)
178 | }
179 | } else {
180 | container.classList.add('sidebar--free')
181 | const appendContainer = getPossibleElementByQuerySelector(siteConfig.appendContainerQuery)
182 | if (appendContainer) {
183 | appendContainer.appendChild(container)
184 | }
185 | }
186 | }
187 | }
188 | }
189 |
190 | render(
191 | ,
198 | container,
199 | )
200 | }
201 |
--------------------------------------------------------------------------------
/src/content-script/compenents/PageSummary.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect } from 'preact/hooks'
2 | import classNames from 'classnames'
3 | import { XCircleFillIcon, GearIcon } from '@primer/octicons-react'
4 | import Browser from 'webextension-polyfill'
5 | import ChatGPTQuery from '@/content-script/compenents/ChatGPTQuery'
6 | // import { extractFromHtml } from '@/utils/article-extractor/cjs/article-extractor.esm'
7 | import { getUserConfig, Language, getProviderConfigs, APP_TITLE } from '@/config'
8 | import { getSummaryPrompt } from '@/content-script/prompt'
9 | import { isIOS } from '@/utils/utils'
10 | import { getPageSummaryContntent, getPageSummaryComments } from '@/content-script/utils'
11 | import { commentSummaryPrompt, pageSummaryPrompt, pageSummaryPromptHighlight } from '@/utils/prompt'
12 | import logoWhite from '@/assets/img/logo-white.png'
13 | import logo from '@/assets/img/logo.png'
14 |
15 | interface Props {
16 | pageSummaryEnable: boolean
17 | pageSummaryWhitelist: string
18 | pageSummaryBlacklist: string
19 | siteRegex: RegExp
20 | }
21 |
22 | function PageSummary(props: Props) {
23 | const { pageSummaryEnable, pageSummaryWhitelist, pageSummaryBlacklist, siteRegex } = props
24 | const [showCard, setShowCard] = useState(false)
25 | const [supportSummary, setSupportSummary] = useState(true)
26 | const [question, setQuestion] = useState('')
27 | const [loading, setLoading] = useState(false)
28 | const [show, setShow] = useState(false)
29 |
30 | const onSwitch = useCallback(() => {
31 | setShowCard((state) => {
32 | const cardState = !state
33 |
34 | if (cardState) {
35 | setQuestion('')
36 | setLoading(false)
37 | }
38 |
39 | return cardState
40 | })
41 | }, [])
42 |
43 | const openOptionsPage = useCallback(() => {
44 | Browser.runtime.sendMessage({ type: 'OPEN_OPTIONS_PAGE' })
45 | }, [])
46 |
47 | const onSummary = useCallback(async () => {
48 | setLoading(true)
49 | setSupportSummary(true)
50 |
51 | setQuestion('')
52 |
53 | const pageComments = await getPageSummaryComments()
54 | const pageContent = await getPageSummaryContntent()
55 | const article = pageComments ? pageComments : pageContent
56 |
57 | const title = article?.title || document.title || ''
58 | const description =
59 | article?.description ||
60 | document.querySelector('meta[name="description"]')?.getAttribute('content') ||
61 | ''
62 | const content = article?.content ? description + article?.content : title + description
63 |
64 | if (article?.content || description) {
65 | const language = window.navigator.language
66 | const userConfig = await getUserConfig()
67 | const providerConfigs = await getProviderConfigs()
68 |
69 | const promptContent = getSummaryPrompt(
70 | content.replace(/(<[^>]+>|\{[^}]+\})/g, ''),
71 | providerConfigs.provider,
72 | )
73 | const replyLanguage = userConfig.language === Language.Auto ? language : userConfig.language
74 |
75 | const prompt = pageComments?.content
76 | ? commentSummaryPrompt({
77 | content: promptContent,
78 | language: replyLanguage,
79 | prompt: userConfig.promptComment
80 | ? userConfig.promptComment
81 | : pageSummaryPromptHighlight,
82 | rate: article?.['rate'],
83 | })
84 | : pageSummaryPrompt({
85 | content: promptContent,
86 | language: replyLanguage,
87 | prompt: userConfig.promptPage ? userConfig.promptPage : pageSummaryPromptHighlight,
88 | })
89 |
90 | setQuestion(prompt)
91 | return
92 | }
93 |
94 | setSupportSummary(false)
95 | }, [])
96 |
97 | useEffect(() => {
98 | Browser.runtime.onMessage.addListener((message) => {
99 | const { type } = message
100 | if (type === 'OPEN_WEB_SUMMARY') {
101 | if (showCard) {
102 | return
103 | }
104 |
105 | setQuestion('')
106 | setShowCard(true)
107 | setLoading(false)
108 | }
109 | })
110 | }, [showCard])
111 |
112 | useEffect(() => {
113 | const hostname = location.hostname
114 | const blacklist = pageSummaryBlacklist.replace(/[\s\r\n]+/g, '')
115 | const whitelist = pageSummaryWhitelist.replace(/[\s\r\n]+/g, '')
116 |
117 | const inWhitelist = !whitelist
118 | ? !blacklist.includes(hostname)
119 | : !blacklist.includes(hostname) && pageSummaryWhitelist.includes(hostname)
120 |
121 | const show =
122 | pageSummaryEnable && ((isIOS && inWhitelist) || (inWhitelist && !siteRegex?.test(hostname)))
123 |
124 | setShow(show)
125 | }, [pageSummaryBlacklist, pageSummaryEnable, pageSummaryWhitelist, siteRegex])
126 |
127 | return (
128 | <>
129 | {showCard ? (
130 |
131 |
132 |
143 |
144 |
145 |
151 |
152 |
153 |
154 |
155 | {question ? (
156 |
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 |
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 |
117 |
118 |
119 |
120 |
121 | Blacklist Sites
122 |
123 |
124 | Do not display Glarity icon on these sites (one URL per line).
125 |
126 |
127 |
135 |
136 |
137 |
140 |
141 |
142 | >
143 | )
144 | }
145 |
146 | export default PageSummaryComponent
147 |
--------------------------------------------------------------------------------
/src/options/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Glarity
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/options/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from 'preact'
2 | import App from './App'
3 |
4 | render(, document.getElementById('app')!)
5 |
--------------------------------------------------------------------------------
/src/options/styles.scss:
--------------------------------------------------------------------------------
1 | .glarity--card {
2 | .wrapper {
3 | padding: 0 10px;
4 | width: 100% !important;
5 | max-width: 100% !important;
6 | }
7 | }
8 |
9 | .glarity--support__sites {
10 | display: flex;
11 | flex-wrap: wrap;
12 | justify-content: start;
13 | max-width: 100vw;
14 | }
15 |
16 | .glarity--support__sites--item {
17 | display: inline-flex;
18 | margin: 0 0 10px !important;
19 | width: 200px;
20 | min-width: 200px;
21 | justify-content: start !important;
22 | }
23 |
24 | .glarity--options {
25 | padding-bottom: 30px;
26 | }
27 |
28 | .glarity--container {
29 | width: 100%;
30 | padding: 10px 15px;
31 | max-width: 100vw;
32 | box-sizing: border-box;
33 | word-wrap: break-word;
34 | }
35 |
36 | @media screen and (max-width: 900px) {
37 | .glarity--w-\[900px\] {
38 | max-width: 100%;
39 | }
40 |
41 | textarea {
42 | max-width: 100%;
43 | width: 100% !important;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/popup/App.tsx:
--------------------------------------------------------------------------------
1 | import logo from '@/assets/img/logo.png'
2 | import '@/assets/styles/base.scss'
3 | import { APP_TITLE } from '@/config'
4 | import { useCallback, useState } from 'react'
5 | import useSWR from 'swr'
6 | import Browser from 'webextension-polyfill'
7 | import './styles.scss'
8 |
9 | const isChrome = /chrome/i.test(navigator.userAgent)
10 |
11 | function App() {
12 | const [question, setQuestion] = useState('')
13 |
14 | const accessTokenQuery = useSWR(
15 | 'accessToken',
16 | () => Browser.runtime.sendMessage({ type: 'GET_ACCESS_TOKEN' }),
17 | { shouldRetryOnError: false },
18 | )
19 | const hideShortcutsTipQuery = useSWR('hideShortcutsTip', async () => {
20 | const { hideShortcutsTip } = await Browser.storage.local.get('hideShortcutsTip')
21 | return !!hideShortcutsTip
22 | })
23 |
24 | const openOptionsPage = useCallback(() => {
25 | Browser.runtime.sendMessage({ type: 'OPEN_OPTIONS_PAGE' })
26 | }, [])
27 |
28 | const openShortcutsPage = useCallback(() => {
29 | Browser.storage.local.set({ hideShortcutsTip: true })
30 | Browser.tabs.create({ url: 'chrome://extensions/shortcuts' })
31 | }, [])
32 |
33 | return (
34 |
35 |
36 |

37 |
38 | {APP_TITLE}
39 |
40 |
41 | {isChrome && !hideShortcutsTipQuery.isLoading && !hideShortcutsTipQuery.data && (
42 |
43 | Tip:{' '}
44 |
45 | setup shortcuts
46 | {' '}
47 | for faster access.
48 |
49 | )}
50 |
51 | )
52 | }
53 |
54 | export default App
55 |
--------------------------------------------------------------------------------
/src/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Glarity
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/popup/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from 'preact'
2 | import App from './App'
3 |
4 | render(, document.getElementById('app')!)
5 |
--------------------------------------------------------------------------------
/src/popup/styles.scss:
--------------------------------------------------------------------------------
1 | .glarity--popup {
2 | .glarity--chatgpt {
3 | border: none;
4 | }
5 |
6 | .glarity--container #gpt-answer.markdown-body.gpt-markdown .glarity--chatgpt--header {
7 | margin-top: 0 !important;
8 | }
9 | }
10 |
11 | .glarity--popup--card {
12 | // margin-top: 10px;
13 |
14 | .content {
15 | padding: 0 !important;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css'
2 | declare module '*.less'
3 | declare module '*.png'
4 | declare module '*.svg' {
5 | export function ReactComponent(props: React.SVGProps): React.ReactElement
6 | const url: string
7 | export default url
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/article-extractor/cjs/index.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions
2 |
3 | import { IOptions as SanitizeOptions } from "sanitize-html";
4 |
5 | export interface Transformation {
6 | patterns: Array,
7 | pre?: (document: Document) => Document
8 | post?: (document: Document) => Document
9 | }
10 |
11 | export function addTransformations(transformations: Array): Number;
12 | export function removeTransformations(options: Array): Number;
13 |
14 | export function getSanitizeHtmlOptions(): SanitizeOptions;
15 | export function setSanitizeHtmlOptions(options: SanitizeOptions): void;
16 |
17 | /**
18 | * @param input url or html
19 | */
20 |
21 | export interface ParserOptions {
22 | /**
23 | * to estimate time to read.
24 | * Default: 300
25 | */
26 | wordsPerMinute: number
27 | /**
28 | * max num of chars generated for description
29 | * Default: 210
30 | */
31 | descriptionTruncateLen: number
32 | /**
33 | * min num of chars required for description
34 | * Default: 180
35 | */
36 | descriptionLengthThreshold: number
37 | /**
38 | * min num of chars required for content
39 | * Default: 200
40 | */
41 | contentLengthThreshold: number
42 | }
43 |
44 | export interface ProxyConfig {
45 | target?: string;
46 | headers?: string[];
47 | }
48 |
49 | export interface FetchOptions {
50 | /**
51 | * list of request headers
52 | * default: null
53 | */
54 | headers?: string[];
55 | /**
56 | * the values to configure proxy
57 | * default: null
58 | */
59 | proxy?: ProxyConfig;
60 | }
61 |
62 | export interface ArticleData {
63 | url?: string;
64 | links?: string[];
65 | title?: string;
66 | description?: string;
67 | image?: string;
68 | author?: string;
69 | content?: string;
70 | source?: string;
71 | published?: string;
72 | ttr?: number;
73 | }
74 |
75 | export function extract(input: string, parserOptions?: ParserOptions, fetchOptions?: FetchOptions): Promise;
76 |
77 | export function extractFromHtml(html: string, url?: string, parserOptions?: ParserOptions): Promise;
78 |
--------------------------------------------------------------------------------
/src/utils/article-extractor/cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@extractus/article-extractor",
3 | "version": "7.2.11",
4 | "main": "./article-extractor.js"
5 | }
--------------------------------------------------------------------------------
/src/utils/bilibili.ts:
--------------------------------------------------------------------------------
1 | import { setParams } from 'gb-url'
2 |
3 | export const getBiliVideoId = (url: string) => {
4 | const matches = url.match(/bilibili.com\/video\/(\w+)\//)
5 | const id = matches ? matches[1] : null
6 | // id = id ? id.replace(/^av/, '') : null
7 |
8 | return id
9 | }
10 |
11 | /**
12 | * Get trinscript
13 | */
14 | export async function getBiliTranscript(url) {
15 | const id = getBiliVideoId(url)
16 |
17 | if (!id) {
18 | return null
19 | }
20 |
21 | let params = {
22 | aid: '',
23 | bvid: '',
24 | }
25 | params = id.startsWith('av')
26 | ? Object.assign(params, { aid: id.replace(/^av/, '') })
27 | : Object.assign(params, {
28 | bvid: id,
29 | })
30 |
31 | const videoUrl = setParams(params, 'https://api.bilibili.com/x/web-interface/view')
32 | const detail = await fetch(videoUrl)
33 | const detailJson = await detail.json()
34 | const { data = {} } = detailJson
35 | const descV2 = data.desc_v2 || []
36 | const desc = descV2.length > 0 ? descV2.map((v) => v.raw_text).join(',') : data.desc
37 |
38 | console.log('detailJson', detailJson)
39 | const trinscriptUrl =
40 | data?.subtitle?.list && data?.subtitle?.list[0] && data?.subtitle?.list[0].subtitle_url
41 |
42 | if (!trinscriptUrl) {
43 | return desc
44 | ? {
45 | transcript: null,
46 | desc,
47 | }
48 | : null
49 | }
50 |
51 | const trinscript = await fetch(trinscriptUrl.replace(/^http/g, 'https'))
52 | const trinscriptJson = await trinscript.json()
53 |
54 | return {
55 | transcript: trinscriptJson?.body,
56 | desc,
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/utils/prompt.ts:
--------------------------------------------------------------------------------
1 | export const articlePromptHighlight = `Please use the above to summarize the highlights.`
2 | export const pagePromptHighlight = `Summarize the highlights of the content and output a useful summary in a few sentences.`
3 | export const googlePatentsPromptHighlight = `Please summarize the highlights of the above article in easy-to-understand terms`
4 | export const pageSummaryPromptHighlight = `Summarize the highlights of the content and output a useful summary in a few sentences.`
5 | export const videoSummaryPromptHightligt = `Instructions: Your output should use the following template:
6 | ### Summary
7 | ### Highlights
8 | - [Emoji] Bulletpoint
9 |
10 | Use up to 3 brief bullet points to summarize the content below, Choose an appropriate emoji for each bullet point. and summarize a short highlight: {{Title}} {{Transcript}}.`
11 | export const searchPromptHighlight = `Using the provided web search results, write a comprehensive reply to the given query. Make sure to cite results using [[number](URL)] notation after the reference. If the provided search results refer to multiple subjects with the same name, write separate answers for each subject. and at last please provide your own insights.`
12 |
13 | export const commentSummaryPromptHightligt_bak = (rate: boolean) => {
14 | return rate
15 | ? `Give a summary of the reviews of this item according to the above, and add a rating.Your output should use the following template:
16 | #### Rating
17 | #### Review Summary`
18 | : `Summarize based on the comments above.Your output should use the following template:
19 | #### Comment Summary`
20 | }
21 |
22 | export const commentSummaryPromptHightligt = `Give a concise summary of the review content (perhaps a video, topic, or product), including both positive and negative points. If the review is about an item, give the pros, cons, ratings, and recommendations for buying the item.`
23 |
24 | export const customizePrompt = `Title: "{{Title}}"
25 | Transcript: "{{Transcript}}"`
26 |
27 | export const customizePromptSearch = `Web search results: {{Search Results}}`
28 |
29 | export const customizePromptPage = `Content: {{content}}`
30 |
31 | export const customizePromptComment = `Comments: {{comments}}`
32 |
33 | export const customizePrompt1 = `Your output should use the following template:
34 | #### Summary
35 | #### Highlights
36 | - [Emoji] Bulletpoint
37 |
38 | Your task is to summarise the text I have given you in up to seven concise bullet points, starting with a short highlight. Choose an appropriate emoji for each bullet point. Use the text above: {{Title}} {{Transcript}}.
39 | `
40 |
41 | export const customizePromptClickbait = `What is the clickbait likelihood of the title and transcript for this video? Please provide a score and a brief explanation for your score, the clickbait score is up to 10, if the clickbait score is less than 5 then answer: 👍 Clickbait Score : Low, otherwise answer: 👎 Clickbait Score : High.
42 |
43 | Example response:
44 | > The lower the Clickbait score, the better.
45 | #### Clickbait Score:
46 | 👍 Clickbait Score : Low or 👎 Clickbait Score : High
47 | #### Explanation:
48 | The title is a bit exaggerated.
49 | `
50 | export const customizePromptCommentAmazon = `Give a summary of the reviews of this item according to the above, and pros, cons, ratings.Your output should use the following template:
51 | #### Rating
52 | #### Review Summary
53 | #### Pros
54 | #### Cons
55 | `
56 |
57 | export const customizePromptCommentYoutube = `Give a summary of the comments for this video, including the different points of view.`
58 |
59 | export const replylanguagePrompt = (language: string) => {
60 | return `Please write in ${language} language.`
61 | }
62 |
63 | export const articlePrompt = ({
64 | title,
65 | url,
66 | content,
67 | language,
68 | prompt,
69 | }: {
70 | title: string
71 | url?: string
72 | content: string
73 | language: string
74 | prompt?: string
75 | }) => {
76 | return `Title: ${title}
77 | Content: ${content}
78 | Instructions: ${prompt ? prompt : articlePromptHighlight}
79 | ${replylanguagePrompt(language)}`
80 | }
81 |
82 | export const videoPrompt = ({
83 | title,
84 | transcript,
85 | language,
86 | prompt,
87 | }: {
88 | title: string
89 | transcript: string
90 | language: string
91 | prompt: string
92 | }) => {
93 | return `Title: ${title}
94 | Transcript: ${transcript}
95 | Instructions: ${prompt}
96 | ${replylanguagePrompt(language)}`
97 | }
98 |
99 | export const searchPrompt = ({
100 | query,
101 | results,
102 | language,
103 | prompt,
104 | }: {
105 | query: string
106 | results: string
107 | language: string
108 | prompt: string
109 | }) => {
110 | const date = new Date()
111 | const year = date.getFullYear()
112 | const month = date.getMonth() + 1
113 | const day = date.getDate()
114 |
115 | return `Web search results: ${results}
116 | Current date: ${year}/${month}/${day}
117 | Instructions: ${prompt}
118 | Query: ${query}
119 | ${replylanguagePrompt(language)}`
120 | }
121 |
122 | export const pageSummaryPrompt = ({
123 | content,
124 | language,
125 | prompt,
126 | }: {
127 | content: string
128 | language: string
129 | prompt?: string
130 | }) => {
131 | return `Content: ${content}
132 | Instructions: ${prompt ? prompt : pageSummaryPromptHighlight}
133 | ${replylanguagePrompt(language)}`
134 | }
135 |
136 | export const commentSummaryPrompt = ({
137 | content,
138 | language,
139 | prompt,
140 | rate,
141 | }: {
142 | content: string
143 | language: string
144 | prompt?: string
145 | rate?: string | null
146 | }) => {
147 | const isRate = !!(rate && rate !== '-1')
148 | return `Comments: ${content}
149 | ${isRate ? 'Customer Ratings:' + rate : ''}
150 | Instructions: ${prompt ? prompt : commentSummaryPromptHightligt}
151 | ${replylanguagePrompt(language)}`
152 | }
153 |
--------------------------------------------------------------------------------
/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import Browser from 'webextension-polyfill'
2 | import { Theme, BASE_URL } from '@/config'
3 |
4 | export const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
5 |
6 | export const isFirefox = navigator.userAgent.indexOf('Firefox') != -1
7 |
8 | export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
9 |
10 | export const AppName = 'Glarity-Summary for Google/YouTube (ChatGPT)'
11 |
12 | export function detectSystemColorScheme() {
13 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
14 | return Theme.Dark
15 | }
16 | return Theme.Light
17 | }
18 |
19 | export function getExtensionVersion() {
20 | return Browser.runtime.getManifest().version
21 | }
22 |
23 | export const changeToast: { type: 'success'; text: string } = {
24 | text: 'Changes saved',
25 | type: 'success',
26 | }
27 |
28 | export function tabSendMsg(tab) {
29 | const { id, url } = tab
30 | if (url.includes(`${BASE_URL}/chat`)) {
31 | Browser.tabs
32 | .sendMessage(id, { type: 'CHATGPT_TAB_CURRENT', data: { isLogin: true } })
33 | .catch(() => {})
34 | } else {
35 | Browser.tabs
36 | .sendMessage(id, { type: 'CHATGPT_TAB_CURRENT', data: { isLogin: false } })
37 | .catch(() => {})
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | prefix: 'glarity--',
4 | corePlugins: {
5 | preflight: false,
6 | },
7 | content: ['./src/**/*.tsx'],
8 | theme: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.ts", "**/*.tsx"],
3 | "exclude": ["node_modules"],
4 | "compilerOptions": {
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "module": "esnext",
7 | "target": "es2018",
8 | "allowJs": true,
9 | "esModuleInterop": true,
10 | "jsx": "react-jsx",
11 | "jsxImportSource": "preact",
12 | "noEmit": true,
13 | "skipLibCheck": true,
14 | "strict": true,
15 | "moduleResolution": "node",
16 | "noImplicitAny": false,
17 | "baseUrl": ".",
18 | "paths": {
19 | "@/*": ["src/*"]
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------