├── .eslintrc.js ├── .github └── workflows │ ├── chatgpthing-chrome-release.yml │ ├── chatgpthing-firefox-release.yml │ ├── release-please.yml │ ├── twitshorter-chrome-release.yml │ └── twitshorter-firefox-release.yml ├── .gitignore ├── .husky └── pre-push ├── .npmrc ├── .prettierrc.js ├── .release-please-manifest.json ├── .vscode └── settings.json ├── README.md ├── apps ├── chatgpthing │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── assets │ │ ├── Sora-Light.woff2 │ │ └── icon.png │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── background.ts │ │ ├── components │ │ │ ├── SpotlightAnswer.tsx │ │ │ ├── SpotlightBox.stories.tsx │ │ │ ├── SpotlightBox.tsx │ │ │ ├── SpotlightBoxContainer.tsx │ │ │ ├── SpotlightFooter.tsx │ │ │ ├── SpotlightForm.tsx │ │ │ └── SpotlightHeader.tsx │ │ ├── contents │ │ │ └── spotlight.tsx │ │ ├── style │ │ │ ├── font.css │ │ │ └── style.css │ │ ├── trpc │ │ │ ├── context.tsx │ │ │ └── router.ts │ │ └── utils │ │ │ ├── ExtensionPostMessageEvent.ts │ │ │ ├── getDocumentTextFromDOM.ts │ │ │ ├── installListener.ts │ │ │ └── toggleSpotlight.ts │ ├── tailwind.config.js │ └── tsconfig.json └── twitshorter │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── assets │ └── icon.png │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── background.ts │ ├── components │ │ ├── TwitShorterBox.tsx │ │ ├── TwitShorterBoxContainer.tsx │ │ ├── TwitShorterHeader.tsx │ │ ├── TwitShorterPrimaryForm.tsx │ │ └── TwitShorterSecondaryForm.tsx │ ├── contents │ │ └── twitshorter.tsx │ ├── style.css │ ├── trpc │ │ ├── context.tsx │ │ └── router.ts │ └── utils │ │ ├── ExtensionPostMessageEvent.ts │ │ └── installListener.ts │ ├── tailwind.config.js │ └── tsconfig.json ├── package.json ├── packages ├── chatgpt │ ├── components │ │ └── ChatGPTContext.tsx │ ├── package.json │ ├── trpc │ │ └── chatGPTRouter.ts │ ├── tsconfig.json │ └── utils │ │ ├── chatGPT.ts │ │ ├── fetchSSE.ts │ │ ├── router.ts │ │ └── streamAsyncIterable.ts ├── eslint-config-custom │ ├── index.js │ └── package.json ├── eslint-plugin-browser-extensions │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── no-new-storage.test.ts │ │ └── no-new-storage.ts │ └── tsconfig.json ├── trpc │ ├── browserRouter.ts │ ├── getTRPCContext.tsx │ ├── package.json │ ├── trpc.ts │ └── tsconfig.json ├── tsconfig │ ├── README.md │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json ├── ui │ ├── .eslintrc.js │ ├── .ladle │ │ └── config.mjs │ ├── HoverCard.tsx │ ├── Tooltip.tsx │ ├── button.tsx │ ├── chatgpt │ │ └── ChatGPTAuth.tsx │ ├── hook │ │ ├── useScrollPosition.tsx │ │ └── useWindowSize.ts │ ├── logo │ │ ├── ChromeLogo.tsx │ │ ├── FirefoxLogo.tsx │ │ └── GithubLogo.tsx │ ├── package.json │ ├── postcss.config.js │ ├── scroll-area.tsx │ ├── separator.tsx │ ├── shortcut.tsx │ ├── style │ │ ├── font.css │ │ └── style.css │ ├── tailwind.config.js │ ├── textarea.tsx │ └── tsconfig.json └── utils │ ├── cn.tsx │ ├── getBrowserNameFromNavigator.tsx │ ├── getExtensionShortcutURL.ts │ ├── getShadowRoot.ts │ ├── package.json │ ├── postMessageEvent.ts │ ├── tsconfig.json │ ├── waitFor.ts │ └── window.tsx ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── release-please-config.json ├── sites ├── web-chatgpthing │ ├── .eslintrc.js │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── app │ │ ├── head.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── icon.ico │ │ ├── images │ │ │ ├── demo1.png │ │ │ ├── logo-transparent.png │ │ │ ├── stars.svg │ │ │ ├── sun.svg │ │ │ └── waves.svg │ │ └── og.jpg │ ├── style │ │ └── globals.css │ ├── tailwind.config.js │ └── tsconfig.json └── web │ ├── .eslintrc.js │ ├── .vscode │ └── settings.json │ ├── CHANGELOG.md │ ├── README.md │ ├── app │ ├── [browserExtensionSlug] │ │ ├── head.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ └── layout.tsx │ ├── components │ ├── Footer.tsx │ └── analytics.tsx │ ├── config │ └── extensionList.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── images │ │ ├── logo │ │ │ ├── chatgpthing.png │ │ │ └── twitshorter.png │ │ └── stars.svg │ └── og │ │ ├── chatgpthing.png │ │ └── twitshorter.png │ ├── style │ ├── Frame 56 (4).png │ └── globals.css │ ├── tailwind.config.js │ └── tsconfig.json └── turbo.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // This tells ESLint to load the config from the package `eslint-config-custom` 4 | extends: ["custom"], 5 | settings: { 6 | next: { 7 | rootDir: ["apps/*/"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/chatgpthing-chrome-release.yml: -------------------------------------------------------------------------------- 1 | name: "chatgpthing:chrome:release" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | timeout-minutes: 3 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Cache pnpm modules 13 | uses: actions/cache@v3 14 | with: 15 | path: ~/.pnpm-store 16 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 17 | restore-keys: | 18 | ${{ runner.os }}- 19 | - uses: pnpm/action-setup@v2.2.4 20 | with: 21 | version: latest 22 | run_install: true 23 | - name: Use Node.js 16.x 24 | uses: actions/setup-node@v3.4.1 25 | with: 26 | node-version: 16.x 27 | cache: "pnpm" 28 | - name: Build the Chrome extension 29 | run: pnpm build --filter=./apps/chatgpthing 30 | - name: Package the extension into a zip artifact for chrome 31 | run: pnpm package --filter=./apps/chatgpthing 32 | - name: Browser Platform Publish 33 | uses: PlasmoHQ/bpp@v3 34 | with: 35 | keys: ${{ secrets.CHATGPTHING_CHROME_KEYS }} 36 | chrome-file: apps/chatgpthing/build/chrome-mv3-prod.zip 37 | -------------------------------------------------------------------------------- /.github/workflows/chatgpthing-firefox-release.yml: -------------------------------------------------------------------------------- 1 | name: "chatgpthing:firefox:release" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | timeout-minutes: 3 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Cache pnpm modules 13 | uses: actions/cache@v3 14 | with: 15 | path: ~/.pnpm-store 16 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 17 | restore-keys: | 18 | ${{ runner.os }}- 19 | - uses: pnpm/action-setup@v2.2.4 20 | with: 21 | version: latest 22 | run_install: true 23 | - name: Use Node.js 16.x 24 | uses: actions/setup-node@v3.4.1 25 | with: 26 | node-version: 16.x 27 | cache: "pnpm" 28 | - name: Package the extension into a zip artifact for firefox 29 | run: pnpm build:firefox -- --zip --filter=./apps/chatgpthing 30 | - name: Browser Platform Publish 31 | uses: PlasmoHQ/bpp@v3 32 | with: 33 | keys: ${{ secrets.CHATGPTHING_FIREFOX_KEYS }} 34 | firefox-file: apps/chatgpthing/build/firefox-prod.zip 35 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please: 8 | timeout-minutes: 2 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: google-github-actions/release-please-action@v3 12 | with: 13 | command: manifest 14 | token: ${{secrets.RELEASE_PLEASE_TOKEN}} 15 | -------------------------------------------------------------------------------- /.github/workflows/twitshorter-chrome-release.yml: -------------------------------------------------------------------------------- 1 | name: "twitshorter:chrome:release" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | timeout-minutes: 3 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Cache pnpm modules 13 | uses: actions/cache@v3 14 | with: 15 | path: ~/.pnpm-store 16 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 17 | restore-keys: | 18 | ${{ runner.os }}- 19 | - uses: pnpm/action-setup@v2.2.4 20 | with: 21 | version: latest 22 | run_install: true 23 | - name: Use Node.js 16.x 24 | uses: actions/setup-node@v3.4.1 25 | with: 26 | node-version: 16.x 27 | cache: "pnpm" 28 | - name: Build the Chrome extension 29 | run: pnpm build --filter=./apps/twitshorter 30 | - name: Package the extension into a zip artifact for chrome 31 | run: pnpm package --filter=./apps/twitshorter 32 | - name: Browser Platform Publish 33 | uses: PlasmoHQ/bpp@v3 34 | with: 35 | keys: ${{ secrets.TWITSHORTER_CHROME_KEYS }} 36 | chrome-file: apps/twitshorter/build/chrome-mv3-prod.zip 37 | -------------------------------------------------------------------------------- /.github/workflows/twitshorter-firefox-release.yml: -------------------------------------------------------------------------------- 1 | name: "twitshorter:firefox:release" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | timeout-minutes: 3 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Cache pnpm modules 13 | uses: actions/cache@v3 14 | with: 15 | path: ~/.pnpm-store 16 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 17 | restore-keys: | 18 | ${{ runner.os }}- 19 | - uses: pnpm/action-setup@v2.2.4 20 | with: 21 | version: latest 22 | run_install: true 23 | - name: Use Node.js 16.x 24 | uses: actions/setup-node@v3.4.1 25 | with: 26 | node-version: 16.x 27 | cache: "pnpm" 28 | - name: Package the extension into a zip artifact for firefox 29 | run: pnpm build:firefox -- --zip --filter=./apps/twitshorter 30 | - name: Browser Platform Publish 31 | uses: PlasmoHQ/bpp@v3 32 | with: 33 | keys: ${{ secrets.TWITSHORTER_FIREFOX_KEYS }} 34 | firefox-file: apps/twitshorter/build/firefox-prod.zip 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # turbo 34 | .turbo 35 | 36 | keys.json 37 | 38 | dist -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm lint && pnpm build 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | module.exports = { 5 | printWidth: 80, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: false, 9 | singleQuote: false, 10 | trailingComma: "none", 11 | bracketSpacing: true, 12 | bracketSameLine: true, 13 | plugins: [require.resolve("@plasmohq/prettier-plugin-sort-imports")], 14 | importOrder: ["^@plasmohq/(.*)$", "^~(.*)$", "^[./]"], 15 | importOrderSeparation: true, 16 | importOrderSortSpecifiers: true 17 | } 18 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps/chatgpthing": "0.1.3", 3 | "apps/twitshorter": "0.0.8", 4 | "sites/web": "0.0.7" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [{ "mode": "auto" }], 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "eslint.validate": ["javascript"], 7 | "editor.formatOnPaste": true, 8 | "workbench.editor.highlightModifiedTabs": true, 9 | "editor.acceptSuggestionOnEnter": "off", 10 | "files.eol": "\n" 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # browser-extensions monorepo 2 | 3 | This monorepo contains all the browser extensions I built and will build in the near future. 4 | 5 | It uses built using the incredible browser extension platform [Plasmo](https://plasmo.com). It uses [Next.js](https://nextjs.org/) for the landing page,[tailwind](https://tailwindcss.com/) and [shadcn/ui](https://github.com/shadcn/ui) components for the UI. The extension uses [trpc](https://trpc.io/) for Typesafe APIs with the help of [trpc-chrome](https://github.com/jlalmes/trpc-chrome). 6 | 7 | ## Note on Performance 8 | 9 | > **Warning** 10 | > This monorepo serves as an example building one or multiple browser extensions. 11 | 12 | > If you see something broken, you can ping me [@kevant01ne](https://twitter.com/kevant01ne). 13 | 14 | ## ChatGPThing 15 | 16 | Query ChatGPT in the context of the current page 17 | 18 | [Website](https://browser-apps.vercel.app/chatgpthing?utm_source=github-repo) 19 | 20 | [Install on Chrome](https://chrome.google.com/webstore/detail/chatgpthing/amiibkaljanlkpjljhlkgjdfemgkklbo?hl=en&authuser=0&utm_source=github-repo) 21 | 22 | [Install on Firefox](https://addons.mozilla.org/en-US/firefox/addon/chatgpthing/?utm_source=github-repo) 23 | 24 | [Onboarding](https://github.com/kant01ne/browser-extensions/tree/main/apps/chatgpthing#onboarding) 25 | 26 | 27 | 28 | ## TwitShorter 29 | 30 | Long tweets summarizer 31 | 32 | [Website](https://browser-apps.vercel.app/twitshorter?utm_source=github-repo) 33 | 34 | [Install on Chrome](https://chrome.google.com/webstore/detail/kdmhppbhoolbfkmfegijgkaffnpellhh/?hl=en&authuser=0&utm_source=github-repo) 35 | 36 | [Install on Firefox](https://addons.mozilla.org/en-US/firefox/addon/twitshorter/?utm_source=github-repo) 37 | 38 | [Onboarding](https://github.com/kant01ne/browser-extensions/tree/main/apps/twitshorter#onboarding) 39 | 40 | 41 | ## What's inside? 42 | 43 | This turborepo uses [pnpm](https://pnpm.io) as a package manager. It includes the following packages/sites/apps: 44 | 45 | ### Apps and Packages 46 | 47 | - `sites/web-chatgpthing`: a [Next.js](https://nextjs.org/) app that contains the source code of the [ChatGPThing landing page](https://chatgpthing.vercel.app/) 48 | - `apps/chatgpthing`: A Browser extension that lets you query ChatGPT on any website with the context of the current page. 49 | - `packages/ui`: a stub React component library shared between the apps and websites. 50 | - `eslint-config-custom`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`) 51 | - `tsconfig`: `tsconfig.json`s used throughout the monorepo 52 | 53 | Each package/app is 100% [TypeScript](https://www.typescriptlang.org/). 54 | 55 | ### Utilities 56 | 57 | This turborepo has some additional tools already setup for you: 58 | 59 | - [TypeScript](https://www.typescriptlang.org/) for static type checking 60 | - [ESLint](https://eslint.org/) for code linting 61 | - [Prettier](https://prettier.io) for code formatting 62 | 63 | ### Build 64 | 65 | To build all apps and packages, run the following command: 66 | 67 | ``` 68 | cd browser-extensions 69 | pnpm run build 70 | ``` 71 | 72 | ### Develop 73 | 74 | To develop all apps and packages, run the following command: 75 | 76 | ``` 77 | cd browser-extensions 78 | pnpm run dev 79 | ``` 80 | 81 | ### Remote Caching 82 | 83 | Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines. 84 | 85 | By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup), then enter the following commands: 86 | 87 | ``` 88 | cd my-turborepo 89 | pnpm dlx turbo login 90 | ``` 91 | 92 | This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview). 93 | 94 | Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your turborepo: 95 | 96 | ``` 97 | pnpm dlx turbo link 98 | ``` 99 | 100 | ## Useful Links 101 | 102 | Learn more about the power of Turborepo: 103 | 104 | - [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks) 105 | - [Caching](https://turbo.build/repo/docs/core-concepts/caching) 106 | - [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) 107 | - [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering) 108 | - [Configuration Options](https://turbo.build/repo/docs/reference/configuration) 109 | - [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference) 110 | -------------------------------------------------------------------------------- /apps/chatgpthing/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["custom"], 3 | root: true 4 | } 5 | -------------------------------------------------------------------------------- /apps/chatgpthing/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | #cache 13 | .turbo 14 | .next 15 | .vercel 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .pnpm-debug.log* 26 | 27 | 28 | # local env files 29 | .env* 30 | 31 | out/ 32 | build/ 33 | dist/ 34 | 35 | # plasmo - https://www.plasmo.com 36 | .plasmo 37 | 38 | # bpp - http://bpp.browser.market/ 39 | keys.json 40 | 41 | # typescript 42 | .tsbuildinfo 43 | -------------------------------------------------------------------------------- /apps/chatgpthing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.1.3](https://github.com/kant01ne/browser-extensions/compare/chatgpthing-v0.1.2...chatgpthing-v0.1.3) (2023-02-16) 4 | 5 | 6 | ### Reverts 7 | 8 | * **chatgpthing:** temporarily update package.json manifest key for _execute_action while releasing to firefox ([99c72cb](https://github.com/kant01ne/browser-extensions/commit/99c72cbbac4c6aa3fda09036dd78db91b5af3623)) 9 | 10 | ## [0.1.2](https://github.com/kant01ne/browser-extensions/compare/chatgpthing-v0.1.1...chatgpthing-v0.1.2) (2023-02-15) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **twitshorter, chatgpthing:** Update onboardings using extensions readmes ([10a7dcd](https://github.com/kant01ne/browser-extensions/commit/10a7dcd6908cd95c02a3ba69cb38d97e88ca0541)) 16 | 17 | ## [0.1.1](https://github.com/kant01ne/browser-extensions/compare/chatgpthing-v0.1.0...chatgpthing-v0.1.1) (2023-02-14) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * **twitshorter:** fix icon ([6068d2c](https://github.com/kant01ne/browser-extensions/commit/6068d2c4aab9a25083b453f903fb0198a24d026d)) 23 | 24 | ## [0.1.0](https://github.com/kant01ne/browser-extensions/compare/chatgpthing-v0.0.4...chatgpthing-v0.1.0) (2023-02-14) 25 | 26 | 27 | ### Features 28 | 29 | * **twitshorter:** Implementation of TwitShorter extension ([fc5ce7a](https://github.com/kant01ne/browser-extensions/commit/fc5ce7a45fbff79ca31770c4cc1088c5efe5143b)) 30 | 31 | 32 | ### Reverts 33 | 34 | * **chatgpthing:** workaround - add _browser to package.json for shortcut withD mv2 firefox ([5e169af](https://github.com/kant01ne/browser-extensions/commit/5e169af79e510b29fb2ec956f64d724810244411)) 35 | 36 | ## [0.0.4](https://github.com/kant01ne/browser-extensions/compare/chatgpthing-v0.0.3...chatgpthing-v0.0.4) (2023-02-09) 37 | 38 | 39 | ### Features 40 | 41 | * **chatgpthing:** Maybe bump version on please-release PR ? ([512b57d](https://github.com/kant01ne/browser-extensions/commit/512b57dd150be4b3b7ff61f026eb07f6798b319a)) 42 | * **chatgpthing:** Maybe bump version on please-release PR ? ([49d12cf](https://github.com/kant01ne/browser-extensions/commit/49d12cff6592749f97492779b55bf9c50042da50)) 43 | * **web-chatgpthing:** Landing page ([a5578fa](https://github.com/kant01ne/browser-extensions/commit/a5578fa3853454770ec7501e5599b01e2f1e4c48)) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * **chatgpthing:** Only adding comments - trying to bump the package version with release-please ([410f1ea](https://github.com/kant01ne/browser-extensions/commit/410f1ea7112b029eef3c768cc0848724d09a4d41)) 49 | * **chatgpthing:** use px instead of rem ([d83e26f](https://github.com/kant01ne/browser-extensions/commit/d83e26f26303eb23bb4c87d07fe1730858160bf0)) 50 | 51 | 52 | ### Miscellaneous Chores 53 | 54 | * **chatgpthing:** release 0.0.3 ([bb789fb](https://github.com/kant01ne/browser-extensions/commit/bb789fbe89516bc862fd91830066752dc000e8eb)) 55 | * **chatgpthing:** release 0.0.4 ([c85b6b2](https://github.com/kant01ne/browser-extensions/commit/c85b6b2c474cc8c45abed80ee50fc3045f956dcd)) 56 | 57 | ## 0.0.3 (2023-02-08) 58 | 59 | 60 | ### Features 61 | 62 | * **chatgpthing:** Maybe bump version on please-release PR ? ([512b57d](https://github.com/kant01ne/browser-extensions/commit/512b57dd150be4b3b7ff61f026eb07f6798b319a)) 63 | * **chatgpthing:** Maybe bump version on please-release PR ? ([49d12cf](https://github.com/kant01ne/browser-extensions/commit/49d12cff6592749f97492779b55bf9c50042da50)) 64 | * **web-chatgpthing:** Landing page ([a5578fa](https://github.com/kant01ne/browser-extensions/commit/a5578fa3853454770ec7501e5599b01e2f1e4c48)) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * **chatgpthing:** use px instead of rem ([d83e26f](https://github.com/kant01ne/browser-extensions/commit/d83e26f26303eb23bb4c87d07fe1730858160bf0)) 70 | 71 | 72 | ### Miscellaneous Chores 73 | 74 | * **chatgpthing:** release 0.0.3 ([bb789fb](https://github.com/kant01ne/browser-extensions/commit/bb789fbe89516bc862fd91830066752dc000e8eb)) 75 | -------------------------------------------------------------------------------- /apps/chatgpthing/README.md: -------------------------------------------------------------------------------- 1 | ## ChatGPThing 2 | 3 | Query ChatGPT in the context of the current page 4 | 5 | [Website](https://browser-apps.vercel.app/chatgpthing?utm_source=github-repo) 6 | 7 | [Install on Chrome](https://chrome.google.com/webstore/detail/chatgpthing/amiibkaljanlkpjljhlkgjdfemgkklbo?hl=en&authuser=0&utm_source=github-repo) 8 | 9 | [Install on Firefox](https://addons.mozilla.org/en-US/firefox/addon/chatgpthing/?utm_source=github-repo) 10 | 11 | 12 | 13 | ## Getting Started 14 | 15 | First, run the development server: 16 | 17 | ```bash 18 | pnpm dev 19 | # or 20 | npm run dev 21 | ``` 22 | 23 | Open your browser and load the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`. 24 | 25 | You can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. To add an options page, simply add a `options.tsx` file to the root of the project, with a react component default exported. Likewise to add a content page, add a `content.ts` file to the root of the project, importing some module and do some logic, then reload the extension on your browser. 26 | 27 | For further guidance, [visit our Documentation](https://docs.plasmo.com/) 28 | 29 | ## Contribution 30 | 31 | This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo). 32 | 33 | ## Making production build 34 | 35 | Run the following: 36 | 37 | ```bash 38 | pnpm build 39 | # or 40 | npm run build 41 | ``` 42 | 43 | This should create a production bundle for your extension, ready to be zipped and published to the stores. 44 | 45 | ## Submit to the webstores 46 | 47 | The easiest way to deploy your Plasmo extension is to use the built-in [bpp](https://bpp.browser.market) GitHub action. Prior to using this action however, make sure to build your extension and upload the first version to the store to establish the basic credentials. Then, simply follow [this setup instruction](https://docs.plasmo.com/framework/workflows/submit) and you should be on your way for automated submission! 48 | 49 | # Onboarding 50 | 51 | ## Welcome to Chatgpthing! 52 | 53 | Thanks for installing Chatgpthing! 54 | 55 | To get started, use the keyboard shortcut (`Alt+Z` or `Option+Z` by default) to open the tool on any page. 56 | 57 | On Windows: 58 | 59 | 60 | 61 | On MacOS: 62 | 63 | 64 | 65 | Try it here! 66 | 67 | 68 | 69 | You can easily update the keyboard shortcut if you prefer a different one: 70 | 71 | 72 | 73 | Alternatively, you can also open the tool by clicking on the extension icon in the toolbar. 74 | 75 | Make sure to pin the extension icon to your toolbar for easy access! 76 | 77 | 78 | 79 | ## Authenticating with open AI 80 | 81 | To use the tool, you need to authenticate with Open AI. Click on the "link" in the box to login or signup to open AI. 82 | 83 | From time to time, you may need to re-authenticate with Open AI or pass Cloudflare checks. These limitations are imposed by open AI. 84 | You can do so by clicking on the "link" in the box. 85 | 86 | 87 | 88 | ## Using the tool 89 | 90 | Once you are authenticated, you can start using the tool. Simply type in your query and press enter to get a response from open AI. 91 | 92 | The context of the page is automatically added to your query. 93 | 94 | 95 | -------------------------------------------------------------------------------- /apps/chatgpthing/assets/Sora-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kant01ne/browser-extensions/e878c96675cf6697ca4db14c479754704be386fe/apps/chatgpthing/assets/Sora-Light.woff2 -------------------------------------------------------------------------------- /apps/chatgpthing/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kant01ne/browser-extensions/e878c96675cf6697ca4db14c479754704be386fe/apps/chatgpthing/assets/icon.png -------------------------------------------------------------------------------- /apps/chatgpthing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpthing", 3 | "displayName": "ChatGPThing", 4 | "version": "0.1.3", 5 | "description": "Query ChatGPT in the context of the current page", 6 | "author": "kant01ne", 7 | "scripts": { 8 | "lint": "eslint --ext .ts,.tsx,.js,.jsx ./src", 9 | "dev": "plasmo dev", 10 | "build": "plasmo build", 11 | "build:firefox": "plasmo build --target=firefox", 12 | "dev:firefox": "plasmo dev --target=firefox", 13 | "package": "plasmo package" 14 | }, 15 | "dependencies": { 16 | "@hookform/resolvers": "^2.9.10", 17 | "@plasmohq/storage": "^1.0.0", 18 | "@radix-ui/react-scroll-area": "^1.0.2", 19 | "@radix-ui/react-separator": "^1.0.1", 20 | "@tanstack/react-query": "^4.24.4", 21 | "@trpc/client": "^10.9.1", 22 | "@trpc/react-query": "^10.9.1", 23 | "@trpc/server": "^10.9.1", 24 | "chatgpt": "workspace:*", 25 | "class-variance-authority": "^0.4.0", 26 | "clsx": "^1.2.1", 27 | "eventsource-parser": "^0.1.0", 28 | "expiry-map": "^2.0.0", 29 | "lodash-es": "^4.17.21", 30 | "lucide-react": "^0.108.0", 31 | "plasmo": "0.64.2", 32 | "react": "18.2.0", 33 | "react-dom": "18.2.0", 34 | "react-hook-form": "^7.43.0", 35 | "react-markdown": "^8.0.5", 36 | "react-wrap-balancer": "^0.4.0", 37 | "tailwind-merge": "^1.9.0", 38 | "tailwindcss-animate": "^1.0.5", 39 | "trpc": "workspace:*", 40 | "trpc-chrome": "^1.0.0", 41 | "tsconfig": "workspace:*", 42 | "ui": "workspace:*", 43 | "utils": "workspace:*", 44 | "uuid": "^9.0.0", 45 | "webextension-polyfill": "^0.10.0", 46 | "zod": "^3.20.2" 47 | }, 48 | "devDependencies": { 49 | "@ladle/react": "^2.5.2", 50 | "@plasmohq/prettier-plugin-sort-imports": "3.6.1", 51 | "@tailwindcss/typography": "^0.5.9", 52 | "@types/chrome": "0.0.210", 53 | "@types/node": "18.11.18", 54 | "@types/react": "18.0.27", 55 | "@types/react-dom": "18.0.10", 56 | "@types/uuid": "^9.0.0", 57 | "@types/webextension-polyfill": "^0.10.0", 58 | "@typescript-eslint/eslint-plugin": "^5.50.0", 59 | "@typescript-eslint/parser": "^5.50.0", 60 | "autoprefixer": "^10.4.13", 61 | "eslint": "^8.33.0", 62 | "eslint-config-prettier": "^8.6.0", 63 | "eslint-import-resolver-typescript": "^3.5.3", 64 | "eslint-config-custom": "workspace:*", 65 | "eslint-plugin-import": "^2.27.5", 66 | "eslint-plugin-react": "^7.32.2", 67 | "eslint-plugin-sort-keys-fix": "^1.1.2", 68 | "postcss": "^8.4.21", 69 | "prettier": "2.8.3", 70 | "tailwindcss": "^3.2.4", 71 | "typescript": "^4.9.4" 72 | }, 73 | "manifest": { 74 | "permissions": [ 75 | "scripting" 76 | ], 77 | "host_permissions": [ 78 | "" 79 | ], 80 | "browser_specific_settings": { 81 | "gecko": { 82 | "id": "$GECKO_ID" 83 | } 84 | }, 85 | "commands": { 86 | "_execute_action": { 87 | "suggested_key": { 88 | "windows": "Alt+Z", 89 | "mac": "Alt+Z", 90 | "chromeos": "Alt+Z", 91 | "linux": "Alt+Z" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /apps/chatgpthing/postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('postcss').ProcessOptions} 3 | */ 4 | module.exports = { 5 | plugins: { 6 | autoprefixer: {}, 7 | tailwindcss: {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/background.ts: -------------------------------------------------------------------------------- 1 | import { appRouter } from "@/trpc/router" 2 | import { installListener } from "@/utils/installListener" 3 | import { toggleSpotlight } from "@/utils/toggleSpotlight" 4 | import { createChromeHandler } from "trpc-chrome/adapter" 5 | import browser from "webextension-polyfill" 6 | 7 | createChromeHandler({ 8 | createContext: ({ req }) => ({ 9 | port: req 10 | }), 11 | router: appRouter 12 | }) 13 | 14 | const handleActionClicked: Parameters< 15 | typeof browser.action.onClicked.addListener 16 | >[0] = async ({ id }) => toggleSpotlight(id) 17 | 18 | ;(browser.action || browser.browserAction).onClicked.addListener( 19 | handleActionClicked 20 | ) 21 | 22 | browser.runtime.onInstalled.addListener(installListener) 23 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/components/SpotlightAnswer.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import React from "react" 3 | import ReactMarkdown from "react-markdown" 4 | import { Button } from "ui/button" 5 | import { ScrollArea } from "ui/scroll-area" 6 | import { RADIX_SCROLL_AREA_SELECTOR, Separator } from "ui/separator" 7 | 8 | export const SpotlightAnswer: React.FC< 9 | React.ComponentProps<"div"> & { 10 | answer?: string 11 | } 12 | > = ({ answer, ...props }) => { 13 | /* 14 | * Refs. 15 | */ 16 | const scrollAreaRef = React.useRef(null) 17 | 18 | /* 19 | * State. 20 | */ 21 | const [copiedToClipboard, setCopiedToClipboard] = React.useState(false) 22 | React.useState(0) 23 | const [hasScrolledUpManually, setHasScrolledUpManually] = 24 | React.useState(false) 25 | 26 | React.useEffect(() => { 27 | if (answer && answer.length && !hasScrolledUpManually) { 28 | const container = scrollAreaRef.current?.querySelector( 29 | RADIX_SCROLL_AREA_SELECTOR 30 | ) 31 | 32 | container?.scrollTo({ behavior: "smooth", top: 10000 }) 33 | } 34 | }, [answer, hasScrolledUpManually]) 35 | 36 | const hasAnswer = React.useMemo(() => answer && answer?.length > 0, [answer]) 37 | React.useEffect(() => { 38 | if (hasScrolledUpManually) return 39 | const container = scrollAreaRef.current?.querySelector( 40 | RADIX_SCROLL_AREA_SELECTOR 41 | ) 42 | 43 | if (!container) return 44 | 45 | container.addEventListener("wheel", checkHasScrolledUpManually) 46 | 47 | function checkHasScrolledUpManually(event: Event & { deltaY?: number }) { 48 | if (event.deltaY && event.deltaY < 0) { 49 | setHasScrolledUpManually(true) 50 | return 51 | } 52 | } 53 | 54 | return () => { 55 | container.removeEventListener("wheel", checkHasScrolledUpManually) 56 | } 57 | }, [hasAnswer, hasScrolledUpManually]) 58 | 59 | /* 60 | * Render. 61 | */ 62 | return ( 63 |
64 |
65 |
66 |
67 | 68 | ChatGPThing 69 | 70 | 71 | 92 |
93 |
104 | 108 | {answer ? ( 109 | 110 | {answer} 111 | 112 | ) : ( 113 |
114 |
115 | Nothing here yet... 116 |
117 |
118 | )} 119 |
120 |
121 |
122 |
123 | 124 |
125 | ) 126 | } 127 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/components/SpotlightBox.stories.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { SpotlightBox } from "./SpotlightBox" 3 | 4 | import "@/style/font.css" 5 | import "@/style/style.css" 6 | 7 | import type { Story } from "@ladle/react" 8 | import React from "react" 9 | import { ChatGPTAuth } from "ui/chatgpt/ChatGPTAuth" 10 | import { Separator } from "ui/separator" 11 | 12 | import { SpotlightAnswer } from "./SpotlightAnswer" 13 | import { SpotlightFooter } from "./SpotlightFooter" 14 | import { SpotlightForm } from "./SpotlightForm" 15 | import { SpotlightHeader } from "./SpotlightHeader" 16 | 17 | /* 18 | * Common. 19 | */ 20 | 21 | const LadleBase: React.FC> = ({ 22 | children 23 | }) => { 24 | return ( 25 |
26 | {children} 27 |
28 | ) 29 | } 30 | 31 | const args = { 32 | answer: 33 | "This is a test answer. Go to [Google](https://google.com). Example code \n```npm install blob```\n", 34 | className: "w-[25vw] max-h-[calc(100vh-12opx)] max-w-[18.5rem]", 35 | disabled: false, 36 | isAuthenticated: true, 37 | isLoading: false, 38 | shortcut: "⌥Z" 39 | } 40 | 41 | const argTypes = { 42 | handleClose: { 43 | action: "handleClose" 44 | }, 45 | handleShortcutUpdate: { 46 | action: "handleShortcutUpdate" 47 | }, 48 | handleSubmit: { 49 | action: "handleSubmit" 50 | } 51 | } 52 | 53 | const getTemplate: () => Story< 54 | React.ComponentPropsWithoutRef & 55 | React.ComponentProps<"div"> & 56 | Pick< 57 | React.ComponentPropsWithoutRef, 58 | | "defaultPrompt" 59 | | "placeholder" 60 | | "handleSubmit" 61 | | "isDisabled" 62 | | "isAuthenticated" 63 | | "isLoading" 64 | | "isOnboarding" 65 | > & 66 | Pick< 67 | React.ComponentPropsWithoutRef, 68 | "handleAuthClick" 69 | > & 70 | Pick< 71 | React.ComponentPropsWithoutRef, 72 | "handleShortcutUpdate" 73 | > & { 74 | answer?: string 75 | shortcut?: string 76 | className?: string 77 | handleClose?: React.MouseEventHandler 78 | } 79 | > = 80 | () => 81 | ({ 82 | answer, 83 | className, 84 | defaultPrompt, 85 | handleAuthClick, 86 | handleClose, 87 | handleShortcutUpdate, 88 | handleSubmit, 89 | isDisabled, 90 | isOnboarding, 91 | isAuthenticated, 92 | isLoading, 93 | shortcut, 94 | ...props 95 | }) => { 96 | return ( 97 | 98 | 99 | 100 | 101 | 106 | 111 | 112 | 117 | 118 | 119 | ) 120 | } 121 | 122 | /* 123 | * Default. 124 | */ 125 | export const Default = getTemplate() 126 | 127 | Default.args = { 128 | ...args, 129 | answer: undefined 130 | } 131 | 132 | Default.argTypes = { 133 | ...argTypes 134 | } 135 | 136 | /* 137 | * NoShortcut. 138 | */ 139 | export const NoShortcut = getTemplate() 140 | 141 | NoShortcut.args = { 142 | ...args, 143 | shortcut: undefined 144 | } 145 | 146 | NoShortcut.argTypes = { 147 | ...argTypes 148 | } 149 | 150 | /* 151 | * CmdAndShiftAndZShorcut. 152 | */ 153 | export const CmdAndShiftAndZShorcut = getTemplate() 154 | 155 | CmdAndShiftAndZShorcut.args = { 156 | ...args, 157 | shortcut: "⌘⇧Z" 158 | } 159 | 160 | CmdAndShiftAndZShorcut.argTypes = { 161 | ...argTypes 162 | } 163 | 164 | /* 165 | * CtrlAndShiftAndEShorcut. 166 | */ 167 | export const CtrlAndShiftAndEShorcut = getTemplate() 168 | 169 | CtrlAndShiftAndEShorcut.args = { 170 | ...args, 171 | shortcut: "⌃⇧E" 172 | } 173 | 174 | CtrlAndShiftAndEShorcut.argTypes = { 175 | ...argTypes 176 | } 177 | 178 | /* 179 | * NeedAuthentication. 180 | */ 181 | export const NeedAuthentication = getTemplate() 182 | 183 | NeedAuthentication.args = { 184 | ...args, 185 | answer: undefined, 186 | isAuthenticated: false 187 | } 188 | 189 | NeedAuthentication.argTypes = { 190 | ...argTypes 191 | } 192 | 193 | /* 194 | * NeedAuthentication. 195 | */ 196 | export const Loading = getTemplate() 197 | 198 | Loading.args = { 199 | ...args, 200 | isLoading: true 201 | } 202 | 203 | Loading.argTypes = { 204 | ...argTypes 205 | } 206 | 207 | /* 208 | * LongAnswer. 209 | */ 210 | export const LongAnswer = getTemplate() 211 | 212 | const answer = ` 213 | Here is a step-by-step guide to create a Chrome extension from scratch: 214 | 215 | Decide on the functionality of the extension: What does your extension do? What problem does it solve? 216 | 217 | Create a new directory for your extension, and within it create the following files: 218 | 219 | manifest.json: This file contains the metadata for your extension, including its name, description, version number, and permissions. 220 | popup.html: This is the HTML file for the popup window that will appear when your extension icon is clicked. 221 | popup.css: This is the CSS file for the popup window. 222 | \`popup.js\`: This is the JavaScript file for the popup window. 223 | In the manifest.json file, include the following code: 224 | 225 | \`\`\`json 226 | { 227 | "manifest_version": 2, 228 | "name": "My Extension", 229 | "description": "This is my first Chrome extension.", 230 | "version": "1.0", 231 | "browser_action": { 232 | "default_popup": "popup.html" 233 | }, 234 | "permissions": [ 235 | "activeTab" 236 | ] 237 | } 238 | \`\`\` 239 | Write the HTML, CSS, and JavaScript code for the popup window. 240 | 241 | Load the extension into Chrome: 242 | 243 | Open Chrome, and click on the three dots in the upper right corner. 244 | Select "More tools" and then "Extensions". 245 | Turn on "Developer mode" in the top right corner. 246 | Click "Load unpacked" and select the directory for your extension. 247 | Test your extension by clicking on the icon in the browser toolbar. 248 | 249 | Make any necessary changes and repeat steps 5 and 6 until your extension is complete. 250 | ` 251 | 252 | LongAnswer.args = { 253 | ...args, 254 | answer, 255 | className: args.className + " fixed top-20 " 256 | } 257 | 258 | LongAnswer.argTypes = { 259 | ...argTypes 260 | } 261 | 262 | /* 263 | WithOnboardingTick. 264 | */ 265 | export const WithOnboardingTick = getTemplate() 266 | 267 | WithOnboardingTick.args = { 268 | ...args, 269 | answer: answer.slice(0, answer.length / 5), 270 | isOnboarding: true 271 | } 272 | 273 | WithOnboardingTick.argTypes = { 274 | ...argTypes 275 | } 276 | 277 | /* 278 | * MiddleAnswer. 279 | */ 280 | export const MiddleAnswer = getTemplate() 281 | 282 | MiddleAnswer.args = { 283 | ...args, 284 | answer: answer.slice(0, answer.length / 5), 285 | className: args.className + " fixed top-20 " 286 | } 287 | 288 | MiddleAnswer.argTypes = { 289 | ...argTypes 290 | } 291 | 292 | /* 293 | * StreamingAnswer. 294 | */ 295 | export const StreamingAnswer: Story< 296 | React.ComponentPropsWithoutRef & 297 | React.ComponentProps<"div"> & 298 | Pick< 299 | React.ComponentPropsWithoutRef, 300 | | "defaultPrompt" 301 | | "placeholder" 302 | | "handleSubmit" 303 | | "isDisabled" 304 | | "isAuthenticated" 305 | | "isLoading" 306 | | "isOnboarding" 307 | > & 308 | Pick< 309 | React.ComponentPropsWithoutRef, 310 | "handleAuthClick" 311 | > & 312 | Pick< 313 | React.ComponentPropsWithoutRef, 314 | "handleShortcutUpdate" 315 | > & { 316 | answer?: string 317 | shortcut?: string 318 | className?: string 319 | handleClose?: React.MouseEventHandler 320 | } 321 | > = ({ 322 | answer: defaultAnswer, 323 | className, 324 | defaultPrompt, 325 | handleAuthClick, 326 | handleClose, 327 | handleShortcutUpdate, 328 | handleSubmit, 329 | isDisabled, 330 | isOnboarding, 331 | isAuthenticated, 332 | isLoading, 333 | shortcut, 334 | ...props 335 | }) => { 336 | const [answer, setAnswer] = React.useState("") 337 | React.useEffect(() => { 338 | const interval = setInterval(() => { 339 | if (!defaultAnswer) return 340 | setAnswer((old) => defaultAnswer.slice(0, old.length + 8)) 341 | }, 200) 342 | return () => clearInterval(interval) 343 | }, [defaultAnswer]) 344 | 345 | return ( 346 | 347 | 348 | 349 | 350 | 355 | 359 | 360 | 365 | 366 | 367 | ) 368 | } 369 | 370 | StreamingAnswer.args = { 371 | ...args, 372 | answer 373 | } 374 | 375 | StreamingAnswer.argTypes = { 376 | ...argTypes 377 | } 378 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/components/SpotlightBox.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import { X } from "lucide-react" 3 | import React from "react" 4 | import { Button } from "ui/button" 5 | 6 | // Shared base styles bu not applied directly to the component as we want to control its size from the containers. 7 | export const SpotlightBoxContainerClassName = 8 | "w-[90vw] md:w-[50vw] w-[25vw] min-w-[320px] max-h-[calc(100vh-120px)] max-w-[420px]" 9 | 10 | export const SpotlightBox: React.FC< 11 | React.ComponentProps<"div"> & { 12 | answer?: string 13 | className?: string 14 | handleClose?: React.MouseEventHandler 15 | } 16 | > = ({ className, children, handleClose, ...props }) => { 17 | /* 18 | * Render. 19 | */ 20 | return ( 21 |
27 | {handleClose ? ( 28 | 35 | ) : null} 36 | {children} 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/components/SpotlightBoxContainer.tsx: -------------------------------------------------------------------------------- 1 | import { SpotlightAnswer } from "@/components/SpotlightAnswer" 2 | import { 3 | SpotlightBox, 4 | SpotlightBoxContainerClassName 5 | } from "@/components/SpotlightBox" 6 | import { SpotlightFooter } from "@/components/SpotlightFooter" 7 | import { SpotlightForm } from "@/components/SpotlightForm" 8 | import { SpotlightHeader } from "@/components/SpotlightHeader" 9 | import { useTRPC } from "@/trpc/context" 10 | import { getDocumentTextFromDOM } from "@/utils/getDocumentTextFromDOM" 11 | import { useMutation, useQuery } from "@tanstack/react-query" 12 | import { useChatGPT } from "chatgpt/components/ChatGPTContext" 13 | import { clsx } from "clsx" 14 | import React from "react" 15 | import { ChatGPTAuth } from "ui/chatgpt/ChatGPTAuth" 16 | import { Separator } from "ui/separator" 17 | import { getBrowserNameFromNavigator } from "utils/getBrowserNameFromNavigator" 18 | 19 | const placeholder = "Summarize this page." 20 | 21 | export const SpotlightBoxContainer: React.FC< 22 | React.ComponentProps<"div"> & { 23 | handleClose: React.MouseEventHandler 24 | } 25 | > = ({ handleClose, className, ...props }) => { 26 | /* 27 | * Hooks. 28 | */ 29 | 30 | const { trpc } = useTRPC() 31 | const { isAuthenticated, answer } = useChatGPT() 32 | 33 | /* 34 | * Queries. 35 | */ 36 | 37 | const { data: browserCommand } = useQuery({ 38 | queryFn: async () => { 39 | return await trpc.browser.browserCommand.query() 40 | }, 41 | queryKey: ["browserCommand"] 42 | }) 43 | 44 | /* 45 | * Mutations. 46 | */ 47 | const getChatGPTAnswerMutation = useMutation({ 48 | mutationFn: ({ prompt }: { prompt?: string }) => 49 | trpc.chatGPT.getAnswer.mutate({ 50 | prompt: prompt && prompt?.length > 0 ? prompt : placeholder, 51 | text: getDocumentTextFromDOM() 52 | }) 53 | }) 54 | 55 | /* 56 | * Render. 57 | */ 58 | return ( 59 | 68 | 69 | 70 | getChatGPTAnswerMutation.mutate(args)} 72 | isAuthenticated={isAuthenticated} 73 | isLoading={getChatGPTAnswerMutation.isLoading} 74 | /> 75 | { 78 | e.preventDefault() 79 | await trpc.browser.openOpenAIAuthPage.mutate() 80 | }} 81 | isAuthenticated={isAuthenticated} 82 | /> 83 | 84 | { 87 | e.preventDefault() 88 | await trpc.browser.openShortcutPage.mutate({ 89 | browser: getBrowserNameFromNavigator() 90 | }) 91 | }} 92 | shortcut={browserCommand?.[0]?.shortcut} 93 | /> 94 | 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/components/SpotlightFooter.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import React from "react" 3 | import { Shortcut } from "ui/shortcut" 4 | import { getBrowserNameFromNavigator } from "utils/getBrowserNameFromNavigator" 5 | import { getExtensionShortcutURL } from "utils/getExtensionShortcutURL" 6 | 7 | export const SpotlightFooter: React.FC< 8 | React.ComponentProps<"a"> & { 9 | shortcut?: string 10 | handleShortcutUpdate?: React.MouseEventHandler 11 | } 12 | > = ({ shortcut, handleShortcutUpdate, className, ...props }) => { 13 | return ( 14 | 26 | {shortcut ? ( 27 | <> 28 | to toggle 29 | 30 | ) : ( 31 | "Add a keyboard shortcut to toggle this tool" 32 | )} 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /apps/chatgpthing/src/components/SpotlightForm.tsx: -------------------------------------------------------------------------------- 1 | import { zodResolver } from "@hookform/resolvers/zod" 2 | import { clsx } from "clsx" 3 | import { Loader2 } from "lucide-react" 4 | import React from "react" 5 | import { SubmitHandler, useForm } from "react-hook-form" 6 | import { Button } from "ui/button" 7 | import { Textarea } from "ui/textarea" 8 | import { z } from "zod" 9 | 10 | const promptSchema = z.object({ 11 | prompt: z.string() 12 | }) 13 | 14 | type SpotlightFormData = z.infer 15 | 16 | export const SpotlightForm: React.FC< 17 | React.ComponentProps<"form"> & { 18 | placeholder?: string 19 | handleKeyDown?: React.KeyboardEventHandler 20 | handleSubmit: SubmitHandler<{ 21 | prompt?: string 22 | }> 23 | isAuthenticated?: boolean 24 | isDisabled?: boolean 25 | isOnboarding?: boolean 26 | isLoading?: boolean 27 | defaultPrompt?: string 28 | } 29 | > = ({ 30 | className, 31 | placeholder = "Summarize this page.", 32 | handleKeyDown, 33 | handleSubmit, 34 | isAuthenticated, 35 | isDisabled, 36 | isLoading, 37 | isOnboarding = false, 38 | defaultPrompt, 39 | ...props 40 | }) => { 41 | const { handleSubmit: handleSubmitHook, register } = 42 | useForm({ 43 | defaultValues: { 44 | prompt: defaultPrompt || "" 45 | }, 46 | resolver: zodResolver(promptSchema) 47 | }) 48 | 49 | /* 50 | * Render. 51 | */ 52 | return ( 53 |
54 |