├── .eslintrc.json
├── .github
└── workflows
│ ├── build.yml
│ └── ci.yaml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── LICENSE
├── README-CN.md
├── README.md
├── assets
├── .gitkeep
├── banner
│ ├── banner.01.png
│ ├── bar.png
│ ├── brand01.png
│ ├── brand02.png
│ ├── brand_nobg.png
│ └── demo.gif
├── icon
│ ├── icon.icns
│ ├── icon.ico
│ ├── icon.png
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon24.png
│ ├── icon32.png
│ └── icon64.png
└── screenshot
│ ├── account.png
│ ├── chat.png
│ ├── code.png
│ ├── console.png
│ ├── list.png
│ ├── plugin.png
│ ├── prompts.png
│ ├── setting.png
│ └── video.png
├── babel.config.js
├── commitlint.config.js
├── forge.config.js
├── jest.config.js
├── package.json
├── prettier.config.js
├── public
└── index.html
├── renovate.json
├── src
├── @types
│ ├── bridge.d.ts
│ ├── deps.d.ts
│ ├── i18next.d.ts
│ ├── image.d.ts
│ └── index.ts
├── App.tsx
├── app
│ ├── api.ts
│ ├── constants.ts
│ ├── hooks.ts
│ ├── images.ts
│ └── store.ts
├── assets
│ └── icon128.png
├── components
│ ├── Button
│ │ ├── Button.spec.tsx
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── ChatPanel
│ │ ├── OperatePanel.tsx
│ │ └── index.tsx
│ ├── Logo
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Modal
│ │ └── prompt.tsx
│ ├── Preset
│ │ ├── detail.tsx
│ │ └── index.tsx
│ ├── Search
│ │ └── index.tsx
│ └── Setting
│ │ ├── Account
│ │ └── index.tsx
│ │ ├── Basic
│ │ └── index.tsx
│ │ ├── Prompt
│ │ └── index.tsx
│ │ └── index.tsx
├── electron
│ ├── apis
│ │ ├── account.ts
│ │ ├── apply.ts
│ │ ├── crawl.ts
│ │ ├── prompt.ts
│ │ └── test.ts
│ ├── client
│ │ ├── bridge.ts
│ │ ├── clipboard.ts
│ │ ├── index.ts
│ │ ├── link.ts
│ │ ├── store.ts
│ │ └── window.ts
│ ├── constants
│ │ ├── common.ts
│ │ ├── event.ts
│ │ └── index.ts
│ ├── global.d.ts
│ ├── main.ts
│ ├── os
│ │ ├── applescript.ts
│ │ ├── index.ts
│ │ ├── shortcuts.ts
│ │ └── tray.ts
│ ├── prompt
│ │ ├── prompts-zh.json
│ │ └── prompts.json
│ ├── server.ts
│ ├── sound
│ │ └── index.ts
│ ├── types.ts
│ └── utils
│ │ ├── global.ts
│ │ ├── log.ts
│ │ ├── util.ts
│ │ └── window.ts
├── features
│ ├── chat
│ │ └── chatSlice.ts
│ ├── clipboard
│ │ └── clipboardSlice.ts
│ ├── history
│ │ └── historySlice.ts
│ ├── preset
│ │ └── presetSlice.ts
│ └── setting
│ │ └── settingSlice.ts
├── i18n.ts
├── index.tsx
├── locales
│ ├── en.json
│ └── zh.json
├── styles
│ ├── GlobalStyle.ts
│ └── Main.ts
└── utils
│ ├── fetch.ts
│ └── index.ts
├── tests
└── setupTests.ts
├── tsconfig.json
├── webpack
├── main.webpack.js
├── renderer.webpack.js
└── rules.webpack.js
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "plugins": ["unused-imports"],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/eslint-recommended",
11 | "plugin:@typescript-eslint/recommended",
12 | "plugin:import/recommended",
13 | "plugin:import/electron",
14 | "plugin:import/typescript"
15 | ],
16 | "rules": {
17 | "@typescript-eslint/ban-ts-comment": "off",
18 | "@typescript-eslint/no-empty-function": "off",
19 | "@typescript-eslint/no-explicit-any": "off",
20 | "@typescript-eslint/no-non-null-assertion": "off",
21 | "@typescript-eslint/no-unused-vars": "off",
22 | "@typescript-eslint/no-var-requires": "off",
23 | "import/no-named-as-default": "off",
24 | "no-constant-condition": "off",
25 | "unused-imports/no-unused-imports": "error",
26 | "unused-imports/no-unused-vars": [
27 | "warn",
28 | {
29 | "vars": "all",
30 | "varsIgnorePattern": "^_",
31 | "args": "after-used",
32 | "argsIgnorePattern": "^_"
33 | }
34 | ]
35 | },
36 | "parser": "@typescript-eslint/parser"
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
7 |
8 | jobs:
9 | createrelease:
10 | name: Create Release
11 | permissions: write-all
12 | runs-on: [ubuntu-latest]
13 | steps:
14 | - name: Create Release
15 | id: create_release
16 | uses: actions/create-release@v1
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 | with:
20 | tag_name: ${{ github.ref }}
21 | release_name: Release ${{ github.ref }}
22 | draft: false
23 | prerelease: false
24 |
25 | - name: Output Release URL File
26 | run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
27 |
28 | - name: Save Release URL File for publish
29 | uses: actions/upload-artifact@v1
30 | with:
31 | name: release_url
32 | path: release_url.txt
33 |
34 | build:
35 | permissions: write-all
36 | runs-on: ${{ matrix.os }}
37 |
38 | strategy:
39 | matrix:
40 | include:
41 | - os: macos-latest
42 | TARGET: macos
43 | PLATFORM: darwin
44 | TYPE: x64
45 | FORMAT: zip
46 | ASSET_MIME: application/zip
47 | - os: windows-latest
48 | TARGET: windows
49 | PLATFORM: squirrel.windows
50 | TYPE: x64
51 | FORMAT: exe
52 | ASSET_MIME: application/vnd.microsoft.portable-executable
53 |
54 | steps:
55 | - name: Checkout Code
56 | uses: actions/checkout@v2
57 |
58 | - name: Setup Node.js
59 | uses: actions/setup-node@v2
60 | with:
61 | node-version: 18
62 |
63 | - name: Install Dependencies
64 | run: |
65 | npm install yarn -g
66 | yarn
67 |
68 | - name: Build Release Files
69 | run: yarn release
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 |
73 | # - name: Show Build Output
74 | # run: ls -al out/make/zip/darwin/x64
75 |
76 | # - name: Upload Artifact
77 | # uses: actions/upload-artifact@v3
78 | # with:
79 | # name: release_on_${{ matrix. os }}
80 | # path: release/
81 | # retention-days: 5
82 |
83 | - name: Load Release URL File from release job
84 | uses: actions/download-artifact@v1
85 | with:
86 | name: release_url
87 |
88 | - name: Get Release File Name & Upload URL
89 | id: get_release_info
90 | shell: bash
91 | run: |
92 | value=`cat release_url/release_url.txt`
93 | echo ::set-output name=upload_url::$value
94 |
95 | - name: Set env
96 | shell: bash
97 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
98 |
99 | - name: Upload Release Asset ${{ matrix.PLATFORM }}
100 | if: runner.os != 'windows'
101 | id: upload-release-asset
102 | uses: actions/upload-release-asset@v1
103 | env:
104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105 | with:
106 | upload_url: ${{ steps.get_release_info.outputs.upload_url }}
107 | asset_path: ./out/make/zip/${{ matrix.PLATFORM }}/${{ matrix.TYPE }}/onepoint-${{ matrix.PLATFORM }}-${{ matrix.TYPE }}-${{ env.RELEASE_VERSION }}.zip
108 | asset_name: onepoint-${{ matrix.PLATFORM }}-${{ matrix.TYPE }}-${{ env.RELEASE_VERSION }}.zip
109 | asset_content_type: ${{ matrix.ASSET_MIME}}
110 |
111 | - name: Upload Release Asset win
112 | if: runner.os == 'windows'
113 | id: upload-release-asset-win
114 | uses: actions/upload-release-asset@v1
115 | env:
116 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
117 | with:
118 | upload_url: ${{ steps.get_release_info.outputs.upload_url }}
119 | asset_path: ./out/make/${{ matrix.PLATFORM }}/${{ matrix.TYPE }}/onepoint-${{ env.RELEASE_VERSION }} Boot.exe
120 | asset_name: onepoint-${{ env.RELEASE_VERSION }} Boot.exe
121 | asset_content_type: ${{ matrix.ASSET_MIME}}
122 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | push:
8 | branches:
9 | - main
10 |
11 | defaults:
12 | run:
13 | shell: bash
14 |
15 | jobs:
16 | all:
17 | name: All
18 |
19 | strategy:
20 | matrix:
21 | os:
22 | - macos-latest
23 | # - windows-latest
24 |
25 | runs-on: ${{matrix.os}}
26 |
27 | steps:
28 | - uses: actions/checkout@v3
29 |
30 | # - name: Setup macOS/linux
31 | # if: ${{ matrix.os != 'windows-latest' }}
32 | # run: ./setup.sh
33 |
34 | # - name: Setup windows
35 | # if: ${{ matrix.os == 'windows-latest' }}
36 | # shell: pwsh
37 | # run: ./setup.ps1
38 |
39 | - name: Install dependencies
40 | run: yarn
41 |
42 | - name: Check formatting
43 | if: ${{ matrix.os != 'windows-latest' }}
44 | run: yarn format-check
45 |
46 | # - name: Package
47 | # run: npm run package
48 | # timeout-minutes: 30
49 |
50 | # - name: Upload artifacts
51 | # uses: actions/upload-artifact@v3
52 | # with:
53 | # name: ${{ matrix.os }}-binary
54 | # path: out
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 | .webpack/
106 | data/
107 | .DS_Store
108 | config.json
109 |
110 | /*.py
111 |
112 | out/
113 | build/
114 | /lsp/
115 | settings.json
116 | resources/**
117 | lsp/**
118 | *.zip
119 | scripts/**
120 |
121 | run_todesktop.sh
122 |
123 | # hidden files
124 | .key
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --edit
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /.webpack
2 | /out
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Box Tsang
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-CN.md:
--------------------------------------------------------------------------------
1 | # onepoint
2 |
3 |
4 | English | 中文
5 |
6 |
7 |
8 |
9 |
10 |
11 | 不仅仅是聊天
12 |
13 |
14 |
15 |
35 |
36 | Onepoint 是一款基于 Electron 的开源 AI 助手,旨在打造极致的桌面端效能工具,最初的目标是实现一个类似苹果的智能辅助悬浮窗,在使用时不占用桌面空间和系统性能,并通过快捷键全局呼起,方便用户随时使用。
37 |
38 | 借助 ChatGPT 技术,用户可以通过对 Onepoint 不断调教,使其生成和重构的内容更加精确到位(onpoint),从而帮助用户提高效率。Onepoint 目前可以在各种编辑场景(如 VSCode、Pages、Microsoft Word 和 Email 等)下使用,同时也覆盖了 Safari 和 Chrome 等阅读场景,真正实现了全场景智能覆盖。
39 |
40 |
41 |
42 |
43 |
44 |
45 | ## 01 功能
46 |
47 |
48 |
49 |
50 |
51 |
52 | **基础**
53 |
54 | - 提供快捷、简约的功能入口,并作用全局,即用即走
55 | - 支持多种 IDE 的代码一键编写与重构能力
56 | - 翻译与文稿写手,支持多种文本编辑场景下的内容总结与输出能力
57 |
58 | **高阶**
59 |
60 | - 伴读助手,支持 Safari 与 Chrome 等浏览器内容总结与输出
61 | - 支持第三方设备(如小爱同学)语音输出
62 | - 个性化 Prompt 与自定义角色预设
63 | - 高阶提问请求参数设定
64 |
65 | **更多**
66 |
67 | - 插件市场支持
68 | - 本地数据存储与导出
69 | - 账号余额查询
70 | - 多语言支持
71 |
72 | ## 02 截图
73 |
74 |
75 | 详情
76 |
77 | #### 极简风
78 |
79 |
80 |
81 | #### 历史模式
82 |
83 |
84 |
85 | #### Code 辅助
86 |
87 |
88 | #### 插件列表
89 |
90 |
91 | #### 设置页
92 |
93 |
94 | #### 账户页
95 |
96 |
97 | #### 自定义 Prompts
98 |
99 |
100 | 更多功能持续推进开发中
101 |
102 |
103 |
104 |
105 |
106 |
111 |
112 |
113 |
114 | ## 03 开始
115 |
116 | 前往 [官网](https://www.1ptai.com/) 下载并试用该工具。
117 |
118 | 如果你遇到相关 Bug,或者是有其他的功能需求,欢迎提 issue 或相关 PR,除了能得到我们大大的赞 👍 以外,还有机会获得个人定制的 avatar 形象,并通过 [NFT](https://opensea.io/zh-CN/collection/onepointai-collection) 的形式免费赠与到你的钱包(详情参见[贡献者须知 - 开发者激励](https://github.com/onepointAI/onepoint/issues/4) )。
119 |
120 | ## 04 开发
121 |
122 | 欢迎为我们提交 PR,或具有建设性的意见,一起做点有意思的事情
123 |
124 | ```
125 | > git clone git@github.com:onepointAI/onepoint.git
126 | > cd onepoint
127 | > yarn
128 | > yarn start
129 | ```
130 |
131 | ## 05 愿景与路线图
132 |
133 | 长远地看,我们希望把 onepoint 打造成个性化的智能辅助工具,以作为各个编辑与阅读软件的能力延伸,同时借助可扩展的插件机制丰富更多样的玩法,它既是工具,也是入口,希望对屏幕前的你有所帮助或启发。
134 |
135 | - 🚗 高可用性:快速便捷的入口,包括良好的用户体验(尽可能少的干扰、优雅的界面与交互和高性能)
136 | - 🔧 高效输出:不是为了替代某某,而是作为原有编辑器的能力补充与增强
137 | - 📖 阅读护航:总结归纳阅读场景,提高获取信息的能力与速度
138 | - 🎈 创意玩法:作为入口提供插件机制满足各类场景,提供 NFT 生态与和谐友好的技术社区氛围
139 | - 🤖 模型训练:提供自定义数据集的模型训练能力(LLMs)
140 |
141 | ## 06 QA
142 |
143 |
144 |
145 | Q1: onepoint 不能用在 windows 平台?
146 |
147 | 聊天、角色切换等基础能力可以正常使用,但 IDE 代码选择与应用、浏览器内容获取等需要调用到原生能力(macOS 通过 applescript 实现),Windows 暂不支持这样的原生调用,但以后会考虑 vbscript 来实现类似的能力。
148 |
149 |
150 |
151 |
152 |
153 | Q2: 怎么使用代码辅助或者网页抓取工具?
154 |
155 | 首先需要点击左侧的图标选择并切换到对应的模式(如代码重构、总结等),然后在 IDE 中选择一段代码或者鼠标聚焦到当前浏览器,通过`command + k` 全局呼起 onepoint,此时会显示是否对应用修改,选择 `yes`。
156 |
157 |
158 |
159 |
160 |
161 | Q3: 网页总结有什么限制吗?
162 |
163 | 目前对抓取网页的字符限制数为 4000(已经提出换行、回车和 html 标签等)以获得更快的速度,后续会通过开关已经上下文分段的能力处理长网页的内容总结
164 |
165 |
166 |
167 |
168 |
169 | ## 贡献者
170 |
171 |
172 |
173 |
174 |
175 | ## License
176 |
177 | [MIT License](./LICENSE)
178 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # onepoint
2 |
3 |
4 | English | 中文
5 |
6 |
7 |
8 |
9 |
10 |
11 | more than just chat
12 |
13 |
14 |
15 |
35 |
36 | Onepoint is an open-source AI assistant based on Electron, designed to create the ultimate desktop productivity tool. Its initial goal was to develop a smart floating window similar to Apple's intelligent assistant that does not take up desktop space or system performance and can be quickly accessed through global hotkeys for user convenience.
37 |
38 | With ChatGPT technology, users can continuously train onepoint to generate and reconstruct content with greater accuracy (onpoint), thereby improving efficiency. Onepoint currently supports various editing scenarios such as VSCode, Pages, Microsoft Word, Email etc, as well as reading scenarios like Safari and Chrome, achieving true full-scene intelligent coverage.
39 |
40 |
41 |
42 |
43 |
44 |
45 | ## 01 Features
46 |
47 |
48 |
49 |
50 |
51 |
52 | **Basical**
53 |
54 | - Provide quick and concise functional access points that act globally and allow for immediate use.
55 | - Support one-click code writing and refactoring capabilities for multiple IDEs.
56 | - Translation and document writing assistant, supporting content summarization and output in various text editing scenarios.
57 |
58 | **Advanced**
59 |
60 | - Reading assistant supporting content summarization and output on browsers such as Safari and Chrome.
61 | - Support for third-party device (such as Xiao Ai) voice output.
62 | - Personalized prompts and custom character presets.
63 | - Advanced question requesting parameter settings.
64 |
65 | **More**
66 |
67 | - Plugin market support.
68 | - Local data storage and export.
69 | - Account balance inquiry.
70 | - Multi-language support.
71 |
72 |
73 |
74 | ## 02 Screenshots
75 |
76 |
77 | Detail
78 |
79 | #### Minimal Mode
80 |
81 |
82 |
83 | #### History Mode
84 |
85 |
86 |
87 | #### Code Assistant
88 |
89 |
90 | #### Plugin List
91 |
92 |
93 | #### Setting Page
94 |
95 |
96 | #### Account Page
97 |
98 |
99 | #### Custom Prompts
100 |
101 |
102 |
103 |
104 |
105 |
110 |
111 |
112 |
113 | ## 03 Getting Started
114 |
115 | Please go to the [official website](https://www.1ptai.com/) to download and try out the tool.
116 |
117 | If you encounter any bugs or have other feature requests, please feel free to submit an issue or related PR. You will not only receive our appreciation 👍 but also have the chance to receive a personalized avatar image, which will be gifted to your wallet for free in the form of an [NFT](https://opensea.io/zh-CN/collection/onepointai-collection) (Detail in [README of contributors](https://github.com/onepointAI/onepoint/issues/5)).
118 |
119 |
120 |
121 | ## 04 Development
122 |
123 | Welcome to submit a Pull Request (PR) or provide constructive feedback for us. Let's do something interesting together.
124 |
125 | ```
126 | > git clone git@github.com:onepointAI/onepoint.git
127 | > cd onepoint
128 | > yarn
129 | > yarn start
130 | ```
131 |
132 | ## 05 Vision & Roadmap
133 |
134 | In the long term, we hope to develop onepoint into a personalized intelligent assistant tool that extends the capabilities of various editing and reading software. At the same time, we aim to enrich its functionality through scalable plugin mechanisms, making it not only a tool but also an entry point that can help or inspire you in front of your screen.
135 |
136 | - 🚗 High availability, fast access with good user experience, elegant interface and interaction, and high performance.
137 | - 💻 Personalized service, providing users with tuning mechanisms to customize their personal intelligent assistants.
138 | - 🔧 Efficient output, not to replace certain tools but to complement and enhance the capabilities of existing editors.
139 | - 📖 Reading assistance, summarizing and organizing reading scenarios to improve the speed of information acquisition.
140 | - 🎈 Creative play, providing plugin mechanisms as an entry point to meet various scenarios and providing an NFT ecosystem with a harmonious technical community atmosphere.
141 | - 🤖 Model Training, providing ability to train models with custom datasets(LLMs).
142 |
143 | ## 06 QA
144 |
145 |
146 |
147 | Q1: Can onepoint be used on the Windows platform?
148 |
149 | Basic abilities such as chatting and switching roles can be used normally, but others such as IDE code selection and application, and browser content acquisition require native capabilities (applescript is used on the Mac platform), which is not yet supported on Windows. In the future, vbscript will be considered to implement similar capabilities.
150 |
151 |
152 |
153 |
154 |
155 | Q2: How to use code helpers or web scraping tools?
156 |
157 | First, you need to click on the icon on the left to select and switch to the corresponding mode (such as code refactoring, summarization, etc.), and then select a piece of code in the IDE or focus the mouse on the current browser. Use `command + k` to globally call up onepoint. At this time, it will display whether to make changes to the application, choose `yes`.
158 |
159 |
160 |
161 |
162 |
163 | Q3: What are the limitations of web scraping?
164 |
165 | Currently, there is a character limit of 4000 for web page crawling (excluding line breaks, carriage returns, and HTML tags) to achieve faster speed. In the future, the ability to segment long web pages with context will be used to summarize their contents.
166 |
167 |
168 |
169 |
170 |
171 | ## Contributors
172 |
173 |
174 |
175 |
176 |
177 | ## License
178 |
179 | [MIT License](./LICENSE)
180 |
--------------------------------------------------------------------------------
/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/.gitkeep
--------------------------------------------------------------------------------
/assets/banner/banner.01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/banner/banner.01.png
--------------------------------------------------------------------------------
/assets/banner/bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/banner/bar.png
--------------------------------------------------------------------------------
/assets/banner/brand01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/banner/brand01.png
--------------------------------------------------------------------------------
/assets/banner/brand02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/banner/brand02.png
--------------------------------------------------------------------------------
/assets/banner/brand_nobg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/banner/brand_nobg.png
--------------------------------------------------------------------------------
/assets/banner/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/banner/demo.gif
--------------------------------------------------------------------------------
/assets/icon/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon.icns
--------------------------------------------------------------------------------
/assets/icon/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon.ico
--------------------------------------------------------------------------------
/assets/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon.png
--------------------------------------------------------------------------------
/assets/icon/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon128.png
--------------------------------------------------------------------------------
/assets/icon/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon16.png
--------------------------------------------------------------------------------
/assets/icon/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon24.png
--------------------------------------------------------------------------------
/assets/icon/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon32.png
--------------------------------------------------------------------------------
/assets/icon/icon64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/icon/icon64.png
--------------------------------------------------------------------------------
/assets/screenshot/account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/account.png
--------------------------------------------------------------------------------
/assets/screenshot/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/chat.png
--------------------------------------------------------------------------------
/assets/screenshot/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/code.png
--------------------------------------------------------------------------------
/assets/screenshot/console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/console.png
--------------------------------------------------------------------------------
/assets/screenshot/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/list.png
--------------------------------------------------------------------------------
/assets/screenshot/plugin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/plugin.png
--------------------------------------------------------------------------------
/assets/screenshot/prompts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/prompts.png
--------------------------------------------------------------------------------
/assets/screenshot/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/setting.png
--------------------------------------------------------------------------------
/assets/screenshot/video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/assets/screenshot/video.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-env',
4 | '@babel/preset-typescript',
5 | [
6 | '@babel/preset-react',
7 | {
8 | runtime: 'automatic',
9 | },
10 | ],
11 | ],
12 | plugins: [
13 | [
14 | '@babel/plugin-transform-runtime',
15 | {
16 | regenerator: true,
17 | },
18 | ],
19 | ],
20 | }
21 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | }
4 |
--------------------------------------------------------------------------------
/forge.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | packagerConfig: {
3 | name: 'onepoint',
4 | executableName: 'onepoint',
5 | icon: 'assets/icon/icon.icns',
6 | extraResource: ['assets'],
7 | },
8 | plugins: [
9 | {
10 | name: '@electron-forge/plugin-webpack',
11 | config: {
12 | devContentSecurityPolicy:
13 | "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: file: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';",
14 | mainConfig: './webpack/main.webpack.js',
15 | renderer: {
16 | config: './webpack/renderer.webpack.js',
17 | entryPoints: [
18 | {
19 | html: './public/index.html',
20 | js: './src/index.tsx',
21 | name: 'main_window',
22 | preload: {
23 | js: './src/electron/client/bridge.ts',
24 | },
25 | },
26 | ],
27 | },
28 | },
29 | },
30 | ],
31 | makers: [
32 | {
33 | name: '@electron-forge/maker-squirrel',
34 | config: {},
35 | },
36 | {
37 | name: '@electron-forge/maker-zip',
38 | platforms: ['darwin'],
39 | },
40 | {
41 | name: '@electron-forge/maker-deb',
42 | config: {
43 | options: {
44 | icon: 'assets/icon/icon.png',
45 | },
46 | },
47 | },
48 | {
49 | name: '@electron-forge/maker-rpm',
50 | config: {},
51 | },
52 | ],
53 | }
54 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | // For a detailed explanation regarding each configuration property, visit:
2 | // https://jestjs.io/docs/en/configuration.html
3 |
4 | module.exports = {
5 | // All imported modules in your tests should be mocked automatically
6 | // automock: false,
7 |
8 | // Stop running tests after `n` failures
9 | // bail: 0,
10 |
11 | // The directory where Jest should store its cached dependency information
12 | // cacheDirectory: "/tmp/jest_rs",
13 |
14 | // Automatically clear mock calls and instances between every test
15 | clearMocks: true,
16 |
17 | // Indicates whether the coverage information should be collected while executing the test
18 | // collectCoverage: false,
19 |
20 | // An array of glob patterns indicating a set of files for which coverage information should be collected
21 | // collectCoverageFrom: undefined,
22 |
23 | // The directory where Jest should output its coverage files
24 | // coverageDirectory: undefined,
25 |
26 | // An array of regexp pattern strings used to skip coverage collection
27 | // coveragePathIgnorePatterns: [
28 | // "/node_modules/"
29 | // ],
30 |
31 | // Indicates which provider should be used to instrument code for coverage
32 | // coverageProvider: "babel",
33 |
34 | // A list of reporter names that Jest uses when writing coverage reports
35 | // coverageReporters: [
36 | // "json",
37 | // "text",
38 | // "lcov",
39 | // "clover"
40 | // ],
41 |
42 | // An object that configures minimum threshold enforcement for coverage results
43 | // coverageThreshold: undefined,
44 |
45 | // A path to a custom dependency extractor
46 | // dependencyExtractor: undefined,
47 |
48 | // Make calling deprecated APIs throw helpful error messages
49 | // errorOnDeprecated: false,
50 |
51 | // Force coverage collection from ignored files using an array of glob patterns
52 | // forceCoverageMatch: [],
53 |
54 | // A path to a module which exports an async function that is triggered once before all test suites
55 | // globalSetup: undefined,
56 |
57 | // A path to a module which exports an async function that is triggered once after all test suites
58 | // globalTeardown: undefined,
59 |
60 | // A set of global variables that need to be available in all test environments
61 | // globals: {},
62 |
63 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
64 | // maxWorkers: "50%",
65 |
66 | // An array of directory names to be searched recursively up from the requiring module's location
67 | // moduleDirectories: [
68 | // "node_modules"
69 | // ],
70 |
71 | // An array of file extensions your modules use
72 | // moduleFileExtensions: [
73 | // "js",
74 | // "json",
75 | // "jsx",
76 | // "ts",
77 | // "tsx",
78 | // "node"
79 | // ],
80 |
81 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
82 | // moduleNameMapper: {},
83 |
84 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
85 | // modulePathIgnorePatterns: [],
86 |
87 | // Activates notifications for test results
88 | // notify: false,
89 |
90 | // An enum that specifies notification mode. Requires { notify: true }
91 | // notifyMode: "failure-change",
92 |
93 | // A preset that is used as a base for Jest's configuration
94 | preset: 'ts-jest',
95 |
96 | // Run tests from one or more projects
97 | // projects: undefined,
98 |
99 | // Use this configuration option to add custom reporters to Jest
100 | // reporters: undefined,
101 |
102 | // Automatically reset mock state between every test
103 | // resetMocks: false,
104 |
105 | // Reset the module registry before running each individual test
106 | // resetModules: false,
107 |
108 | // A path to a custom resolver
109 | // resolver: undefined,
110 |
111 | // Automatically restore mock state between every test
112 | // restoreMocks: false,
113 |
114 | // The root directory that Jest should scan for tests and modules within
115 | // rootDir: undefined,
116 |
117 | // A list of paths to directories that Jest should use to search for files in
118 | // roots: [
119 | // ""
120 | // ],
121 |
122 | // Allows you to use a custom runner instead of Jest's default test runner
123 | // runner: "jest-runner",
124 |
125 | // The paths to modules that run some code to configure or set up the testing environment before each test
126 | // setupFiles: [],
127 |
128 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
129 | setupFilesAfterEnv: ['./tests/setupTests.ts'],
130 |
131 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
132 | // snapshotSerializers: [],
133 |
134 | // The test environment that will be used for testing
135 | testEnvironment: 'jsdom',
136 |
137 | // Options that will be passed to the testEnvironment
138 | // testEnvironmentOptions: {},
139 |
140 | // Adds a location field to test results
141 | // testLocationInResults: false,
142 |
143 | // The glob patterns Jest uses to detect test files
144 | // testMatch: [
145 | // "**/__tests__/**/*.[jt]s?(x)",
146 | // "**/?(*.)+(spec|test).[tj]s?(x)"
147 | // ],
148 |
149 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
150 | // testPathIgnorePatterns: [
151 | // "/node_modules/"
152 | // ],
153 |
154 | // The regexp pattern or array of patterns that Jest uses to detect test files
155 | // testRegex: [],
156 |
157 | // This option allows the use of a custom results processor
158 | // testResultsProcessor: undefined,
159 |
160 | // This option allows use of a custom test runner
161 | // testRunner: "jasmine2",
162 |
163 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
164 | // testURL: "http://localhost",
165 |
166 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
167 | // timers: "real",
168 |
169 | // A map from regular expressions to paths to transformers
170 | // transform: undefined,
171 |
172 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
173 | // transformIgnorePatterns: [
174 | // "/node_modules/"
175 | // ],
176 |
177 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
178 | // unmockedModulePathPatterns: undefined,
179 |
180 | // Indicates whether each individual test should be reported during the run
181 | // verbose: undefined,
182 |
183 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
184 | // watchPathIgnorePatterns: [],
185 |
186 | // Whether to use watchman for file crawling
187 | // watchman: true,
188 | }
189 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "onepoint",
3 | "author": "Edwiin Tsang ",
4 | "version": "0.1.6",
5 | "homepage": "https://www.1ptai.com/",
6 | "description": "An Electron boilerplate including TypeScript, React, Jest and ESLint.",
7 | "main": ".webpack/main",
8 | "scripts": {
9 | "start": "electron-forge start",
10 | "package": "electron-forge package",
11 | "make": "electron-forge make",
12 | "release": "electron-forge publish",
13 | "lint": "eslint . --ext js,ts",
14 | "lint-fix": "eslint --ext .ts,.tsx --fix .",
15 | "lint-staged": "lint-staged",
16 | "format": "prettier --write .",
17 | "format-check": "prettier --check .",
18 | "test": "jest",
19 | "prepare": "husky install",
20 | "rebuild": "./node_modules/.bin/electron-rebuild"
21 | },
22 | "lint-staged": {
23 | "*.{js,ts,jsx,tsx}": [
24 | "yarn format",
25 | "yarn lint"
26 | ]
27 | },
28 | "keywords": [],
29 | "license": "MIT",
30 | "dependencies": {
31 | "applescript": "^1.0.0",
32 | "chatgpt": "^5.1.3",
33 | "electron-clipboard-watcher": "^1.0.1",
34 | "electron-log": "^5.0.0-beta.16",
35 | "electron-store": "^8.1.0",
36 | "i18next": "^22.4.15",
37 | "i18next-http-backend": "^2.2.0",
38 | "node-abi": "^3.33.0",
39 | "openai": "^3.2.1",
40 | "phantomjscloud": "^3.5.5",
41 | "pubsub-js": "^1.9.4",
42 | "react": "17.0.2",
43 | "react-dom": "17.0.2",
44 | "react-hot-loader": "4.13.0",
45 | "react-i18next": "^12.2.0",
46 | "react-markdown": "^8.0.6",
47 | "react-syntax-highlighter": "^15.5.0",
48 | "socks-proxy-agent": "^7.0.0",
49 | "styled-components": "5.3.0"
50 | },
51 | "devDependencies": {
52 | "@babel/core": "7.14.6",
53 | "@babel/plugin-transform-runtime": "7.14.5",
54 | "@babel/preset-env": "7.14.5",
55 | "@babel/preset-react": "7.14.5",
56 | "@babel/preset-typescript": "7.14.5",
57 | "@commitlint/cli": "^17.5.1",
58 | "@commitlint/config-conventional": "^17.4.4",
59 | "@electron-forge/cli": "^6.0.4",
60 | "@electron-forge/maker-deb": "^6.0.4",
61 | "@electron-forge/maker-rpm": "^6.0.4",
62 | "@electron-forge/maker-squirrel": "^6.0.4",
63 | "@electron-forge/maker-zip": "^6.0.4",
64 | "@electron-forge/plugin-webpack": "^6.1.0",
65 | "@marshallofsound/webpack-asset-relocator-loader": "0.5.0",
66 | "@reduxjs/toolkit": "^1.9.3",
67 | "@testing-library/jest-dom": "5.14.1",
68 | "@testing-library/react": "11.2.7",
69 | "@types/electron-devtools-installer": "2.2.0",
70 | "@types/jest": "26.0.23",
71 | "@types/react": "17.0.11",
72 | "@types/react-dom": "17.0.8",
73 | "@types/react-syntax-highlighter": "^15.5.6",
74 | "@types/styled-components": "5.1.10",
75 | "@typescript-eslint/eslint-plugin": "^5.57.1",
76 | "@typescript-eslint/parser": "^5.57.1",
77 | "@vercel/webpack-asset-relocator-loader": "^1.7.3",
78 | "@zeit/webpack-asset-relocator-loader": "^0.8.0",
79 | "ajv": "^7",
80 | "ajv-formats": "^2.1.1",
81 | "antd": "^5.3.3",
82 | "babel-loader": "8.2.2",
83 | "commitizen": "^4.3.0",
84 | "cross-env": "7.0.3",
85 | "cz-conventional-changelog": "^3.3.0",
86 | "electron": "^24.0.0",
87 | "electron-rebuild": "^3.2.9",
88 | "eslint": "^8.37.0",
89 | "eslint-config-airbnb": "^19.0.4",
90 | "eslint-config-prettier": "8.3.0",
91 | "eslint-config-standard": "16.0.3",
92 | "eslint-plugin-import": "^2.27.5",
93 | "eslint-plugin-jsx-a11y": "^6.7.1",
94 | "eslint-plugin-node": "11.1.0",
95 | "eslint-plugin-prettier": "3.4.0",
96 | "eslint-plugin-promise": "5.1.0",
97 | "eslint-plugin-react": "^7.32.2",
98 | "eslint-plugin-react-hooks": "^4.6.0",
99 | "eslint-plugin-standard": "5.0.0",
100 | "eslint-plugin-unused-imports": "^2.0.0",
101 | "file-loader": "^6.2.0",
102 | "html2plaintext": "^2.1.4",
103 | "husky": "^8.0.3",
104 | "jest": "27.0.4",
105 | "lint-staged": "^13.2.0",
106 | "npm-run-all": "4.1.5",
107 | "prettier": "2.3.1",
108 | "react-redux": "^8.0.5",
109 | "ts-jest": "27.0.3",
110 | "typescript": "4.3.4",
111 | "wait-on": "5.3.0",
112 | "watcher": "^2.2.2"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: false,
3 | singleQuote: true,
4 | arrowParens: 'avoid',
5 | }
6 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Electron starter
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "semanticCommits": true,
4 | "stabilityDays": 3,
5 | "prCreation": "not-pending",
6 | "labels": ["dependencies"]
7 | }
8 |
--------------------------------------------------------------------------------
/src/@types/bridge.d.ts:
--------------------------------------------------------------------------------
1 | import { api } from '../electron/client/bridge'
2 |
3 | declare global {
4 | // eslint-disable-next-line
5 | interface Window {
6 | Main: typeof api
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/@types/deps.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'pubsub-js'
2 |
--------------------------------------------------------------------------------
/src/@types/i18next.d.ts:
--------------------------------------------------------------------------------
1 | import 'i18next'
2 |
3 | declare module 'i18next' {
4 | interface CustomTypeOptions {
5 | returnNull: false
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/@types/image.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.png'
2 | declare module '*.jpeg'
3 | declare module '*.jpg'
4 | declare module '*.gif'
5 |
--------------------------------------------------------------------------------
/src/@types/index.ts:
--------------------------------------------------------------------------------
1 | export interface PluginType {
2 | logo: string
3 | id: PresetType
4 | title: PresetType
5 | desc?: string
6 | loading: boolean
7 | inputDisable?: boolean
8 | nostore?: boolean
9 | monitorClipboard?: boolean
10 | monitorBrowser?: boolean
11 | }
12 | export interface PresetModule {
13 | listVisible: boolean
14 | builtInPlugins: PluginType[]
15 | currentPreset: PresetType
16 | }
17 |
18 | export enum PresetType {
19 | Casual = 'Casual',
20 | Translator = 'Translator',
21 | Summarizer = 'Summarizer',
22 | Programmer = 'Programmer',
23 | Analyst = 'Analyst',
24 | }
25 |
26 | export interface PanelVisible {
27 | plugin?: boolean
28 | setting?: boolean
29 | chatPanel?: boolean
30 | }
31 |
32 | export interface DataType {
33 | key: string
34 | character: string
35 | prompt: string
36 | }
37 |
38 | export interface PosType {
39 | posX: number
40 | posY: number
41 | }
42 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState, useLayoutEffect } from 'react'
2 | import { Image, message } from 'antd'
3 | import common from './electron/constants/common'
4 | import PubSub from 'pubsub-js'
5 | import { MoreOutlined } from '@ant-design/icons'
6 | import { GlobalStyle } from './styles/GlobalStyle'
7 | import { ChatPanel } from './components/ChatPanel'
8 | import { Setting } from './components/Setting'
9 | import { Preset } from './components/Preset'
10 | import { Logo } from './components/Logo'
11 | import Search from './components/Search'
12 | import { Prompt as PromptModal } from './components/Modal/prompt'
13 |
14 | import { init as initI18n } from './i18n'
15 | import { useAppDispatch, useAppSelector } from './app/hooks'
16 | import { StoreKey } from './app/constants'
17 | import { draggableStyle } from './utils'
18 | import { setVisible as setChatVisible } from './features/chat/chatSlice'
19 | import {
20 | setListVisible as setPresetListVisible,
21 | setPreset,
22 | } from './features/preset/presetSlice'
23 | import {
24 | setVisible as setSettingVisible,
25 | setMinimal,
26 | setLng,
27 | setContexual,
28 | setStore as setStoreSet,
29 | defaultVals,
30 | } from './features/setting/settingSlice'
31 | import { setUrl, setSelection } from './features/clipboard/clipboardSlice'
32 |
33 | import {
34 | selection_change,
35 | url_change,
36 | setting_show,
37 | } from './electron/constants/event'
38 | import { PresetType, PanelVisible } from './@types'
39 | interface Tips {
40 | type: 'success' | 'error' | 'warning'
41 | message: string
42 | }
43 |
44 | export function App() {
45 | const [show, setShow] = useState(false)
46 | const presetState = useAppSelector(state => state.preset)
47 | const [messageApi, contextHolder] = message.useMessage()
48 | const dispatch = useAppDispatch()
49 | useRef(null)
50 | const preset = presetState.builtInPlugins.filter(
51 | p => p.title === presetState.currentPreset
52 | )
53 | const presetIcon = preset.length > 0 ? preset[0].logo : null
54 |
55 | // TODO: need to perf
56 | const getSettings = async () => {
57 | const getter = window.Main.getSettings
58 | const lng = await getter(StoreKey.Set_Lng)
59 | dispatch(setLng(lng || defaultVals.lng))
60 | initI18n(lng)
61 | setShow(true)
62 | const storeSet = await getter(StoreKey.Set_StoreChat)
63 | dispatch(setStoreSet(storeSet || defaultVals.store))
64 | const contextual = await getter(StoreKey.Set_Contexual)
65 | dispatch(setContexual(contextual || defaultVals.contexual))
66 | const simpleMode = await getter(StoreKey.Set_SimpleMode)
67 | dispatch(setMinimal(simpleMode || false))
68 | }
69 |
70 | useLayoutEffect(() => {
71 | getSettings()
72 | }, [])
73 |
74 | useEffect(() => {
75 | if (common.production()) {
76 | window.addEventListener('mousemove', event => {
77 | const flag = event.target === document.documentElement
78 | window.Main.ignoreWinMouse(flag)
79 | })
80 | }
81 | window.Main.on(
82 | selection_change,
83 | (selection: { txt: string; app: string }) => {
84 | const { txt, app } = selection
85 | dispatch(setSelection({ txt, app }))
86 | dispatch(setChatVisible(!!txt && !!app))
87 | }
88 | )
89 | window.Main.on(url_change, (selection: { url: string }) => {
90 | const { url } = selection
91 | dispatch(setUrl({ url }))
92 | dispatch(setChatVisible(true))
93 | })
94 | window.Main.on(setting_show, () =>
95 | showPanel({
96 | setting: true,
97 | })
98 | )
99 | PubSub.subscribe('tips', (name: string, data: Tips) => {
100 | const { type, message } = data
101 | messageApi.open({
102 | type,
103 | content: message,
104 | })
105 | })
106 | PubSub.subscribe('showPanel', (name: string, data: PanelVisible) => {
107 | showPanel(data)
108 | })
109 | }, [])
110 |
111 | const showPanel = (options: PanelVisible) => {
112 | const { plugin, setting, chatPanel } = options
113 | dispatch(setPresetListVisible(!!plugin && !setting && !chatPanel))
114 | dispatch(setSettingVisible(!plugin && !!setting && !chatPanel))
115 | dispatch(setChatVisible(!plugin && !setting && !!chatPanel))
116 | }
117 |
118 | const onPresetChange = (preset: PresetType) => {
119 | dispatch(setPreset(preset))
120 | window.Main.setUsePreset(preset)
121 | }
122 |
123 | return show ? (
124 | <>
125 |
126 | {contextHolder}
127 |
128 |
129 | {presetIcon ? (
130 |
136 | showPanel({
137 | plugin: true,
138 | })
139 | }
140 | />
141 | ) : null}
142 |
143 | {
146 | PubSub.publish('showPromptModal')
147 | }}
148 | />
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | >
157 | ) : null
158 | }
159 |
160 | const padding = 15
161 | const styles = {
162 | container: {
163 | backgroundColor: '#FFF',
164 | border: 'none',
165 | borderRadius: 15,
166 | borderWidth: 1,
167 | borderColor: '#FFF',
168 | overflow: 'hidden',
169 | },
170 | inputWrap: {
171 | position: 'relative',
172 | display: 'flex',
173 | flexDirection: 'row',
174 | border: 'none',
175 | borderWidth: 0,
176 | borderColor: '#FFF',
177 | justifyContent: 'space-between',
178 | alignItems: 'center',
179 | padding,
180 | ...draggableStyle(true),
181 | } as React.CSSProperties,
182 | nonDragable: {
183 | ...draggableStyle(false),
184 | } as React.CSSProperties,
185 | moreIcon: {
186 | fontSize: 20,
187 | margin: '0 10px',
188 | ...draggableStyle(false),
189 | },
190 | }
191 |
--------------------------------------------------------------------------------
/src/app/api.ts:
--------------------------------------------------------------------------------
1 | export const baseApiHost = 'http://127.0.0.1:4000'
2 |
--------------------------------------------------------------------------------
/src/app/constants.ts:
--------------------------------------------------------------------------------
1 | import { chat, translate, code, post } from './images'
2 | import { PluginType } from '../@types'
3 |
4 | export const Casual = 'Casual'
5 | export const Translator = 'Translator'
6 | export const Summarizer = 'Summarizer'
7 | export const Programmer = 'Programmer'
8 | export const Analyst = 'Analyst'
9 | export const BuiltInPlugins = [
10 | {
11 | logo: chat,
12 | id: Casual,
13 | title: Casual,
14 | loading: false,
15 | desc: 'Chat mode, feel free to ask any questions you want.',
16 | },
17 | {
18 | logo: code,
19 | id: Programmer,
20 | title: Programmer,
21 | loading: false,
22 | inputDisable: true,
23 | desc: 'Code Master, generate or refactor the code you want.',
24 | nostore: true,
25 | },
26 | {
27 | logo: post,
28 | id: Summarizer,
29 | title: Summarizer,
30 | loading: false,
31 | inputDisable: true,
32 | desc: 'Content analysis summary assistant, helps you read and browse web pages more effectively.',
33 | nostore: true,
34 | monitorBrowser: true,
35 | },
36 | {
37 | logo: translate,
38 | id: Translator,
39 | title: Translator,
40 | loading: false,
41 | inputDisable: false,
42 | desc: 'Language expert, proficient in various languages from different countries.',
43 | nostore: true,
44 | },
45 | ] as PluginType[]
46 |
47 | export const Prompts_Link =
48 | 'https://github.com/onepointAI/awesome-chatgpt-prompts/blob/main/prompts.csv'
49 | export const Prompts_ZH_Link =
50 | 'https://github.com/PlexPt/awesome-chatgpt-prompts-zh/blob/main/prompts-zh.json'
51 |
52 | export const Models = ['gpt-3.5-turbo-0301']
53 | export const StoreKey = {
54 | Set_Model: 'KEY_MODEL',
55 | Set_BasePath: 'BASE_PATH',
56 | Set_ApiKey: 'APIKEY_GPT',
57 | Set_Lng: 'LNG',
58 | Set_StoreChat: 'STORE_CHAT',
59 | Set_SimpleMode: 'SIMPLE_MODE',
60 | Set_Contexual: 'CONTEXUAL',
61 | History_Chat: 'CHAT_HISTORY',
62 | List_Prompt: 'PROMPT_LIST',
63 | Map_Pluginprompt: 'PLUGIN_PROMPT_MAP',
64 | }
65 |
--------------------------------------------------------------------------------
/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
2 | import type { RootState, AppDispatch } from './store'
3 |
4 | export const useAppDispatch: () => AppDispatch = useDispatch
5 | export const useAppSelector: TypedUseSelectorHook = useSelector
6 |
--------------------------------------------------------------------------------
/src/app/images.ts:
--------------------------------------------------------------------------------
1 | // https://imgloc.com/
2 | export const brand = 'https://i.imgur.com/T5ELmVC.png'
3 | export const logo = 'https://i.postimg.cc/tTJ3yHM9/pointer.png'
4 | export const robot = 'https://www.1ptai.com/logo_v2.png'
5 |
6 | export const chat = 'https://i.328888.xyz/2023/04/05/i8Skpc.png'
7 | export const post = 'https://i.328888.xyz/2023/04/05/i8S8Tz.png'
8 | export const code = 'https://i.328888.xyz/2023/04/05/i8SjEq.png'
9 | export const translate = 'https://i.328888.xyz/2023/04/05/i8SNkw.png'
10 | export const loadingGif =
11 | 'https://superstorefinder.net/support/wp-content/uploads/2018/01/elastic.gif'
12 | export const searchLogo = 'https://i.328888.xyz/2023/04/06/iI8ySQ.png'
13 | export const searchLogov2 = 'https://i.328888.xyz/2023/04/09/ic2Y8N.png'
14 | export const logoLoading = 'http://superstorefinder.net/img/ripple-loader.svg'
15 | export const logoSpin = 'https://i.328888.xyz/2023/04/09/icbeld.gif'
16 |
--------------------------------------------------------------------------------
/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, combineReducers } from '@reduxjs/toolkit'
2 | import chatReducer, {
3 | initialState as chatInitState,
4 | } from '../features/chat/chatSlice'
5 | import presetReducer, {
6 | initialState as presetInitState,
7 | } from '../features/preset/presetSlice'
8 | import settingReducer, {
9 | initialState as settingInitState,
10 | } from '../features/setting/settingSlice'
11 | import clipboardReducer, {
12 | initialState as clipboardInitState,
13 | } from '../features/clipboard/clipboardSlice'
14 |
15 | export type StateType = {
16 | chat: typeof chatInitState
17 | preset: typeof presetInitState
18 | setting: typeof settingInitState
19 | clipboard: typeof clipboardInitState
20 | }
21 |
22 | export const initialState: StateType = {
23 | chat: chatInitState,
24 | preset: presetInitState,
25 | setting: settingInitState,
26 | clipboard: clipboardInitState,
27 | }
28 |
29 | const store = configureStore({
30 | reducer: combineReducers({
31 | chat: chatReducer,
32 | preset: presetReducer,
33 | setting: settingReducer,
34 | clipboard: clipboardReducer,
35 | }),
36 | preloadedState: initialState,
37 | })
38 |
39 | export type RootState = ReturnType
40 | export type AppDispatch = typeof store.dispatch
41 | export default store
42 |
--------------------------------------------------------------------------------
/src/assets/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/src/assets/icon128.png
--------------------------------------------------------------------------------
/src/components/Button/Button.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react'
2 | import { Button } from './index'
3 |
4 | test('button should renders', () => {
5 | const { getByText } = render(ButtonContent )
6 |
7 | expect(getByText('ButtonContent')).toBeTruthy()
8 | expect(getByText('ButtonContent')).toHaveAttribute('type', 'button')
9 | })
10 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode, ButtonHTMLAttributes } from 'react'
2 |
3 | import { Container } from './styles'
4 |
5 | type ButtonProps = {
6 | children: ReactNode
7 | } & ButtonHTMLAttributes
8 |
9 | export function Button(props: ButtonProps) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Button/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const Container = styled.button`
4 | height: 42px;
5 | padding: 0 24px;
6 |
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 |
11 | background: #8257e6;
12 | border-radius: 8px;
13 | border: 0;
14 |
15 | color: #fff;
16 | font-size: 16px;
17 | font-weight: bold;
18 |
19 | cursor: pointer;
20 |
21 | &:hover {
22 | filter: brightness(0.9);
23 | }
24 |
25 | &:active {
26 | filter: brightness(0.7);
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/src/components/ChatPanel/OperatePanel.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd'
2 |
3 | interface Props {
4 | tips: string
5 | app?: string
6 | confirmFn: () => void
7 | cancelFn: () => void
8 | }
9 |
10 | export function OperatePanel(props: Props) {
11 | const { tips, app, confirmFn, cancelFn } = props
12 | return (
13 |
14 |
15 | {tips}
16 | {app ? {app} : null}
17 |
18 |
19 | Yes
20 |
21 |
22 | No
23 |
24 |
25 | )
26 | }
27 |
28 | const padding = 15
29 | const styles = {
30 | selectWrap: {
31 | backgroundColor: 'rgb(240 240 240)',
32 | fontSize: 13,
33 | padding,
34 | },
35 | selection: {
36 | color: 'rgb(74 74 74)',
37 | marginRight: 20,
38 | },
39 | selectApp: {
40 | fontSize: 15,
41 | fontWeight: 'bold',
42 | },
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/ChatPanel/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from 'react'
2 | import { Divider, Button, Alert, ConfigProvider } from 'antd'
3 | import { useTranslation } from 'react-i18next'
4 | import PubSub from 'pubsub-js'
5 |
6 | import ReactMarkdown from 'react-markdown'
7 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
8 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
9 | import { CopyOutlined, ClearOutlined, SoundOutlined } from '@ant-design/icons'
10 |
11 | import { useAppSelector, useAppDispatch } from '../../app/hooks'
12 | import { BuiltInPlugins, StoreKey } from '../../app/constants'
13 | import {
14 | fetchChatResp,
15 | fetchWebCrawlResp,
16 | setCurPrompt,
17 | saveResp,
18 | } from '../../features/chat/chatSlice'
19 | import { setSelection, setUrl } from '../../features/clipboard/clipboardSlice'
20 | import { PluginType, PresetType } from '../../@types'
21 | import { ChatContent } from '../../electron/types'
22 | import { OperatePanel } from './OperatePanel'
23 |
24 | export function ChatPanel() {
25 | const { t } = useTranslation()
26 | const dispatch = useAppDispatch()
27 | const chatState = useAppSelector(state => state.chat)
28 | const presetState = useAppSelector(state => state.preset)
29 | const settingState = useAppSelector(state => state.setting)
30 | const clipboardState = useAppSelector(state => state.clipboard)
31 | const [minimal, setMinimal] = useState(true)
32 | const [chatList, setChatList] = useState([])
33 | const [showSelection, setShowSelection] = useState(false)
34 | const [showUrl, setShowUrl] = useState('')
35 | const [usePlugin, setUsePlugin] = useState()
36 | const bottomLineRef = useRef(null)
37 |
38 | const fetchChatList = async () => {
39 | const list = await window.Main.getChatList(presetState.currentPreset)
40 | setChatList(list)
41 | }
42 |
43 | const fetchMinimal = async () => {
44 | const minimal = await window.Main.getSettings(StoreKey.Set_SimpleMode)
45 | setMinimal(minimal || false)
46 | }
47 |
48 | useEffect(() => {
49 | if (!chatState.isGenerating && bottomLineRef) {
50 | setTimeout(() => {
51 | bottomLineRef.current?.scrollIntoView({ behavior: 'smooth' })
52 | }, 100)
53 | if (!usePlugin?.nostore && !minimal && settingState.store) {
54 | dispatch(
55 | setCurPrompt({
56 | preset: presetState.currentPreset,
57 | content: '',
58 | })
59 | )
60 | dispatch(
61 | saveResp({
62 | preset: presetState.currentPreset,
63 | content: '',
64 | })
65 | )
66 | fetchChatList()
67 | }
68 | }
69 | }, [chatState.isGenerating])
70 |
71 | useEffect(() => {
72 | fetchMinimal()
73 | }, [settingState.minimal])
74 |
75 | useEffect(() => {
76 | // TODO: should use id
77 | const plugin = BuiltInPlugins.filter(
78 | item => presetState.currentPreset === item.title
79 | )[0]
80 | setUsePlugin(plugin)
81 | fetchChatList()
82 | }, [
83 | presetState.currentPreset,
84 | chatState.curPrompt[presetState.currentPreset],
85 | ])
86 |
87 | useEffect(() => {
88 | setShowSelection(
89 | !!clipboardState.selectTxt &&
90 | !!clipboardState.selectApp &&
91 | !!usePlugin?.inputDisable
92 | )
93 | }, [
94 | clipboardState.selectTxt,
95 | clipboardState.selectApp,
96 | usePlugin?.inputDisable,
97 | ])
98 |
99 | useEffect(() => {
100 | setShowUrl(clipboardState.url)
101 | }, [clipboardState.url, usePlugin?.inputDisable])
102 |
103 | const speakRsp = (resp: string) => {
104 | window.Main.speakText(resp)
105 | }
106 |
107 | const copyRsp = (resp: string) => {
108 | window.Main.copyText(resp)
109 | PubSub.publish('tips', {
110 | type: 'success',
111 | message: 'Copyed Successfully',
112 | })
113 | }
114 |
115 | const delRecord = async (index?: number) => {
116 | if (typeof index === 'undefined') return
117 | const list = await window.Main.removeChat(presetState.currentPreset, index)
118 | PubSub.publish('tips', {
119 | type: 'success',
120 | message: 'Deleted successfully',
121 | })
122 | dispatch(
123 | setCurPrompt({
124 | preset: presetState.currentPreset,
125 | content: '',
126 | })
127 | )
128 | dispatch(
129 | saveResp({
130 | preset: presetState.currentPreset,
131 | content: '',
132 | })
133 | )
134 | setChatList(list)
135 | }
136 |
137 | const atemptChange = (resp: string) => {
138 | window.Main.attemptChange(resp.replace(/^`{3}[^\n]+|`{3}$/g, ''))
139 | }
140 |
141 | const doRequest = (txt: string) => {
142 | const qa = txt
143 | dispatch(setSelection({ txt: '', app: '' }))
144 | dispatch(
145 | fetchChatResp({
146 | prompt: qa,
147 | preset: presetState.currentPreset,
148 | })
149 | )
150 | }
151 |
152 | const doSummaryWebsite = (url: string) => {
153 | dispatch(setUrl({ url: '' }))
154 | dispatch(
155 | fetchWebCrawlResp({
156 | url,
157 | preset: presetState.currentPreset,
158 | })
159 | )
160 | }
161 |
162 | const cancelRequest = () => {
163 | dispatch(
164 | setSelection({
165 | txt: '',
166 | app: '',
167 | })
168 | )
169 | }
170 |
171 | const showCopyFromEditor = () => {
172 | return showSelection && usePlugin?.id === PresetType.Programmer ? (
173 | doRequest(clipboardState.selectTxt)}
177 | cancelFn={() => cancelRequest()}
178 | />
179 | ) : null
180 | }
181 |
182 | const showSelectUrl = () => {
183 | return clipboardState.url && usePlugin?.monitorBrowser ? (
184 | doSummaryWebsite(clipboardState.url)}
188 | cancelFn={() => cancelRequest()}
189 | />
190 | ) : null
191 | }
192 |
193 | const showPrompt = (prompt: string, minimal?: boolean) => {
194 | return !minimal && !usePlugin?.nostore ? (
195 | ➜ {prompt}
196 | ) : null
197 | }
198 |
199 | const showReply = (response: string, minimal?: boolean, index?: number) => {
200 | return (
201 |
202 |
203 |
218 | ) : (
219 |
220 | {children}
221 |
222 | )
223 | },
224 | }}
225 | />
226 |
227 | {/* TODO: ban in windows & linux */}
228 | {response ? (
229 | <>
230 |
231 |
232 | atemptChange(response)}
236 | style={styles.attemptBtn}
237 | >
238 | {t('Attempt Change')}
239 |
240 | speakRsp(response)}
243 | />
244 | copyRsp(response)}
247 | />
248 | delRecord(index)}
251 | />
252 |
253 | >
254 | ) : null}
255 |
256 | )
257 | }
258 |
259 | const respContent = chatState.resp[presetState.currentPreset]
260 | const showContent =
261 | showSelection || showUrl || respContent || chatState.respErr
262 | const showChat =
263 | ((chatState.visible && showContent) || !minimal) &&
264 | !settingState.visible &&
265 | !presetState.listVisible
266 |
267 | const curPrompt = chatState.curPrompt[presetState.currentPreset]
268 | return showChat ? (
269 |
276 |
277 | {chatState.respErr ? (
278 |
279 | ) : null}
280 |
281 | {showCopyFromEditor()}
282 | {showSelectUrl()}
283 | {!minimal
284 | ? chatList.map((chat, index) => (
285 |
286 | {showPrompt(chat.prompt, minimal)}
287 | {showReply(chat.response, minimal, index)}
288 |
289 | ))
290 | : null}
291 | {/* need to separate prompt and resp */}
292 | {curPrompt ? showPrompt(curPrompt, minimal) : null}
293 | {respContent ? showReply(respContent) : null}
294 | {chatState.webCrawlResp ? showReply(chatState.webCrawlResp) : null}
295 |
296 |
297 |
298 | ) : null
299 | }
300 |
301 | const padding = 15
302 | const styles = {
303 | requestWrap: {
304 | backgroundColor: 'rgb(241 241 241)',
305 | fontSize: 15,
306 | lineHeight: '20px',
307 | fontWeight: 'bold',
308 | padding: '7px 45px 7px 45px',
309 | },
310 | replyWrap: {
311 | position: 'relative',
312 | backgroundColor: '#FFF',
313 | fontSize: 14,
314 | lineHeight: 2.5,
315 | padding,
316 | },
317 | mdWrap: {
318 | marginRight: 30,
319 | marginLeft: 30,
320 | overflow: 'auto',
321 | },
322 | history: {
323 | maxHeight: 400,
324 | overflow: 'auto',
325 | },
326 | bottomRspWrap: {
327 | position: 'relative',
328 | display: 'flex',
329 | flexDirection: 'row',
330 | justifyContent: 'center',
331 | alignItems: 'center',
332 | } as React.CSSProperties,
333 | speakIcon: {
334 | position: 'absolute',
335 | marginRight: 30,
336 | right: 65,
337 | top: 10,
338 | },
339 | copyIcon: {
340 | position: 'absolute',
341 | marginRight: 15,
342 | right: 45,
343 | top: 10,
344 | },
345 | clearIcon: {
346 | position: 'absolute',
347 | right: 25,
348 | top: 10,
349 | },
350 | attemptBtn: {
351 | width: 300,
352 | fontSize: 12,
353 | fontWeight: 'bold',
354 | },
355 | }
356 |
--------------------------------------------------------------------------------
/src/components/Logo/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { Image } from 'antd'
3 | import PubSub from 'pubsub-js'
4 | import { searchLogov2, logoSpin } from '../../app/images'
5 | import { useAppSelector } from '../../app/hooks'
6 | import { draggableStyle } from '../../utils'
7 | interface Props {
8 | guardian?: boolean
9 | }
10 |
11 | export function Logo(props: Props) {
12 | const { guardian } = props
13 | const chatState = useAppSelector(state => state.chat)
14 |
15 | useEffect(() => {
16 | if (guardian) {
17 | // remote.getCurrentWindow().setPosition(10, 10)
18 | }
19 | }, [guardian])
20 |
21 | return (
22 |
23 |
{
29 | PubSub.publish('showPanel', {
30 | setting: true,
31 | })
32 | }}
33 | />
34 | {chatState.inputDiabled ? (
35 |
39 | ) : null}
40 |
41 | )
42 | }
43 |
44 | const styles = {
45 | container: {
46 | position: 'relative',
47 | alignItems: 'center',
48 | justifyContent: 'center',
49 | ...draggableStyle(false),
50 | } as React.CSSProperties,
51 | guardian: {
52 | position: 'absolute',
53 | right: 30,
54 | bottom: 100,
55 | width: 200,
56 | height: 300,
57 | overflow: 'auto',
58 | flexDirection: 'column',
59 | // alignItems: 'flex-end',
60 | // justifyContent: 'flex-end',
61 | padding: 20,
62 | textAlign: 'right',
63 | } as React.CSSProperties,
64 | guardLoad: {
65 | position: 'absolute',
66 | right: 14,
67 | top: 15,
68 | width: 50,
69 | height: 50,
70 | } as React.CSSProperties,
71 | loading: {
72 | position: 'absolute',
73 | right: -5,
74 | top: -5,
75 | width: 50,
76 | height: 50,
77 | } as React.CSSProperties,
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/Logo/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const Container = styled.button`
4 | height: 42px;
5 | padding: 0 24px;
6 |
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 |
11 | background: #8257e6;
12 | border-radius: 8px;
13 | border: 0;
14 |
15 | color: #fff;
16 | font-size: 16px;
17 | font-weight: bold;
18 |
19 | cursor: pointer;
20 |
21 | &:hover {
22 | filter: brightness(0.9);
23 | }
24 |
25 | &:active {
26 | filter: brightness(0.7);
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/src/components/Modal/prompt.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { Modal, Select, ConfigProvider } from 'antd'
3 | import { useTranslation } from 'react-i18next'
4 | import PubSub from 'pubsub-js'
5 | import { useAppSelector } from '../../app/hooks'
6 | interface SelectOption {
7 | value: string
8 | label: string
9 | }
10 |
11 | export function Prompt() {
12 | const { t } = useTranslation()
13 | const presetState = useAppSelector(state => state.preset)
14 | const [isModalOpen, setIsModalOpen] = useState(false)
15 | const [selectOptions, setSelectOptions] = useState([])
16 | const [curval, setCurval] = useState('')
17 |
18 | const getPromptList = async () => {
19 | const list = await window.Main.getPromptList()
20 | const options = list.map((item: { character: string }) => {
21 | return {
22 | label: item.character,
23 | value: item.character,
24 | }
25 | })
26 | setSelectOptions(options)
27 | }
28 |
29 | const getUseCharacter = async () => {
30 | const prompt = await window.Main.getPluginPrompt(presetState.currentPreset)
31 | setCurval(prompt.character)
32 | }
33 |
34 | useEffect(() => {
35 | getUseCharacter()
36 | }, [presetState.currentPreset])
37 |
38 | useEffect(() => {
39 | getPromptList()
40 | PubSub.subscribe('showPromptModal', () => {
41 | showModal()
42 | })
43 | }, [])
44 |
45 | const showModal = () => {
46 | setIsModalOpen(true)
47 | getPromptList()
48 | }
49 | const handleOk = () => {
50 | setIsModalOpen(false)
51 | }
52 | const handleCancel = () => {
53 | setIsModalOpen(false)
54 | }
55 | const onChange = (value: string) => {
56 | console.log(`selected ${value}`)
57 | setCurval(value)
58 | window.Main.setPluginPrompt(presetState.currentPreset, value)
59 | }
60 | const onSearch = (value: string) => {
61 | console.log('search:', value)
62 | }
63 |
64 | return (
65 |
72 |
78 |
88 | (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
89 | }
90 | options={selectOptions}
91 | />
92 |
93 |
94 | )
95 | }
96 |
97 | const styles = {
98 | inputWrap: {
99 | margin: '10px 0',
100 | width: '100%',
101 | },
102 | }
103 |
--------------------------------------------------------------------------------
/src/components/Preset/detail.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, List, Skeleton, Divider } from 'antd'
2 | import PubSub from 'pubsub-js'
3 | import { useAppSelector } from '../../app/hooks'
4 | import { PresetType } from '../../@types'
5 |
6 | interface Props {
7 | onPresetChange: (preset: PresetType) => void
8 | }
9 |
10 | const padding = 15
11 | export function PresetDetail(props: Props) {
12 | const { onPresetChange } = props
13 | const presetState = useAppSelector(state => state.preset)
14 | return presetState.listVisible ? (
15 | <>
16 |
17 | <>
18 | (
25 | {
32 | return
33 | }}
34 | >
35 | Edit
36 | ,
37 | ]}
38 | onClick={() => {
39 | onPresetChange(item.title)
40 | PubSub.publish('showPanel', {})
41 | }}
42 | >
43 |
44 | }
47 | title={item.title}
48 | description={item.desc}
49 | />
50 |
51 |
52 | )}
53 | />
54 | >
55 | >
56 | ) : null
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Preset/index.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, List, Skeleton, Divider } from 'antd'
2 | import { useTranslation } from 'react-i18next'
3 | import PubSub from 'pubsub-js'
4 | import { useAppSelector } from '../../app/hooks'
5 | import { PresetType } from '../../@types'
6 |
7 | interface Props {
8 | onPresetChange: (preset: PresetType) => void
9 | }
10 |
11 | const padding = 15
12 | export function Preset(props: Props) {
13 | const { t } = useTranslation()
14 | const { onPresetChange } = props
15 | const presetState = useAppSelector(state => state.preset)
16 | return presetState.listVisible ? (
17 | <>
18 |
19 | <>
20 | (
27 | {
35 | // return ;
36 | // }}
37 | // >
38 | // Edit
39 | // ,
40 | // ]
41 | // }
42 | onClick={() => {
43 | onPresetChange(item.title)
44 | PubSub.publish('showPanel', {})
45 | }}
46 | >
47 |
48 | }
51 | title={t(item.title)}
52 | description={t(item.desc)}
53 | />
54 |
55 |
56 | )}
57 | />
58 | >
59 | >
60 | ) : null
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/Search/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react'
2 | import { Input } from 'antd'
3 | import PubSub from 'pubsub-js'
4 | import { useTranslation } from 'react-i18next'
5 |
6 | import {
7 | setInputDisabled,
8 | fetchChatResp,
9 | setCurPrompt,
10 | saveResp,
11 | } from '../../features/chat/chatSlice'
12 |
13 | import { useAppDispatch, useAppSelector } from '../../app/hooks'
14 | import { draggableStyle } from '../../utils'
15 |
16 | export default function () {
17 | const { t } = useTranslation()
18 | const [prompt, setPrompt] = useState('')
19 | const chatState = useAppSelector(state => state.chat)
20 | const presetState = useAppSelector(state => state.preset)
21 | const dispatch = useAppDispatch()
22 | useRef(null)
23 |
24 | const search = async () => {
25 | if (!prompt) return
26 | dispatch(setInputDisabled(true))
27 | dispatch(
28 | setCurPrompt({
29 | preset: presetState.currentPreset,
30 | content: prompt,
31 | })
32 | )
33 | dispatch(
34 | saveResp({
35 | preset: presetState.currentPreset,
36 | content: '',
37 | })
38 | )
39 | dispatch(
40 | fetchChatResp({
41 | prompt: `${prompt}`,
42 | preset: presetState.currentPreset,
43 | })
44 | )
45 | }
46 |
47 | const onInputChange = (
48 | e: React.ChangeEvent
49 | ) => {
50 | const val = e.target.value
51 | switch (val) {
52 | case '/':
53 | PubSub.publish('showPanel', {
54 | plugin: true,
55 | })
56 | break
57 | case '/s':
58 | PubSub.publish('showPanel', {
59 | setting: true,
60 | })
61 | break
62 | default:
63 | PubSub.publish('showPanel', {})
64 | }
65 | setPrompt(val)
66 | }
67 |
68 | return (
69 | search()}
80 | disabled={chatState.inputDiabled}
81 | onFocus={() =>
82 | PubSub.publish('showPanel', {
83 | chatPanel: true,
84 | })
85 | }
86 | />
87 | )
88 | }
89 |
90 | const styles = {
91 | search: {
92 | height: 40,
93 | resize: 'none',
94 | ...draggableStyle(false),
95 | } as React.CSSProperties,
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/Setting/Account/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { Progress, Button, Form, Input, Select, Alert, Spin } from 'antd'
3 | import { useTranslation } from 'react-i18next'
4 | import { useAppDispatch, useAppSelector } from '../../../app/hooks'
5 | import { Models, StoreKey } from '../../../app/constants'
6 | import { fetchAccountDetail } from '../../../features/setting/settingSlice'
7 |
8 | const { Option } = Select
9 |
10 | export default function () {
11 | const { t } = useTranslation()
12 | const [form] = Form.useForm()
13 | const [, forceUpdate] = useState({})
14 | const dispatch = useAppDispatch()
15 | const settingState = useAppSelector(state => state.setting)
16 | const [saveSuc, setSaveSuc] = useState(false)
17 |
18 | const formatDate = (num: number) => {
19 | return num.toString().padStart(2, '0')
20 | }
21 |
22 | const formatDateStr = (date: Date) => {
23 | return `${date.getFullYear()}-${formatDate(
24 | date.getMonth() + 1
25 | )}-${formatDate(date.getDate())}`
26 | }
27 |
28 | const refreshPage = () => {
29 | const date = new Date()
30 | const prevDate = new Date(date.valueOf() - 1000 * 60 * 60 * 24 * 99)
31 | dispatch(
32 | fetchAccountDetail({
33 | startDate: formatDateStr(prevDate),
34 | endDate: formatDateStr(date),
35 | })
36 | )
37 | }
38 |
39 | // To disable submit button at the beginning.
40 | useEffect(() => {
41 | forceUpdate({})
42 | refreshPage()
43 | }, [])
44 |
45 | useEffect(() => {
46 | form.resetFields()
47 | }, [settingState])
48 |
49 | const onFinish = (values: any) => {
50 | setSaveSuc(true)
51 | window.Main.setStore(StoreKey.Set_BasePath, values.basePath)
52 | window.Main.setStore(StoreKey.Set_ApiKey, values.apikey)
53 | window.Main.setStore(StoreKey.Set_Model, values.model)
54 | refreshPage()
55 | }
56 |
57 | return (
58 |
59 |
60 |
61 |
{t('Token Usage')}
62 |
68 |
69 |
82 |
83 |
84 |
85 |
88 |
89 |
94 |
95 | {Models.map(model => (
96 |
97 | {model}
98 |
99 | ))}
100 |
101 |
102 |
103 |
104 | {t('Submit')}
105 |
106 |
107 |
108 | {t('Refresh')}
109 |
110 |
111 |
112 |
113 |
114 | {saveSuc ? (
115 |
116 | ) : null}
117 |
118 |
119 | )
120 | }
121 |
122 | const styles = {
123 | wrap: {
124 | paddingTop: 10,
125 | },
126 | inner: {
127 | marginBottom: 10,
128 | paddingLeft: 20,
129 | paddingRight: 20,
130 | },
131 | title: {
132 | fontSize: 14,
133 | color: 'rgb(10, 11, 60)',
134 | },
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/Setting/Basic/index.tsx:
--------------------------------------------------------------------------------
1 | import { Select, Spin, Switch, Space } from 'antd'
2 | import { useTranslation } from 'react-i18next'
3 | import { useAppDispatch, useAppSelector } from '../../../app/hooks'
4 | import { StoreKey } from '../../../app/constants'
5 | import {
6 | setMinimal,
7 | setLng,
8 | setContexual,
9 | setStore as setStoreSet,
10 | // defaultVals,
11 | } from '../../../features/setting/settingSlice'
12 | import { Languages, localeOptions } from '../../../i18n'
13 |
14 | export default function () {
15 | const { t, i18n } = useTranslation()
16 | const dispatch = useAppDispatch()
17 | const settingState = useAppSelector(state => state.setting)
18 | const setStore = (key: string, value: string | boolean | number) => {
19 | window.Main.setStore(key, value)
20 | }
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
{t('Language')}
29 |
{
33 | dispatch(setLng(val))
34 | setStore(StoreKey.Set_Lng, val)
35 | i18n.changeLanguage(localeOptions[val])
36 | window.Main.changeLanguage(val)
37 | }}
38 | value={settingState.lng}
39 | options={[
40 | {
41 | value: 'English',
42 | label: 'English',
43 | },
44 | {
45 | value: '中文',
46 | label: '中文',
47 | },
48 | ]}
49 | />
50 |
51 |
52 |
53 |
{t('Save Chat History')}
54 |
{
58 | dispatch(setStoreSet(val))
59 | setStore(StoreKey.Set_StoreChat, val)
60 | }}
61 | value={settingState.store}
62 | options={[
63 | {
64 | value: 1,
65 | label: 'YES',
66 | },
67 | {
68 | value: 0,
69 | label: 'NO',
70 | },
71 | ]}
72 | />
73 |
74 |
75 |
76 |
{t('Quantity Of Context')}
77 |
{
81 | dispatch(setContexual(val))
82 | setStore(StoreKey.Set_Contexual, val)
83 | }}
84 | value={settingState.contextual}
85 | options={[
86 | {
87 | value: 5,
88 | label: '5',
89 | },
90 | {
91 | value: 10,
92 | label: '10',
93 | },
94 | {
95 | value: 15,
96 | label: '15',
97 | },
98 | {
99 | value: 20,
100 | label: '20',
101 | },
102 | ]}
103 | />
104 |
105 |
106 |
107 |
{t('Minimalist Mode(shrink panel)')}
108 |
{
112 | dispatch(setMinimal(val))
113 | setStore(StoreKey.Set_SimpleMode, val)
114 | }}
115 | />
116 |
117 |
118 |
119 |
120 | )
121 | }
122 |
123 | const styles = {
124 | wrap: {
125 | paddingTop: 10,
126 | },
127 | inner: {
128 | marginBottom: 10,
129 | paddingLeft: 20,
130 | paddingRight: 20,
131 | },
132 | title: {
133 | fontSize: 14,
134 | marginBottom: 5,
135 | color: 'rgb(10, 11, 60)',
136 | },
137 | simpleMode: {
138 | marginTop: 20,
139 | },
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/Setting/Prompt/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { useTranslation } from 'react-i18next'
3 | import { Button, Space, Table, Modal, Form, Input } from 'antd'
4 | import PubSub from 'pubsub-js'
5 | import type { ColumnsType } from 'antd/es/table'
6 | import { Prompts_Link } from '../../../app/constants'
7 | import { DataType } from '../../../@types'
8 |
9 | export default function () {
10 | const { t } = useTranslation()
11 | const [form] = Form.useForm()
12 | const [promptList, setPromptList] = useState([])
13 | const [isModalOpen, setIsModalOpen] = useState(false)
14 | const [isEdit, setIsEdit] = useState(false)
15 | const [former, setFormer] = useState('')
16 | const [formValues, setFormValues] = useState({
17 | layout: 'vertical',
18 | character: '',
19 | prompt: '',
20 | })
21 |
22 | const getPromptList = async () => {
23 | const list = await window.Main.getPromptList()
24 | setPromptList(list)
25 | }
26 |
27 | useEffect(() => {
28 | getPromptList()
29 | }, [])
30 |
31 | const showModal = (record?: DataType) => {
32 | setIsEdit(!!record)
33 | setFormer(record ? record.character : '')
34 | setFormValues({
35 | layout: 'vertical',
36 | character: record?.character || '',
37 | prompt: record?.prompt || '',
38 | })
39 | setIsModalOpen(true)
40 | }
41 |
42 | useEffect(() => {
43 | form.setFieldsValue(formValues)
44 | }, [formValues])
45 |
46 | const handleSave = async () => {
47 | const { character, prompt } = form.getFieldsValue()
48 | let list = []
49 | if (isEdit) {
50 | list = await window.Main.editPrompt(former, character, prompt)
51 | } else {
52 | list = await window.Main.addPrompt(character, prompt)
53 | }
54 | PubSub.publish('tips', {
55 | type: list ? 'success' : 'error',
56 | message: list ? t('Saved successfully') : t('Duplicated Character'),
57 | })
58 | if (list) {
59 | setPromptList(list)
60 | }
61 | setIsModalOpen(false)
62 | }
63 |
64 | const handleDelete = async (record: DataType) => {
65 | const { character } = record
66 | const list = await window.Main.removePrompt(character)
67 | PubSub.publish('tips', {
68 | type: list ? 'success' : 'error',
69 | message: list ? t('Removed successfully') : t('Remove Error'),
70 | })
71 | if (list) {
72 | setPromptList(list)
73 | }
74 | }
75 |
76 | const handleCancel = () => {
77 | setIsModalOpen(false)
78 | }
79 |
80 | const jumpReference = () => {
81 | window.Main.jumpLink(Prompts_Link)
82 | }
83 |
84 | const columns: ColumnsType = [
85 | {
86 | title: t('Character'),
87 | dataIndex: 'character',
88 | key: 'character',
89 | },
90 | {
91 | title: t('Prompt'),
92 | dataIndex: 'prompt',
93 | key: 'prompt',
94 | },
95 | {
96 | title: t('Action'),
97 | key: 'action',
98 | render: (_, record) => (
99 |
100 | {
102 | showModal(record)
103 | }}
104 | >
105 | Edit
106 |
107 | {
110 | handleDelete(record)
111 | }}
112 | >
113 | Delete
114 |
115 |
116 | ),
117 | },
118 | ]
119 |
120 | return (
121 |
156 | )
157 | }
158 |
159 | const styles = {
160 | wrap: {
161 | paddingTop: 10,
162 | height: 400,
163 | overflow: 'auto',
164 | },
165 | addBtn: {
166 | margin: '0px 0px 10px 10px',
167 | },
168 | }
169 |
--------------------------------------------------------------------------------
/src/components/Setting/index.tsx:
--------------------------------------------------------------------------------
1 | import { Tabs, Divider, ConfigProvider } from 'antd'
2 | import { useTranslation } from 'react-i18next'
3 | import {
4 | SettingFilled,
5 | UserOutlined,
6 | UsbOutlined,
7 | MacCommandOutlined,
8 | } from '@ant-design/icons'
9 | import Account from './Account'
10 | import Basic from './Basic'
11 | import Prompt from './Prompt'
12 | import { useAppSelector } from '../../app/hooks'
13 |
14 | export function Setting() {
15 | const settingState = useAppSelector(state => state.setting)
16 | const { t } = useTranslation()
17 |
18 | return settingState.visible ? (
19 |
26 |
27 |
28 |
34 |
35 | {t('Setting')}
36 |
37 | ),
38 | key: '1',
39 | children: ,
40 | },
41 | {
42 | label: (
43 |
44 |
45 | {t('Account')}
46 |
47 | ),
48 | key: '2',
49 | children: ,
50 | },
51 | {
52 | label: (
53 |
54 |
55 | {t('Prompts')}
56 |
57 | ),
58 | key: '3',
59 | children: ,
60 | // disabled: true,
61 | },
62 | {
63 | label: (
64 |
65 |
66 | {t('Advanced')}
67 |
68 | ),
69 | key: '4',
70 | children: ,
71 | disabled: true,
72 | },
73 | {
74 | label: (
75 |
76 |
77 | {t('Plugins')}
78 |
79 | ),
80 | key: '5',
81 | children: 'Tab 3',
82 | disabled: true,
83 | },
84 | ]}
85 | />
86 |
87 |
88 | ) : null
89 | }
90 |
91 | const styles = {
92 | wrap: {
93 | backgroundColor: '#F8F8F8',
94 | padding: 15,
95 | },
96 | }
97 |
--------------------------------------------------------------------------------
/src/electron/apis/account.ts:
--------------------------------------------------------------------------------
1 | import Store from 'electron-store'
2 | import { StoreKey } from '../../app/constants'
3 | import { BalanceResponse } from '../types'
4 |
5 | const store = new Store()
6 |
7 | export default async (req: any, res: any) => {
8 | const { start_date, end_date } = req.body
9 | const basePath = store.get(StoreKey.Set_BasePath) as string
10 | const apiHost = basePath || `https://closeai.deno.dev`
11 | const apiKey = store.get(StoreKey.Set_ApiKey) as string
12 | const usemodel = store.get(StoreKey.Set_Model) as string
13 | const basic = {
14 | apiHost,
15 | usemodel,
16 | apiKey,
17 | }
18 |
19 | try {
20 | const response = await fetch(
21 | `${apiHost}/v1/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`,
22 | {
23 | method: 'GET',
24 | headers: {
25 | 'Content-Type': 'application/json',
26 | Authorization: `Bearer ${apiKey}`,
27 | },
28 | }
29 | )
30 |
31 | const usageData = (await response.json()) as BalanceResponse
32 | res.send({
33 | code: 0,
34 | result: {
35 | usageData,
36 | basic,
37 | },
38 | })
39 | } catch (e: any) {
40 | res.send({
41 | code: -1,
42 | result: {
43 | message: e.message,
44 | basic,
45 | },
46 | })
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/electron/apis/apply.ts:
--------------------------------------------------------------------------------
1 | import { activeApp, applySelection } from '../os/applescript'
2 | import { Singleton } from '../utils/global'
3 |
4 | export default async (req: any, res: any) => {
5 | await activeApp(Singleton.getInstance().getRecentApp())
6 | const result = await applySelection()
7 | res.send({
8 | code: 0,
9 | result,
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/electron/apis/crawl.ts:
--------------------------------------------------------------------------------
1 | import { ERR_CODES } from '../types'
2 | import { getAiInstance } from '../server'
3 | import { Logger } from '../utils/util'
4 | import { getPluginPrompt } from '../client/store'
5 | import { getBrowserContnet } from '../os/applescript'
6 | import { PresetType } from '../../@types'
7 |
8 | interface UserMsg {
9 | role: string
10 | content: string
11 | }
12 |
13 | function getUserContent(content: string): UserMsg {
14 | return {
15 | role: 'user',
16 | content,
17 | }
18 | }
19 |
20 | function generatePayload(contents: UserMsg[], preset: PresetType) {
21 | return {
22 | model: 'gpt-3.5-turbo-0301',
23 | messages: [
24 | {
25 | role: 'system',
26 | content: getPluginPrompt(preset).prompt,
27 | },
28 | ...contents,
29 | ],
30 | }
31 | }
32 |
33 | export default async (req: any, res: any) => {
34 | const { preset } = req.body
35 | try {
36 | const resp = (await getBrowserContnet()) as string
37 | const text = resp.replace(/[\r\n\t ]/g, '')
38 | if (text.length > 4000) {
39 | res.send({
40 | code: ERR_CODES.TOKEN_TOO_LONG,
41 | result: null,
42 | message:
43 | 'The webpage content is too long(Exceeds 4000 characters.), which will affect the speed and experience of summarizing(Long article summary support is coming soon, please stay tuned)',
44 | })
45 | return
46 | }
47 |
48 | // let chunkLen = 250;
49 | // let total = text.length;
50 | // let o = []
51 | // Logger.log('length ===>', total)
52 | // for(let i = 0; i < total; i = i+chunkLen) {
53 | // o.push(text.slice(i, i+chunkLen))
54 | // }
55 | // console.log('o ===>', o)
56 | // const contents = [] as UserMsg[];
57 | // [...o, 'summarize this website:'].map((content: string) => {
58 | // contents.push(getUserContent(content))
59 | // })
60 |
61 | const contents = [getUserContent(text)]
62 | const completion = await getAiInstance().createChatCompletion(
63 | generatePayload(contents, preset)
64 | )
65 |
66 | console.log(completion.data.choices)
67 | const result = completion.data.choices
68 | const respContent = result[0].message.content
69 |
70 | res.send({
71 | code: 0,
72 | result: respContent,
73 | })
74 | } catch (e) {
75 | Logger.error(e)
76 | res.send({
77 | code: ERR_CODES.NETWORK_CONGESTION,
78 | result: e,
79 | })
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/electron/apis/prompt.ts:
--------------------------------------------------------------------------------
1 | import Store from 'electron-store'
2 | import { ERR_CODES } from '../types'
3 | import { generatePayload, getAiInstance } from '../server'
4 | import { Logger } from '../utils/util'
5 | import { setChat } from '../client/store'
6 | import { StoreKey, BuiltInPlugins } from '../../app/constants'
7 |
8 | const store = new Store()
9 |
10 | export default async (req: any, res: any) => {
11 | const { prompt, preset } = req.body
12 | Logger.log('ask question', prompt)
13 | res.setHeader('Content-type', 'application/octet-stream')
14 | if (!getAiInstance()) {
15 | res.write(String(ERR_CODES.NOT_SET_APIKEY))
16 | res.end()
17 | return
18 | }
19 | let result = ''
20 | try {
21 | const response = await getAiInstance().createChatCompletion(
22 | generatePayload(prompt, preset),
23 | { responseType: 'stream' }
24 | )
25 | const stream = response.data
26 | stream.on('data', (chunk: Buffer) => {
27 | const payloads = chunk.toString().split('\n\n')
28 | for (const payload of payloads) {
29 | if (payload.includes('[DONE]')) return
30 | if (payload.startsWith('data:')) {
31 | const data = payload.replace(/(\n)?^data:\s*/g, '')
32 | try {
33 | const delta = JSON.parse(data.trim())
34 | const resp = delta.choices[0].delta?.content
35 | result += resp || ''
36 | res.write(resp || '')
37 | Logger.log('chunk resp', resp)
38 | } catch (error) {
39 | const errmsg = `Error with JSON.parse and ${payload}.\n${error}`
40 | Logger.log(errmsg)
41 | res.write(errmsg)
42 | res.end()
43 | }
44 | }
45 | }
46 | })
47 | stream.on('end', () => {
48 | Logger.log('Stream done')
49 | const usePlugin = BuiltInPlugins.filter(
50 | plugin => plugin.title === preset
51 | )[0]
52 | if (store.get(StoreKey.Set_StoreChat) && !usePlugin.nostore) {
53 | setChat({
54 | prompt,
55 | response: result,
56 | preset,
57 | })
58 | }
59 | res.end()
60 | })
61 | stream.on('error', (e: Error) => {
62 | Logger.error(e)
63 | res.write(e.message)
64 | res.end()
65 | })
66 | } catch (e) {
67 | Logger.error(e)
68 | if (e instanceof Error) {
69 | res.write(String(ERR_CODES.NETWORK_CONGESTION))
70 | res.end()
71 | } else {
72 | res.write(JSON.stringify(e))
73 | res.end()
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/electron/apis/test.ts:
--------------------------------------------------------------------------------
1 | import { clipboard } from 'electron'
2 | import { getBrowserContnet } from '../os/applescript'
3 | import { Singleton } from '../utils/global'
4 | import { Logger } from '../utils/util'
5 | import { getAiInstance, generatePayload } from '../server'
6 | const h2p = require('html2plaintext')
7 |
8 | export default async (req: any, res: any) => {
9 | try {
10 | if (!getAiInstance()) {
11 | res.send({
12 | code: -1,
13 | result: 'openkey not set!',
14 | })
15 | return
16 | }
17 | clipboard.writeText('test resp text')
18 | try {
19 | // await activeApp(Singleton.getInstance().getRecentApp())
20 | const content = await getBrowserContnet()
21 | const plainText = h2p(content)
22 | Logger.log('getBrowserContnet:', plainText)
23 |
24 | const completion = await getAiInstance().createChatCompletion(
25 | generatePayload(`${plainText}`, 'Summarizer')
26 | )
27 | Logger.log(completion.data.choices)
28 | const result = completion.data.choices
29 | const respContent = result[0].message.content
30 | clipboard.writeText(respContent)
31 | Singleton.getInstance().setCopyStateSource(true)
32 | res.send({
33 | code: 0,
34 | result,
35 | })
36 | } catch (e) {
37 | Logger.error('getBrowserContnet:', e)
38 | }
39 | } catch (e) {
40 | res.send({
41 | code: -1,
42 | result: e,
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/electron/client/bridge.ts:
--------------------------------------------------------------------------------
1 | import { contextBridge, ipcRenderer } from 'electron'
2 | import { PresetType, PosType } from '../../@types'
3 | import { Languages } from '../../i18n'
4 | import {
5 | winIgnoreMouse,
6 | winMouseMove,
7 | attemptChange,
8 | usePreset,
9 | jumpLink,
10 | copyText,
11 | speakText,
12 | setStore,
13 | getStore,
14 | removeChat,
15 | getChatList,
16 | addPrompt,
17 | removePrompt,
18 | getPromptList,
19 | editPrompt,
20 | setPluginPrompt,
21 | getPluginPrompt,
22 | changeLanguage,
23 | runScript,
24 | setWinSize,
25 | } from '../constants/event'
26 |
27 | export const api = {
28 | /**
29 | * Emit events
30 | */
31 | ignoreWinMouse: (ignore: boolean) => ipcRenderer.send(winIgnoreMouse, ignore),
32 | moveWindowPos: (pos: PosType) => ipcRenderer.send(winMouseMove, pos),
33 | setWinSize: (w: number, h: number) => ipcRenderer.send(setWinSize, { w, h }),
34 | setUsePreset: (preset: PresetType) => ipcRenderer.send(usePreset, preset),
35 | jumpLink: (link: string) => ipcRenderer.send(jumpLink, link),
36 | changeLanguage: (lng: Languages) => ipcRenderer.send(changeLanguage, lng),
37 | setStore: (key: string, blob: any) =>
38 | ipcRenderer.invoke(setStore, { key, blob }),
39 | getSettings: (key: string) => ipcRenderer.invoke(getStore, key),
40 | attemptChange: (changes: string) =>
41 | ipcRenderer.invoke(attemptChange, changes),
42 | copyText: (changes: string) => ipcRenderer.invoke(copyText, changes),
43 | speakText: (changes: string) => ipcRenderer.invoke(speakText, changes),
44 | getChatList: (preset: PresetType) => ipcRenderer.invoke(getChatList, preset),
45 | removeChat: (preset: PresetType, index: number) =>
46 | ipcRenderer.invoke(removeChat, { preset, index }),
47 | addPrompt: (character: string, prompt: string) =>
48 | ipcRenderer.invoke(addPrompt, { character, prompt }),
49 | editPrompt: (former: string, character: string, prompt: string) =>
50 | ipcRenderer.invoke(editPrompt, { former, character, prompt }),
51 | removePrompt: (character: string) =>
52 | ipcRenderer.invoke(removePrompt, { character }),
53 | getPromptList: () => ipcRenderer.invoke(getPromptList),
54 | setPluginPrompt: (plugin: string, character: string) =>
55 | ipcRenderer.invoke(setPluginPrompt, { plugin, character }),
56 | getPluginPrompt: (plugin: string) =>
57 | ipcRenderer.invoke(getPluginPrompt, { plugin }),
58 | runScript: (script: string) => ipcRenderer.invoke(runScript, script),
59 |
60 | /**
61 | * Provide an easier way to listen to events
62 | */
63 | on: (channel: string, callback: (data: any) => void) => {
64 | ipcRenderer.on(channel, (_, data) => callback(data))
65 | },
66 | }
67 |
68 | contextBridge?.exposeInMainWorld('Main', api)
69 |
--------------------------------------------------------------------------------
/src/electron/client/clipboard.ts:
--------------------------------------------------------------------------------
1 | import { IpcMainInvokeEvent, ipcMain, BrowserWindow, clipboard } from 'electron'
2 | import { Logger } from '../utils/util'
3 | import { Singleton } from '../utils/global'
4 | import { activeApp, applySelection } from '../os/applescript'
5 | import { copyText, attemptChange } from '../constants/event'
6 |
7 | const clipboardWatcher = require('electron-clipboard-watcher')
8 |
9 | export function listen(_win: BrowserWindow | null) {
10 | clipboardWatcher({
11 | watchDelay: 200,
12 | onImageChange() {},
13 | onTextChange(_text: string) {
14 | if (Singleton.getInstance().getCopyFromElectron()) {
15 | Singleton.getInstance().setCopyStateSource(false)
16 | }
17 | // TODO: Pop-up notifications for clipboard content changes are unnecessary and can have a significant impact on user experience.
18 | // setWindowVisile(true)
19 | // win?.webContents.send(clipboard_change, text)
20 | },
21 | })
22 |
23 | ipcMain.handle(
24 | copyText,
25 | async (event: IpcMainInvokeEvent, changes: string) => {
26 | try {
27 | clipboard.writeText(changes)
28 | return true
29 | } catch (e) {
30 | Logger.error(e)
31 | return false
32 | }
33 | }
34 | )
35 |
36 | ipcMain.handle(
37 | attemptChange,
38 | async (event: IpcMainInvokeEvent, changes: string) => {
39 | try {
40 | clipboard.writeText(changes)
41 | await activeApp(Singleton.getInstance().getRecentApp())
42 | await applySelection()
43 | } catch (e) {
44 | Logger.error(e)
45 | }
46 | }
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/electron/client/index.ts:
--------------------------------------------------------------------------------
1 | export * from './bridge'
2 | export * from './clipboard'
3 | export * from './link'
4 | export * from './store'
5 | export * from './window'
6 |
--------------------------------------------------------------------------------
/src/electron/client/link.ts:
--------------------------------------------------------------------------------
1 | import { IpcMainInvokeEvent, ipcMain, shell } from 'electron'
2 | import { jumpLink } from '../constants/event'
3 |
4 | export function setupLinkHandlers() {
5 | ipcMain.on(jumpLink, async (event: IpcMainInvokeEvent, link: string) => {
6 | shell.openExternal(link)
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/src/electron/client/store.ts:
--------------------------------------------------------------------------------
1 | import { t } from 'i18next'
2 | import { IpcMainInvokeEvent, ipcMain } from 'electron'
3 | import Store from 'electron-store'
4 | import { ChatContent, PromptSet } from '../types'
5 | import {
6 | setStore,
7 | getStore,
8 | removeChat,
9 | addPrompt,
10 | editPrompt,
11 | removePrompt,
12 | getChatList as getChatListEvt,
13 | getPromptList as getPromptListEvt,
14 | getPluginPrompt as getPluginPromptEvt,
15 | setPluginPrompt as setPluginPromptEvt,
16 | } from '../constants/event'
17 | import { PresetType } from '../../@types'
18 | import {
19 | Casual,
20 | Translator,
21 | Summarizer,
22 | Programmer,
23 | Analyst,
24 | StoreKey,
25 | } from '../../app/constants'
26 | import { Languages } from '../../i18n'
27 | import * as prompts from '../prompt/prompts.json'
28 |
29 | const store = new Store()
30 |
31 | // TODO: need to refactor to schema
32 | export function init() {
33 | const promptTemplates = store.get(StoreKey.List_Prompt) as string | undefined
34 | const pluginPrompts = store.get(StoreKey.Map_Pluginprompt) as
35 | | string
36 | | undefined
37 | let lng = store.get(StoreKey.Set_Lng) as Languages | undefined
38 | if (typeof lng === 'undefined') {
39 | store.set(StoreKey.Set_Lng, 'English')
40 | lng = 'English'
41 | }
42 | if (typeof promptTemplates === 'undefined') {
43 | const _prompts = prompts.map(item => {
44 | return {
45 | character: item.act,
46 | prompt: item.prompt,
47 | }
48 | })
49 | store.set(StoreKey.List_Prompt, JSON.stringify(_prompts))
50 | }
51 | if (typeof pluginPrompts === 'undefined') {
52 | setPluginPrompt({ plugin: Casual, character: t('Casual') })
53 | setPluginPrompt({ plugin: Programmer, character: t('Programmer') })
54 | setPluginPrompt({ plugin: Summarizer, character: t('Summarizer') })
55 | setPluginPrompt({ plugin: Analyst, character: t('Analyst') })
56 | setPluginPrompt({ plugin: Translator, character: t('Translator') })
57 | }
58 | }
59 |
60 | export function setupStoreHandlers() {
61 | ipcMain.handle(
62 | setStore,
63 | async (
64 | event: IpcMainInvokeEvent,
65 | { key, blob }: { key: string; blob: any }
66 | ) => {
67 | console.log('== store ==>', key, blob)
68 | store.set(key, blob)
69 | }
70 | )
71 |
72 | ipcMain.handle(getStore, async (event: IpcMainInvokeEvent, key: string) => {
73 | return store.get(key)
74 | })
75 |
76 | ipcMain.handle(
77 | getChatListEvt,
78 | async (event: IpcMainInvokeEvent, preset: PresetType) => {
79 | return getChatList(preset)
80 | }
81 | )
82 |
83 | ipcMain.handle(
84 | removeChat,
85 | async (
86 | event: IpcMainInvokeEvent,
87 | { preset, index }: { preset: PresetType; index: number }
88 | ) => {
89 | const list = getChatList(preset)
90 | list.splice(index, 1)
91 | const mapStr = store.get(StoreKey.History_Chat) as string | undefined
92 |
93 | if (typeof mapStr !== 'undefined') {
94 | const chatMap = JSON.parse(mapStr)
95 | chatMap[preset] = list
96 | store.set(StoreKey.History_Chat, JSON.stringify(chatMap))
97 | return list
98 | } else {
99 | return []
100 | }
101 | }
102 | )
103 |
104 | ipcMain.handle(
105 | addPrompt,
106 | async (
107 | event: IpcMainInvokeEvent,
108 | { character, prompt }: { character: string; prompt: string }
109 | ) => {
110 | const list = getPromptList()
111 | const index = list.findIndex(item => item.character === character)
112 | if (index !== -1) {
113 | return false
114 | }
115 | list.push({
116 | character,
117 | prompt,
118 | })
119 | store.set(StoreKey.List_Prompt, JSON.stringify(list))
120 | return list
121 | }
122 | )
123 |
124 | ipcMain.handle(
125 | editPrompt,
126 | async (
127 | event: IpcMainInvokeEvent,
128 | {
129 | former,
130 | character,
131 | prompt,
132 | }: { former: string; character: string; prompt: string }
133 | ) => {
134 | const list = getPromptList()
135 | const index = list.findIndex(item => item.character === former)
136 | if (index !== -1) {
137 | list.splice(index, 1)
138 | list.splice(index, 1, {
139 | character,
140 | prompt,
141 | })
142 | store.set(StoreKey.List_Prompt, JSON.stringify(list))
143 | return list
144 | }
145 | return false
146 | }
147 | )
148 |
149 | ipcMain.handle(
150 | removePrompt,
151 | async (event: IpcMainInvokeEvent, { character }: { character: string }) => {
152 | const list = getPromptList()
153 | const index = list.findIndex(item => item.character === character)
154 | if (index !== -1) {
155 | list.splice(index, 1)
156 | store.set(StoreKey.List_Prompt, JSON.stringify(list))
157 | return list
158 | }
159 | return false
160 | }
161 | )
162 |
163 | ipcMain.handle(getPromptListEvt, async () => {
164 | return getPromptList()
165 | })
166 |
167 | ipcMain.handle(
168 | getPluginPromptEvt,
169 | async (event: IpcMainInvokeEvent, { plugin }: { plugin: PresetType }) => {
170 | return getPluginPrompt(plugin)
171 | }
172 | )
173 |
174 | ipcMain.handle(
175 | setPluginPromptEvt,
176 | async (
177 | event: IpcMainInvokeEvent,
178 | { plugin, character }: { plugin: string; character: string }
179 | ) => {
180 | return setPluginPrompt({ plugin, character })
181 | }
182 | )
183 | }
184 |
185 | export function setChat({
186 | prompt,
187 | response,
188 | preset,
189 | }: {
190 | prompt: string
191 | response: string
192 | preset: PresetType
193 | }) {
194 | const mapStr = store.get(StoreKey.History_Chat) as string | undefined
195 | if (typeof mapStr !== 'undefined') {
196 | const chatMap = JSON.parse(mapStr)
197 | const list = chatMap[preset]
198 | if (Array.isArray(list)) {
199 | list.push({
200 | prompt,
201 | response,
202 | })
203 | }
204 | store.set(StoreKey.History_Chat, JSON.stringify(chatMap))
205 | } else {
206 | const map = {
207 | [preset]: [
208 | {
209 | prompt,
210 | response,
211 | },
212 | ],
213 | }
214 | store.set(StoreKey.History_Chat, JSON.stringify(map))
215 | }
216 | }
217 |
218 | export function getChatList(type: PresetType): ChatContent[] {
219 | const mapStr = store.get(StoreKey.History_Chat) as string | undefined
220 | if (typeof mapStr !== 'undefined') {
221 | const chatMap = JSON.parse(mapStr)
222 | const list = chatMap[type] || []
223 | return list
224 | } else {
225 | return []
226 | }
227 | }
228 |
229 | export function getPromptList(): PromptSet[] {
230 | const mapStr = store.get(StoreKey.List_Prompt) as string | undefined
231 | if (typeof mapStr !== 'undefined') {
232 | const promptList = JSON.parse(mapStr)
233 | return promptList
234 | } else {
235 | return []
236 | }
237 | }
238 |
239 | export function getPromptByCharacter(character: string): string {
240 | const list = getPromptList()
241 | const selectedItems = list.filter(item => item.character === character)
242 | if (selectedItems.length > 0) {
243 | return selectedItems[0].prompt
244 | }
245 | return ''
246 | }
247 |
248 | export function setPluginPrompt({
249 | plugin,
250 | character,
251 | }: {
252 | plugin: string
253 | character: string
254 | }) {
255 | const mapStr = store.get(StoreKey.Map_Pluginprompt) as string | undefined
256 | if (typeof mapStr !== 'undefined') {
257 | const map = JSON.parse(mapStr)
258 | map[plugin] = character
259 | store.set(StoreKey.Map_Pluginprompt, JSON.stringify(map))
260 | } else {
261 | const map = {
262 | [plugin]: character,
263 | }
264 | store.set(StoreKey.Map_Pluginprompt, JSON.stringify(map))
265 | }
266 | }
267 |
268 | export function getPluginPrompt(plugin: PresetType): PromptSet {
269 | const mapStr = store.get(StoreKey.Map_Pluginprompt) as string | undefined
270 | if (typeof mapStr !== 'undefined') {
271 | const map = JSON.parse(mapStr)
272 | const character = map[plugin] || ''
273 | return {
274 | character,
275 | prompt: getPromptByCharacter(character),
276 | }
277 | }
278 | return {
279 | character: '',
280 | prompt: '',
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/src/electron/client/window.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindow, ipcMain } from 'electron'
2 | import { winIgnoreMouse, winMouseMove, setWinSize } from '../constants/event'
3 |
4 | export function setupWindowHandlers(win: BrowserWindow | null) {
5 | ipcMain.on(winIgnoreMouse, (_, ignore) => {
6 | win?.setIgnoreMouseEvents(ignore, { forward: true })
7 | })
8 |
9 | ipcMain.on(winMouseMove, (_, pos) => {
10 | win?.setPosition(pos.posX, pos.posY)
11 | })
12 |
13 | ipcMain.on(setWinSize, (_, size) => {
14 | console.log('???win', size)
15 | win?.setSize(size.w, size.h)
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/electron/constants/common.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | linux(): boolean {
3 | return process.platform === 'linux'
4 | },
5 | macOS(): boolean {
6 | return process.platform === 'darwin'
7 | },
8 | windows(): boolean {
9 | return process.platform === 'win32'
10 | },
11 | production(): boolean {
12 | return process.env.NODE_ENV !== 'development'
13 | },
14 | dev(): boolean {
15 | return process.env.NODE_ENV === 'development'
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/electron/constants/event.ts:
--------------------------------------------------------------------------------
1 | // receiver
2 | export const winIgnoreMouse = 'winIgnoreMouse'
3 | export const winMouseMove = 'winMouseMove'
4 | export const attemptChange = 'attemptChange'
5 | export const usePreset = 'usePreset'
6 | export const jumpLink = 'jumpLink'
7 | export const copyText = 'copyText'
8 | export const speakText = 'speakText'
9 | export const setStore = 'setStore'
10 | export const getStore = 'getStore'
11 | export const removeChat = 'removeChat'
12 | export const getChatList = 'getChatList'
13 | export const addPrompt = 'addPrompt'
14 | export const editPrompt = 'editPrompt'
15 | export const removePrompt = 'removePrompt'
16 | export const getPromptList = 'getPromptList'
17 | export const setPluginPrompt = 'setPluginPrompt'
18 | export const getPluginPrompt = 'getPluginPrompt'
19 | export const changeLanguage = 'changeLanguage'
20 | export const runScript = 'runScript'
21 | export const setWinSize = 'setWinSize'
22 |
23 | // sender
24 | export const clipboard_change = 'clipboard_change'
25 | export const selection_change = 'selection_change'
26 | export const url_change = 'url_change'
27 | export const setting_show = 'setting_show'
28 |
--------------------------------------------------------------------------------
/src/electron/constants/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/src/electron/constants/index.ts
--------------------------------------------------------------------------------
/src/electron/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'compression'
2 |
--------------------------------------------------------------------------------
/src/electron/main.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, ipcMain } from 'electron'
2 | import Store from 'electron-store'
3 | import { PresetType } from '../@types'
4 |
5 | import initLog from './utils/log'
6 | import { Singleton } from './utils/global'
7 | import { setWindowVisile } from './utils/window'
8 |
9 | import { setupSoundHandlers } from './sound'
10 | import {
11 | listen as setupClipboardHandlers,
12 | setupStoreHandlers,
13 | init as initStore,
14 | setupLinkHandlers,
15 | setupWindowHandlers,
16 | } from './client'
17 | import {
18 | setupScriptHandlers,
19 | listen as setupShortcutHandlers,
20 | initTray,
21 | } from './os'
22 | import { StoreKey } from '../app/constants'
23 | import { init as initI18n, Languages } from '../i18n'
24 | require('./server')
25 |
26 | declare const MAIN_WINDOW_WEBPACK_ENTRY: string
27 | declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string
28 | const userLog = initLog()
29 | const store = new Store()
30 | let win: BrowserWindow | null
31 |
32 | function initWindow() {
33 | win = new BrowserWindow({
34 | resizable: false,
35 | width: 800,
36 | height: 600,
37 | frame: false,
38 | show: true,
39 | transparent: true,
40 | backgroundColor: '#00000000',
41 | skipTaskbar: true,
42 | webPreferences: {
43 | webSecurity: false,
44 | backgroundThrottling: false,
45 | contextIsolation: true,
46 | webviewTag: true,
47 | nodeIntegration: true,
48 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
49 | },
50 | })
51 |
52 | if (!app.isPackaged) {
53 | win?.webContents.openDevTools({
54 | mode: 'bottom',
55 | })
56 | }
57 | win.loadURL(MAIN_WINDOW_WEBPACK_ENTRY)
58 | win.on('closed', () => {
59 | win = null
60 | })
61 | win.on('blur', () => {
62 | setWindowVisile({
63 | win,
64 | visible: false,
65 | })
66 | })
67 | app.dock?.hide()
68 |
69 | initI18n(store.get(StoreKey.Set_Lng) as Languages)
70 | initStore()
71 | registerListeners()
72 | }
73 |
74 | async function registerListeners() {
75 | ipcMain.on('usePreset', (_, preset: PresetType) => {
76 | Singleton.getInstance().setCurPreset(preset)
77 | })
78 | setupWindowHandlers(win)
79 | setupClipboardHandlers(win)
80 | setupShortcutHandlers(win)
81 | setupSoundHandlers()
82 | setupStoreHandlers()
83 | setupLinkHandlers()
84 | setupScriptHandlers()
85 | }
86 |
87 | app
88 | .on('ready', initWindow)
89 | .whenReady()
90 | .then(() => win && initTray(win, app))
91 | .catch(e => {
92 | console.error(e)
93 | userLog.error(e)
94 | })
95 |
96 | app.on('window-all-closed', () => {
97 | // if (process.platform !== 'darwin') {
98 | app.quit()
99 | // }
100 | })
101 |
102 | app.on('activate', () => {
103 | if (BrowserWindow.getAllWindows().length === 0) {
104 | initWindow()
105 | }
106 | })
107 |
--------------------------------------------------------------------------------
/src/electron/os/applescript.ts:
--------------------------------------------------------------------------------
1 | import { IpcMainInvokeEvent, ipcMain } from 'electron'
2 | import { Logger } from '../utils/util'
3 | import { runScript } from '../constants/event'
4 | const applescript = require('applescript')
5 |
6 | export function setupScriptHandlers() {
7 | ipcMain.handle(
8 | runScript,
9 | async (event: IpcMainInvokeEvent, script: string) => {
10 | runAppleScript(script)
11 | }
12 | )
13 | }
14 |
15 | function runAppleScript(script: string) {
16 | const date = Date.now()
17 | return new Promise((resolve, reject) => {
18 | applescript.execString(script, (err: any, rtn: any) => {
19 | Logger.log(`runscript: ${script}`, Date.now() - date)
20 | if (err) {
21 | reject(err)
22 | return
23 | }
24 | resolve(rtn)
25 | })
26 | })
27 | }
28 |
29 | export function printer(words: string) {
30 | return `
31 | set textBuffer to "${words}"
32 | repeat with i from 1 to count characters of textBuffer
33 | set the clipboard to (character i of textBuffer)
34 | delay 0.05
35 | keystroke "v" using command down
36 | end repeat
37 | `
38 | }
39 | export function getSelection() {
40 | const script = `
41 | tell application "System Events" to keystroke "c" using {command down}
42 | delay 0.5
43 | set selectedText to the clipboard
44 | `
45 | return runAppleScript(script)
46 | }
47 |
48 | export function applySelection() {
49 | const script = `
50 | tell application "System Events" to keystroke "v" using {command down}
51 | delay 1
52 | set selectedText to the clipboard
53 | `
54 | return runAppleScript(script)
55 | }
56 |
57 | export function getRecentApp() {
58 | const script = `
59 | tell application "System Events"
60 | set frontmostAppName to displayed name of first application process whose frontmost is true
61 | end tell`
62 | return runAppleScript(script)
63 | }
64 |
65 | export function activeApp(app: string) {
66 | const script = `
67 | tell application "${app}"
68 | activate
69 | end tell
70 | `
71 | return runAppleScript(script)
72 | }
73 |
74 | export function getBrowserContnet() {
75 | const script = `
76 | tell application "Google Chrome"
77 | tell window 1
78 | tell active tab
79 | execute javascript "document.documentElement.innerText"
80 | end tell
81 | end tell
82 | end tell
83 | `
84 | return runAppleScript(script)
85 | }
86 |
87 | export function getBrowserUrl(browser: string) {
88 | const safariScript = `tell application "Safari" to get the URL of the current tab of window 1
89 | `
90 | const chromeScript = `
91 | tell application "Google Chrome" to get the URL of the active tab of window 1
92 | `
93 | if (browser.includes('Chrome')) return runAppleScript(chromeScript)
94 | if (browser.includes('Safari')) return runAppleScript(safariScript)
95 | return ''
96 | }
97 |
98 | export function speakTxt(txt: string, rate: number) {
99 | // using "${voice}"
100 | const script = `
101 | tell application "Finder" to say "${txt}" speaking rate ${rate}
102 | `
103 | return runAppleScript(script)
104 | }
105 |
--------------------------------------------------------------------------------
/src/electron/os/index.ts:
--------------------------------------------------------------------------------
1 | export * from './tray'
2 | export * from './applescript'
3 | export * from './shortcuts'
4 |
--------------------------------------------------------------------------------
/src/electron/os/shortcuts.ts:
--------------------------------------------------------------------------------
1 | import os from 'os'
2 | import { BrowserWindow, globalShortcut, clipboard } from 'electron'
3 | import { getRecentApp, getSelection, getBrowserUrl } from './applescript'
4 | import { BuiltInPlugins } from '../../app/constants'
5 | import { Logger } from '../utils/util'
6 | import { setWindowVisile } from '../utils/window'
7 | import { Singleton } from '../utils/global'
8 | import { selection_change, url_change } from '../constants/event'
9 |
10 | export const config = {
11 | shortCut: {
12 | showAndHidden: 'CommandOrControl+k',
13 | },
14 | }
15 |
16 | function setApp() {
17 | /* eslint-disable no-async-promise-executor */
18 | return new Promise(async (resolve, reject) => {
19 | try {
20 | const app = (await getRecentApp()) as string
21 | Logger.log('appName', app)
22 | Singleton.getInstance().setRecentApp(app)
23 | resolve(app)
24 | } catch (e) {
25 | reject(e)
26 | }
27 | })
28 | }
29 |
30 | export function listen(win: BrowserWindow | null) {
31 | globalShortcut.register(config.shortCut.showAndHidden, async () => {
32 | const visible = win?.isVisible() && win.isFocused()
33 | if (!visible) {
34 | try {
35 | if (os.platform() !== 'darwin') {
36 | throw new Error('Only support macOS')
37 | }
38 | const preset = Singleton.getInstance().getCurPreset()
39 | const plugin = BuiltInPlugins.filter(plugin => plugin.title === preset)
40 | const app = await setApp()
41 | if (plugin.length > 0) {
42 | const usePlugin = plugin[0]
43 | if (usePlugin.monitorClipboard) {
44 | const clipboardContent = clipboard.readText()
45 | const selection = await getSelection()
46 | Logger.log('selectionTxt =>', selection)
47 | win?.webContents.send(selection_change, {
48 | txt: selection,
49 | app,
50 | })
51 | clipboard.writeText(clipboardContent)
52 | } else if (usePlugin.monitorBrowser) {
53 | const url = await getBrowserUrl(app)
54 | win?.webContents.send(url_change, {
55 | url,
56 | })
57 | }
58 | }
59 |
60 | setWindowVisile({
61 | win,
62 | visible: true,
63 | })
64 | } catch (e) {
65 | Logger.error(e)
66 | setWindowVisile({
67 | win,
68 | visible: true,
69 | })
70 | }
71 | return
72 | }
73 | setWindowVisile({
74 | win,
75 | visible: false,
76 | })
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/src/electron/os/tray.ts:
--------------------------------------------------------------------------------
1 | import { Tray, Menu, shell, dialog, BrowserWindow, ipcMain } from 'electron'
2 | import i18n, { t } from 'i18next'
3 | import path from 'path'
4 | import { config } from './shortcuts'
5 | import { setWindowVisile } from '../utils/window'
6 | import common from '../constants/common'
7 | import { setting_show, changeLanguage } from '../constants/event'
8 | import { Languages, localeOptions } from '../../i18n'
9 | import pkg from '../../../package.json'
10 |
11 | function getTrayImagePath() {
12 | if (common.production()) {
13 | return path.join(process.resourcesPath, 'assets/icon/icon24.png')
14 | }
15 | return 'assets/icon/icon24.png'
16 | }
17 |
18 | export function initTray(win: BrowserWindow, app: Electron.App) {
19 | const getMenuTemplate = () => {
20 | return Menu.buildFromTemplate([
21 | {
22 | label: t('Feedback & Help'),
23 | click: () => {
24 | process.nextTick(() => {
25 | shell.openExternal('https://github.com/onepointAI/onepoint/issues')
26 | })
27 | },
28 | },
29 | { type: 'separator' },
30 | {
31 | label: t('Display Window'),
32 | accelerator: config.shortCut.showAndHidden,
33 | click: () => {
34 | setWindowVisile({
35 | win,
36 | visible: true,
37 | })
38 | },
39 | },
40 | {
41 | label: t('Settings'),
42 | click: () => {
43 | win?.webContents.send(setting_show)
44 | setWindowVisile({
45 | win,
46 | visible: true,
47 | })
48 | },
49 | },
50 | { type: 'separator' },
51 | {
52 | role: 'quit',
53 | label: t('exit'),
54 | },
55 | {
56 | label: t('reload'),
57 | click() {
58 | app.relaunch()
59 | app.quit()
60 | },
61 | },
62 | { type: 'separator' },
63 | {
64 | label: t('About'),
65 | click() {
66 | dialog.showMessageBox({
67 | title: t('onepoint'),
68 | message: t(
69 | 'More than just chat. Write, read, and code with powerful AI anywhere.'
70 | ),
71 | detail: t('Version:') + pkg.version,
72 | })
73 | },
74 | },
75 | ])
76 | }
77 |
78 | const tray = new Tray(getTrayImagePath())
79 | const buildMenu = () => {
80 | const contextMenu = getMenuTemplate()
81 | tray.setToolTip(t('onepoint | more than just chat'))
82 | tray.setContextMenu(contextMenu)
83 | }
84 |
85 | buildMenu()
86 | ipcMain.on(changeLanguage, (_, lng: Languages) => {
87 | i18n.changeLanguage(localeOptions[lng])
88 | buildMenu()
89 | })
90 | }
91 |
--------------------------------------------------------------------------------
/src/electron/prompt/prompts-zh.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "act": "随便侃侃",
4 | "prompt": ""
5 | },
6 | {
7 | "act": "编程大师",
8 | "prompt": "希望你能像大师一样精通各种编程语言。您需要将我的请求翻译成代码或重构我提交的代码。不需要解释和注释,直接返回可以直接使用的markdown格式即可。再次强调,只需要代码,不需要解释。"
9 | },
10 | {
11 | "act": "翻译专家",
12 | "prompt": "我希望你担任英文或中文的翻译、拼写证明和修辞改进的角色。我会用中文或英文与你交流,你会认得语言,翻译并用更优美精致的语言回答我,如果我说中文,请用英语回答我,否则如果我说英语,请用中文翻译"
13 | },
14 | {
15 | "act": "网页大纲",
16 | "prompt": "希望你是一个总结内容的专家。我会发给你一篇或多篇文字,你需要先用简洁的语言概括要点,然后尝试挖掘更深层次的核心要点,最后用编号的标题组织起来。"
17 | },
18 | {
19 | "act": "文本分析",
20 | "prompt": "希望你是一个很擅长挖掘内容重点的人,我会发给你一段文本,请告诉我他字面上的意思,并尝试探索更深层次的意义"
21 | },
22 | {
23 | "act": "IT 架构师",
24 | "prompt": "我希望你担任 IT 架构师。我将提供有关应用程序或其他数字产品功能的一些详细信息,而您的工作是想出将其集成到 IT 环境中的方法。这可能涉及分析业务需求、执行差距分析以及将新系统的功能映射到现有 IT 环境。接下来的步骤是创建解决方案设计、物理网络蓝图、系统集成接口定义和部署环境蓝图。我的第一个请求是“我需要帮助来集成 CMS 系统”。\n"
25 | },
26 | {
27 | "act": "表情符号翻译",
28 | "prompt": "我要你把我写的句子翻译成表情符号。我会写句子,你会用表情符号表达它。我只是想让你用表情符号来表达它。除了表情符号,我不希望你回复任何内容。当我需要用英语告诉你一些事情时,我会用 {like this} 这样的大括号括起来。我的第一句话是“你好,请问你的职业是什么?”\n"
29 | },
30 | {
31 | "act": "脱口秀喜剧演员",
32 | "prompt": "我想让你扮演一个脱口秀喜剧演员。我将为您提供一些与时事相关的话题,您将运用您的智慧、创造力和观察能力,根据这些话题创建一个例程。您还应该确保将个人轶事或经历融入日常活动中,以使其对观众更具相关性和吸引力。我的第一个请求是“我想要幽默地看待政治”。\n"
33 | },
34 | {
35 | "act": "AI写作导师",
36 | "prompt": "我想让你做一个 AI 写作导师。我将为您提供一名需要帮助改进其写作的学生,您的任务是使用人工智能工具(例如自然语言处理)向学生提供有关如何改进其作文的反馈。您还应该利用您在有效写作技巧方面的修辞知识和经验来建议学生可以更好地以书面形式表达他们的想法和想法的方法。我的第一个请求是“我需要有人帮我修改我的硕士论文”。\n"
37 | },
38 | {
39 | "act": "医生",
40 | "prompt": "我想让你扮演医生的角色,想出创造性的治疗方法来治疗疾病。您应该能够推荐常规药物、草药和其他天然替代品。在提供建议时,您还需要考虑患者的年龄、生活方式和病史。我的第一个建议请求是“为患有关节炎的老年患者提出一个侧重于整体治疗方法的治疗计划”。\n"
41 | },
42 | {
43 | "act": "会计师",
44 | "prompt": "我希望你担任会计师,并想出创造性的方法来管理财务。在为客户制定财务计划时,您需要考虑预算、投资策略和风险管理。在某些情况下,您可能还需要提供有关税收法律法规的建议,以帮助他们实现利润最大化。我的第一个建议请求是“为小型企业制定一个专注于成本节约和长期投资的财务计划”。\n"
45 | },
46 | {
47 | "act": "厨师",
48 | "prompt": "我需要有人可以推荐美味的食谱,这些食谱包括营养有益但又简单又不费时的食物,因此适合像我们这样忙碌的人以及成本效益等其他因素,因此整体菜肴最终既健康又经济!我的第一个要求——“一些清淡而充实的东西,可以在午休时间快速煮熟”\n"
49 | },
50 | {
51 | "act": "金融分析师",
52 | "prompt": "需要具有使用技术分析工具理解图表的经验的合格人员提供的帮助,同时解释世界各地普遍存在的宏观经济环境,从而帮助客户获得长期优势需要明确的判断,因此需要通过准确写下的明智预测来寻求相同的判断!第一条陈述包含以下内容——“你能告诉我们根据当前情况未来的股市会是什么样子吗?”。\n"
53 | },
54 | {
55 | "act": "投资经理",
56 | "prompt": "从具有金融市场专业知识的经验丰富的员工那里寻求指导,结合通货膨胀率或回报估计等因素以及长期跟踪股票价格,最终帮助客户了解行业,然后建议最安全的选择,他/她可以根据他们的要求分配资金和兴趣!开始查询 - “目前投资短期前景的最佳方式是什么?”\n"
57 | },
58 | {
59 | "act": "心理学家",
60 | "prompt": "我想让你扮演一个心理学家。我会告诉你我的想法。我希望你能给我科学的建议,让我感觉更好。我的第一个想法,{ 在这里输入你的想法,如果你解释得更详细,我想你会得到更准确的答案。}\n"
61 | }
62 | ]
63 |
--------------------------------------------------------------------------------
/src/electron/prompt/prompts.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "act": "Casual",
4 | "prompt": ""
5 | },
6 | {
7 | "act": "Programming Master",
8 | "prompt": "I hope you can master various programming languages like a master. Please translate my request into code or refactor the code I submitted. No need for explanations or comments, just return the markdown format that can be directly used. Again, only code is needed, no explanation required."
9 | },
10 | {
11 | "act": "Translator",
12 | "prompt": "I hope you can serve as a Chinese-English translator. When I input in Chinese, please translate it into English; when I input in English, please translate it into Chinese. You don't need to understand what my question is about, just translate what I say. If my text is a question, you don't need to try to answer it, just translate my question. Once again, do not answer the text I input, just translate it."
13 | },
14 | {
15 | "act": "Summarizer",
16 | "prompt": "I hope you are an expert at summarizing content. I will send you one or more texts, and you need to summarize the main points in concise language first, then try to dig deeper core points, and finally organize them with numbered headings."
17 | },
18 | {
19 | "act": "Analyst",
20 | "prompt": "I hope you are a person who is very good at digging out the key points of the content, I will send you a text, please tell me what it means literally, and try to explore the deeper meaning."
21 | },
22 | {
23 | "act": "随便侃侃",
24 | "prompt": ""
25 | },
26 | {
27 | "act": "编程大师",
28 | "prompt": "希望你能像大师一样精通各种编程语言。您需要将我的请求翻译成代码或重构我提交的代码。不需要解释和注释,直接返回可以直接使用的markdown格式即可。再次强调,只需要代码,不需要解释。"
29 | },
30 | {
31 | "act": "翻译专家",
32 | "prompt": "我希望你担任中英翻译,当我输入的是中文,你用英语翻译,当我输入的是英文,你用中文翻译,你不喜欢理解我的问题是什么,只要翻译我所说的话即可,当我输入的文字是一个疑问句是,你不用尝试去回答他,直接翻译我的疑问就好,再次重申,不要回答我输入的文字,只要翻译它就好了"
33 | },
34 | {
35 | "act": "网页大纲",
36 | "prompt": "希望你是一个总结内容的专家。我会发给你一篇或多篇文字,你需要先用简洁的语言概括要点,然后尝试挖掘更深层次的核心要点,最后用编号的标题组织起来。"
37 | },
38 | {
39 | "act": "文本分析",
40 | "prompt": "希望你是一个很擅长挖掘内容重点的人,我会发给你一段文本,请告诉我他字面上的意思,并尝试探索更深层次的意义"
41 | },
42 | {
43 | "act": "IT 架构师",
44 | "prompt": "我希望你担任 IT 架构师。我将提供有关应用程序或其他数字产品功能的一些详细信息,而您的工作是想出将其集成到 IT 环境中的方法。这可能涉及分析业务需求、执行差距分析以及将新系统的功能映射到现有 IT 环境。接下来的步骤是创建解决方案设计、物理网络蓝图、系统集成接口定义和部署环境蓝图。我的第一个请求是“我需要帮助来集成 CMS 系统”。\n"
45 | },
46 | {
47 | "act": "表情符号翻译",
48 | "prompt": "我要你把我写的句子翻译成表情符号。我会写句子,你会用表情符号表达它。我只是想让你用表情符号来表达它。除了表情符号,我不希望你回复任何内容。当我需要用英语告诉你一些事情时,我会用 {like this} 这样的大括号括起来。我的第一句话是“你好,请问你的职业是什么?”\n"
49 | },
50 | {
51 | "act": "脱口秀喜剧演员",
52 | "prompt": "我想让你扮演一个脱口秀喜剧演员。我将为您提供一些与时事相关的话题,您将运用您的智慧、创造力和观察能力,根据这些话题创建一个例程。您还应该确保将个人轶事或经历融入日常活动中,以使其对观众更具相关性和吸引力。我的第一个请求是“我想要幽默地看待政治”。\n"
53 | },
54 | {
55 | "act": "AI写作导师",
56 | "prompt": "我想让你做一个 AI 写作导师。我将为您提供一名需要帮助改进其写作的学生,您的任务是使用人工智能工具(例如自然语言处理)向学生提供有关如何改进其作文的反馈。您还应该利用您在有效写作技巧方面的修辞知识和经验来建议学生可以更好地以书面形式表达他们的想法和想法的方法。我的第一个请求是“我需要有人帮我修改我的硕士论文”。\n"
57 | },
58 | {
59 | "act": "医生",
60 | "prompt": "我想让你扮演医生的角色,想出创造性的治疗方法来治疗疾病。您应该能够推荐常规药物、草药和其他天然替代品。在提供建议时,您还需要考虑患者的年龄、生活方式和病史。我的第一个建议请求是“为患有关节炎的老年患者提出一个侧重于整体治疗方法的治疗计划”。\n"
61 | },
62 | {
63 | "act": "会计师",
64 | "prompt": "我希望你担任会计师,并想出创造性的方法来管理财务。在为客户制定财务计划时,您需要考虑预算、投资策略和风险管理。在某些情况下,您可能还需要提供有关税收法律法规的建议,以帮助他们实现利润最大化。我的第一个建议请求是“为小型企业制定一个专注于成本节约和长期投资的财务计划”。\n"
65 | },
66 | {
67 | "act": "厨师",
68 | "prompt": "我需要有人可以推荐美味的食谱,这些食谱包括营养有益但又简单又不费时的食物,因此适合像我们这样忙碌的人以及成本效益等其他因素,因此整体菜肴最终既健康又经济!我的第一个要求——“一些清淡而充实的东西,可以在午休时间快速煮熟”\n"
69 | },
70 | {
71 | "act": "金融分析师",
72 | "prompt": "需要具有使用技术分析工具理解图表的经验的合格人员提供的帮助,同时解释世界各地普遍存在的宏观经济环境,从而帮助客户获得长期优势需要明确的判断,因此需要通过准确写下的明智预测来寻求相同的判断!第一条陈述包含以下内容——“你能告诉我们根据当前情况未来的股市会是什么样子吗?”。\n"
73 | },
74 | {
75 | "act": "投资经理",
76 | "prompt": "从具有金融市场专业知识的经验丰富的员工那里寻求指导,结合通货膨胀率或回报估计等因素以及长期跟踪股票价格,最终帮助客户了解行业,然后建议最安全的选择,他/她可以根据他们的要求分配资金和兴趣!开始查询 - “目前投资短期前景的最佳方式是什么?”\n"
77 | },
78 | {
79 | "act": "心理学家",
80 | "prompt": "我想让你扮演一个心理学家。我会告诉你我的想法。我希望你能给我科学的建议,让我感觉更好。我的第一个想法,{ 在这里输入你的想法,如果你解释得更详细,我想你会得到更准确的答案。}\n"
81 | }
82 | ]
83 |
--------------------------------------------------------------------------------
/src/electron/server.ts:
--------------------------------------------------------------------------------
1 | import compression from 'compression'
2 | import Store from 'electron-store'
3 | import express from 'express'
4 | import accountApi from './apis/account'
5 | import promptApi from './apis/prompt'
6 | import applyApi from './apis/apply'
7 | import testApi from './apis/test'
8 | import crawlApi from './apis/crawl'
9 | import { PresetType } from '../@types'
10 | import { StoreKey } from '../app/constants'
11 | import { Logger } from './utils/util'
12 | import { getChatList, getPluginPrompt } from './client/store'
13 |
14 | const { Configuration, OpenAIApi } = require('openai')
15 | const store = new Store()
16 | let openai = null as any
17 | let prevBasePath: string
18 | let prevApiKey: string
19 |
20 | function getContextual(prompt: PresetType) {
21 | const num = (store.get(StoreKey.Set_Contexual) as number) || 0
22 | const sPreset = [
23 | {
24 | role: 'system',
25 | content: getPluginPrompt(prompt).prompt,
26 | },
27 | ]
28 |
29 | if (prompt && num) {
30 | const list = getChatList(prompt).slice(-num)
31 | const contexual = list.map(item => {
32 | return [
33 | {
34 | role: 'user',
35 | content: item.prompt,
36 | },
37 | {
38 | role: 'assistant',
39 | content: item.response,
40 | },
41 | ]
42 | })
43 | return [...contexual.flat(), ...sPreset]
44 | }
45 | return [...sPreset]
46 | }
47 |
48 | export function generatePayload(content: string, prompt: PresetType) {
49 | return {
50 | model: 'gpt-3.5-turbo-0301',
51 | messages: [
52 | ...getContextual(prompt),
53 | {
54 | role: 'user',
55 | content,
56 | },
57 | ],
58 | temperature: 0,
59 | top_p: 1,
60 | frequency_penalty: 1,
61 | presence_penalty: 1,
62 | stream: true,
63 | }
64 | }
65 |
66 | export function getAiInstance() {
67 | const basePath = store.get(StoreKey.Set_BasePath) as string
68 | const apiKey = store.get(StoreKey.Set_ApiKey) as string
69 |
70 | if (openai && prevApiKey === apiKey && prevBasePath === basePath) {
71 | return openai
72 | }
73 |
74 | if (apiKey) {
75 | const _basePath = basePath || 'https://closeai.deno.dev'
76 | openai = new OpenAIApi(
77 | new Configuration({
78 | apiKey,
79 | basePath: _basePath + '/v1',
80 | })
81 | )
82 | prevApiKey = apiKey
83 | prevBasePath = _basePath
84 | return openai
85 | }
86 | return null
87 | }
88 |
89 | const app = express()
90 | const port = 4000
91 | app.use(compression())
92 | app.use(express.json())
93 | app.use(express.urlencoded({ extended: true }))
94 | app.post('/prompt', promptApi)
95 | app.post('/apply', applyApi)
96 | app.post('/test', testApi)
97 | app.post('/account', accountApi)
98 | app.post('/crawl', crawlApi)
99 | app.listen(port, async () => {
100 | Logger.log(`onepoint listening on port ${port}!`)
101 | })
102 |
--------------------------------------------------------------------------------
/src/electron/sound/index.ts:
--------------------------------------------------------------------------------
1 | import { IpcMainInvokeEvent, ipcMain } from 'electron'
2 | import { speakText as speakTextEvt } from '../constants/event'
3 | import { speakTxt } from '../os/applescript'
4 | import { Logger } from '../utils/util'
5 |
6 | export function setupSoundHandlers() {
7 | ipcMain.handle(
8 | speakTextEvt,
9 | async (event: IpcMainInvokeEvent, resp: string) => {
10 | try {
11 | // https://gist.github.com/mculp/4b95752e25c456d425c6
12 | speakTxt(resp, 200)
13 | return true
14 | } catch (e) {
15 | Logger.error(e)
16 | return false
17 | }
18 | }
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/electron/types.ts:
--------------------------------------------------------------------------------
1 | export interface BalanceResponse {
2 | total_usage: number
3 | }
4 | export interface ChatContent {
5 | prompt: string
6 | response: string
7 | }
8 |
9 | export interface PromptSet {
10 | character: string
11 | prompt: string
12 | }
13 |
14 | export enum ERR_CODES {
15 | NETWORK_CONGESTION = -1000,
16 | TIMEOUT = -999,
17 | NOT_SET_APIKEY = -998,
18 | TOKEN_TOO_LONG = -997,
19 | }
20 |
21 | export const CODE_SPLIT = '&^.>'
22 |
--------------------------------------------------------------------------------
/src/electron/utils/global.ts:
--------------------------------------------------------------------------------
1 | import { PresetType } from '../../@types'
2 | export class Singleton {
3 | private static instance: Singleton
4 |
5 | private static copyFromElectron = false
6 |
7 | private static recentApp: string
8 |
9 | private static preset: PresetType = PresetType.Casual
10 |
11 | public static getInstance(): Singleton {
12 | if (!Singleton.instance) {
13 | Singleton.instance = new Singleton()
14 | }
15 | return Singleton.instance
16 | }
17 |
18 | public setRecentApp(app: string) {
19 | Singleton.recentApp = app
20 | }
21 |
22 | public setCopyStateSource(fromElectron: boolean) {
23 | Singleton.copyFromElectron = fromElectron
24 | }
25 |
26 | public setCurPreset(preset: PresetType) {
27 | Singleton.preset = preset
28 | }
29 |
30 | public getCopyFromElectron() {
31 | return Singleton.copyFromElectron
32 | }
33 |
34 | public getRecentApp() {
35 | return Singleton.recentApp
36 | }
37 |
38 | public getCurPreset() {
39 | return Singleton.preset
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/electron/utils/log.ts:
--------------------------------------------------------------------------------
1 | import log from 'electron-log'
2 |
3 | export default (): typeof log => {
4 | log.transports.file.level = 'debug'
5 | log.transports.file.fileName = 'user.log'
6 | log.transports.file.format =
7 | '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}'
8 | log.transports.file.maxSize = 1048576
9 | return log
10 | }
11 |
--------------------------------------------------------------------------------
/src/electron/utils/util.ts:
--------------------------------------------------------------------------------
1 | export const Logger = console
2 |
--------------------------------------------------------------------------------
/src/electron/utils/window.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindow } from 'electron'
2 |
3 | export const setWindowVisile = (opt: {
4 | win: BrowserWindow | null
5 | visible?: boolean
6 | }) => {
7 | const { win, visible } = opt
8 | if (!visible) {
9 | win?.hide()
10 | win?.blur()
11 | return
12 | }
13 |
14 | // win?.setAlwaysOnTop(true)
15 | win?.setVisibleOnAllWorkspaces(true, {
16 | visibleOnFullScreen: true,
17 | })
18 | win?.focus()
19 | win?.show()
20 | }
21 |
--------------------------------------------------------------------------------
/src/features/chat/chatSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
2 | import { PresetType } from '../../@types'
3 | import { baseApiHost } from '../../app/api'
4 | import { timeoutPromise } from '../../utils/fetch'
5 | import { ERR_CODES } from '../../electron/types'
6 |
7 | interface ChatModule {
8 | resp: Record
9 | visible: boolean
10 | inputDiabled: boolean
11 | respErr: boolean
12 | respErrMsg: string
13 | curPrompt: Record
14 | isGenerating: boolean
15 | webCrawlResp: string
16 | }
17 |
18 | export const initialState: ChatModule = {
19 | resp: {},
20 | visible: false,
21 | inputDiabled: false,
22 | respErr: false,
23 | respErrMsg: '',
24 | curPrompt: {},
25 | isGenerating: false,
26 | webCrawlResp: '', // Distinguishing proprietary err & errmsg
27 | }
28 |
29 | interface PresetContent {
30 | preset: PresetType
31 | content: string
32 | }
33 |
34 | export const chatSlice = createSlice({
35 | name: 'chat',
36 | initialState,
37 | reducers: {
38 | saveResp: (state, action: PayloadAction) => {
39 | const {
40 | payload: { preset, content },
41 | } = action
42 | const resp = state.resp
43 | resp[preset] = content
44 | state.resp = resp
45 | },
46 | setVisible: (state, action: PayloadAction) => {
47 | const { payload } = action
48 | state.visible = payload
49 | },
50 | setInputDisabled: (state, action: PayloadAction) => {
51 | const { payload } = action
52 | state.inputDiabled = payload
53 | },
54 | setRespErr: (state, action: PayloadAction) => {
55 | const { payload } = action
56 | state.respErr = payload
57 | },
58 | setRespErrMsg: (state, action: PayloadAction) => {
59 | const { payload } = action
60 | state.respErrMsg = payload
61 | },
62 | setCurPrompt: (state, action: PayloadAction) => {
63 | const {
64 | payload: { preset, content },
65 | } = action
66 | const curPrompt = state.curPrompt
67 | curPrompt[preset] = content
68 | state.curPrompt = curPrompt
69 | },
70 | setGenerating: (state, action: PayloadAction) => {
71 | const { payload } = action
72 | state.isGenerating = payload
73 | },
74 | saveWebCrawlResp: (state, action: PayloadAction) => {
75 | const { payload } = action
76 | state.webCrawlResp = payload
77 | },
78 | },
79 | })
80 |
81 | export const {
82 | saveResp,
83 | setVisible,
84 | setInputDisabled,
85 | setRespErr,
86 | setRespErrMsg,
87 | setCurPrompt,
88 | setGenerating,
89 | saveWebCrawlResp,
90 | } = chatSlice.actions
91 |
92 | export const fetchChatResp = createAsyncThunk(
93 | 'chat/fetchChatResp',
94 | async (
95 | args: {
96 | prompt: string
97 | preset: PresetType
98 | },
99 | { dispatch }
100 | ) => {
101 | const { prompt, preset } = args
102 | dispatch(setInputDisabled(true))
103 | dispatch(setRespErr(false))
104 | dispatch(setGenerating(true))
105 |
106 | const request = () => {
107 | /* eslint-disable no-async-promise-executor */
108 | return new Promise(async (resolve, reject) => {
109 | const response = await fetch(`${baseApiHost}/prompt`, {
110 | method: 'POST',
111 | headers: {
112 | 'Content-Type': 'application/json',
113 | },
114 | body: JSON.stringify({
115 | prompt,
116 | preset,
117 | }),
118 | })
119 |
120 | const reader = response.body
121 | ?.pipeThrough(new TextDecoderStream())
122 | .getReader()
123 |
124 | let str = ''
125 | let shown = false
126 | while (true) {
127 | if (!reader) break
128 | const { value, done } = await reader.read()
129 | if (done) break
130 | if (!shown) {
131 | dispatch(setVisible(true))
132 | shown = true
133 | }
134 | if (Number(value) === ERR_CODES.NOT_SET_APIKEY) {
135 | reject(new Error('please set your apikey first.'))
136 | break
137 | }
138 |
139 | if (Number(value) === ERR_CODES.NETWORK_CONGESTION) {
140 | reject(
141 | new Error('Network error. Check whether you have set up a proxy')
142 | )
143 | break
144 | }
145 | str += value
146 | dispatch(
147 | saveResp({
148 | preset,
149 | content: str,
150 | })
151 | )
152 | }
153 | resolve(true)
154 | })
155 | }
156 |
157 | Promise.race([
158 | timeoutPromise(
159 | 20000,
160 | 'High network latency. Check whether you have set up a proxy'
161 | ),
162 | request(),
163 | ])
164 | .then()
165 | .catch(e => {
166 | dispatch(setVisible(true))
167 | dispatch(setRespErr(true))
168 | dispatch(setRespErrMsg(e.message))
169 | })
170 | .finally(() => {
171 | dispatch(setGenerating(false))
172 | dispatch(setInputDisabled(false))
173 | })
174 | }
175 | )
176 |
177 | export const fetchWebCrawlResp = createAsyncThunk(
178 | 'chat/fetchWebCrawlResp',
179 | async (
180 | args: {
181 | url: string
182 | preset: PresetType
183 | },
184 | { dispatch }
185 | ) => {
186 | const { url, preset } = args
187 | dispatch(setInputDisabled(true))
188 | dispatch(setRespErr(false))
189 | dispatch(setGenerating(true))
190 |
191 | const request = async () => {
192 | const response = await fetch(`${baseApiHost}/crawl`, {
193 | method: 'POST',
194 | headers: {
195 | 'Content-Type': 'application/json',
196 | },
197 | body: JSON.stringify({
198 | url,
199 | preset,
200 | }),
201 | })
202 | return response.json()
203 | }
204 |
205 | Promise.race([
206 | timeoutPromise(
207 | 20000,
208 | 'High network latency. Check whether you have set up a proxy'
209 | ),
210 | request(),
211 | ])
212 | .then(resp => {
213 | const { result, code, message } = resp
214 | if (code === 0) {
215 | dispatch(setVisible(true))
216 | dispatch(saveWebCrawlResp(result))
217 | } else {
218 | dispatch(setRespErr(true))
219 | dispatch(setRespErrMsg(message))
220 | }
221 | })
222 | .catch(e => {
223 | dispatch(setVisible(true))
224 | dispatch(setRespErr(true))
225 | dispatch(setRespErrMsg(e.message))
226 | })
227 | .finally(() => {
228 | dispatch(setGenerating(false))
229 | dispatch(setInputDisabled(false))
230 | })
231 | }
232 | )
233 |
234 | export default chatSlice.reducer
235 |
--------------------------------------------------------------------------------
/src/features/clipboard/clipboardSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2 |
3 | interface ClipboardModule {
4 | selectTxt: string
5 | selectApp: string
6 | url: string
7 | }
8 |
9 | interface Selection {
10 | txt: string
11 | app: string
12 | }
13 |
14 | interface Url {
15 | url: string
16 | }
17 |
18 | export const initialState: ClipboardModule = {
19 | selectTxt: '',
20 | selectApp: '',
21 | url: '',
22 | }
23 |
24 | export const clipboardSlice = createSlice({
25 | name: 'clipboard',
26 | initialState,
27 | reducers: {
28 | setSelection: (state, action: PayloadAction) => {
29 | const { payload } = action
30 | state.selectTxt = payload.txt
31 | state.selectApp = payload.app
32 | },
33 | setUrl: (state, action: PayloadAction) => {
34 | const { payload } = action
35 | state.url = payload.url
36 | },
37 | },
38 | })
39 |
40 | export const { setSelection, setUrl } = clipboardSlice.actions
41 | export default clipboardSlice.reducer
42 |
--------------------------------------------------------------------------------
/src/features/history/historySlice.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onepointAI/onepoint/7d99f7e9a91dd54dc317c85f3891af8fc215d46a/src/features/history/historySlice.ts
--------------------------------------------------------------------------------
/src/features/preset/presetSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2 | import { BuiltInPlugins } from '../../app/constants'
3 | import { PresetModule, PresetType } from '../../@types'
4 |
5 | export const initialState: PresetModule = {
6 | listVisible: false,
7 | builtInPlugins: BuiltInPlugins,
8 | currentPreset: PresetType.Casual,
9 | }
10 |
11 | export const presetSlice = createSlice({
12 | name: 'preset',
13 | initialState,
14 | reducers: {
15 | setListVisible: (state, action: PayloadAction) => {
16 | const { payload } = action
17 | state.listVisible = payload
18 | },
19 | setPreset: (state, action: PayloadAction) => {
20 | const { payload } = action
21 | state.currentPreset = payload
22 | },
23 | },
24 | })
25 |
26 | export const { setListVisible, setPreset } = presetSlice.actions
27 | export default presetSlice.reducer
28 |
--------------------------------------------------------------------------------
/src/features/setting/settingSlice.ts:
--------------------------------------------------------------------------------
1 | // import { init as initI18n } from '../../i18n'
2 | import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
3 | import { timeoutPromise } from '../../utils/fetch'
4 | import { baseApiHost } from '../../app/api'
5 | import { Languages } from '../../i18n'
6 | interface SettingModule {
7 | visible: boolean
8 | billUsage: number
9 | basePath: string
10 | apikey: string
11 | loadAccount: boolean
12 | usemodel: string
13 | minimal: boolean
14 | contextual: number
15 | store: number
16 | lng: Languages
17 | }
18 |
19 | export const defaultVals = {
20 | lng: 'English',
21 | store: 0,
22 | contexual: 0,
23 | minimal: false,
24 | }
25 |
26 | export const initialState: SettingModule = {
27 | visible: false,
28 | loadAccount: true,
29 | billUsage: 0,
30 | basePath: '',
31 | apikey: '',
32 | usemodel: '',
33 | minimal: defaultVals.minimal,
34 | contextual: defaultVals.contexual,
35 | store: defaultVals.store,
36 | lng: defaultVals.lng,
37 | }
38 |
39 | export const settingSlice = createSlice({
40 | name: 'setting',
41 | initialState,
42 | reducers: {
43 | setVisible: (state, action: PayloadAction) => {
44 | const { payload } = action
45 | state.visible = payload
46 | },
47 | setUsage: (state, action: PayloadAction) => {
48 | const { payload } = action
49 | state.billUsage = payload
50 | },
51 | setBasePath: (state, action: PayloadAction) => {
52 | const { payload } = action
53 | state.basePath = payload
54 | },
55 | setApikey: (state, action: PayloadAction) => {
56 | const { payload } = action
57 | state.apikey = payload
58 | },
59 | setUsemodel: (state, action: PayloadAction) => {
60 | const { payload } = action
61 | state.usemodel = payload
62 | },
63 | setLoadAccount: (state, action: PayloadAction) => {
64 | const { payload } = action
65 | state.loadAccount = payload
66 | },
67 | setMinimal: (state, action: PayloadAction) => {
68 | const { payload } = action
69 | state.minimal = payload
70 | },
71 | setContexual: (state, action: PayloadAction) => {
72 | const { payload } = action
73 | state.contextual = payload
74 | },
75 | setStore: (state, action: PayloadAction) => {
76 | const { payload } = action
77 | state.store = payload
78 | },
79 | setLng: (state, action: PayloadAction) => {
80 | const { payload } = action
81 | state.lng = payload
82 | },
83 | },
84 | })
85 |
86 | export const {
87 | setVisible,
88 | setUsage,
89 | setBasePath,
90 | setApikey,
91 | setUsemodel,
92 | setLoadAccount,
93 | setMinimal,
94 | setContexual,
95 | setStore,
96 | setLng,
97 | } = settingSlice.actions
98 |
99 | export const initState = createAsyncThunk(
100 | 'setting/initState',
101 | async (args: null, { dispatch }) => {
102 | // const config = await connector.getProject()
103 | // connector.refreshTokens()
104 | }
105 | )
106 |
107 | export const fetchAccountDetail = createAsyncThunk(
108 | 'setting/fetchAccountDetail',
109 | async (
110 | args: {
111 | startDate: string
112 | endDate: string
113 | },
114 | { dispatch }
115 | ) => {
116 | const { startDate, endDate } = args
117 | dispatch(setLoadAccount(true))
118 | const request = async () => {
119 | const response = await fetch(`${baseApiHost}/account`, {
120 | method: 'POST',
121 | headers: {
122 | 'Content-Type': 'application/json',
123 | },
124 | body: JSON.stringify({
125 | start_date: startDate,
126 | end_date: endDate,
127 | }),
128 | })
129 | return response.json()
130 | }
131 |
132 | Promise.race([
133 | timeoutPromise(
134 | 5000,
135 | 'High network latency. Check whether you have set up a proxy'
136 | ),
137 | request(),
138 | ])
139 | .then(resp => {
140 | console.log(resp)
141 | const {
142 | basic: { apiHost, apiKey, usemodel },
143 | usageData,
144 | } = resp.result
145 | dispatch(setBasePath(apiHost))
146 | dispatch(setApikey(apiKey))
147 | dispatch(setUsemodel(usemodel))
148 | if (resp.code === 0) {
149 | dispatch(setUsage(Math.round(usageData.total_usage)))
150 | }
151 | })
152 | .catch(e => {
153 | console.log(e)
154 | })
155 | .finally(() => {
156 | dispatch(setLoadAccount(false))
157 | })
158 | }
159 | )
160 |
161 | export default settingSlice.reducer
162 |
--------------------------------------------------------------------------------
/src/i18n.ts:
--------------------------------------------------------------------------------
1 | import { use } from 'i18next'
2 | import { initReactI18next } from 'react-i18next'
3 | import HttpBackend from 'i18next-http-backend'
4 |
5 | import en from './locales/en.json'
6 | import zh from './locales/zh.json'
7 |
8 | export const localeOptions = {
9 | English: 'en',
10 | 中文: 'zh',
11 | }
12 |
13 | export type Languages = keyof typeof localeOptions
14 |
15 | export const init = (locale: Languages = 'English') => {
16 | const lng = localeOptions[locale]
17 | use(initReactI18next)
18 | .use(HttpBackend)
19 | .init({
20 | lng,
21 | fallbackLng: lng,
22 | interpolation: {
23 | escapeValue: false,
24 | },
25 | resources: {
26 | en: { translation: en },
27 | zh: { translation: zh },
28 | },
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom'
2 | import { Provider } from 'react-redux'
3 | import store from './app/store'
4 | import { App } from './App'
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | )
12 |
--------------------------------------------------------------------------------
/src/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Casual": "Casual",
3 | "Programmer": "Programmer",
4 | "Translator": "Translator",
5 | "Summarizer": "Summarizer",
6 | "Analyst": "Analyst",
7 | "Feedback & Help": "Feedback & Help",
8 | "Display Window": "Display Window",
9 | "Settings": "Settings",
10 | "About": "About",
11 | "More than just chat. Write, read, and code with powerful AI anywhere.": "More than just chat. Write, read, and code with powerful AI anywhere.",
12 | "Version:": "Version:",
13 | "onepoint | more than just chat": "onepoint | more than just chat",
14 | "exit": "exit",
15 | "Type '/' to bring up the plugin list, or enter your question directly in the input box.": "Type '/' to bring up the plugin list, or enter your question directly in the input box.",
16 | "Select a character": "Select a character",
17 | "Token Usage": "Token Usage",
18 | "Please enter your API key first.": "Please enter your API key first.",
19 | "Submit": "Submit",
20 | "Refresh": "Refresh",
21 | "Loading...": "Loading...",
22 | "Language": "Language",
23 | "Select A Language": "Select A Language",
24 | "Save Chat History": "Save Chat History",
25 | "Save Chat": "Save Chat",
26 | "Quantity Of Context": "Quantity Of Context",
27 | "Minimalist Mode(shrink panel)": "Minimalist Mode(shrink panel)",
28 | "Saved successfully": "Saved successfully",
29 | "Duplicated Character": "Duplicated Character",
30 | "Removed successfully": "Removed successfully",
31 | "Remove Error": "Remove Error",
32 | "Character": "Character",
33 | "Prompt": "Prompt",
34 | "Action": "Action",
35 | "Please enter the role.": "Please enter the role.",
36 | "Please input your prompt.": "Please input your prompt.",
37 | "Prompt Reference": "Prompt Reference",
38 | "Setting": "Setting",
39 | "Account": "Account",
40 | "Prompts": "Prompts",
41 | "Advanced": "Advanced",
42 | "Plugins": "Plugins",
43 | "please set your apikey first.": "please set your apikey first.",
44 | "High network latency. Check whether you have set up a proxy": "High network latency. Check whether you have set up a proxy",
45 | "Network error. Check whether you have set up a proxy": "Network error. Check whether you have set up a proxy",
46 | "Prompt Setting": "Prompt Setting",
47 | "Chat mode, feel free to ask any questions you want.": "Chat mode, feel free to ask any questions you want.",
48 | "Code Master, generate or refactor the code you want.": "Code Master, generate or refactor the code you want.",
49 | "Content analysis summary assistant, helps you read and browse web pages more effectively.": "Content analysis summary assistant, helps you read and browse web pages more effectively.",
50 | "Language expert, proficient in various languages from different countries.": "Language expert, proficient in various languages from different countries.",
51 | "Base Path": "Base Path",
52 | "Model": "Model",
53 | "reload": "reload",
54 | "Sure operate the selection in ": "Sure operate the selection in ",
55 | "Summarize this page?": "Summarize this page?",
56 | "Attempt Change": "Attempt Change",
57 | "The webpage content is too long(Exceeds 4000 characters.), which will affect the speed and experience of summarizing(Long article summary support is coming soon, please stay tuned)": "The webpage content is too long(Exceeds 4000 characters.), which will affect the speed and experience of summarizing(Long article summary support is coming soon, please stay tuned)"
58 | }
59 |
--------------------------------------------------------------------------------
/src/locales/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "Casual": "随便侃侃",
3 | "Programmer": "编程模式",
4 | "Translator": "翻译专家",
5 | "Summarizer": "网页大纲",
6 | "Analyst": "文本分析",
7 | "Feedback & Help": "反馈与帮助",
8 | "Display Window": "显示窗口",
9 | "Settings": "设置",
10 | "About": "关于",
11 | "More than just chat. Write, read, and code with powerful AI anywhere.": "不仅仅是聊天,利用AI辅助你阅读、写作与编程,用完即走",
12 | "Version:": "版本号:",
13 | "onepoint | more than just chat": "onepoint | 不仅仅是聊天",
14 | "exit": "退出",
15 | "Type '/' to bring up the plugin list, or enter your question directly in the input box.": "输入“/”呼出插件列表,或直接输入框中提出问题。",
16 | "Select a character": "选择一位角色",
17 | "Token Usage": "Token 使用量",
18 | "Please enter your API key first.": "请先输入你的apikey",
19 | "Submit": "提交",
20 | "Refresh": "刷新",
21 | "Loading...": "加载中...",
22 | "Language": "语言",
23 | "Select A Language": "选择一种语言",
24 | "Save Chat History": "保存聊天记录",
25 | "Save Chat": "保存聊天",
26 | "Quantity Of Context": "上下文数量",
27 | "Minimalist Mode(shrink panel)": "极简模式(折叠面板)",
28 | "Saved successfully": "保存成功",
29 | "Duplicated Character": "重复的角色",
30 | "Removed successfully": "删除成功",
31 | "Remove Error": "删除失败",
32 | "Character": "角色",
33 | "Prompt": "Prompt",
34 | "Action": "操作",
35 | "Please enter the role.": "请输入角色",
36 | "Please input your prompt.": "请输入相关Prompt",
37 | "Prompt Reference": "Prompt 参考",
38 | "Setting": "设置",
39 | "Account": "账户",
40 | "Prompts": "Prompts",
41 | "Advanced": "高级",
42 | "Plugins": "插件",
43 | "please set your apikey first.": "请先设置你的apikey",
44 | "High network latency. Check whether you have set up a proxy": "网络拥堵,请检查是否设置了代理。",
45 | "Network error. Check whether you have set up a proxy": "网络错误,请检查是否设置了代理。",
46 | "Prompt Setting": "Prompt设置",
47 | "Chat mode, feel free to ask any questions you want.": "聊天模式,随意提出任何你想问的问题。",
48 | "Code Master, generate or refactor the code you want.": "编程模式,生成或重构你想要的代码。",
49 | "Content analysis summary assistant, helps you read and browse web pages more effectively.": "内容分析摘要助手,帮助您更有效地阅读和浏览网页。",
50 | "Language expert, proficient in various languages from different countries.": "语言专家,精通来自不同国家的各种语言。",
51 | "Base Path": "API 请求域名",
52 | "Model": "模型",
53 | "reload": "重启",
54 | "Sure operate the selection in ": "确定在以下应用中格式化代码吗:",
55 | "Summarize this page?": "确定要总结当前的网页内容吗?",
56 | "Attempt Change": "应用修改",
57 | "The webpage content is too long(Exceeds 4000 characters.), which will affect the speed and experience of summarizing(Long article summary support is coming soon, please stay tuned)": "所选网页内容太长(超出4000字),将影响总结的速度与体验(PS: 长文内容支持中,敬请期待)"
58 | }
59 |
--------------------------------------------------------------------------------
/src/styles/GlobalStyle.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components'
2 |
3 | export const GlobalStyle = createGlobalStyle`
4 | * {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 |
10 | body {
11 | font-family: Arial, Helvetica, sans-serif;
12 | font-size: 16px;
13 | background-color: rgba(0,0,0,0);
14 | overflow: hidden;
15 | }
16 |
17 | .ant-list-item:hover {
18 | background-color: #F3F3F3;
19 | cursor: pointer;
20 | }
21 | .anticon:hover {
22 | background-color: #F3F3F3;
23 | cursor: pointer;
24 | }
25 | ol li {
26 | list-style-type:decimal !important;
27 | list-style-position:inside !important;
28 | }
29 | `
30 |
31 | // highlightColor: rgb(10, 11, 60)
32 |
--------------------------------------------------------------------------------
/src/styles/Main.ts:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from 'styled-components'
2 |
3 | const rotate = keyframes`
4 | from {
5 | transform: rotate(0deg);
6 | }
7 | to {
8 | transform: rotate(360deg);
9 | }
10 | `
11 |
12 | const bounceInDown = keyframes`
13 | from,
14 | 60%,
15 | 75%,
16 | 90%,
17 | to {
18 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
19 | }
20 |
21 | 0% {
22 | opacity: 0;
23 | transform: translate3d(0, -3000px, 0) scaleY(3);
24 | }
25 |
26 | 60% {
27 | opacity: 1;
28 | transform: translate3d(0, 25px, 0) scaleY(0.9);
29 | }
30 |
31 | 75% {
32 | transform: translate3d(0, -10px, 0) scaleY(0.95);
33 | }
34 |
35 | 90% {
36 | transform: translate3d(0, 5px, 0) scaleY(0.985);
37 | }
38 |
39 | to {
40 | transform: translate3d(0, 0, 0);
41 | }
42 | `
43 |
44 | export const Container = styled.div`
45 | background-color: "#FFF",
46 | width: 800px;
47 | height: 100vh;
48 | flex-direction: "column",
49 | justify-content: "center",
50 | button {
51 | margin-top: 24px;
52 | }
53 | `
54 |
55 | export const Image = styled.img`
56 | width: 240px;
57 | animation: ${rotate} 15s linear infinite;
58 | `
59 |
60 | export const Text = styled.p`
61 | margin-top: 24px;
62 | font-size: 18px;
63 | `
64 |
65 | export const Logo = styled.img`
66 | width: 100px;
67 | animation: ${bounceInDown} 2s linear infinite;
68 | `
69 |
--------------------------------------------------------------------------------
/src/utils/fetch.ts:
--------------------------------------------------------------------------------
1 | export const timeoutPromise = (timeout: number, timeoutTips: string) => {
2 | return new Promise((resolve, reject) => {
3 | setTimeout(() => {
4 | reject(new Error(timeoutTips))
5 | }, timeout)
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export const draggableStyle = (draggle: boolean) => {
2 | return {
3 | WebkitAppRegion: draggle ? 'drag' : 'no-drag',
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/setupTests.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect'
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "lib": ["dom", "es2015", "es2016", "es2017"],
6 | "allowJs": true,
7 | "jsx": "react-jsx",
8 | "sourceMap": true,
9 | "outDir": "./dist",
10 | "strict": true,
11 | "esModuleInterop": true,
12 | "resolveJsonModule": true
13 | },
14 | "settings": {
15 | "import/resolver": {
16 | "node": {
17 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/webpack/main.webpack.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | resolve: {
3 | extensions: ['.ts', '.js'],
4 | },
5 | entry: './src/electron/main.ts',
6 | module: {
7 | rules: [
8 | ...require('./rules.webpack'),
9 | {
10 | test: /\.(m?js|node)$/,
11 | parser: { amd: false },
12 | use: {
13 | // loader: '@marshallofsound/webpack-asset-relocator-loader',
14 | loader: '@vercel/webpack-asset-relocator-loader',
15 | options: {
16 | outputAssetBase: 'native_modules',
17 | },
18 | },
19 | },
20 | ],
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/webpack/renderer.webpack.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | resolve: {
3 | extensions: ['.ts', '.tsx', '.js'],
4 | },
5 | module: {
6 | rules: require('./rules.webpack'),
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/webpack/rules.webpack.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | test: /\.node$/,
4 | use: 'node-loader',
5 | },
6 | {
7 | test: /\.(js|ts|tsx)$/,
8 | exclude: /node_modules/,
9 | use: {
10 | loader: 'babel-loader',
11 | },
12 | },
13 | {
14 | test: /\.(png|jpe?g|gif)$/i,
15 | loader: 'file-loader',
16 | options: {
17 | name: '[path][name].[ext]',
18 | },
19 | },
20 | ]
21 |
--------------------------------------------------------------------------------