├── .eslintrc.json ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug-report---问题报告.md │ └── feature-request---新功能请求.md ├── dependabot.yml └── workflows │ ├── pr-tests.yml │ ├── pre-release-build.yml │ ├── scripts │ └── verify-search-engine-configs.mjs │ ├── tagged-release.yml │ └── verify-configs.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CURRENT_CHANGE.md ├── LICENSE ├── README.md ├── README_IN.md ├── README_JA.md ├── README_TR.md ├── README_ZH.md ├── build.mjs ├── package-lock.json ├── package.json ├── safari ├── appdmg.json ├── build.sh ├── export-options.plist ├── project.patch ├── project.pre.patch └── project_developer.patch ├── screenshots ├── preview_github_rightclickmenu.jpg ├── preview_google_floatingwindow_conversationbranch.jpg ├── preview_independentpanel.jpg ├── preview_reddit_selectiontools.jpg ├── preview_settings.jpg └── preview_youtube.jpg └── src ├── _locales ├── de │ └── main.json ├── en │ └── main.json ├── es │ └── main.json ├── fr │ └── main.json ├── i18n-react.mjs ├── i18n.mjs ├── in │ └── main.json ├── it │ └── main.json ├── ja │ └── main.json ├── ko │ └── main.json ├── pt │ └── main.json ├── resources.mjs ├── ru │ └── main.json ├── tr │ └── main.json ├── zh-hans │ └── main.json └── zh-hant │ └── main.json ├── background ├── commands.mjs ├── index.mjs └── menus.mjs ├── components ├── ConfirmButton │ └── index.jsx ├── ConversationCard │ └── index.jsx ├── ConversationItem │ └── index.jsx ├── CopyButton │ └── index.jsx ├── DecisionCard │ └── index.jsx ├── DeleteButton │ └── index.jsx ├── FeedbackForChatGPTWeb │ └── index.jsx ├── FloatingToolbar │ └── index.jsx ├── InputBox │ └── index.jsx ├── MarkdownRender │ ├── Hyperlink.jsx │ ├── Pre.jsx │ ├── markdown-without-katex.jsx │ ├── markdown.jsx │ └── mykatex.min.css ├── ReadButton │ └── index.jsx ├── WebJumpBackNotification │ └── index.jsx └── index.mjs ├── config ├── index.mjs └── language.mjs ├── content-script ├── index.jsx ├── menu-tools │ └── index.mjs ├── selection-tools │ └── index.mjs ├── site-adapters │ ├── arxiv │ │ └── index.mjs │ ├── baidu │ │ └── index.mjs │ ├── bilibili │ │ └── index.mjs │ ├── brave │ │ └── index.mjs │ ├── duckduckgo │ │ └── index.mjs │ ├── followin │ │ └── index.mjs │ ├── github │ │ └── index.mjs │ ├── gitlab │ │ └── index.mjs │ ├── index.mjs │ ├── juejin │ │ └── index.mjs │ ├── quora │ │ └── index.mjs │ ├── reddit │ │ └── index.mjs │ ├── stackoverflow │ │ └── index.mjs │ ├── weixin │ │ └── index.mjs │ ├── youtube │ │ └── index.mjs │ └── zhihu │ │ └── index.mjs └── styles.scss ├── fonts ├── SLXVc1nY6HkvangtZmpQdkhzfH5lkSscQyyS4J0.woff2 ├── SLXVc1nY6HkvangtZmpQdkhzfH5lkSscRiyS.woff2 ├── SLXVc1nY6HkvangtZmpQdkhzfH5lkSscSCyS4J0.woff2 └── styles.css ├── hooks ├── use-clamp-window-size.mjs ├── use-config.mjs ├── use-theme.mjs ├── use-window-size.mjs └── use-window-theme.mjs ├── logo.png ├── manifest.json ├── manifest.v2.json ├── pages ├── IndependentPanel │ ├── App.jsx │ ├── index.html │ ├── index.jsx │ └── styles.scss └── styles.scss ├── popup ├── Popup.jsx ├── index.html ├── index.jsx ├── sections │ ├── AdvancedPart.jsx │ ├── ApiModes.jsx │ ├── FeaturePages.jsx │ ├── GeneralPart.jsx │ ├── ModulesPart.jsx │ ├── SelectionTools.jsx │ └── SiteAdapters.jsx └── styles.scss ├── rules.json ├── services ├── apis │ ├── azure-openai-api.mjs │ ├── bard-web.mjs │ ├── bing-web.mjs │ ├── chatglm-api.mjs │ ├── chatgpt-web.mjs │ ├── claude-api.mjs │ ├── claude-web.mjs │ ├── custom-api.mjs │ ├── moonshot-api.mjs │ ├── moonshot-web.mjs │ ├── ollama-api.mjs │ ├── openai-api.mjs │ ├── poe-web.mjs │ ├── shared.mjs │ └── waylaidwanderer-api.mjs ├── clients │ ├── bard │ │ └── index.mjs │ ├── bing │ │ ├── BingImageCreator.js │ │ └── index.mjs │ ├── claude │ │ └── index.mjs │ └── poe │ │ ├── graphql │ │ ├── AddHumanMessageMutation.graphql │ │ ├── AddMessageBreakMutation.graphql │ │ ├── AutoSubscriptionMutation.graphql │ │ ├── BioFragment.graphql │ │ ├── ChatAddedSubscription.graphql │ │ ├── ChatFragment.graphql │ │ ├── ChatPaginationQuery.graphql │ │ ├── ChatViewQuery.graphql │ │ ├── DeleteHumanMessagesMutation.graphql │ │ ├── HandleFragment.graphql │ │ ├── LoginWithVerificationCodeMutation.graphql │ │ ├── MessageAddedSubscription.graphql │ │ ├── MessageDeletedSubscription.graphql │ │ ├── MessageFragment.graphql │ │ ├── MessageRemoveVoteMutation.graphql │ │ ├── MessageSetVoteMutation.graphql │ │ ├── SendVerificationCodeForLoginMutation.graphql │ │ ├── ShareMessagesMutation.graphql │ │ ├── SignupWithVerificationCodeMutation.graphql │ │ ├── StaleChatUpdateMutation.graphql │ │ ├── SummarizePlainPostQuery.graphql │ │ ├── SummarizeQuotePostQuery.graphql │ │ ├── SummarizeSharePostQuery.graphql │ │ ├── UserSnippetFragment.graphql │ │ ├── ViewerInfoQuery.graphql │ │ ├── ViewerStateFragment.graphql │ │ └── ViewerStateUpdatedSubscription.graphql │ │ ├── index.mjs │ │ └── websocket.js ├── init-session.mjs ├── local-session.mjs └── wrappers.mjs └── utils ├── change-children-font-size.mjs ├── create-element-at-position.mjs ├── crop-text.mjs ├── ends-with-question-mark.mjs ├── eventsource-parser.mjs ├── fetch-bg.mjs ├── fetch-sse.mjs ├── get-client-position.mjs ├── get-conversation-pairs.mjs ├── get-core-content-text.mjs ├── get-possible-element-by-query-selector.mjs ├── index.mjs ├── is-edge.mjs ├── is-firefox.mjs ├── is-mobile.mjs ├── is-safari.mjs ├── jwt-token-generator.mjs ├── limited-fetch.mjs ├── model-name-convert.mjs ├── open-url.mjs ├── parse-float-with-clamp.mjs ├── parse-int-with-clamp.mjs ├── set-element-position-in-viewport.mjs ├── update-ref-height.mjs └── wait-for-element-to-exist-and-select.mjs /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:react/recommended"], 7 | "overrides": [], 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "react/react-in-jsx-scope": "off" 14 | }, 15 | "ignorePatterns": ["build/**", "build.mjs", "src/utils/is-mobile.mjs"], 16 | "settings": { 17 | "react": { 18 | "version": "detect" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/services/clients/** linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See https://github.com/josStorer/chatGPTBox/wiki/Development&Contributing -------------------------------------------------------------------------------- /.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 | 10 | **Describe the bug** 11 | **问题描述** 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | **如何复现** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | **期望行为** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | **截图说明** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Please complete the following information):** 31 | **请补全以下内容** 32 | - OS: [e.g. Windows] 33 | - Browser: [e.g. chrome, safari] 34 | - Extension Version: [e.g. v2.0.2] 35 | 36 | **Additional context** 37 | **其他** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request---新功能请求.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request / 新功能请求 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | **新功能是否与解决某个问题相关, 请描述** 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | **你期望的新功能实现方案** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Additional context** 19 | **其他** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "chore" 9 | include: "scope" -------------------------------------------------------------------------------- /.github/workflows/pr-tests.yml: -------------------------------------------------------------------------------- 1 | name: pr-tests 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - "opened" 7 | - "reopened" 8 | - "synchronize" 9 | paths: 10 | - "src/**" 11 | - "build.mjs" 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | - run: npm ci 23 | - run: npm run lint 24 | - run: npm run build -------------------------------------------------------------------------------- /.github/workflows/pre-release-build.yml: -------------------------------------------------------------------------------- 1 | name: pre-release 2 | on: 3 | workflow_dispatch: 4 | # push: 5 | # branches: 6 | # - master 7 | # paths: 8 | # - "src/**" 9 | # - "!src/**/*.json" 10 | # - "build.mjs" 11 | # tags-ignore: 12 | # - "v*" 13 | 14 | permissions: 15 | id-token: "write" 16 | contents: "write" 17 | 18 | jobs: 19 | build_and_release: 20 | runs-on: ubuntu-22.04 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | - run: npm ci 28 | - run: npm run build 29 | 30 | - uses: josStorer/get-current-time@v2 31 | id: current-time 32 | with: 33 | format: YYYY_MMDD_HHmm 34 | 35 | - uses: actions/upload-artifact@v4 36 | with: 37 | name: Chromium_Build_${{ steps.current-time.outputs.formattedTime }} 38 | path: build/chromium/* 39 | 40 | - uses: actions/upload-artifact@v4 41 | with: 42 | name: Firefox_Build_${{ steps.current-time.outputs.formattedTime }} 43 | path: build/firefox/* 44 | 45 | - uses: actions/upload-artifact@v4 46 | with: 47 | name: Chromium_Build_WithoutKatex_${{ steps.current-time.outputs.formattedTime }} 48 | path: build/chromium-without-katex-and-tiktoken/* 49 | 50 | - uses: actions/upload-artifact@v4 51 | with: 52 | name: Firefox_Build_WithoutKatex_${{ steps.current-time.outputs.formattedTime }} 53 | path: build/firefox-without-katex-and-tiktoken/* 54 | 55 | - uses: marvinpinto/action-automatic-releases@v1.2.1 56 | with: 57 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 58 | automatic_release_tag: "latest" 59 | prerelease: true 60 | title: "Development Build" 61 | files: | 62 | build/chromium.zip 63 | build/firefox.zip 64 | build/chromium-without-katex-and-tiktoken.zip 65 | build/firefox-without-katex-and-tiktoken.zip 66 | -------------------------------------------------------------------------------- /.github/workflows/tagged-release.yml: -------------------------------------------------------------------------------- 1 | name: tagged-release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | permissions: 8 | id-token: "write" 9 | contents: "write" 10 | env: 11 | GH_TOKEN: ${{ github.token }} 12 | 13 | jobs: 14 | build_and_release: 15 | runs-on: macos-12 16 | 17 | steps: 18 | - run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV 19 | - uses: actions/checkout@v4 20 | with: 21 | ref: master 22 | 23 | - name: Update manifest.json version 24 | uses: jossef/action-set-json-field@v2.2 25 | with: 26 | file: src/manifest.json 27 | field: version 28 | value: ${{ env.VERSION }} 29 | 30 | - name: Update manifest.v2.json version 31 | uses: jossef/action-set-json-field@v2.2 32 | with: 33 | file: src/manifest.v2.json 34 | field: version 35 | value: ${{ env.VERSION }} 36 | 37 | - name: Push files 38 | continue-on-error: true 39 | run: | 40 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 41 | git config --global user.name "github-actions[bot]" 42 | git commit -am "release v${{ env.VERSION }}" 43 | git push 44 | 45 | - run: | 46 | gh release create ${{github.ref_name}} -d -F CURRENT_CHANGE.md -t ${{github.ref_name}} 47 | 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: 20 51 | - run: npm ci 52 | 53 | - uses: actions/setup-python@v5 54 | with: 55 | python-version: '3.10' # for appdmg 56 | - uses: maxim-lobanov/setup-xcode@v1 57 | with: 58 | xcode-version: 14.2 59 | - run: sed -i '' "s/0.0.0/${{ env.VERSION }}/g" safari/project.pre.patch 60 | - run: sed -i '' "s/0.0.0/${{ env.VERSION }}/g" safari/project.patch 61 | - run: npm run build:safari 62 | 63 | - run: | 64 | gh release upload ${{github.ref_name}} build/chromium.zip 65 | gh release upload ${{github.ref_name}} build/firefox.zip 66 | gh release upload ${{github.ref_name}} build/safari.dmg 67 | gh release upload ${{github.ref_name}} build/chromium-without-katex-and-tiktoken.zip 68 | gh release upload ${{github.ref_name}} build/firefox-without-katex-and-tiktoken.zip 69 | 70 | - run: | 71 | gh release edit ${{github.ref_name}} --draft=false 72 | -------------------------------------------------------------------------------- /.github/workflows/verify-configs.yml: -------------------------------------------------------------------------------- 1 | name: verify-configs 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "0 6 * * *" 6 | 7 | jobs: 8 | verify_configs: 9 | runs-on: ubuntu-22.04 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: 20 16 | - run: npm ci 17 | - run: npm run verify 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build/ 5 | .DS_Store 6 | *.zip 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | src/manifest.json 3 | src/manifest.v2.json -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CURRENT_CHANGE.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | this is a patch for v2.5.7, including some minor fixes and improvements 4 | 5 | ## Features 6 | - unlimited custom API Modes (#731, #717, #712, #659, #647) 7 | 8 | 9 | 10 | 11 | 12 | - Option to always display floating window, disable sidebar for all site adapters (#747, #753) f50249e7 226fb108 b96ba7c0 13 | 14 | ![image](https://github.com/user-attachments/assets/6975496b-3700-4de5-ae31-6d35ce6e9e80) 15 | 16 | - Add Ollama native API to support keep alive parameters (#748) 6877a1b7 48817006 17 | 18 | 19 | 20 | - Option to allow ESC to close all floating windows (#750) 21 | - allow exporting and importing all data (#740) 22 | 23 | 24 | 25 | ## Improvements 26 | - for simplified chinese users, use Kimi.Moonshot Web for free by default, while other users default to using Claude.ai for free and a better user experience 27 | - improve chatglm support (#696, #464) 28 | - improve style conflicts (#724, #378) 29 | - improve user experience for claude.ai and kimi.moonshot.cn 30 | 31 | ## Fixes 32 | - fix firefox bilibili summary (#761) 33 | - fix Buffer is not defined when using tiny package (#691, https://github.com/josStorer/chatGPTBox/issues/752#issuecomment-2240977750) 34 | 35 | ## Chores 36 | - Added Claude 3.5 Sonnet API to available models e7cec334 37 | - Add gpt-4o-mini for both web and api access (#749) 38 | - update enforcement rule 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 josStorer 4 | Copyright (c) 2022 wong2 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README_JA.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

ChatGPT Box

6 | 7 |
8 | 9 | 深い ChatGPT 統合をブラウザに、完全無料で。 10 | 11 | [![license][license-image]][license-url] 12 | [![release][release-image]][release-url] 13 | [![size](https://img.shields.io/badge/minified%20size-390%20kB-blue)][release-url] 14 | [![verfiy][verify-image]][verify-url] 15 | 16 | [English](README.md)   |   [Indonesia](README_IN.md)   |   [简体中文](README_ZH.md)   |   日本語   |   [Türkçe](README_TR.md) 17 | 18 | ### インストール 19 | 20 | [![Chrome][Chrome-image]][Chrome-url] 21 | [![Edge][Edge-image]][Edge-url] 22 | [![Firefox][Firefox-image]][Firefox-url] 23 | [![Safari][Safari-image]][Safari-url] 24 | [![Android][Android-image]][Android-url] 25 | [![Github][Github-image]][Github-url] 26 | 27 | [ガイド](https://github.com/josStorer/chatGPTBox/wiki/Guide)   |   [プレビュー](#プレビュー)   |   [開発 & コントリビュート][dev-url]   |   [ビデオデモ](https://www.youtube.com/watch?v=E1smDxJvTRs)   |   [クレジット](#クレジット) 28 | 29 | [dev-url]: https://github.com/josStorer/chatGPTBox/wiki/Development&Contributing 30 | 31 | [license-image]: http://img.shields.io/badge/license-MIT-blue.svg 32 | 33 | [license-url]: https://github.com/josStorer/chatGPTBox/blob/master/LICENSE 34 | 35 | [release-image]: https://img.shields.io/github/release/josStorer/chatGPTBox.svg 36 | 37 | [release-url]: https://github.com/josStorer/chatGPTBox/releases/latest 38 | 39 | [verify-image]: https://github.com/josStorer/chatGPTBox/workflows/verify-configs/badge.svg 40 | 41 | [verify-url]: https://github.com/josStorer/chatGPTBox/actions/workflows/verify-configs.yml 42 | 43 | [Chrome-image]: https://img.shields.io/badge/-Chrome-brightgreen?logo=google-chrome&logoColor=white 44 | 45 | [Chrome-url]: https://chrome.google.com/webstore/detail/chatgptbox/eobbhoofkanlmddnplfhnmkfbnlhpbbo 46 | 47 | [Edge-image]: https://img.shields.io/badge/-Edge-blue?logo=microsoft-edge&logoColor=white 48 | 49 | [Edge-url]: https://microsoftedge.microsoft.com/addons/detail/fission-chatbox-best/enjmfilpkbbabhgeoadmdpjjpnahkogf 50 | 51 | [Firefox-image]: https://img.shields.io/badge/-Firefox-orange?logo=firefox-browser&logoColor=white 52 | 53 | [Firefox-url]: https://addons.mozilla.org/firefox/addon/chatgptbox/ 54 | 55 | [Safari-image]: https://img.shields.io/badge/-Safari-blue?logo=safari&logoColor=white 56 | 57 | [Safari-url]: https://apps.apple.com/app/fission-chatbox/id6446611121 58 | 59 | [Android-image]: https://img.shields.io/badge/-Android-brightgreen?logo=android&logoColor=white 60 | 61 | [Android-url]: https://github.com/josStorer/chatGPTBox/wiki/Install#install-to-android 62 | 63 | [Github-image]: https://img.shields.io/badge/-Github-black?logo=github&logoColor=white 64 | 65 | [Github-url]: https://github.com/josStorer/chatGPTBox/wiki/Install 66 | 67 |
68 | 69 | ## ニュース 70 | 71 | - この拡張機能はあなたのデータを収集しません。コード内の `fetch(` と `XMLHttpRequest(` をグローバル検索して、すべてのネットワークリクエストの呼び出しを見つけることで確認できます。コードの量はそれほど多くないので、簡単にできます。 72 | 73 | - このツールは、あなたが明示的に要求しない限り、ChatGPT にデータを送信しません。デフォルトでは、拡張機能は手動で有効にする必要があります ChatGPT へのリクエストは、"Ask ChatGPT" をクリックするか、選択フローティングツールをトリガーした場合にのみ送信されます。(issue #407) 74 | 75 | - https://github.com/BerriAI/litellm / https://github.com/songquanpeng/one-api のようなプロジェクトを使用して、LLM APIをOpenAI形式に変換し、それらをChatGPTBoxの `カスタムモデル` モードと組み合わせて使用することができます 76 | 77 | - もちろんです。ChatGPTBoxの `カスタムモデル` モードを使用する際には、[Ollama](https://github.com/josStorer/chatGPTBox/issues/616#issuecomment-1975186467) / https://openrouter.ai/docs#models もご利用いただけます 78 | 79 | ## ✨ 機能 80 | 81 | - 🌈 いつでもどのページでもチャットダイアログボックスを呼び出すことができます。 (Ctrl+B) 82 | - 📱 モバイル機器のサポート。 83 | - 📓 右クリックメニューで任意のページを要約。 (Alt+B) 84 | - 📖 独立した会話ページ。 (Ctrl+Shift+H) 85 | - 🔗 複数の API をサポート(無料および Plus ユーザー向け Web API、GPT-3.5、GPT-4、Claude、New Bing、Moonshot、セルフホスト、Azure など)。 86 | - 📦 よく使われる様々なウェブサイト(Reddit、Quora、YouTube、GitHub、GitLab、StackOverflow、Zhihu、Bilibili)の統合。 ([wimdenherder](https://github.com/wimdenherder) にインスパイアされました) 87 | - 🔍 すべての主要検索エンジンと統合し、追加のサイトをサポートするためのカスタムクエリ。 88 | - 🧰 選択ツールと右クリックメニューで、翻訳、要約、推敲、感情分析、段落分割、コード説明、クエリーなど、さまざまなタスクを実行できます。 89 | - 🗂️ 静的なカードは、複数の支店での会話のためのフローティングチャットボックスをサポートしています。 90 | - 🖨️ チャット記録を完全に保存することも、部分的にコピーすることも簡単です。 91 | - 🎨 コードのハイライトや複雑な数式など、強力なレンダリングをサポート。 92 | - 🌍 言語設定のサポート。 93 | - 📝 カスタム API アドレスのサポート 94 | - ⚙️ すべてのサイト適応と選択ツール(バブル)は、自由にオンまたはオフに切り替えることができ、不要なモジュールを無効にすることができます。 95 | - 💡 セレクションツールやサイトへの適応は簡単に開発・拡張できます。[開発 & コントリビュート][dev-url]のセクションを参照。 96 | - 😉 チャットして回答の質を高められます。 97 | 98 | ## プレビュー 99 | 100 |
101 | 102 | **検索エンジンの統合、フローティングウィンドウ、会話ブランチ** 103 | 104 | ![preview_google_floatingwindow_conversationbranch](screenshots/preview_google_floatingwindow_conversationbranch.jpg) 105 | 106 | **よく使われるウェブサイトや選択ツールとの統合** 107 | 108 | ![preview_reddit_selectiontools](screenshots/preview_reddit_selectiontools.jpg) 109 | 110 | **独立会話ページ** 111 | 112 | ![preview_independentpanel](screenshots/preview_independentpanel.jpg) 113 | 114 | **Git 分析、右クリックメニュー** 115 | 116 | ![preview_github_rightclickmenu](screenshots/preview_github_rightclickmenu.jpg) 117 | 118 | **ビデオ要約** 119 | 120 | ![preview_youtube](screenshots/preview_youtube.jpg) 121 | 122 | **モバイルサポート** 123 | 124 | ![image](https://user-images.githubusercontent.com/13366013/225529110-9221c8ce-ad41-423e-b6ec-097981e74b66.png) 125 | 126 | **設定** 127 | 128 | ![preview_settings](screenshots/preview_settings.jpg) 129 | 130 |
131 | 132 | ## クレジット 133 | 134 | このプロジェクトは、私の他のリポジトリ [josStorer/chatGPT-search-engine-extension](https://github.com/josStorer/chatGPT-search-engine-extension) に基づいています 135 | 136 | [josStorer/chatGPT-search-engine-extension](https://github.com/josStorer/chatGPT-search-engine-extension) は [wong2/chat-gpt-google-extension](https://github.com/wong2/chat-gpt-google-extension) (参考にしました)からフォークされ、2022年12月14日から切り離されています 137 | 138 | [wong2/chat-gpt-google-extension](https://github.com/wong2/chat-gpt-google-extension) は [ZohaibAhmed/ChatGPT-Google](https://github.com/ZohaibAhmed/ChatGPT-Google) にインスパイアされています([upstream-c54528b](https://github.com/wong2/chatgpt-google-extension/commit/c54528b0e13058ab78bfb433c92603db017d1b6b)) 139 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgptbox", 3 | "scripts": { 4 | "build": "node build.mjs --production", 5 | "build:safari": "bash ./safari/build.sh", 6 | "dev": "node build.mjs --development", 7 | "analyze": "node build.mjs --analyze", 8 | "lint": "eslint --ext .js,.mjs,.jsx .", 9 | "lint:fix": "eslint --ext .js,.mjs,.jsx . --fix", 10 | "pretty": "prettier --write ./**/*.{js,mjs,jsx,json,css,scss}", 11 | "stage": "run-script-os", 12 | "stage:default": "git add $(git diff --name-only --cached --diff-filter=d)", 13 | "stage:win32": "powershell git add $(git diff --name-only --cached --diff-filter=d)", 14 | "verify": "node .github/workflows/scripts/verify-search-engine-configs.mjs" 15 | }, 16 | "pre-commit": [ 17 | "pretty", 18 | "stage", 19 | "lint" 20 | ], 21 | "dependencies": { 22 | "@mozilla/readability": "^0.5.0", 23 | "@nem035/gpt-3-encoder": "^1.1.7", 24 | "@picocss/pico": "^1.5.13", 25 | "@primer/octicons-react": "^18.3.0", 26 | "buffer": "^6.0.3", 27 | "countries-list": "^2.6.1", 28 | "crypto-browserify": "^3.12.0", 29 | "diff": "^5.2.0", 30 | "file-saver": "^2.0.5", 31 | "github-markdown-css": "^5.6.1", 32 | "gpt-3-encoder": "^1.1.4", 33 | "graphql": "^16.9.0", 34 | "i18next": "^22.4.15", 35 | "js-sha3": "^0.9.3", 36 | "jsonwebtoken": "8.5.1", 37 | "katex": "^0.16.11", 38 | "lodash-es": "^4.17.21", 39 | "md5": "^2.3.0", 40 | "parse5": "^6.0.1", 41 | "preact": "^10.22.1", 42 | "process": "^0.11.10", 43 | "prop-types": "^15.8.1", 44 | "random-int": "^3.0.0", 45 | "react": "npm:@preact/compat@^17.1.2", 46 | "react-bootstrap-icons": "^1.11.4", 47 | "react-dom": "npm:@preact/compat@^17.1.2", 48 | "react-draggable": "^4.4.6", 49 | "react-i18next": "^12.2.0", 50 | "react-markdown": "^8.0.7", 51 | "react-tabs": "^4.3.0", 52 | "react-toastify": "^9.1.3", 53 | "rehype-highlight": "^6.0.0", 54 | "rehype-katex": "^6.0.3", 55 | "rehype-raw": "^6.1.1", 56 | "remark-breaks": "^3.0.3", 57 | "remark-gfm": "^3.0.1", 58 | "remark-math": "^5.1.1", 59 | "stream-browserify": "^3.0.0", 60 | "util": "^0.12.5", 61 | "uuid": "^9.0.1", 62 | "webextension-polyfill": "^0.12.0" 63 | }, 64 | "devDependencies": { 65 | "@babel/core": "^7.24.7", 66 | "@babel/plugin-transform-react-jsx": "^7.24.7", 67 | "@babel/plugin-transform-runtime": "^7.24.7", 68 | "@babel/preset-env": "^7.24.7", 69 | "@types/archiver": "^5.3.4", 70 | "@types/fs-extra": "^11.0.4", 71 | "@types/jsdom": "^21.1.7", 72 | "@types/webextension-polyfill": "^0.10.7", 73 | "archiver": "^5.3.2", 74 | "babel-loader": "^9.1.3", 75 | "css-loader": "^6.11.0", 76 | "css-minimizer-webpack-plugin": "^5.0.1", 77 | "eslint": "^8.57.0", 78 | "eslint-plugin-react": "^7.34.3", 79 | "fs-extra": "^11.2.0", 80 | "graphql-tag": "^2.12.6", 81 | "jsdom": "^21.1.2", 82 | "less-loader": "^11.1.4", 83 | "mini-css-extract-plugin": "^2.9.0", 84 | "node-fetch": "^3.3.2", 85 | "pre-commit": "^1.2.2", 86 | "prettier": "^2.8.8", 87 | "progress-bar-webpack-plugin": "^2.1.0", 88 | "run-script-os": "^1.1.6", 89 | "sass": "^1.77.6", 90 | "sass-loader": "^13.3.3", 91 | "string-replace-loader": "^3.1.0", 92 | "terser-webpack-plugin": "^5.3.10", 93 | "webpack": "^5.92.1", 94 | "webpack-bundle-analyzer": "^4.10.2" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /safari/appdmg.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Fission - ChatBox", 3 | "icon": "../src/logo.png", 4 | "contents": [ 5 | { "x": 448, "y": 344, "type": "link", "path": "/Applications" }, 6 | { "x": 192, "y": 344, "type": "file", "path": "../build/Fission - ChatBox.app" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /safari/build.sh: -------------------------------------------------------------------------------- 1 | git apply safari/project.pre.patch 2 | npm run build 3 | xcrun safari-web-extension-converter ./build/firefox \ 4 | --project-location ./build/safari --app-name "Fission - ChatBox" \ 5 | --bundle-identifier dev.josStorer.chatGPTBox --force --no-prompt --no-open 6 | git apply safari/project.patch 7 | xcodebuild archive -project "./build/safari/Fission - ChatBox/Fission - ChatBox.xcodeproj" \ 8 | -scheme "Fission - ChatBox (macOS)" -configuration Release -archivePath "./build/safari/Fission - ChatBox.xcarchive" 9 | xcodebuild -exportArchive -archivePath "./build/safari/Fission - ChatBox.xcarchive" \ 10 | -exportOptionsPlist ./safari/export-options.plist -exportPath ./build 11 | npm install -D appdmg 12 | rm ./build/safari.dmg 13 | appdmg ./safari/appdmg.json ./build/safari.dmg -------------------------------------------------------------------------------- /safari/export-options.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | method 6 | mac-application 7 | 8 | -------------------------------------------------------------------------------- /safari/project.patch: -------------------------------------------------------------------------------- 1 | --- a/build/safari/Fission - ChatBox/Fission - ChatBox.xcodeproj/project.pbxproj 2 | +++ b/build/safari/Fission - ChatBox/Fission - ChatBox.xcodeproj/project.pbxproj 3 | -------------------------------------------------------------------------------- /safari/project.pre.patch: -------------------------------------------------------------------------------- 1 | --- a/src/manifest.v2.json 2 | +++ b/src/manifest.v2.json 3 | @@ -1,5 +1,5 @@ 4 | { 5 | - "name": "ChatGPTBox", 6 | + "name": "Fission - ChatBox", 7 | "description": "Integrating ChatGPT into your browser deeply, everything you need is here", 8 | "version": "0.0.0", 9 | "manifest_version": 2, 10 | @@ -28,7 +28,7 @@ 11 | "scripts": [ 12 | "background.js" 13 | ], 14 | - "persistent": true 15 | + "persistent": true 16 | }, 17 | "browser_action": { 18 | "default_popup": "popup.html?popup=true" 19 | -------------------------------------------------------------------------------- /screenshots/preview_github_rightclickmenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josStorer/chatGPTBox/86940f6d88dc91337a5234462e365eebf8d494cd/screenshots/preview_github_rightclickmenu.jpg -------------------------------------------------------------------------------- /screenshots/preview_google_floatingwindow_conversationbranch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josStorer/chatGPTBox/86940f6d88dc91337a5234462e365eebf8d494cd/screenshots/preview_google_floatingwindow_conversationbranch.jpg -------------------------------------------------------------------------------- /screenshots/preview_independentpanel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josStorer/chatGPTBox/86940f6d88dc91337a5234462e365eebf8d494cd/screenshots/preview_independentpanel.jpg -------------------------------------------------------------------------------- /screenshots/preview_reddit_selectiontools.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josStorer/chatGPTBox/86940f6d88dc91337a5234462e365eebf8d494cd/screenshots/preview_reddit_selectiontools.jpg -------------------------------------------------------------------------------- /screenshots/preview_settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josStorer/chatGPTBox/86940f6d88dc91337a5234462e365eebf8d494cd/screenshots/preview_settings.jpg -------------------------------------------------------------------------------- /screenshots/preview_youtube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josStorer/chatGPTBox/86940f6d88dc91337a5234462e365eebf8d494cd/screenshots/preview_youtube.jpg -------------------------------------------------------------------------------- /src/_locales/i18n-react.mjs: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import { initReactI18next } from 'react-i18next' 3 | import { resources } from './resources' 4 | 5 | i18n.use(initReactI18next).init({ 6 | resources, 7 | fallbackLng: 'en', 8 | interpolation: { 9 | escapeValue: false, // not needed for react as it escapes by default 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /src/_locales/i18n.mjs: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import { resources } from './resources' 3 | 4 | i18n.init({ 5 | resources, 6 | fallbackLng: 'en', 7 | }) 8 | -------------------------------------------------------------------------------- /src/_locales/resources.mjs: -------------------------------------------------------------------------------- 1 | import de from './de/main.json' 2 | import en from './en/main.json' 3 | import es from './es/main.json' 4 | import fr from './fr/main.json' 5 | import inTrans from './in/main.json' 6 | import it from './it/main.json' 7 | import ja from './ja/main.json' 8 | import ko from './ko/main.json' 9 | import pt from './pt/main.json' 10 | import ru from './ru/main.json' 11 | import tr from './tr/main.json' 12 | import zhHans from './zh-hans/main.json' 13 | import zhHant from './zh-hant/main.json' 14 | 15 | export const resources = { 16 | de: { 17 | translation: de, 18 | }, 19 | en: { 20 | translation: en, 21 | }, 22 | es: { 23 | translation: es, 24 | }, 25 | fr: { 26 | translation: fr, 27 | }, 28 | in: { 29 | translation: inTrans, 30 | }, 31 | it: { 32 | translation: it, 33 | }, 34 | ja: { 35 | translation: ja, 36 | }, 37 | ko: { 38 | translation: ko, 39 | }, 40 | pt: { 41 | translation: pt, 42 | }, 43 | ru: { 44 | translation: ru, 45 | }, 46 | tr: { 47 | translation: tr, 48 | }, 49 | zh: { 50 | translation: zhHans, 51 | }, 52 | zhHant: { 53 | translation: zhHant, 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /src/background/commands.mjs: -------------------------------------------------------------------------------- 1 | import Browser from 'webextension-polyfill' 2 | import { config as menuConfig } from '../content-script/menu-tools/index.mjs' 3 | 4 | export function registerCommands() { 5 | Browser.commands.onCommand.addListener(async (command, tab) => { 6 | const message = { 7 | itemId: command, 8 | selectionText: '', 9 | useMenuPosition: false, 10 | } 11 | console.debug('command triggered', message) 12 | 13 | if (command in menuConfig) { 14 | if (menuConfig[command].action) { 15 | menuConfig[command].action(true, tab) 16 | } 17 | 18 | if (menuConfig[command].genPrompt) { 19 | const currentTab = (await Browser.tabs.query({ active: true, currentWindow: true }))[0] 20 | Browser.tabs.sendMessage(currentTab.id, { 21 | type: 'CREATE_CHAT', 22 | data: message, 23 | }) 24 | } 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/background/menus.mjs: -------------------------------------------------------------------------------- 1 | import Browser from 'webextension-polyfill' 2 | import { defaultConfig, getPreferredLanguageKey, getUserConfig } from '../config/index.mjs' 3 | import { changeLanguage, t } from 'i18next' 4 | import { config as menuConfig } from '../content-script/menu-tools/index.mjs' 5 | 6 | const menuId = 'ChatGPTBox-Menu' 7 | const onClickMenu = (info, tab) => { 8 | Browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { 9 | const currentTab = tabs[0] 10 | const message = { 11 | itemId: info.menuItemId.replace(menuId, ''), 12 | selectionText: info.selectionText, 13 | useMenuPosition: tab.id === currentTab.id, 14 | } 15 | console.debug('menu clicked', message) 16 | 17 | if (defaultConfig.selectionTools.includes(message.itemId)) { 18 | Browser.tabs.sendMessage(currentTab.id, { 19 | type: 'CREATE_CHAT', 20 | data: message, 21 | }) 22 | } else if (message.itemId in menuConfig) { 23 | if (menuConfig[message.itemId].action) { 24 | menuConfig[message.itemId].action(true, tab) 25 | } 26 | 27 | if (menuConfig[message.itemId].genPrompt) { 28 | Browser.tabs.sendMessage(currentTab.id, { 29 | type: 'CREATE_CHAT', 30 | data: message, 31 | }) 32 | } 33 | } 34 | }) 35 | } 36 | export function refreshMenu() { 37 | if (Browser.contextMenus.onClicked.hasListener(onClickMenu)) 38 | Browser.contextMenus.onClicked.removeListener(onClickMenu) 39 | Browser.contextMenus.removeAll().then(async () => { 40 | if ((await getUserConfig()).hideContextMenu) return 41 | 42 | await getPreferredLanguageKey().then((lang) => { 43 | changeLanguage(lang) 44 | }) 45 | Browser.contextMenus.create({ 46 | id: menuId, 47 | title: 'ChatGPTBox', 48 | contexts: ['all'], 49 | }) 50 | 51 | for (const [k, v] of Object.entries(menuConfig)) { 52 | Browser.contextMenus.create({ 53 | id: menuId + k, 54 | parentId: menuId, 55 | title: t(v.label), 56 | contexts: ['all'], 57 | }) 58 | } 59 | Browser.contextMenus.create({ 60 | id: menuId + 'separator1', 61 | parentId: menuId, 62 | contexts: ['selection'], 63 | type: 'separator', 64 | }) 65 | for (const index in defaultConfig.selectionTools) { 66 | const key = defaultConfig.selectionTools[index] 67 | const desc = defaultConfig.selectionToolsDesc[index] 68 | Browser.contextMenus.create({ 69 | id: menuId + key, 70 | parentId: menuId, 71 | title: t(desc), 72 | contexts: ['selection'], 73 | }) 74 | } 75 | 76 | Browser.contextMenus.onClicked.addListener(onClickMenu) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /src/components/ConfirmButton/index.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next' 2 | import { useEffect, useRef, useState } from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | ConfirmButton.propTypes = { 6 | onConfirm: PropTypes.func.isRequired, 7 | text: PropTypes.string.isRequired, 8 | } 9 | 10 | function ConfirmButton({ onConfirm, text }) { 11 | const { t } = useTranslation() 12 | const [waitConfirm, setWaitConfirm] = useState(false) 13 | const confirmRef = useRef(null) 14 | 15 | useEffect(() => { 16 | if (waitConfirm) confirmRef.current.focus() 17 | }, [waitConfirm]) 18 | 19 | return ( 20 | 21 | 42 | 54 | 55 | ) 56 | } 57 | 58 | export default ConfirmButton 59 | -------------------------------------------------------------------------------- /src/components/ConversationItem/index.jsx: -------------------------------------------------------------------------------- 1 | import { memo, useState } from 'react' 2 | import { ChevronDownIcon, XCircleIcon, SyncIcon } from '@primer/octicons-react' 3 | import CopyButton from '../CopyButton' 4 | import ReadButton from '../ReadButton' 5 | import PropTypes from 'prop-types' 6 | import MarkdownRender from '../MarkdownRender/markdown.jsx' 7 | import { useTranslation } from 'react-i18next' 8 | 9 | function AnswerTitle({ descName }) { 10 | const { t } = useTranslation() 11 | 12 | return

{descName ? `${descName}:` : t('Loading...')}

13 | } 14 | 15 | AnswerTitle.propTypes = { 16 | descName: PropTypes.string, 17 | } 18 | 19 | export function ConversationItem({ type, content, descName, onRetry }) { 20 | const { t } = useTranslation() 21 | const [collapsed, setCollapsed] = useState(false) 22 | 23 | switch (type) { 24 | case 'question': 25 | return ( 26 |
27 |
28 |

{t('You')}:

29 |
30 | content.replace(/\n$/, '')} size={14} /> 31 | content} size={14} /> 32 | {!collapsed ? ( 33 | setCollapsed(true)} 37 | > 38 | 39 | 40 | ) : ( 41 | setCollapsed(false)} 45 | > 46 | 47 | 48 | )} 49 |
50 |
51 | {!collapsed && {content}} 52 |
53 | ) 54 | case 'answer': 55 | return ( 56 |
57 |
58 | 59 |
60 | {onRetry && ( 61 | 62 | 63 | 64 | )} 65 | {descName && ( 66 | content.replace(/\n$/, '')} size={14} /> 67 | )} 68 | {descName && content} size={14} />} 69 | {!collapsed ? ( 70 | setCollapsed(true)} 74 | > 75 | 76 | 77 | ) : ( 78 | setCollapsed(false)} 82 | > 83 | 84 | 85 | )} 86 |
87 |
88 | {!collapsed && {content}} 89 |
90 | ) 91 | case 'error': 92 | return ( 93 |
94 |
95 |

{t('Error')}:

96 |
97 | {onRetry && ( 98 | 99 | 100 | 101 | )} 102 | content.replace(/\n$/, '')} size={14} /> 103 | {!collapsed ? ( 104 | setCollapsed(true)} 108 | > 109 | 110 | 111 | ) : ( 112 | setCollapsed(false)} 116 | > 117 | 118 | 119 | )} 120 |
121 |
122 | {!collapsed && {content}} 123 |
124 | ) 125 | } 126 | } 127 | 128 | ConversationItem.propTypes = { 129 | type: PropTypes.oneOf(['question', 'answer', 'error']).isRequired, 130 | content: PropTypes.string.isRequired, 131 | descName: PropTypes.string, 132 | onRetry: PropTypes.func, 133 | } 134 | 135 | export default memo(ConversationItem) 136 | -------------------------------------------------------------------------------- /src/components/CopyButton/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { CheckIcon, CopyIcon } from '@primer/octicons-react' 3 | import PropTypes from 'prop-types' 4 | import { useTranslation } from 'react-i18next' 5 | 6 | CopyButton.propTypes = { 7 | contentFn: PropTypes.func.isRequired, 8 | size: PropTypes.number.isRequired, 9 | className: PropTypes.string, 10 | } 11 | 12 | function CopyButton({ className, contentFn, size }) { 13 | const { t } = useTranslation() 14 | const [copied, setCopied] = useState(false) 15 | 16 | const onClick = () => { 17 | navigator.clipboard 18 | .writeText(contentFn()) 19 | .then(() => setCopied(true)) 20 | .then(() => 21 | setTimeout(() => { 22 | setCopied(false) 23 | }, 600), 24 | ) 25 | } 26 | 27 | return ( 28 | 33 | {copied ? : } 34 | 35 | ) 36 | } 37 | 38 | export default CopyButton 39 | -------------------------------------------------------------------------------- /src/components/DecisionCard/index.jsx: -------------------------------------------------------------------------------- 1 | import { LightBulbIcon, SearchIcon } from '@primer/octicons-react' 2 | import { useState, useEffect } from 'react' 3 | import PropTypes from 'prop-types' 4 | import ConversationCard from '../ConversationCard' 5 | import { getPossibleElementByQuerySelector, endsWithQuestionMark } from '../../utils' 6 | import { useTranslation } from 'react-i18next' 7 | import { useConfig } from '../../hooks/use-config.mjs' 8 | 9 | function DecisionCard(props) { 10 | const { t } = useTranslation() 11 | const [triggered, setTriggered] = useState(false) 12 | const [render, setRender] = useState(false) 13 | const config = useConfig(() => { 14 | setRender(true) 15 | }) 16 | 17 | const question = props.question 18 | 19 | const updatePosition = () => { 20 | if (!render) return 21 | 22 | const container = props.container 23 | const siteConfig = props.siteConfig 24 | container.classList.remove('chatgptbox-sidebar-free') 25 | 26 | if (config.appendQuery) { 27 | const appendContainer = getPossibleElementByQuerySelector([config.appendQuery]) 28 | if (appendContainer) { 29 | appendContainer.appendChild(container) 30 | return 31 | } 32 | } 33 | 34 | if (config.prependQuery) { 35 | const prependContainer = getPossibleElementByQuerySelector([config.prependQuery]) 36 | if (prependContainer) { 37 | prependContainer.prepend(container) 38 | return 39 | } 40 | } 41 | 42 | if (!siteConfig) return 43 | 44 | if (config.insertAtTop) { 45 | const resultsContainerQuery = getPossibleElementByQuerySelector( 46 | siteConfig.resultsContainerQuery, 47 | ) 48 | if (resultsContainerQuery) resultsContainerQuery.prepend(container) 49 | } else { 50 | const sidebarContainer = getPossibleElementByQuerySelector(siteConfig.sidebarContainerQuery) 51 | if (sidebarContainer) { 52 | sidebarContainer.prepend(container) 53 | } else { 54 | const appendContainer = getPossibleElementByQuerySelector(siteConfig.appendContainerQuery) 55 | if (appendContainer) { 56 | container.classList.add('chatgptbox-sidebar-free') 57 | appendContainer.appendChild(container) 58 | } else { 59 | const resultsContainerQuery = getPossibleElementByQuerySelector( 60 | siteConfig.resultsContainerQuery, 61 | ) 62 | if (resultsContainerQuery) resultsContainerQuery.prepend(container) 63 | } 64 | } 65 | } 66 | } 67 | 68 | useEffect(() => updatePosition(), [config]) 69 | 70 | return ( 71 | render && ( 72 |
73 | {(() => { 74 | if (question) 75 | switch (config.triggerMode) { 76 | case 'always': 77 | return 78 | case 'manually': 79 | if (triggered) { 80 | return 81 | } 82 | return ( 83 |

setTriggered(true)}> 84 | 85 | {t('Ask ChatGPT')} 86 | 87 |

88 | ) 89 | case 'questionMark': 90 | if (endsWithQuestionMark(question.trim())) { 91 | return 92 | } 93 | if (triggered) { 94 | return 95 | } 96 | return ( 97 |

setTriggered(true)}> 98 | 99 | {t('Ask ChatGPT')} 100 | 101 |

102 | ) 103 | } 104 | else 105 | return ( 106 |

107 | 108 | {t('No Input Found')} 109 | 110 |

111 | ) 112 | })()} 113 |
114 | ) 115 | ) 116 | } 117 | 118 | DecisionCard.propTypes = { 119 | session: PropTypes.object.isRequired, 120 | question: PropTypes.string.isRequired, 121 | siteConfig: PropTypes.object.isRequired, 122 | container: PropTypes.object.isRequired, 123 | } 124 | 125 | export default DecisionCard 126 | -------------------------------------------------------------------------------- /src/components/DeleteButton/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { useTranslation } from 'react-i18next' 4 | import { TrashIcon } from '@primer/octicons-react' 5 | 6 | DeleteButton.propTypes = { 7 | onConfirm: PropTypes.func.isRequired, 8 | size: PropTypes.number.isRequired, 9 | text: PropTypes.string.isRequired, 10 | } 11 | 12 | function DeleteButton({ onConfirm, size, text }) { 13 | const { t } = useTranslation() 14 | const [waitConfirm, setWaitConfirm] = useState(false) 15 | const confirmRef = useRef(null) 16 | 17 | useEffect(() => { 18 | if (waitConfirm) confirmRef.current.focus() 19 | }, [waitConfirm]) 20 | 21 | return ( 22 | 23 | 45 | { 50 | setWaitConfirm(true) 51 | }} 52 | > 53 | 54 | 55 | 56 | ) 57 | } 58 | 59 | export default DeleteButton 60 | -------------------------------------------------------------------------------- /src/components/FeedbackForChatGPTWeb/index.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { memo, useCallback, useState } from 'react' 3 | import { ThumbsupIcon, ThumbsdownIcon } from '@primer/octicons-react' 4 | import Browser from 'webextension-polyfill' 5 | import { useTranslation } from 'react-i18next' 6 | 7 | const FeedbackForChatGPTWeb = (props) => { 8 | const { t } = useTranslation() 9 | const [action, setAction] = useState(null) 10 | 11 | const clickThumbsUp = useCallback(async () => { 12 | if (action) { 13 | return 14 | } 15 | setAction('thumbsUp') 16 | await Browser.runtime.sendMessage({ 17 | type: 'FEEDBACK', 18 | data: { 19 | conversation_id: props.conversationId, 20 | message_id: props.messageId, 21 | rating: 'thumbsUp', 22 | }, 23 | }) 24 | }, [props, action]) 25 | 26 | const clickThumbsDown = useCallback(async () => { 27 | if (action) { 28 | return 29 | } 30 | setAction('thumbsDown') 31 | await Browser.runtime.sendMessage({ 32 | type: 'FEEDBACK', 33 | data: { 34 | conversation_id: props.conversationId, 35 | message_id: props.messageId, 36 | rating: 'thumbsDown', 37 | text: '', 38 | tags: [], 39 | }, 40 | }) 41 | }, [props, action]) 42 | 43 | return ( 44 |
45 | 49 | 50 | 51 | 57 | 58 | 59 |
60 | ) 61 | } 62 | 63 | FeedbackForChatGPTWeb.propTypes = { 64 | messageId: PropTypes.string.isRequired, 65 | conversationId: PropTypes.string.isRequired, 66 | } 67 | 68 | export default memo(FeedbackForChatGPTWeb) 69 | -------------------------------------------------------------------------------- /src/components/InputBox/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { isFirefox, isMobile, isSafari, updateRefHeight } from '../../utils' 4 | import { useTranslation } from 'react-i18next' 5 | import { getUserConfig } from '../../config/index.mjs' 6 | 7 | export function InputBox({ onSubmit, enabled, postMessage, reverseResizeDir }) { 8 | const { t } = useTranslation() 9 | const [value, setValue] = useState('') 10 | const reverseDivRef = useRef(null) 11 | const inputRef = useRef(null) 12 | const resizedRef = useRef(false) 13 | const [internalReverseResizeDir, setInternalReverseResizeDir] = useState(reverseResizeDir) 14 | 15 | useEffect(() => { 16 | setInternalReverseResizeDir( 17 | !isSafari() && !isFirefox() && !isMobile() ? internalReverseResizeDir : false, 18 | ) 19 | }, []) 20 | 21 | const virtualInputRef = internalReverseResizeDir ? reverseDivRef : inputRef 22 | 23 | useEffect(() => { 24 | inputRef.current.focus() 25 | 26 | const onResizeY = () => { 27 | if (virtualInputRef.current.h !== virtualInputRef.current.offsetHeight) { 28 | virtualInputRef.current.h = virtualInputRef.current.offsetHeight 29 | if (!resizedRef.current) { 30 | resizedRef.current = true 31 | virtualInputRef.current.style.maxHeight = '' 32 | } 33 | } 34 | } 35 | virtualInputRef.current.h = virtualInputRef.current.offsetHeight 36 | virtualInputRef.current.addEventListener('mousemove', onResizeY) 37 | }, []) 38 | 39 | useEffect(() => { 40 | if (!resizedRef.current) { 41 | if (!internalReverseResizeDir) { 42 | updateRefHeight(inputRef) 43 | virtualInputRef.current.h = virtualInputRef.current.offsetHeight 44 | virtualInputRef.current.style.maxHeight = '160px' 45 | } 46 | } 47 | }) 48 | 49 | useEffect(() => { 50 | if (enabled) 51 | getUserConfig().then((config) => { 52 | if (config.focusAfterAnswer) inputRef.current.focus() 53 | }) 54 | }, [enabled]) 55 | 56 | const handleKeyDownOrClick = (e) => { 57 | e.stopPropagation() 58 | if (e.type === 'click' || (e.keyCode === 13 && e.shiftKey === false)) { 59 | e.preventDefault() 60 | if (enabled) { 61 | if (!value) return 62 | onSubmit(value) 63 | setValue('') 64 | } else { 65 | postMessage({ stop: true }) 66 | } 67 | } 68 | } 69 | 70 | return ( 71 |
72 |
83 |