├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── cross-platform-release.yml │ ├── cross-platform-test.yml │ ├── release-please.yml │ └── slack-notify.yml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.js ├── .npmrc ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── AUTHORS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public └── logo.png ├── screenshots ├── can't-be-oepn-in-macos.png └── chat-completion.png ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Info.plist ├── build.rs ├── capabilities │ ├── desktop.json │ └── migrated.json ├── gen │ └── schemas │ │ ├── acl-manifests.json │ │ ├── capabilities.json │ │ ├── desktop-schema.json │ │ └── macOS-schema.json ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── src │ └── main.rs └── tauri.conf.json ├── src ├── assets │ ├── chatbot.png │ ├── chatgpt-avatar.png │ ├── illustrations │ │ └── no-data.svg │ └── lotties │ │ └── recorder.json ├── components │ ├── Avatar │ │ └── index.tsx │ ├── ChatBox │ │ ├── ChatBubble.tsx │ │ ├── ChatMessages.tsx │ │ ├── ContactHeader.tsx │ │ ├── InputBox.tsx │ │ ├── Markdown.tsx │ │ ├── MediaUploader.tsx │ │ ├── MessageSpinner.tsx │ │ ├── Recorder.tsx │ │ └── index.tsx │ ├── Configuration │ │ ├── AudioTranscription.tsx │ │ ├── AudioTranslation.tsx │ │ ├── ChatCompletion.tsx │ │ ├── Completion.tsx │ │ ├── ConfigurationWrapper.tsx │ │ ├── ImageGeneration.tsx │ │ └── index.ts │ ├── ConversationList │ │ ├── ConversationItem.tsx │ │ ├── EmptyItem.tsx │ │ ├── ItemWrapper.tsx │ │ └── index.tsx │ ├── Divider │ │ └── index.tsx │ ├── EmojiPicker │ │ └── index.tsx │ ├── Icons │ │ ├── AzureLogoIcon.tsx │ │ ├── LoadingIcon.tsx │ │ ├── OpenAILogoIcon.tsx │ │ ├── OutlinePlusIcon.tsx │ │ ├── OutlineTranslationIcon.tsx │ │ ├── SolidCloseIcon.tsx │ │ ├── SolidSendIcon.tsx │ │ ├── SolidSettingsBrightnessIcon.tsx │ │ ├── SolidTranslationIcon.tsx │ │ └── index.tsx │ ├── ImportAndExportDexie │ │ └── index.tsx │ ├── InputSlider │ │ └── index.tsx │ ├── Loading │ │ └── index.tsx │ ├── Sidebar │ │ ├── Items.tsx │ │ └── index.tsx │ └── Waveform │ │ └── index.tsx ├── configurations │ ├── audioTranscription.ts │ ├── audioTranslation.ts │ ├── chatCompletion.ts │ ├── completion.ts │ ├── imageGeneration.ts │ └── index.ts ├── containers │ ├── Conversation │ │ └── index.tsx │ └── Settings │ │ └── index.tsx ├── db │ └── index.ts ├── hooks │ ├── index.ts │ ├── useAppData.ts │ ├── useAudio.ts │ ├── useChatCompletion.ts │ ├── useClients.ts │ ├── useCompletion.ts │ ├── useDB.ts │ ├── useImageGeneration.ts │ ├── useOnline.ts │ ├── useSettings.ts │ ├── useSpeech.ts │ ├── useStoreMessages.ts │ └── useTheme.ts ├── layouts │ └── index.tsx ├── main.tsx ├── routers │ └── index.ts ├── shared │ ├── constants.ts │ ├── countries.ts │ └── utils.ts ├── stores │ ├── conversation.ts │ ├── global.ts │ └── settings.ts ├── styles.css ├── types │ ├── conversation.ts │ ├── global.ts │ └── settings.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:import/recommended", 12 | "plugin:import/typescript", 13 | "plugin:react/recommended", 14 | "plugin:react-hooks/recommended" 15 | ], 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": { 18 | "ecmaVersion": "latest", 19 | "ecmaFeatures": { 20 | "jsx": true 21 | } 22 | }, 23 | "settings": { 24 | "import/resolver": { 25 | "alias": { 26 | "map": [ 27 | [ 28 | "src", 29 | "./src" 30 | ] 31 | ], 32 | "extensions": [ 33 | ".ts", 34 | ".tsx" 35 | ] 36 | } 37 | } 38 | }, 39 | "rules": { 40 | "react/jsx-uses-react": "off", 41 | "react/react-in-jsx-scope": "off" 42 | } 43 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [YanceyOfficial] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. macOS 13.2.1] 29 | - Version [e.g. 1.0.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | 6 | 7 | ### Additional context 8 | 9 | 10 | 11 | --- 12 | 13 | ### What is the purpose of this pull request? 14 | 15 | - [ ] Bug fix 16 | - [ ] New Feature 17 | - [ ] Documentation update 18 | - [ ] Other 19 | 20 | ### Before submitting the PR, please make sure you do the following 21 | 22 | - [ ] Read the [Contributing Guidelines](https://github.com/YanceyOfficial/hyperchat/blob/master/CONTRIBUTING.md). 23 | - [ ] Read the [Pull Request Guidelines](https://github.com/YanceyOfficial/hyperchat/blob/master/CONTRIBUTING.md#pull-request-guidelines) and follow the [Angular Team's Commit Message Guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit). 24 | - [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate. 25 | - [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`). 26 | - [ ] Ideally, include relevant tests that fail without this PR but pass with it. 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' 9 | directory: '/' 10 | schedule: 11 | interval: 'weekly' 12 | target-branch: 'master' 13 | commit-message: 14 | prefix: 'deps' 15 | open-pull-requests-limit: 20 16 | - package-ecosystem: 'npm' 17 | directory: '/website' 18 | schedule: 19 | interval: 'weekly' 20 | target-branch: 'develop' 21 | commit-message: 22 | prefix: 'deps' 23 | open-pull-requests-limit: 20 24 | -------------------------------------------------------------------------------- /.github/workflows/cross-platform-release.yml: -------------------------------------------------------------------------------- 1 | name: 'cross-platform-release' 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | 8 | # This is the example from the readme. 9 | # On each push to the `release` branch it will create or update a GitHub release, build your app, and upload the artifacts to the release. 10 | 11 | jobs: 12 | publish-tauri: 13 | permissions: 14 | contents: write 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | include: 19 | - platform: 'macos-latest' # for Arm based macs (M1 and above). 20 | args: '--target aarch64-apple-darwin' 21 | - platform: 'macos-latest' # for Intel based macs. 22 | args: '--target x86_64-apple-darwin' 23 | - platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04. 24 | args: '' 25 | - platform: 'windows-latest' 26 | args: '' 27 | 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - name: setup node 33 | uses: actions/setup-node@v4 34 | with: 35 | node-version: lts/* 36 | 37 | - name: setup pnpm 38 | uses: pnpm/action-setup@v4 39 | with: 40 | version: 9 41 | 42 | - name: install Rust stable 43 | uses: dtolnay/rust-toolchain@stable 44 | with: 45 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. 46 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 47 | 48 | - name: install dependencies (ubuntu only) 49 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. 50 | run: | 51 | sudo apt-get update 52 | sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf 53 | # webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2. 54 | # You can remove the one that doesn't apply to your app to speed up the workflow a bit. 55 | 56 | - name: install frontend dependencies 57 | run: pnpm install # change this to npm, pnpm or bun depending on which one you use. 58 | 59 | - uses: tauri-apps/tauri-action@v0 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} 63 | TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} 64 | with: 65 | tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. 66 | releaseName: 'App v__VERSION__' 67 | releaseBody: 'See the assets to download this version and install.' 68 | releaseDraft: true 69 | prerelease: false 70 | args: ${{ matrix.args }} 71 | -------------------------------------------------------------------------------- /.github/workflows/cross-platform-test.yml: -------------------------------------------------------------------------------- 1 | name: 'cross-platform-test' 2 | 3 | on: [pull_request] 4 | 5 | # This workflow will build your tauri app without uploading it anywhere. 6 | 7 | jobs: 8 | test-tauri: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - platform: 'macos-latest' # for Arm based macs (M1 and above). 14 | args: '--target aarch64-apple-darwin' 15 | - platform: 'macos-latest' # for Intel based macs. 16 | args: '--target x86_64-apple-darwin' 17 | - platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04. 18 | args: '' 19 | - platform: 'windows-latest' 20 | args: '' 21 | 22 | runs-on: ${{ matrix.platform }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: setup node 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: lts/* 30 | 31 | - name: setup pnpm 32 | uses: pnpm/action-setup@v4 33 | with: 34 | version: 9 35 | 36 | - name: install Rust stable 37 | uses: dtolnay/rust-toolchain@stable 38 | with: 39 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. 40 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 41 | 42 | - name: install dependencies (ubuntu only) 43 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. 44 | run: | 45 | sudo apt-get update 46 | sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf 47 | # webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2. 48 | # You can remove the one that doesn't apply to your app to speed up the workflow a bit. 49 | 50 | - name: install frontend dependencies 51 | run: pnpm install # change this to npm, pnpm or bun depending on which one you use. 52 | 53 | # If tagName and releaseId are omitted tauri-action will only build the app and won't try to upload any assets. 54 | - uses: tauri-apps/tauri-action@v0 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} 58 | TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} 59 | with: 60 | args: ${{ matrix.args }} 61 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - uses: googleapis/release-please-action@v4 19 | with: 20 | release-type: node 21 | package-name: release-please-action 22 | extra-files: | 23 | src-tauri/Cargo.toml 24 | src-tauri/tauri.conf.json 25 | -------------------------------------------------------------------------------- /.github/workflows/slack-notify.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: slack-notification 3 | jobs: 4 | slackNotification: 5 | name: Slack Notification when Pushing 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Slack Notification when Pushing 10 | uses: rtCamp/action-slack-notify@v2 11 | env: 12 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 13 | SLACK_CHANNEL: github-actions 14 | SLACK_TITLE: '${{ github.actor }} is ${{ github.event_name }}ing the ${{ github.ref }} to ${{ github.repository }}.' 15 | SLACK_FOOTER: 'Powered by Yancey Inc. and its affiliates.' 16 | SLACK_COLOR: ${{ job.status }} 17 | SLACK_MESSAGE: 'Commit: ${{ github.event.head_commit.message }}.' 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .idea 17 | .DS_Store 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | .env -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'src/*.{js,jsx,ts,tsx}': ['pnpm run prettier', 'pnpm run lint'] 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | auto-install-peers=true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "bracketSpacing": true, 5 | "proseWrap": "preserve", 6 | "semi": false, 7 | "printWidth": 80, 8 | "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.css": "tailwindcss" 4 | }, 5 | "editor.quickSuggestions": { 6 | "strings": true 7 | }, 8 | "tailwindCSS.includeLanguages": { 9 | "html": "html", 10 | "javascript": "javascript" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Lighthouse project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Yancey Leo 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code Of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, political party, or sexual identity and orientation. Note, however, that religion, political party, or other ideological affiliation provide no exemptions for the behavior we outline as unacceptable in this Code of Conduct. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team by DM at [Hyper Chat](https://discord.gg/tHzYjTqn). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 44 | 45 | [homepage]: https://www.contributor-covenant.org 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Hyper Chat Contributing Guide 2 | 3 | Hi! We are really excited that you are interested in contributing to Hyper Chat. Before submitting your contribution, please make sure to take a moment and read through the following guide: 4 | 5 | ## Repo Setup 6 | 7 | To develop and test the core `Hyper Chat` package, please respect the [Tauri Guide](https://tauri.app/v1/guides/). 8 | 9 | ## Pull Request Guidelines 10 | 11 | - Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch. 12 | 13 | - If adding a new feature: 14 | 15 | - Add accompanying test case. 16 | - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it. 17 | 18 | - If fixing bug: 19 | 20 | - If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`. 21 | - Provide a detailed description of the bug in the PR. Live demo preferred. 22 | - Add appropriate test coverage if applicable. 23 | 24 | - It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. 25 | 26 | - Make sure tests pass! 27 | 28 | - Commit messages must follow the [Angular Team's Commit Message Guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit) so that changelogs can be automatically generated. Commit messages are automatically validated before commit. 29 | - No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit. 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yancey Leo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyper Chat 2 | 3 | ![Hyper Chat](./screenshots/chat-completion.png) 4 | 5 | [![CodeQL](https://github.com/HyperChatBot/hyperchat/actions/workflows/github-code-scanning/codeql/badge.svg?branch=master)](https://github.com/HyperChatBot/hyperchat/actions/workflows/github-code-scanning/codeql) 6 | [![Release](https://github.com/HyperChatBot/hyperchat/actions/workflows/cross-platform-release.yml/badge.svg)](https://github.com/HyperChatBot/hyperchat/actions/workflows/cross-platform-release.yml) 7 | [![Test](https://github.com/HyperChatBot/hyperchat/actions/workflows/cross-platform-test.yml/badge.svg)](https://github.com/HyperChatBot/hyperchat/actions/workflows/cross-platform-test.yml) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 9 | [![Code Style](https://img.shields.io/badge/code%20style-prettier-green)](https://prettier.io/) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/HyperChatBot/hyperchat/pulls) 11 | [![Node](https://img.shields.io/badge/Node.js-%3E%3D18.19.0-green.svg)](https://nodejs.org/en/) 12 | [![Rust](https://img.shields.io/badge/Rust-%3E%3D1.81.0-orange.svg)](https://nodejs.org/en/) 13 | [![Version](https://img.shields.io/badge/Version-v2.0.1-blue.svg)](https://nodejs.org/en/) 14 | [![Twitter](https://img.shields.io/badge/Twitter-Connect-brightgreen?logo=twitter)](https://twitter/YanceyOfficial) 15 | 16 | ## Introduction 17 | 18 | Hyper Chat is a high-performance cross-platform AI chat application for desktop that is compatible with both OpenAI and Azure OpenAI services' APIs. In addition, Hyper Chat also provides features such as Text Completion, Image Generation, Audio Transcription, and Audio Translation. 19 | 20 | ## To start using Hyper Chat 21 | 22 | You can download Hyper Chat on our [Landing Page](https://hyperchat.yancey.app), or manual download on [GitHub Release](https://github.com/HyperChatBot/hyperchat/releases/). 23 | 24 | We always keep the dev tools(eg: Command + Option + I) open in the production environment. In Hyper Chat, everything is transparent and controllable. 25 | 26 | ### macOS 27 | 28 | As Hyper Chat is not planning to be released on the App Store, you may encounter the following issue when you open it for the first time. Please follow the steps below to resolve it: 29 | 30 | ![can't-be-oepn-in-macos](./screenshots/can't-be-oepn-in-macos.png) 31 | 32 | 1. Move the Hyper Chat.app to the /Applications directory. 33 | 2. Open your terminal App, execute the command `chmod +x /Applications/Hyper\ Chat.app/Contents/MacOS/Hyper\ Chat`. 34 | 35 | ## To start developing Hyper Chat 36 | 37 | ### Prerequisites 38 | 39 | We have chosen [Tauri](https://tauri.app/) as our cross-platform base. Please make sure that [Rust](https://www.rust-lang.org/) is installed on your system. 40 | 41 | Then, to install Tauri CLI globally, please follow the tutorial on [create-tauri-app](https://github.com/tauri-apps/create-tauri-app). We recommend using `cargo install tauri-cli`. 42 | 43 | Additionally, we use [React](https://react.dev/) + [Vite](https://vitejs.dev/) for rendering and packaging pages, so please install [Node.js](https://nodejs.org/en) and [pnpm](https://pnpm.io/) globally in advance. 44 | 45 | ### Recommended IDE Setup 46 | 47 | - [VS Code](https://code.visualstudio.com/) 48 | - [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) 49 | - [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 50 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 51 | - [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 52 | - [Eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 53 | 54 | ### Available Scripts 55 | 56 | - To start tauri development window, you can execute `cargo tauri dev`. 57 | - To build the bundle, you can execute `cargo tauri build`. 58 | 59 | ## Contributing 60 | 61 | The main purpose of this repository is to continue to evolve Hyper Chat, making it faster and easier to use. Development of Hyper Chat happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving Hyper Chat. 62 | 63 | ### [Code of Conduct](./CODE_OF_CONDUCT.md) 64 | 65 | Hyper Chat has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated. 66 | 67 | ### [Contributing Guide](./CONTRIBUTING.md) 68 | 69 | Read our [contributing guide](./CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Hyper Chat. 70 | 71 | ### Good Issues 72 | 73 | Please make sure to read the [Issue Reporting Checklist](./.github/ISSUE_TEMPLATE/bug_report.md) before opening an issue. Issues not conforming to the guidelines may be closed immediately. 74 | 75 | ## Upgrade Plans 76 | 77 | We are continuously working to enhance Hyper Chat's capabilities and performance. Here are some of the features and upgrades that we plan to add in the future releases: 78 | 79 | - Support function call and plugin 80 | - Support audio input 81 | - Support for Claude, Gemini, Llama and so on 82 | - Improve Performance 83 | 84 | ## Discussions 85 | 86 | If you have any questions or feedback about Hyper Chat, please visit our [official discussion forum](https://github.com/orgs/HyperChatBot/discussions/71) to start a conversation with our team or other users. We are committed to making Hyper Chat the best possible chat application, and your feedback plays a crucial role in achieving this goal. 87 | 88 | ## Thanks 89 | 90 | The UI design is inspired by [Chat-Web-App-UI-Kit](https://www.figma.com/community/file/1167012734150108159/Chat-Web-App-UI-Kit), Thank you [Figma UI Free](https://www.figma.com/@figmauifree)! 91 | 92 | ## License 93 | 94 | Hyper Chat is licensed under the terms of the [MIT licensed](https://opensource.org/licenses/MIT). 95 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | If you discover a security issue in Hyper Chat, please report it by sending an email to [yanceyofficial@gmail.com](mailto:yanceyofficial@gmail.com). 4 | 5 | This will allow us to assess the risk, and make a fix available before we add a bug report to the GitHub repository. 6 | 7 | Thanks for helping make Hyper Chat safe for everyone! 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hyper Chat 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperchat", 3 | "private": true, 4 | "version": "2.0.1", 5 | "type": "module", 6 | "description": "ChatGPT AI Bot.", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview", 11 | "tauri": "tauri", 12 | "prettier": "prettier ./.prettierrc -w ./src", 13 | "lint": "eslint --cache --fix", 14 | "prepare": "husky install", 15 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" 16 | }, 17 | "dependencies": { 18 | "@azure/openai": "1.0.0-beta.12", 19 | "@emoji-mart/data": "^1.2.1", 20 | "@emoji-mart/react": "^1.1.1", 21 | "@emotion/react": "^11.13.3", 22 | "@emotion/styled": "^11.13.0", 23 | "@heroicons/react": "^2.1.5", 24 | "@lottiefiles/react-lottie-player": "^3.5.4", 25 | "@mui/material": "^6.1.2", 26 | "@tauri-apps/api": "^2.0.2", 27 | "@tauri-apps/plugin-fs": "~2.0.0", 28 | "@tauri-apps/plugin-shell": "~2.0.0", 29 | "@tauri-apps/plugin-updater": "~2.0.0", 30 | "axios": "^1.7.7", 31 | "change-case": "^5.4.4", 32 | "classnames": "^2.5.1", 33 | "dexie": "^4.0.8", 34 | "dexie-export-import": "^4.1.2", 35 | "dexie-react-hooks": "^1.1.7", 36 | "emoji-mart": "^5.6.0", 37 | "formik": "^2.4.6", 38 | "immer": "^10.1.1", 39 | "js-tiktoken": "^1.0.15", 40 | "luxon": "^3.5.0", 41 | "microsoft-cognitiveservices-speech-sdk": "^1.40.0", 42 | "notistack": "^3.0.1", 43 | "openai": "^4.67.2", 44 | "react": "^18.3.1", 45 | "react-dom": "^18.3.1", 46 | "react-markdown": "^9.0.1", 47 | "react-router-dom": "^6.26.2", 48 | "react-syntax-highlighter": "^15.5.0", 49 | "recoil": "^0.7.7", 50 | "rehype-mathjax": "^6.0.0", 51 | "remark-gfm": "^4.0.0", 52 | "remark-math": "^6.0.0", 53 | "uuid": "^10.0.0", 54 | "wavesurfer.js": "^7.8.6", 55 | "yancey-js-util": "^3.2.0" 56 | }, 57 | "devDependencies": { 58 | "@commitlint/cli": "^19.5.0", 59 | "@commitlint/config-conventional": "^19.5.0", 60 | "@tauri-apps/cli": "^2.0.2", 61 | "@types/luxon": "^3.4.2", 62 | "@types/node": "^22.7.5", 63 | "@types/react": "^18.3.11", 64 | "@types/react-dom": "^18.3.0", 65 | "@types/react-syntax-highlighter": "^15.5.13", 66 | "@types/uuid": "^10.0.0", 67 | "@types/wavesurfer.js": "^6.0.12", 68 | "@typescript-eslint/eslint-plugin": "^8.8.1", 69 | "@typescript-eslint/parser": "^8.8.1", 70 | "@vitejs/plugin-react": "^4.3.2", 71 | "autoprefixer": "^10.4.20", 72 | "conventional-changelog-cli": "^5.0.0", 73 | "eslint": "^9.12.0", 74 | "eslint-import-resolver-alias": "^1.1.2", 75 | "eslint-plugin-import": "^2.31.0", 76 | "eslint-plugin-react": "^7.37.1", 77 | "eslint-plugin-react-hooks": "^4.6.2", 78 | "husky": "^9.1.6", 79 | "lint-staged": "^15.2.10", 80 | "postcss": "^8.4.47", 81 | "postcss-import": "^16.1.0", 82 | "prettier": "^3.3.3", 83 | "prettier-plugin-organize-imports": "^4.1.0", 84 | "prettier-plugin-tailwindcss": "^0.6.8", 85 | "tailwindcss": "^3.4.13", 86 | "typescript": "^5.6.2", 87 | "vite": "^5.4.8" 88 | } 89 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/public/logo.png -------------------------------------------------------------------------------- /screenshots/can't-be-oepn-in-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/screenshots/can't-be-oepn-in-macos.png -------------------------------------------------------------------------------- /screenshots/chat-completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/screenshots/chat-completion.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperchat" 3 | version = "2.0.1" 4 | description = "ChatGPT AI Bot." 5 | authors = ["Yancey Leo"] 6 | license = "MIT" 7 | repository = "https://github.com/HyperChatBot/hyperchat" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [build-dependencies] 13 | tauri-build = { version = "2", features = [] } 14 | 15 | [dependencies] 16 | tauri = { version = "2", features = [ "protocol-asset", "devtools"] } 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0" 19 | tauri-plugin-fs = "2" 20 | tauri-plugin-shell = "2" 21 | 22 | [features] 23 | # this feature is used for production builds or when `devPath` points to the filesystem 24 | # DO NOT REMOVE!! 25 | custom-protocol = ["tauri/custom-protocol"] 26 | 27 | [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 28 | tauri-plugin-updater = "2" 29 | -------------------------------------------------------------------------------- /src-tauri/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | Request microphone access for WebRTC 7 | 8 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/capabilities/desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "desktop-capability", 3 | "platforms": [ 4 | "macOS", 5 | "windows", 6 | "linux" 7 | ], 8 | "permissions": [ 9 | "updater:default" 10 | ] 11 | } -------------------------------------------------------------------------------- /src-tauri/capabilities/migrated.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "migrated", 3 | "description": "permissions that were migrated from v1", 4 | "local": true, 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:default", 10 | "fs:allow-read-file", 11 | "fs:allow-write-file", 12 | "fs:allow-read-dir", 13 | "fs:allow-copy-file", 14 | "fs:allow-mkdir", 15 | "fs:allow-remove", 16 | "fs:allow-remove", 17 | "fs:allow-rename", 18 | "fs:allow-exists", 19 | { 20 | "identifier": "fs:scope", 21 | "allow": [ 22 | "**" 23 | ] 24 | }, 25 | "shell:allow-execute", 26 | "shell:allow-open" 27 | ] 28 | } -------------------------------------------------------------------------------- /src-tauri/gen/schemas/capabilities.json: -------------------------------------------------------------------------------- 1 | {"desktop-capability":{"identifier":"desktop-capability","description":"","local":true,"permissions":["updater:default"],"platforms":["macOS","windows","linux"]},"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-remove","fs:allow-rename","fs:allow-exists",{"identifier":"fs:scope","allow":["**"]},"shell:allow-execute","shell:allow-open"]}} -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command 5 | #[tauri::command] 6 | fn greet(name: &str) -> String { 7 | format!("Hello, {}! You've been greeted from Rust!", name) 8 | } 9 | 10 | fn main() { 11 | tauri::Builder::default() 12 | .plugin(tauri_plugin_shell::init()) 13 | .plugin(tauri_plugin_fs::init()) 14 | .plugin(tauri_plugin_updater::Builder::new().build()) 15 | .invoke_handler(tauri::generate_handler![greet]) 16 | .run(tauri::generate_context!()) 17 | .expect("error while running tauri application"); 18 | } 19 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "pnpm dev", 4 | "beforeBuildCommand": "pnpm build", 5 | "frontendDist": "../dist", 6 | "devUrl": "http://localhost:1420" 7 | }, 8 | "bundle": { 9 | "active": true, 10 | "icon": [ 11 | "icons/32x32.png", 12 | "icons/128x128.png", 13 | "icons/128x128@2x.png", 14 | "icons/icon.icns", 15 | "icons/icon.ico" 16 | ], 17 | "targets": "all", 18 | "createUpdaterArtifacts": "v1Compatible" 19 | }, 20 | "productName": "Hyper Chat", 21 | "mainBinaryName": "Hyper Chat", 22 | "version": "2.0.1", 23 | "identifier": "app.yancey.hyperchat", 24 | "plugins": { 25 | "updater": { 26 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQ5OTc4NjI4NEIzMTdDMUIKUldRYmZERkxLSWFYMlY5NkYwRE55Z1NCRHA1T3lxTHBkaUhCN2dUQ1hkUU9ZTm8zNzJTcFpSNzMK", 27 | "endpoints": [ 28 | "https://faas.yancey.app/api/updater/{{target}}/{{arch}}/{{current_version}}?code=PjS1zF7Vc1QMrJ80C3xJS1tqkGqH-pT4oNlPfkFBYt0YAzFupnB5tA==" 29 | ], 30 | "windows": { 31 | "installMode": "passive" 32 | } 33 | } 34 | }, 35 | "app": { 36 | "withGlobalTauri": true, 37 | "windows": [ 38 | { 39 | "fullscreen": false, 40 | "resizable": true, 41 | "title": "Hyper Chat", 42 | "width": 1280, 43 | "height": 900, 44 | "minWidth": 1280, 45 | "minHeight": 900 46 | } 47 | ], 48 | "security": { 49 | "assetProtocol": { 50 | "scope": [ 51 | "**" 52 | ], 53 | "enable": true 54 | }, 55 | "csp": "default-src 'self'; img-src * 'self' data: https: asset: https://asset.localhost; style-src 'self' 'unsafe-inline'; connect-src ipc: http://ipc.localhost * asset: https://asset.localhost" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/assets/chatbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src/assets/chatbot.png -------------------------------------------------------------------------------- /src/assets/chatgpt-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyperChatBot/hyperchat/786b866ad6e148ec6b3a18211f4c81a9f8740306/src/assets/chatgpt-avatar.png -------------------------------------------------------------------------------- /src/components/Avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { FC } from 'react' 3 | 4 | interface Props { 5 | src: string 6 | size?: 'xs' | 'md' 7 | className?: string 8 | onClick?: () => void 9 | } 10 | 11 | const Avatar: FC = ({ src, size, className, onClick }) => ( 12 | avatar 24 | ) 25 | 26 | export default Avatar 27 | -------------------------------------------------------------------------------- /src/components/ChatBox/ChatBubble.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { DateTime } from 'luxon' 3 | import { FC, ReactNode, memo } from 'react' 4 | import { Roles } from 'src/types/conversation' 5 | import Avatar from '../Avatar' 6 | 7 | interface Props { 8 | role: Roles 9 | avatar: string 10 | children: ReactNode 11 | date: number 12 | } 13 | 14 | const ChatBubble: FC = ({ role, avatar, date, children }) => ( 15 |
20 | {role === Roles.Assistant && ( 21 | 27 | )} 28 | 29 |
35 |
43 | {children} 44 |
45 |

50 | {DateTime.fromMillis(date).toLocaleString( 51 | DateTime.DATETIME_SHORT_WITH_SECONDS 52 | )} 53 |

54 |
55 |
56 | ) 57 | 58 | export default memo( 59 | ChatBubble, 60 | (prevProps, nextProps) => prevProps.children === nextProps.children 61 | ) 62 | -------------------------------------------------------------------------------- /src/components/ChatBox/ChatMessages.tsx: -------------------------------------------------------------------------------- 1 | import { SpeakerWaveIcon } from '@heroicons/react/24/outline' 2 | import classNames from 'classnames' 3 | import { ChatCompletionContentPartText } from 'openai/resources' 4 | import { FC, memo, useEffect, useMemo, useRef, useState } from 'react' 5 | import { useRecoilValue } from 'recoil' 6 | import ChatGPTLogoImg from 'src/assets/chatbot.png' 7 | import NoDataIllustration from 'src/assets/illustrations/no-data.svg' 8 | import { useSettings, useSpeech } from 'src/hooks' 9 | import { isSupportAudio } from 'src/shared/utils' 10 | import { currConversationState, loadingState } from 'src/stores/conversation' 11 | import { currProductState } from 'src/stores/global' 12 | import { Message, Roles } from 'src/types/conversation' 13 | import { Products } from 'src/types/global' 14 | import Waveform from '../Waveform' 15 | import ChatBubble from './ChatBubble' 16 | import Markdown from './Markdown' 17 | import MessageSpinner from './MessageSpinner' 18 | 19 | const ChatMessages: FC = () => { 20 | const chatBoxRef = useRef(null) 21 | const loading = useRecoilValue(loadingState) 22 | const { settings } = useSettings() 23 | const currProduct = useRecoilValue(currProductState) 24 | const currConversation = useRecoilValue(currConversationState) 25 | const [audioUrl, setAudioUrl] = useState('') 26 | const createSpeech = useSpeech() 27 | const hasMessages = useMemo( 28 | () => currConversation && currConversation.messages.length > 0, 29 | [currConversation] 30 | ) 31 | 32 | const getBotLogo = (role: Roles) => 33 | role === Roles.Assistant 34 | ? settings?.assistantAvatarFilename 35 | ? settings.assistantAvatarFilename 36 | : ChatGPTLogoImg 37 | : '' 38 | 39 | const scrollToBottom = () => { 40 | if (!chatBoxRef.current) return 41 | const $el = chatBoxRef.current 42 | 43 | if ($el.scrollHeight > $el.scrollTop + $el.clientHeight + 24) { 44 | $el.scrollTo({ 45 | top: $el.scrollHeight, 46 | left: 0 47 | }) 48 | } 49 | } 50 | 51 | const createTTSUrl = async (text: string) => { 52 | if (typeof createSpeech === 'function') { 53 | const url = await createSpeech(text) 54 | if (url) { 55 | setAudioUrl(url) 56 | } 57 | } 58 | } 59 | 60 | const getAudioFilename = (message: Message) => { 61 | if (isSupportAudio(currProduct) && message.content[0].type === 'audio') { 62 | return message.content[0].audioUrl.url 63 | } 64 | 65 | return '' 66 | } 67 | 68 | useEffect(() => { 69 | scrollToBottom() 70 | }, [currConversation]) 71 | 72 | return ( 73 |
80 | {hasMessages ? ( 81 | <> 82 | {currConversation?.messages.map((message) => ( 83 | 89 | {getAudioFilename(message) ? ( 90 | <> 91 | 92 | {message.content} 93 | 94 | ) : ( 95 | <> 96 | {loading && !message.content ? ( 97 | 98 | ) : message.role === Roles.Assistant ? ( 99 |
100 | 107 | 122 | {audioUrl &&
124 | ) : ( 125 |
126 | {message.content.map((item, key) => { 127 | if (item.type === 'image_url') { 128 | return ( 129 | 134 | ) 135 | } 136 | 137 | if (item.type === 'text') { 138 | return

{item.text}

139 | } 140 | })} 141 |
142 | )} 143 | 144 | )} 145 |
146 | ))} 147 | 148 | {loading && currProduct !== Products.ChatCompletion && ( 149 | 154 | 155 | 156 | )} 157 | 158 | ) : ( 159 | NoDataIllustration 164 | )} 165 |
166 | ) 167 | } 168 | 169 | export default memo(ChatMessages) 170 | -------------------------------------------------------------------------------- /src/components/ChatBox/ContactHeader.tsx: -------------------------------------------------------------------------------- 1 | import { AdjustmentsVerticalIcon } from '@heroicons/react/24/outline' 2 | import { 3 | CheckIcon, 4 | PencilSquareIcon, 5 | TrashIcon 6 | } from '@heroicons/react/24/solid' 7 | import Input from '@mui/material/Input' 8 | import classNames from 'classnames' 9 | import { enqueueSnackbar } from 'notistack' 10 | import { FC, KeyboardEvent, memo, useEffect, useState } from 'react' 11 | import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' 12 | import ChatGPTLogoImg from 'src/assets/chatbot.png' 13 | import { useDB } from 'src/hooks' 14 | import { BAN_ACTIVE_HINT, EMPTY_CHAT_HINT } from 'src/shared/constants' 15 | import { 16 | avatarPickerVisibleState, 17 | currConversationState, 18 | loadingState, 19 | summaryInputVisibleState 20 | } from 'src/stores/conversation' 21 | import { configurationDrawerVisibleState, onlineState } from 'src/stores/global' 22 | import { EmojiPickerProps } from 'src/types/global' 23 | import Avatar from '../Avatar' 24 | import EmojiPicker from '../EmojiPicker' 25 | 26 | const ContactHeader: FC = () => { 27 | const loading = useRecoilValue(loadingState) 28 | const [currConversation, setCurrConversation] = useRecoilState( 29 | currConversationState 30 | ) 31 | const [summaryInputVisible, setSummaryInputVisible] = useRecoilState( 32 | summaryInputVisibleState 33 | ) 34 | const [avatarPickerVisible, setAvatarPickerVisible] = useRecoilState( 35 | avatarPickerVisibleState 36 | ) 37 | const isOnline = useRecoilValue(onlineState) 38 | const setConfigurationDrawerVisible = useSetRecoilState( 39 | configurationDrawerVisibleState 40 | ) 41 | const [isTyping, setIsTyping] = useState(false) 42 | const [summaryValue, setSummaryValue] = useState( 43 | currConversation?.summary || '' 44 | ) 45 | const { updateOneById, deleteOneById } = useDB('conversations') 46 | 47 | const summary = 48 | currConversation?.summary || 49 | currConversation?.conversationId || 50 | EMPTY_CHAT_HINT 51 | 52 | const openAvatarPicker = () => { 53 | if (loading) { 54 | enqueueSnackbar(BAN_ACTIVE_HINT, { variant: 'warning' }) 55 | return 56 | } 57 | setAvatarPickerVisible(true) 58 | } 59 | 60 | const openSummaryInput = () => { 61 | if (loading) { 62 | enqueueSnackbar(BAN_ACTIVE_HINT, { variant: 'warning' }) 63 | return 64 | } 65 | if (!currConversation) return 66 | setSummaryValue(currConversation?.summary || '') 67 | setSummaryInputVisible(true) 68 | } 69 | 70 | const openConfigurationDrawer = () => { 71 | if (loading) { 72 | enqueueSnackbar(BAN_ACTIVE_HINT, { variant: 'warning' }) 73 | return 74 | } 75 | setConfigurationDrawerVisible(true) 76 | } 77 | 78 | const saveSummary = async () => { 79 | if (summaryValue.trim().length === 0) return 80 | 81 | if (currConversation) { 82 | const changes = { 83 | summary: summaryValue, 84 | updatedAt: +new Date() 85 | } 86 | await updateOneById(currConversation.conversationId, changes) 87 | setCurrConversation({ ...currConversation, ...changes }) 88 | setSummaryInputVisible(false) 89 | } 90 | } 91 | 92 | const handleKeyDown = (e: KeyboardEvent) => { 93 | if (e.key === 'Enter' && !isTyping) { 94 | e.preventDefault() 95 | saveSummary() 96 | } 97 | } 98 | 99 | const saveAvatar = async (data: EmojiPickerProps) => { 100 | if (currConversation) { 101 | const changes = { 102 | avatar: data.native, 103 | updatedAt: +new Date() 104 | } 105 | await updateOneById(currConversation.conversationId, changes) 106 | setCurrConversation({ ...currConversation, ...changes }) 107 | setAvatarPickerVisible(false) 108 | } 109 | } 110 | 111 | const deleteCurrConversation = async () => { 112 | if (loading) { 113 | enqueueSnackbar(BAN_ACTIVE_HINT, { variant: 'warning' }) 114 | return 115 | } 116 | if (currConversation) { 117 | await deleteOneById(currConversation.conversationId) 118 | } 119 | } 120 | 121 | useEffect(() => { 122 | setSummaryInputVisible(false) 123 | setAvatarPickerVisible(false) 124 | }, [currConversation]) 125 | 126 | return ( 127 |
128 |
129 | {currConversation?.avatar ? ( 130 |
134 | {currConversation?.avatar} 135 |
136 | ) : ( 137 | 138 | )} 139 | 140 | {avatarPickerVisible && } 141 | 142 |
143 |
144 | {summaryInputVisible ? ( 145 | <> 146 | setSummaryValue(e.target.value)} 151 | onCompositionStart={() => setIsTyping(true)} 152 | onCompositionEnd={() => setIsTyping(false)} 153 | className="w-80" 154 | sx={{ 155 | '.MuiInput-input': { 156 | padding: 0 157 | } 158 | }} 159 | /> 160 | 161 | 162 | ) : ( 163 |
167 |

{summary}

168 | {!!currConversation && } 169 |
170 | )} 171 |
172 | 173 |

174 | 180 | 181 | {isOnline ? 'Online' : 'Offline'} 182 | 183 |

184 |
185 |
186 | {currConversation && ( 187 |
188 |
192 | 193 |
194 |
198 | 199 |
200 |
201 | )} 202 |
203 | ) 204 | } 205 | 206 | export default memo(ContactHeader) 207 | -------------------------------------------------------------------------------- /src/components/ChatBox/InputBox.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { produce } from 'immer' 3 | import { 4 | ChatCompletionContentPart, 5 | ChatCompletionContentPartImage, 6 | ChatCompletionContentPartText 7 | } from 'openai/resources' 8 | import { FC, memo, useEffect, useRef, useState } from 'react' 9 | import { useRecoilState, useRecoilValue } from 'recoil' 10 | import items from 'src/components/Sidebar/Items' 11 | import { 12 | useAudio, 13 | useChatCompletion, 14 | useCompletion, 15 | useImageGeneration 16 | } from 'src/hooks' 17 | import { 18 | audioFileState, 19 | base64ImagesState, 20 | currConversationState, 21 | loadingState, 22 | userInputState 23 | } from 'src/stores/conversation' 24 | import { currProductState } from 'src/stores/global' 25 | import { settingsState } from 'src/stores/settings' 26 | import { AudioContentPart } from 'src/types/conversation' 27 | import { Products } from 'src/types/global' 28 | import { LoadingIcon, SolidCloseIcon, SolidSendIcon } from '../Icons' 29 | import WaveForm from '../Waveform' 30 | import MediaUploader from './MediaUploader' 31 | import AudioRecorder from './Recorder' 32 | 33 | const InputBox: FC = () => { 34 | const currConversation = useRecoilValue(currConversationState) 35 | const currProduct = useRecoilValue(currProductState) 36 | const settings = useRecoilValue(settingsState) 37 | const loading = useRecoilValue(loadingState) 38 | const [userInput, setUserInput] = useRecoilState(userInputState) 39 | const [audioFile, setAudioFile] = useRecoilState(audioFileState) 40 | const [base64Images, setBase64Images] = useRecoilState(base64ImagesState) 41 | const createChatCompletion = useChatCompletion() 42 | const createAudio = useAudio() 43 | const createImage = useImageGeneration() 44 | const createCompletion = useCompletion() 45 | const textareaRef = useRef(null) 46 | const [isTyping, setIsTyping] = useState(false) 47 | const mediaType = items.find( 48 | (item) => item.product === currProduct 49 | )?.multiMedia 50 | 51 | const deleteBase64Image = (idx: number) => { 52 | setBase64Images( 53 | produce(base64Images, (draft) => { 54 | draft?.splice(idx, 1) 55 | }) 56 | ) 57 | } 58 | 59 | const resetInput = () => { 60 | setUserInput('') 61 | setAudioFile({ 62 | filename: '', 63 | binary: undefined 64 | }) 65 | setBase64Images(null) 66 | } 67 | 68 | const validate = () => { 69 | if (loading) return false 70 | return userInput.trim().length !== 0 71 | } 72 | 73 | const handleRequest = () => { 74 | if (!settings || !validate()) return 75 | 76 | if (currProduct === Products.ChatCompletion) { 77 | const chatMessageImageContent: 78 | | ChatCompletionContentPartImage[] 79 | | undefined = base64Images?.map((imageUrl) => ({ 80 | type: 'image_url', 81 | image_url: { 82 | url: imageUrl 83 | } 84 | })) 85 | 86 | const chatMessageTextContent: ChatCompletionContentPartText = { 87 | type: 'text', 88 | text: userInput 89 | } 90 | 91 | const chatCompletionUserMessage: ChatCompletionContentPart[] = [ 92 | ...(chatMessageImageContent || []), 93 | chatMessageTextContent 94 | ] 95 | 96 | if (createChatCompletion) { 97 | createChatCompletion(chatCompletionUserMessage) 98 | } 99 | } 100 | 101 | if ( 102 | currProduct === Products.AudioTranscription || 103 | currProduct === Products.AudioTranslation 104 | ) { 105 | const audioContentPart: AudioContentPart[] = [ 106 | { 107 | type: 'audio', 108 | audioUrl: { url: audioFile.filename }, 109 | text: userInput, 110 | binary: audioFile.binary 111 | } 112 | ] 113 | 114 | if (createAudio) { 115 | if (currProduct === Products.AudioTranscription) { 116 | const createAudioTranscription = 117 | createAudio[Products.AudioTranscription] 118 | createAudioTranscription(audioContentPart) 119 | } 120 | 121 | if (currProduct === Products.AudioTranslation) { 122 | const createAudioTranslation = createAudio[Products.AudioTranslation] 123 | createAudioTranslation(audioContentPart) 124 | } 125 | } 126 | } 127 | 128 | if (currProduct === Products.ImageGeneration) { 129 | const imageGenerationTextContent: ChatCompletionContentPartText[] = [ 130 | { 131 | type: 'text', 132 | text: userInput 133 | } 134 | ] 135 | 136 | if (createImage) { 137 | createImage(imageGenerationTextContent) 138 | } 139 | } 140 | 141 | if (currProduct === Products.Completion) { 142 | const completionTextContent: ChatCompletionContentPartText[] = [ 143 | { 144 | type: 'text', 145 | text: userInput 146 | } 147 | ] 148 | 149 | if (createCompletion) { 150 | createCompletion(completionTextContent) 151 | } 152 | } 153 | 154 | resetInput() 155 | } 156 | 157 | // FIXME: I cannot declare the type of `event` correctly. 158 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 159 | // @ts-ignore 160 | const handleKeyDown = (event) => { 161 | if (event.key === 'Enter' && event.shiftKey) { 162 | event.preventDefault() 163 | 164 | const start = event.target.selectionStart 165 | const end = event.target.selectionEnd 166 | const value = event.target.value 167 | 168 | setUserInput(value.substring(0, start) + '\n' + value.substring(end)) 169 | event.target.selectionStart = event.target.selectionEnd = start + 1 170 | } 171 | 172 | if (event.key === 'Enter' && !event.shiftKey && !isTyping) { 173 | event.preventDefault() 174 | handleRequest() 175 | } 176 | } 177 | 178 | useEffect(() => { 179 | if (textareaRef && textareaRef.current) { 180 | textareaRef.current.style.height = 'inherit' 181 | textareaRef.current.style.height = `${textareaRef.current?.scrollHeight}px` 182 | textareaRef.current.style.overflow = `${ 183 | textareaRef?.current?.scrollHeight > 400 ? 'auto' : 'hidden' 184 | }` 185 | } 186 | }, [userInput]) 187 | 188 | if (!currConversation) return null 189 | 190 | return ( 191 |
192 | {Array.isArray(base64Images) && base64Images.length > 0 && ( 193 |
194 | {base64Images.map((image, idx) => ( 195 |
196 | 197 | deleteBase64Image(idx)} 200 | /> 201 | 202 | 203 |
204 | ))} 205 |
206 | )} 207 | {audioFile.filename && ( 208 |
209 | 210 |
211 | )} 212 | 213 | {mediaType && ( 214 | 218 | )} 219 | 220 |