├── ChatGPT
├── .gitattributes
├── .gitignore
├── .husky
│ └── pre-commit
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
│ └── extensions.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README-ZH_CN.md
├── README.md
├── UPDATE_LOG.md
├── assets
│ ├── auto-update.png
│ ├── chatgpt-cmd.gif
│ ├── chatgpt-cmd.png
│ ├── chatgpt-control-center-general.png
│ ├── chatgpt-dalle2-tray.png
│ ├── chatgpt-export.png
│ ├── chatgpt-popup-search.png
│ ├── chatgpt-sync-prompts.png
│ ├── chatgpt.gif
│ ├── install.png
│ └── zsxq.png
├── casks
│ └── chatgpt.rb
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ └── logo.png
├── rustfmt.toml
├── src-tauri
│ ├── Cargo.toml
│ ├── build.rs
│ ├── icons
│ │ ├── 128x128.png
│ │ ├── 128x128@2x.png
│ │ ├── 32x32.png
│ │ ├── Square107x107Logo.png
│ │ ├── Square142x142Logo.png
│ │ ├── Square150x150Logo.png
│ │ ├── Square284x284Logo.png
│ │ ├── Square30x30Logo.png
│ │ ├── Square310x310Logo.png
│ │ ├── Square44x44Logo.png
│ │ ├── Square71x71Logo.png
│ │ ├── Square89x89Logo.png
│ │ ├── StoreLogo.png
│ │ ├── icon.icns
│ │ ├── icon.ico
│ │ ├── icon.png
│ │ ├── tray-icon-light.png
│ │ └── tray-icon.png
│ ├── src
│ │ ├── app
│ │ │ ├── cmd.rs
│ │ │ ├── cors.rs
│ │ │ ├── fs_extra.rs
│ │ │ ├── gpt.rs
│ │ │ ├── menu.rs
│ │ │ ├── mod.rs
│ │ │ ├── setup.rs
│ │ │ └── window.rs
│ │ ├── conf.rs
│ │ ├── main.rs
│ │ ├── scripts
│ │ │ ├── chat.js
│ │ │ ├── cmd.js
│ │ │ ├── core.js
│ │ │ ├── dalle2.js
│ │ │ ├── export.js
│ │ │ ├── markdown.export.js
│ │ │ └── popup.core.js
│ │ ├── utils.rs
│ │ └── vendors
│ │ │ ├── floating-ui-core.js
│ │ │ ├── floating-ui-dom.js
│ │ │ ├── html2canvas.js
│ │ │ ├── jspdf.js
│ │ │ ├── turndown-plugin-gfm.js
│ │ │ └── turndown.js
│ └── tauri.conf.json
├── src
│ ├── components
│ │ ├── FilePath
│ │ │ └── index.tsx
│ │ ├── Markdown
│ │ │ ├── Editor.tsx
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── SwitchOrigin
│ │ │ └── index.tsx
│ │ └── Tags
│ │ │ └── index.tsx
│ ├── hooks
│ │ ├── useChatModel.ts
│ │ ├── useColumns.tsx
│ │ ├── useData.ts
│ │ ├── useInit.ts
│ │ ├── useJson.ts
│ │ └── useTable.tsx
│ ├── icons
│ │ └── SplitIcon.tsx
│ ├── layout
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── main.scss
│ ├── main.tsx
│ ├── routes.tsx
│ ├── utils.ts
│ ├── view
│ │ ├── about
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── awesome
│ │ │ ├── Form.tsx
│ │ │ ├── config.tsx
│ │ │ └── index.tsx
│ │ ├── dashboard
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── download
│ │ │ ├── config.tsx
│ │ │ └── index.tsx
│ │ ├── markdown
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── model
│ │ │ ├── SyncCustom
│ │ │ │ ├── Form.tsx
│ │ │ │ ├── config.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── SyncPrompts
│ │ │ │ ├── config.tsx
│ │ │ │ ├── index.scss
│ │ │ │ └── index.tsx
│ │ │ ├── SyncRecord
│ │ │ │ ├── config.tsx
│ │ │ │ └── index.tsx
│ │ │ └── UserCustom
│ │ │ │ ├── Form.tsx
│ │ │ │ ├── config.tsx
│ │ │ │ └── index.tsx
│ │ ├── notes
│ │ │ ├── config.tsx
│ │ │ └── index.tsx
│ │ └── settings
│ │ │ ├── General.tsx
│ │ │ ├── LangHelper.tsx
│ │ │ ├── MainWindow.tsx
│ │ │ ├── Speakers.ts
│ │ │ ├── TrayWindow.tsx
│ │ │ └── index.tsx
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock
├── LICENSE
├── LangHelper
├── Assess
│ ├── IflytekAssessment.py
│ ├── SpeechSuper.py
│ └── __init__.py
├── Recognition
│ ├── IflytekRec.py
│ └── __init__.py
├── Resource
│ ├── cn
│ │ ├── read_sentence_cn.mp3
│ │ ├── read_sentence_cn.pcm
│ │ ├── read_sentence_cn.txt
│ │ ├── read_sentence_cn.wav
│ │ ├── read_syllable_cn.pcm
│ │ └── read_syllable_cn.txt
│ ├── en
│ │ ├── oral_translation_en.pcm
│ │ ├── oral_translation_en.txt
│ │ ├── picture_talk_en.pcm
│ │ ├── picture_talk_en.txt
│ │ ├── read_choice_en.pcm
│ │ ├── read_choice_en.txt
│ │ ├── read_sentence_en.pcm
│ │ ├── read_sentence_en.txt
│ │ ├── read_word_en.pcm
│ │ ├── read_word_en.txt
│ │ ├── retell_en.pcm
│ │ ├── retell_en.txt
│ │ ├── topic_en.pcm
│ │ └── topic_en.txt
│ ├── rec
│ │ └── realtime.pcm
│ ├── speaker-info.txt
│ └── tts_models--en--vctk--vits
│ │ ├── config.json
│ │ └── speaker_ids.json
├── main.py
└── recoder.py
└── README.md
/ChatGPT/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-vendored
2 | *.tsx linguist-vendored
3 | *.scss linguist-vendored
4 | src/**/*.ts linguist-vendored
--------------------------------------------------------------------------------
/ChatGPT/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | node_modules/
3 |
4 | .yarn/*
5 | .pnp.*
6 |
7 | # rust
8 | target/
9 |
10 | # Logs
11 | logs
12 | *.log
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | pnpm-debug.log*
17 | lerna-debug.log*
18 |
19 | dist
20 | dist-ssr
21 | *.local
22 |
23 | # Editor directories and files
24 | .vscode/*
25 | !.vscode/extensions.json
26 | .idea
27 | .DS_Store
28 | *.suo
29 | *.ntvs*
30 | *.njsproj
31 | *.sln
32 | *.sw?
33 |
--------------------------------------------------------------------------------
/ChatGPT/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npm run pretty-quick
5 | cargo fmt
6 | git add .
--------------------------------------------------------------------------------
/ChatGPT/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 | engine-strict=true
--------------------------------------------------------------------------------
/ChatGPT/.prettierignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | node_modules/
3 | yarn.lock
4 | *.lock
5 |
6 | casks/
7 |
8 | # rust
9 | src-tauri/
10 | target/
11 | Cargo.lock
12 | *.toml
13 |
14 | # Logs
15 | logs
16 | *.log
17 | npm-debug.log*
18 | yarn-debug.log*
19 | yarn-error.log*
20 | pnpm-debug.log*
21 | lerna-debug.log*
22 |
23 | dist
24 | dist-ssr
25 | *.local
26 |
27 | # Editor directories and files
28 | .vscode/*
29 | !.vscode/extensions.json
30 | .idea
31 | .DS_Store
32 | *.suo
33 | *.ntvs*
34 | *.njsproj
35 | *.sln
36 | *.sw?
37 |
38 | assets/**
39 | public/**
40 |
41 | .gitattributes
42 | .gitignore
43 | .prettierignore
44 |
45 | LICENSE
--------------------------------------------------------------------------------
/ChatGPT/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "singleQuote": true,
4 | "semi": true,
5 | "tabWidth": 2,
6 | "printWidth": 100
7 | }
8 |
--------------------------------------------------------------------------------
/ChatGPT/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
3 | }
4 |
--------------------------------------------------------------------------------
/ChatGPT/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["src-tauri"]
3 |
4 | # fix: mac v1.2.0 can not copy/paste
5 | # https://github.com/tauri-apps/tauri/issues/5669
6 | [profile.release]
7 | strip = true
8 | lto = true
9 | opt-level = "s"
10 |
--------------------------------------------------------------------------------
/ChatGPT/README.md:
--------------------------------------------------------------------------------
1 | adapted from [ChatGPT桌面版](https://github.com/lencx/ChatGPT)
2 |
--------------------------------------------------------------------------------
/ChatGPT/assets/auto-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/auto-update.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-cmd.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-cmd.gif
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-cmd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-cmd.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-control-center-general.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-control-center-general.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-dalle2-tray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-dalle2-tray.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-export.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-popup-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-popup-search.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt-sync-prompts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt-sync-prompts.png
--------------------------------------------------------------------------------
/ChatGPT/assets/chatgpt.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/chatgpt.gif
--------------------------------------------------------------------------------
/ChatGPT/assets/install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/install.png
--------------------------------------------------------------------------------
/ChatGPT/assets/zsxq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/assets/zsxq.png
--------------------------------------------------------------------------------
/ChatGPT/casks/chatgpt.rb:
--------------------------------------------------------------------------------
1 | cask "chatgpt" do
2 | version "0.12.0"
3 | arch = Hardware::CPU.arch.to_s
4 | sha256s = {
5 | "x86_64" => "d7f32d11f86ad8ac073dd451452124324e1c9154c318f15b77b5cd254000a3c4",
6 | "aarch64" => "c4c10eeb4a2c9a885da13047340372f461d411711c20472fc673fbf958bf6378"
7 | }
8 | if arch == "arm64" then arch = "aarch64" end
9 | url "https://github.com/lencx/ChatGPT/releases/download/v#{version}/ChatGPT_#{version}_macos_#{arch}.dmg"
10 | sha256 sha256s[arch]
11 |
12 | name "ChatGPT"
13 | desc "Desktop wrapper for OpenAI ChatGPT"
14 | homepage "https://github.com/lencx/ChatGPT#readme"
15 |
16 | app "ChatGPT.app"
17 |
18 | uninstall quit: "com.lencx.chatgpt"
19 |
20 | zap trash: [
21 | "~/.chatgpt",
22 | "~/Library/Caches/com.lencx.chatgpt",
23 | "~/Library/HTTPStorages/com.lencx.chatgpt.binarycookies",
24 | "~/Library/Preferences/com.lencx.chatgpt.plist",
25 | "~/Library/Saved Application State/com.lencx.chatgpt.savedState",
26 | "~/Library/WebKit/com.lencx.chatgpt",
27 | ]
28 | end
29 |
--------------------------------------------------------------------------------
/ChatGPT/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ChatGPT
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ChatGPT/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chatgpt",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev:fe": "vite",
6 | "build:fe": "tsc && vite build",
7 | "dev": "tauri dev",
8 | "build": "tauri build",
9 | "updater": "tr updater",
10 | "release": "tr release --git",
11 | "fix:conf": "tr override --json.tauri_updater_active=false",
12 | "fix:tray": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon-light.png\" --json.tauri_systemTray_iconAsTemplate=false",
13 | "fix:tray:mac": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon.png\" --json.tauri_systemTray_iconAsTemplate=true",
14 | "download": "tr download --mdfile=README.md,README-ZH_CN.md --f1=52 --f2=43",
15 | "fmt:rs": "cargo fmt",
16 | "tr": "tr",
17 | "tauri": "tauri",
18 | "prettier": "prettier -c --write '**/*.{js,md,ts,tsx,yml}'",
19 | "pretty-quick": "pretty-quick --staged",
20 | "prepare": "husky install"
21 | },
22 | "license": "MIT",
23 | "author": "lencx ",
24 | "keywords": [
25 | "chatgpt",
26 | "app",
27 | "desktop",
28 | "tauri",
29 | "macos",
30 | "linux",
31 | "windows"
32 | ],
33 | "homepage": "https://github.com/lencx/ChatGPT",
34 | "bugs": "https://github.com/lencx/ChatGPT/issues",
35 | "repository": {
36 | "type": "git",
37 | "url": "https://github.com/lencx/ChatGPT"
38 | },
39 | "dependencies": {
40 | "@ant-design/icons": "^4.8.0",
41 | "@monaco-editor/react": "^4.4.6",
42 | "@tauri-apps/api": "^1.2.0",
43 | "antd": "^5.1.0",
44 | "clsx": "^1.2.1",
45 | "dayjs": "^1.11.7",
46 | "github-markdown-css": "^5.1.0",
47 | "lodash": "^4.17.21",
48 | "monaco-editor": "^0.34.1",
49 | "react": "^18.2.0",
50 | "react-dom": "^18.2.0",
51 | "react-markdown": "^8.0.4",
52 | "react-resizable-panels": "^0.0.33",
53 | "react-router-dom": "^6.4.5",
54 | "react-syntax-highlighter": "^15.5.0",
55 | "rehype-raw": "^6.1.1",
56 | "remark-comment-config": "^7.0.1",
57 | "remark-gfm": "^3.0.1",
58 | "uuid": "^9.0.0"
59 | },
60 | "devDependencies": {
61 | "@tauri-apps/cli": "^1.2.2",
62 | "@tauri-release/cli": "^0.2.5",
63 | "@types/lodash": "^4.14.191",
64 | "@types/node": "^18.7.10",
65 | "@types/react": "^18.0.15",
66 | "@types/react-dom": "^18.0.6",
67 | "@types/react-syntax-highlighter": "^15.5.6",
68 | "@types/uuid": "^9.0.0",
69 | "@vitejs/plugin-react": "^3.0.0",
70 | "husky": "^8.0.3",
71 | "prettier": "^2.8.3",
72 | "pretty-quick": "^3.1.3",
73 | "sass": "^1.56.2",
74 | "typescript": "^4.9.4",
75 | "vite": "^4.0.0",
76 | "vite-plugin-commonjs": "^0.6.2",
77 | "vite-tsconfig-paths": "^4.0.2"
78 | },
79 | "engines": {
80 | "node": "^14.18.0 || ^16.0.0 || ^18.0.0"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/ChatGPT/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/public/logo.png
--------------------------------------------------------------------------------
/ChatGPT/rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 100
2 | hard_tabs = false
3 | tab_spaces = 2
4 | newline_style = "Auto"
5 | use_small_heuristics = "Default"
6 | reorder_imports = true
7 | reorder_modules = true
8 | remove_nested_parens = true
9 | edition = "2021"
10 | merge_derives = true
11 | use_try_shorthand = false
12 | use_field_init_shorthand = false
13 | force_explicit_abi = true
14 | # imports_granularity = "Crate"
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "chatgpt"
3 | version = "0.0.0"
4 | authors = ["lencx "]
5 | description = "ChatGPT Desktop Application"
6 | repository = "https://github.com/lencx/ChatGPT"
7 | license = "MIT"
8 | edition = "2021"
9 | rust-version = "1.57"
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 |
13 | [build-dependencies]
14 | tauri-build = {version = "1.2.1", features = [] }
15 |
16 | [dependencies]
17 | anyhow = "1.0.66"
18 | serde_json = "1.0"
19 | log = "0.4.17"
20 | csv = "1.1.6"
21 | thiserror = "1.0.38"
22 | walkdir = "2.3.2"
23 | regex = "1.7.0"
24 | reqwest = {version = "0.11.13", features = ["json"] }
25 | wry = "0.24.1"
26 | dark-light = "1.0.0"
27 | serde = { version = "1.0", features = ["derive"] }
28 | tokio = { version = "1.23.0", features = ["macros"] }
29 | tauri = { version = "1.2.4", features = ["devtools", "fs-create-dir", "fs-exists", "fs-read-dir", "fs-read-file", "fs-remove-dir", "fs-remove-file", "fs-write-file", "global-shortcut", "global-shortcut-all", "os-all", "path-all", "process-all", "shell-open-api", "system-tray", "updater"] }
30 | tauri-plugin-positioner = { git = "https://github.com/lencx/tauri-plugins-workspace", features = ["system-tray"] }
31 | tauri-plugin-log = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev", features = ["colored"] }
32 | tauri-plugin-autostart = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev" }
33 | tauri-plugin-window-state = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev" }
34 | hyper = "0.14.11"
35 | webview = "0.1.1"
36 | # sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite"] }
37 |
38 | [features]
39 | # by default Tauri runs in production mode
40 | # when `tauri dev` runs, it is executed with `cargo run --no-default-features` if `devPath` is an URL
41 | default = [ "custom-protocol" ]
42 | # this feature is used for production builds where `devPath` points to the filesystem
43 | # DO NOT remove this
44 | custom-protocol = [ "tauri/custom-protocol" ]
45 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/tray-icon-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/tray-icon-light.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/icons/tray-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/ChatGPT/src-tauri/icons/tray-icon.png
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/app/cmd.rs:
--------------------------------------------------------------------------------
1 | use crate::utils;
2 | use log::error;
3 | use std::{fs, path::PathBuf};
4 | use tauri::{api, command, AppHandle, Manager};
5 |
6 | #[command]
7 | pub fn drag_window(app: AppHandle) {
8 | app.get_window("core").unwrap().start_dragging().unwrap();
9 | }
10 |
11 | #[command]
12 | pub fn fullscreen(app: AppHandle) {
13 | let win = app.get_window("core").unwrap();
14 | if win.is_fullscreen().unwrap() {
15 | win.set_fullscreen(false).unwrap();
16 | } else {
17 | win.set_fullscreen(true).unwrap();
18 | }
19 | }
20 |
21 | #[command]
22 | pub fn download(app: AppHandle, name: String, blob: Vec) {
23 | let win = app.app_handle().get_window("core");
24 | let path = utils::app_root().join(PathBuf::from(name));
25 | utils::create_file(&path).unwrap();
26 | fs::write(&path, blob).unwrap();
27 | tauri::api::dialog::message(
28 | win.as_ref(),
29 | "Save File",
30 | format!("PATH: {}", path.display()),
31 | );
32 | }
33 |
34 | #[command]
35 | pub fn save_file(app: AppHandle, name: String, content: String) {
36 | let win = app.app_handle().get_window("core");
37 | let path = utils::app_root().join(PathBuf::from(name));
38 | utils::create_file(&path).unwrap();
39 | fs::write(&path, content).unwrap();
40 | tauri::api::dialog::message(
41 | win.as_ref(),
42 | "Save File",
43 | format!("PATH: {}", path.display()),
44 | );
45 | }
46 |
47 | #[command]
48 | pub fn open_link(app: AppHandle, url: String) {
49 | api::shell::open(&app.shell_scope(), url, None).unwrap();
50 | }
51 |
52 | #[command]
53 | pub fn run_check_update(app: AppHandle, silent: bool, has_msg: Option) {
54 | utils::run_check_update(app, silent, has_msg);
55 | }
56 |
57 | #[command]
58 | pub fn open_file(path: PathBuf) {
59 | utils::open_file(path);
60 | }
61 |
62 | #[command]
63 | pub async fn get_data(app: AppHandle, url: String, is_msg: Option) -> Option {
64 | let is_msg = is_msg.unwrap_or(false);
65 | let res = if is_msg {
66 | utils::get_data(&url, Some(&app)).await
67 | } else {
68 | utils::get_data(&url, None).await
69 | };
70 | res.unwrap_or_else(|err| {
71 | error!("chatgpt_client_http: {}", err);
72 | None
73 | })
74 | }
75 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/app/cors.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use reqwest::{Client, Method, RequestBuilder,Proxy};
3 | // use wry::http::UriScheme;
4 |
5 | #[derive(Default, Serialize, Deserialize,Debug)]
6 | pub struct Message {
7 | message: String,
8 | data:String,
9 | }
10 |
11 | #[tauri::command]
12 | pub async fn fetch_data(
13 | method: String,
14 | url: String,
15 | body: Option
16 | ) -> Result{
17 | println!("{},{},{:?}",method,url,body);
18 |
19 | let req_builder: RequestBuilder;
20 | let method = match method.as_str() {
21 | "GET" => Method::GET,
22 | "POST" => Method::POST,
23 | "PUT" => Method::PUT,
24 | "DELETE" => Method::DELETE,
25 | _ => {
26 | // Handle invalid method
27 | return Err("Invalid method".to_string());
28 | }
29 | };
30 | if url.contains("localhost"){
31 | let mut client_builder = Client::builder();
32 | // let mut client_builder = Client::builder()
33 | // .pool_max_idle_per_host(15);
34 | // .pool_idle_timeout(Duration::from_secs(10))
35 | // .build()?;
36 | client_builder = client_builder.proxy(Proxy::custom(move |url| {
37 | None:: // bypass proxy for localhost
38 | }));
39 | let client = client_builder.build().map_err(|e| format!("Failed to create client: {}", e))?;
40 | req_builder = client.request(method, &url);
41 | } else {
42 | let client =Client::new();
43 | req_builder = client.request(method, &url);
44 | }
45 |
46 | let req_builder = req_builder.header("Content-Type", "application/json"); // Add header
47 | let resp = match body {
48 | Some(data) => req_builder.body(data).send().await.map_err(|e| format!("Request failed: {}", e))?,
49 | None => req_builder.send().await.map_err(|e| format!("Request failed: {}", e))?,
50 | };
51 |
52 | let message: Message = resp.json().await.map_err(|e| format!("Failed to parse response: {}", e))?;
53 | println!("response:{:?}",message);
54 | Ok(message) // Return the response directly
55 | }
56 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/app/fs_extra.rs:
--------------------------------------------------------------------------------
1 | // https://github.com/tauri-apps/tauri-plugin-fs-extra/blob/dev/src/lib.rs
2 |
3 | // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
4 | // SPDX-License-Identifier: Apache-2.0
5 | // SPDX-License-Identifier: MIT
6 |
7 | use serde::{ser::Serializer, Serialize};
8 | use std::{
9 | path::PathBuf,
10 | time::{SystemTime, UNIX_EPOCH},
11 | };
12 | use tauri::command;
13 |
14 | #[cfg(unix)]
15 | use std::os::unix::fs::{MetadataExt, PermissionsExt};
16 | #[cfg(windows)]
17 | use std::os::windows::fs::MetadataExt;
18 |
19 | type Result = std::result::Result;
20 |
21 | #[derive(Debug, thiserror::Error)]
22 | pub enum Error {
23 | #[error(transparent)]
24 | Io(#[from] std::io::Error),
25 | }
26 |
27 | impl Serialize for Error {
28 | fn serialize(&self, serializer: S) -> std::result::Result
29 | where
30 | S: Serializer,
31 | {
32 | serializer.serialize_str(self.to_string().as_ref())
33 | }
34 | }
35 |
36 | #[derive(Serialize)]
37 | #[serde(rename_all = "camelCase")]
38 | struct Permissions {
39 | readonly: bool,
40 | #[cfg(unix)]
41 | mode: u32,
42 | }
43 |
44 | #[cfg(unix)]
45 | #[derive(Serialize)]
46 | #[serde(rename_all = "camelCase")]
47 | struct UnixMetadata {
48 | dev: u64,
49 | ino: u64,
50 | mode: u32,
51 | nlink: u64,
52 | uid: u32,
53 | gid: u32,
54 | rdev: u64,
55 | blksize: u64,
56 | blocks: u64,
57 | }
58 |
59 | #[derive(Serialize)]
60 | #[serde(rename_all = "camelCase")]
61 | pub struct Metadata {
62 | accessed_at_ms: u64,
63 | pub created_at_ms: u64,
64 | modified_at_ms: u64,
65 | is_dir: bool,
66 | is_file: bool,
67 | is_symlink: bool,
68 | size: u64,
69 | permissions: Permissions,
70 | #[cfg(unix)]
71 | #[serde(flatten)]
72 | unix: UnixMetadata,
73 | #[cfg(windows)]
74 | file_attributes: u32,
75 | }
76 |
77 | pub fn system_time_to_ms(time: std::io::Result) -> u64 {
78 | time
79 | .map(|t| {
80 | let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
81 | duration_since_epoch.as_millis() as u64
82 | })
83 | .unwrap_or_default()
84 | }
85 |
86 | #[command]
87 | pub async fn metadata(path: PathBuf) -> Result {
88 | let metadata = std::fs::metadata(path)?;
89 | let file_type = metadata.file_type();
90 | let permissions = metadata.permissions();
91 | Ok(Metadata {
92 | accessed_at_ms: system_time_to_ms(metadata.accessed()),
93 | created_at_ms: system_time_to_ms(metadata.created()),
94 | modified_at_ms: system_time_to_ms(metadata.modified()),
95 | is_dir: file_type.is_dir(),
96 | is_file: file_type.is_file(),
97 | is_symlink: file_type.is_symlink(),
98 | size: metadata.len(),
99 | permissions: Permissions {
100 | readonly: permissions.readonly(),
101 | #[cfg(unix)]
102 | mode: permissions.mode(),
103 | },
104 | #[cfg(unix)]
105 | unix: UnixMetadata {
106 | dev: metadata.dev(),
107 | ino: metadata.ino(),
108 | mode: metadata.mode(),
109 | nlink: metadata.nlink(),
110 | uid: metadata.uid(),
111 | gid: metadata.gid(),
112 | rdev: metadata.rdev(),
113 | blksize: metadata.blksize(),
114 | blocks: metadata.blocks(),
115 | },
116 | #[cfg(windows)]
117 | file_attributes: metadata.file_attributes(),
118 | })
119 | }
120 |
121 | // #[command]
122 | // pub async fn exists(path: PathBuf) -> bool {
123 | // path.exists()
124 | // }
125 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/app/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cmd;
2 | pub mod fs_extra;
3 | pub mod gpt;
4 | pub mod menu;
5 | pub mod setup;
6 | pub mod window;
7 | pub mod cors;
8 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/app/setup.rs:
--------------------------------------------------------------------------------
1 | use crate::{app::window, conf::AppConf, utils};
2 | use log::{error, info};
3 | use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcutManager, Manager};
4 | use wry::application::accelerator::Accelerator;
5 |
6 | pub fn init(app: &mut App) -> std::result::Result<(), Box> {
7 | info!("stepup");
8 | let app_conf = AppConf::read();
9 | let url = app_conf.main_origin.to_string();
10 | let theme = AppConf::theme_mode();
11 | let handle = app.app_handle();
12 |
13 | tauri::async_runtime::spawn(async move {
14 | info!("stepup_tray");
15 | window::tray_window(&handle);
16 | });
17 |
18 | if let Some(v) = app_conf.clone().global_shortcut {
19 | info!("global_shortcut: `{}`", v);
20 | match v.parse::() {
21 | Ok(_) => {
22 | info!("global_shortcut_register");
23 | let handle = app.app_handle();
24 | let mut shortcut = app.global_shortcut_manager();
25 | shortcut
26 | .register(&v, move || {
27 | if let Some(w) = handle.get_window("core") {
28 | if w.is_visible().unwrap() {
29 | w.hide().unwrap();
30 | } else {
31 | w.show().unwrap();
32 | w.set_focus().unwrap();
33 | }
34 | }
35 | })
36 | .unwrap_or_else(|err| {
37 | error!("global_shortcut_register_error: {}", err);
38 | });
39 | }
40 | Err(err) => {
41 | error!("global_shortcut_parse_error: {}", err);
42 | }
43 | }
44 | } else {
45 | info!("global_shortcut_unregister");
46 | };
47 |
48 | let app_conf2 = app_conf.clone();
49 | if app_conf.hide_dock_icon {
50 | #[cfg(target_os = "macos")]
51 | app.set_activation_policy(tauri::ActivationPolicy::Accessory);
52 | } else {
53 | let app = app.handle();
54 | tauri::async_runtime::spawn(async move {
55 | let link = if app_conf2.main_dashboard {
56 | "index.html"
57 | } else {
58 | &url
59 | };
60 | info!("main_window: {}", link);
61 | let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(link.into()))
62 | .title("ChatGPT")
63 | .resizable(true)
64 | .fullscreen(false)
65 | .inner_size(app_conf2.main_width, app_conf2.main_height)
66 | .theme(Some(theme))
67 | .always_on_top(app_conf2.stay_on_top)
68 | .initialization_script(&utils::user_script())
69 | .initialization_script(include_str!("../scripts/core.js"))
70 | .user_agent(&app_conf2.ua_window);
71 |
72 | #[cfg(target_os = "macos")]
73 | {
74 | main_win = main_win
75 | .title_bar_style(app_conf2.clone().titlebar())
76 | .hidden_title(true);
77 | }
78 |
79 | if url == "https://chat.openai.com" && !app_conf2.main_dashboard {
80 | main_win = main_win
81 | .initialization_script(include_str!("../vendors/floating-ui-core.js"))
82 | .initialization_script(include_str!("../vendors/floating-ui-dom.js"))
83 | .initialization_script(include_str!("../vendors/html2canvas.js"))
84 | .initialization_script(include_str!("../vendors/jspdf.js"))
85 | .initialization_script(include_str!("../vendors/turndown.js"))
86 | .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js"))
87 | .initialization_script(include_str!("../scripts/popup.core.js"))
88 | .initialization_script(include_str!("../scripts/export.js"))
89 | .initialization_script(include_str!("../scripts/markdown.export.js"))
90 | .initialization_script(include_str!("../scripts/cmd.js"))
91 | .initialization_script(include_str!("../scripts/chat.js"))
92 | }
93 |
94 | main_win.build().unwrap();
95 | });
96 | }
97 |
98 | // auto_update
99 | let auto_update = app_conf.get_auto_update();
100 | if auto_update != "disable" {
101 | info!("run_check_update");
102 | let app = app.handle();
103 | utils::run_check_update(app, auto_update == "silent", None);
104 | }
105 |
106 | Ok(())
107 | }
108 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/app/window.rs:
--------------------------------------------------------------------------------
1 | use crate::{conf::AppConf, utils};
2 | use log::info;
3 | use std::time::SystemTime;
4 | use tauri::{utils::config::WindowUrl, window::WindowBuilder, Manager};
5 |
6 | pub fn tray_window(handle: &tauri::AppHandle) {
7 | let app_conf = AppConf::read();
8 | let theme = AppConf::theme_mode();
9 | let app = handle.clone();
10 |
11 | tauri::async_runtime::spawn(async move {
12 | let link = if app_conf.tray_dashboard {
13 | "index.html"
14 | } else {
15 | &app_conf.tray_origin
16 | };
17 | let mut tray_win = WindowBuilder::new(&app, "tray", WindowUrl::App(link.into()))
18 | .title("ChatGPT")
19 | .resizable(false)
20 | .fullscreen(false)
21 | .inner_size(app_conf.tray_width, app_conf.tray_height)
22 | .decorations(false)
23 | .always_on_top(true)
24 | .theme(Some(theme))
25 | .initialization_script(&utils::user_script())
26 | .initialization_script(include_str!("../scripts/core.js"))
27 | .user_agent(&app_conf.ua_tray);
28 |
29 | if app_conf.tray_origin == "https://chat.openai.com" && !app_conf.tray_dashboard {
30 | tray_win = tray_win
31 | .initialization_script(include_str!("../vendors/floating-ui-core.js"))
32 | .initialization_script(include_str!("../vendors/floating-ui-dom.js"))
33 | .initialization_script(include_str!("../scripts/cmd.js"))
34 | .initialization_script(include_str!("../scripts/chat.js"))
35 | .initialization_script(include_str!("../scripts/popup.core.js"))
36 | }
37 |
38 | tray_win.build().unwrap().hide().unwrap();
39 | });
40 | }
41 |
42 | pub fn dalle2_window(
43 | handle: &tauri::AppHandle,
44 | query: Option,
45 | title: Option,
46 | is_new: Option,
47 | ) {
48 | info!("dalle2_query: {:?}", query);
49 | let theme = AppConf::theme_mode();
50 | let app = handle.clone();
51 |
52 | let query = if query.is_some() {
53 | format!("window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", query.unwrap())
54 | } else {
55 | "".to_string()
56 | };
57 |
58 | let label = if is_new.unwrap_or(true) {
59 | let timestamp = SystemTime::now()
60 | .duration_since(SystemTime::UNIX_EPOCH)
61 | .unwrap()
62 | .as_secs();
63 | format!("dalle2_{}", timestamp)
64 | } else {
65 | "dalle2".to_string()
66 | };
67 |
68 | if app.get_window("dalle2").is_none() {
69 | tauri::async_runtime::spawn(async move {
70 | WindowBuilder::new(
71 | &app,
72 | label,
73 | WindowUrl::App("https://labs.openai.com".into()),
74 | )
75 | .title(title.unwrap_or_else(|| "DALL·E 2".to_string()))
76 | .resizable(true)
77 | .fullscreen(false)
78 | .inner_size(800.0, 600.0)
79 | .always_on_top(false)
80 | .theme(Some(theme))
81 | .initialization_script(include_str!("../scripts/core.js"))
82 | .initialization_script(&query)
83 | .initialization_script(include_str!("../scripts/dalle2.js"))
84 | .build()
85 | .unwrap();
86 | });
87 | } else {
88 | let dalle2_win = app.get_window("dalle2").unwrap();
89 | dalle2_win.show().unwrap();
90 | dalle2_win.set_focus().unwrap();
91 | }
92 | }
93 |
94 | pub mod cmd {
95 | use super::*;
96 | use log::info;
97 | use tauri::{command, utils::config::WindowUrl, window::WindowBuilder, Manager};
98 |
99 | #[tauri::command]
100 | pub fn dalle2_search_window(app: tauri::AppHandle, query: String) {
101 | dalle2_window(
102 | &app.app_handle(),
103 | Some(query),
104 | Some("ChatGPT & DALL·E 2".to_string()),
105 | None,
106 | );
107 | }
108 |
109 | #[tauri::command]
110 | pub fn control_window(handle: tauri::AppHandle) {
111 | tauri::async_runtime::spawn(async move {
112 | if handle.get_window("main").is_none() {
113 | WindowBuilder::new(
114 | &handle,
115 | "main",
116 | WindowUrl::App("index.html?type=control".into()),
117 | )
118 | .title("Control Center")
119 | .resizable(true)
120 | .fullscreen(false)
121 | .inner_size(1200.0, 700.0)
122 | .min_inner_size(1000.0, 600.0)
123 | .build()
124 | .unwrap();
125 | } else {
126 | let main_win = handle.get_window("main").unwrap();
127 | main_win.show().unwrap();
128 | main_win.set_focus().unwrap();
129 | }
130 | });
131 | }
132 |
133 | #[command]
134 | pub fn wa_window(
135 | app: tauri::AppHandle,
136 | label: String,
137 | title: String,
138 | url: String,
139 | script: Option,
140 | ) {
141 | info!("wa_window: {} :=> {}", title, url);
142 | let win = app.get_window(&label);
143 | if win.is_none() {
144 | tauri::async_runtime::spawn(async move {
145 | tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App(url.parse().unwrap()))
146 | .initialization_script(&script.unwrap_or_default())
147 | .initialization_script(include_str!("../scripts/core.js"))
148 | .title(title)
149 | .inner_size(960.0, 700.0)
150 | .resizable(true)
151 | .build()
152 | .unwrap();
153 | });
154 | } else if let Some(v) = win {
155 | if !v.is_visible().unwrap() {
156 | v.show().unwrap();
157 | }
158 | v.eval("window.location.reload()").unwrap();
159 | v.set_focus().unwrap();
160 | }
161 | }
162 |
163 | #[command]
164 | pub fn window_reload(app: tauri::AppHandle, label: &str) {
165 | app
166 | .app_handle()
167 | .get_window(label)
168 | .unwrap()
169 | .eval("window.location.reload()")
170 | .unwrap();
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(
2 | all(not(debug_assertions), target_os = "windows"),
3 | windows_subsystem = "windows"
4 | )]
5 |
6 | mod app;
7 | mod conf;
8 | mod utils;
9 |
10 | use app::{cmd, fs_extra, gpt, menu, setup, window, cors};
11 | use conf::AppConf;
12 | use tauri_plugin_autostart::MacosLauncher;
13 | use tauri_plugin_log::{
14 | fern::colors::{Color, ColoredLevelConfig},
15 | LogTarget,
16 | };
17 |
18 | #[tokio::main]
19 | async fn main() {
20 | let app_conf = AppConf::read().write();
21 | // If the file does not exist, creating the file will block menu synchronization
22 | utils::create_chatgpt_prompts();
23 | let context = tauri::generate_context!();
24 |
25 | gpt::download_list("chat.download.json", "download", None, None);
26 | gpt::download_list("chat.notes.json", "notes", None, None);
27 |
28 | let mut log = tauri_plugin_log::Builder::default()
29 | .targets([
30 | // LogTarget::LogDir,
31 | // LOG PATH: ~/.chatgpt/ChatGPT.log
32 | LogTarget::Folder(utils::app_root()),
33 | LogTarget::Stdout,
34 | LogTarget::Webview,
35 | ])
36 | .level(log::LevelFilter::Debug);
37 |
38 | if cfg!(debug_assertions) {
39 | log = log.with_colors(ColoredLevelConfig {
40 | error: Color::Red,
41 | warn: Color::Yellow,
42 | debug: Color::Blue,
43 | info: Color::BrightGreen,
44 | trace: Color::Cyan,
45 | });
46 | }
47 |
48 | let mut builder = tauri::Builder::default()
49 | .plugin(log.build())
50 | .plugin(tauri_plugin_positioner::init())
51 | .plugin(tauri_plugin_autostart::init(
52 | MacosLauncher::LaunchAgent,
53 | None,
54 | ))
55 | .invoke_handler(tauri::generate_handler![
56 | cmd::drag_window,
57 | cmd::fullscreen,
58 | cmd::download,
59 | cmd::save_file,
60 | cmd::open_link,
61 | cmd::run_check_update,
62 | cmd::open_file,
63 | cmd::get_data,
64 | gpt::get_chat_model_cmd,
65 | gpt::parse_prompt,
66 | gpt::sync_prompts,
67 | gpt::sync_user_prompts,
68 | gpt::cmd_list,
69 | gpt::download_list,
70 | gpt::get_download_list,
71 | fs_extra::metadata,
72 | conf::cmd::get_app_conf,
73 | conf::cmd::reset_app_conf,
74 | conf::cmd::get_theme,
75 | conf::cmd::form_confirm,
76 | conf::cmd::form_cancel,
77 | conf::cmd::form_msg,
78 | window::cmd::wa_window,
79 | window::cmd::control_window,
80 | window::cmd::window_reload,
81 | window::cmd::dalle2_search_window,
82 | cors::fetch_data,
83 | ])
84 | .setup(setup::init)
85 | .menu(menu::init());
86 |
87 | if app_conf.tray {
88 | builder = builder.system_tray(menu::tray_menu());
89 | }
90 |
91 | if app_conf.save_window_state {
92 | builder = builder.plugin(tauri_plugin_window_state::Builder::default().build());
93 | }
94 |
95 | builder
96 | .on_menu_event(menu::menu_handler)
97 | .on_system_tray_event(menu::tray_handler)
98 | .on_window_event(move |event| {
99 | if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() {
100 | let win = event.window().clone();
101 | let app_conf = AppConf::read();
102 | if win.label() == "core" {
103 | if app_conf.isinit {
104 | tauri::api::dialog::ask(
105 | Some(event.window()),
106 | "",
107 | "Do you want to exit the application when you click the [x] button?",
108 | move |is_ok| {
109 | app_conf
110 | .amend(serde_json::json!({ "isinit" : false, "main_close": is_ok }))
111 | .write();
112 | if is_ok {
113 | std::process::exit(0);
114 | } else {
115 | win.minimize().unwrap();
116 | }
117 | },
118 | );
119 | } else if app_conf.main_close {
120 | std::process::exit(0);
121 | } else {
122 | win.minimize().unwrap();
123 | }
124 | } else {
125 | event.window().close().unwrap();
126 | }
127 | api.prevent_close();
128 | }
129 | })
130 | .run(context)
131 | .expect("error while running ChatGPT application");
132 | }
133 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/scripts/dalle2.js:
--------------------------------------------------------------------------------
1 | // *** Core Script - DALL·E 2 ***
2 |
3 | function init() {
4 | document.addEventListener("click", (e) => {
5 | const origin = e.target.closest("a");
6 | if (!origin || !origin.target) return;
7 | if (origin && origin.href && origin.target !== '_self') {
8 | if (/\/(login|signup)$/.test(window.location.href)) {
9 | origin.target = '_self';
10 | } else {
11 | invoke('open_link', { url: origin.href });
12 | }
13 | }
14 | });
15 |
16 | if (window.searchInterval) {
17 | clearInterval(window.searchInterval);
18 | }
19 |
20 | window.searchInterval = setInterval(() => {
21 | const searchInput = document.querySelector('.image-prompt-form-wrapper form>.text-input');
22 | if (searchInput) {
23 | clearInterval(window.searchInterval);
24 |
25 | if (!window.__CHATGPT_QUERY__) return;
26 | const query = decodeURIComponent(window.__CHATGPT_QUERY__);
27 | searchInput.focus();
28 | searchInput.value = query;
29 | }
30 | }, 200)
31 | }
32 |
33 | if (
34 | document.readyState === "complete" ||
35 | document.readyState === "interactive"
36 | ) {
37 | init();
38 | } else {
39 | document.addEventListener("DOMContentLoaded", init);
40 | }
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/scripts/markdown.export.js:
--------------------------------------------------------------------------------
1 | var ExportMD = (function () {
2 | if (!TurndownService || !turndownPluginGfm) return;
3 | const hljsREG = /^.*(hljs).*(language-[a-z0-9]+).*$/i;
4 | const gfm = turndownPluginGfm.gfm
5 | const turndownService = new TurndownService({
6 | hr: '---'
7 | })
8 | .use(gfm)
9 | .addRule('code', {
10 | filter: (node) => {
11 | if (node.nodeName === 'CODE' && hljsREG.test(node.classList.value)) {
12 | return 'code';
13 | }
14 | },
15 | replacement: (content, node) => {
16 | const classStr = node.getAttribute('class');
17 | if (hljsREG.test(classStr)) {
18 | const lang = classStr.match(/.*language-(\w+)/)[1];
19 | if (lang) {
20 | return `\`\`\`${lang}\n${content}\n\`\`\``;
21 | }
22 | return `\`\`\`\n${content}\n\`\`\``;
23 | }
24 | }
25 | })
26 | .addRule('ignore', {
27 | filter: ['button', 'img'],
28 | replacement: () => '',
29 | })
30 | .addRule('table', {
31 | filter: 'table',
32 | replacement: function(content, node) {
33 | return `\`\`\`${content}\n\`\`\``;
34 | },
35 | });
36 |
37 | return turndownService;
38 | }({}));
39 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/scripts/popup.core.js:
--------------------------------------------------------------------------------
1 | // *** Core Script - DALL·E 2 Core ***
2 |
3 | async function init() {
4 | const chatConf = await invoke('get_app_conf') || {};
5 | if (!chatConf.popup_search) return;
6 | if (!window.FloatingUIDOM) return;
7 |
8 | const styleDom = document.createElement('style');
9 | styleDom.innerHTML = `
10 | #chagpt-selection-menu {
11 | display: none;
12 | width: max-content;
13 | position: absolute;
14 | top: 0;
15 | left: 0;
16 | background: #4a4a4a;
17 | color: white;
18 | font-weight: bold;
19 | padding: 3px 5px;
20 | border-radius: 2px;
21 | font-size: 10px;
22 | cursor: pointer;
23 | }
24 | `;
25 | document.head.append(styleDom);
26 |
27 | const selectionMenu = document.createElement('div');
28 | selectionMenu.id = 'chagpt-selection-menu';
29 | selectionMenu.innerHTML = 'DALL·E 2';
30 | document.body.appendChild(selectionMenu);
31 | const { computePosition, flip, offset, shift } = window.FloatingUIDOM;
32 |
33 | document.body.addEventListener('mousedown', async (e) => {
34 | selectionMenu.style.display = 'none';
35 | if (e.target.id === 'chagpt-selection-menu') {
36 | await invoke('dalle2_search_window', { query: encodeURIComponent(window.__DALLE2_CONTENT__) });
37 | } else {
38 | delete window.__DALLE2_CONTENT__;
39 | }
40 | });
41 |
42 | document.body.addEventListener("mouseup", async (e) => {
43 | selectionMenu.style.display = 'none';
44 | const selection = window.getSelection();
45 | window.__DALLE2_CONTENT__ = selection.toString().trim();
46 |
47 | if (!window.__DALLE2_CONTENT__) return;
48 |
49 | if (selection.rangeCount > 0) {
50 | const range = selection.getRangeAt(0);
51 | const rect = range.getClientRects()[0];
52 |
53 | const rootEl = document.createElement('div');
54 | rootEl.style.top = `${rect.top}px`;
55 | rootEl.style.position = 'fixed';
56 | rootEl.style.left = `${rect.left}px`;
57 | document.body.appendChild(rootEl);
58 |
59 | selectionMenu.style.display = 'block';
60 | computePosition(rootEl, selectionMenu, {
61 | placement: 'top',
62 | middleware: [
63 | flip(),
64 | offset(5),
65 | shift({ padding: 5 })
66 | ]
67 | }).then(({x, y}) => {
68 | Object.assign(selectionMenu.style, {
69 | left: `${x}px`,
70 | top: `${y}px`,
71 | });
72 | });
73 | }
74 | });
75 | }
76 |
77 | if (
78 | document.readyState === "complete" ||
79 | document.readyState === "interactive"
80 | ) {
81 | init();
82 | } else {
83 | document.addEventListener("DOMContentLoaded", init);
84 | }
85 |
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/src/vendors/turndown-plugin-gfm.js:
--------------------------------------------------------------------------------
1 | var turndownPluginGfm = (function (exports) {
2 | 'use strict';
3 |
4 | var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;
5 |
6 | function highlightedCodeBlock (turndownService) {
7 | turndownService.addRule('highlightedCodeBlock', {
8 | filter: function (node) {
9 | var firstChild = node.firstChild;
10 | return (
11 | node.nodeName === 'DIV' &&
12 | highlightRegExp.test(node.className) &&
13 | firstChild &&
14 | firstChild.nodeName === 'PRE'
15 | )
16 | },
17 | replacement: function (content, node, options) {
18 | var className = node.className || '';
19 | var language = (className.match(highlightRegExp) || [null, ''])[1];
20 |
21 | return (
22 | '\n\n' + options.fence + language + '\n' +
23 | node.firstChild.textContent +
24 | '\n' + options.fence + '\n\n'
25 | )
26 | }
27 | });
28 | }
29 |
30 | function strikethrough (turndownService) {
31 | turndownService.addRule('strikethrough', {
32 | filter: ['del', 's', 'strike'],
33 | replacement: function (content) {
34 | return '~' + content + '~'
35 | }
36 | });
37 | }
38 |
39 | var indexOf = Array.prototype.indexOf;
40 | var every = Array.prototype.every;
41 | var rules = {};
42 |
43 | rules.tableCell = {
44 | filter: ['th', 'td'],
45 | replacement: function (content, node) {
46 | return cell(content, node)
47 | }
48 | };
49 |
50 | rules.tableRow = {
51 | filter: 'tr',
52 | replacement: function (content, node) {
53 | var borderCells = '';
54 | var alignMap = { left: ':--', right: '--:', center: ':-:' };
55 |
56 | if (isHeadingRow(node)) {
57 | for (var i = 0; i < node.childNodes.length; i++) {
58 | var border = '---';
59 | var align = (
60 | node.childNodes[i].getAttribute('align') || ''
61 | ).toLowerCase();
62 |
63 | if (align) border = alignMap[align] || border;
64 |
65 | borderCells += cell(border, node.childNodes[i]);
66 | }
67 | }
68 | return '\n' + content + (borderCells ? '\n' + borderCells : '')
69 | }
70 | };
71 |
72 | rules.table = {
73 | // Only convert tables with a heading row.
74 | // Tables with no heading row are kept using `keep` (see below).
75 | filter: function (node) {
76 | return node.nodeName === 'TABLE' && isHeadingRow(node.rows[0])
77 | },
78 |
79 | replacement: function (content) {
80 | // Ensure there are no blank lines
81 | content = content.replace('\n\n', '\n');
82 | return '\n\n' + content + '\n\n'
83 | }
84 | };
85 |
86 | rules.tableSection = {
87 | filter: ['thead', 'tbody', 'tfoot'],
88 | replacement: function (content) {
89 | return content
90 | }
91 | };
92 |
93 | // A tr is a heading row if:
94 | // - the parent is a THEAD
95 | // - or if its the first child of the TABLE or the first TBODY (possibly
96 | // following a blank THEAD)
97 | // - and every cell is a TH
98 | function isHeadingRow (tr) {
99 | var parentNode = tr.parentNode;
100 | return (
101 | parentNode.nodeName === 'THEAD' ||
102 | (
103 | parentNode.firstChild === tr &&
104 | (parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&
105 | every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })
106 | )
107 | )
108 | }
109 |
110 | function isFirstTbody (element) {
111 | var previousSibling = element.previousSibling;
112 | return (
113 | element.nodeName === 'TBODY' && (
114 | !previousSibling ||
115 | (
116 | previousSibling.nodeName === 'THEAD' &&
117 | /^\s*$/i.test(previousSibling.textContent)
118 | )
119 | )
120 | )
121 | }
122 |
123 | function cell (content, node) {
124 | var index = indexOf.call(node.parentNode.childNodes, node);
125 | var prefix = ' ';
126 | if (index === 0) prefix = '| ';
127 | return prefix + content + ' |'
128 | }
129 |
130 | function tables (turndownService) {
131 | turndownService.keep(function (node) {
132 | return node.nodeName === 'TABLE' && !isHeadingRow(node.rows[0])
133 | });
134 | for (var key in rules) turndownService.addRule(key, rules[key]);
135 | }
136 |
137 | function taskListItems (turndownService) {
138 | turndownService.addRule('taskListItems', {
139 | filter: function (node) {
140 | return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
141 | },
142 | replacement: function (content, node) {
143 | return (node.checked ? '[x]' : '[ ]') + ' '
144 | }
145 | });
146 | }
147 |
148 | function gfm (turndownService) {
149 | turndownService.use([
150 | highlightedCodeBlock,
151 | strikethrough,
152 | tables,
153 | taskListItems
154 | ]);
155 | }
156 |
157 | exports.gfm = gfm;
158 | exports.highlightedCodeBlock = highlightedCodeBlock;
159 | exports.strikethrough = strikethrough;
160 | exports.tables = tables;
161 | exports.taskListItems = taskListItems;
162 |
163 | return exports;
164 | }({}));
--------------------------------------------------------------------------------
/ChatGPT/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "beforeDevCommand": "npm run dev:fe",
4 | "beforeBuildCommand": "npm run build:fe",
5 | "devPath": "http://localhost:1420",
6 | "distDir": "../dist"
7 | },
8 | "package": {
9 | "productName": "ChatGPT",
10 | "version": "0.12.0"
11 | },
12 | "tauri": {
13 | "allowlist": {
14 | "all": false,
15 | "globalShortcut": {
16 | "all": true
17 | },
18 | "fs": {
19 | "all": false,
20 | "readFile": true,
21 | "writeFile": true,
22 | "readDir": true,
23 | "createDir": true,
24 | "exists": true,
25 | "removeFile": true,
26 | "removeDir": true,
27 | "scope": [
28 | "$HOME/.chatgpt/**"
29 | ]
30 | },
31 | "path": {
32 | "all": true
33 | },
34 | "os": {
35 | "all": true
36 | },
37 | "process": {
38 | "all": true,
39 | "exit": true,
40 | "relaunch": true,
41 | "relaunchDangerousAllowSymlinkMacos": true
42 | }
43 | },
44 | "systemTray": {
45 | "iconPath": "icons/tray-icon.png",
46 | "iconAsTemplate": true,
47 | "menuOnLeftClick": false
48 | },
49 | "bundle": {
50 | "active": true,
51 | "category": "DeveloperTool",
52 | "copyright": "",
53 | "deb": {
54 | "depends": []
55 | },
56 | "externalBin": [],
57 | "icon": [
58 | "icons/32x32.png",
59 | "icons/128x128.png",
60 | "icons/128x128@2x.png",
61 | "icons/icon.icns",
62 | "icons/icon.ico"
63 | ],
64 | "identifier": "com.lencx.chatgpt",
65 | "longDescription": "ChatGPT Desktop Application",
66 | "macOS": {
67 | "entitlements": null,
68 | "exceptionDomain": "",
69 | "frameworks": [],
70 | "providerShortName": null,
71 | "signingIdentity": null
72 | },
73 | "shortDescription": "ChatGPT",
74 | "targets": "all",
75 | "windows": {
76 | "certificateThumbprint": null,
77 | "digestAlgorithm": "sha256",
78 | "timestampUrl": "",
79 | "webviewInstallMode": {
80 | "silent": true,
81 | "type": "embedBootstrapper"
82 | }
83 | }
84 | },
85 | "security": {
86 | "csp": null
87 | },
88 | "updater": {
89 | "active": true,
90 | "dialog": false,
91 | "endpoints": [
92 | "https://lencx.github.io/ChatGPT/install.json"
93 | ],
94 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEIxMjY4OUI5MTVFNjBEMDUKUldRRkRlWVZ1WWttc1NGWEE0RFNSb0RqdnhsekRJZTkwK2hVLzhBZTZnaHExSEZ1ZEdzWkpXTHkK"
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/ChatGPT/src/components/FilePath/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import clsx from 'clsx';
3 | import { path, shell } from '@tauri-apps/api';
4 |
5 | import { chatRoot } from '@/utils';
6 |
7 | interface FilePathProps {
8 | paths?: string;
9 | label?: string;
10 | className?: string;
11 | content?: string;
12 | url?: string;
13 | }
14 |
15 | const FilePath: FC = ({ className, label = 'PATH', paths = '', url, content }) => {
16 | const [filePath, setPath] = useState('');
17 |
18 | useEffect(() => {
19 | if (!path && !url) return;
20 | (async () => {
21 | if (url) {
22 | setPath(url);
23 | return;
24 | }
25 | setPath(await path.join(await chatRoot(), ...paths.split('/').filter((i) => !!i)));
26 | })();
27 | }, [url, paths]);
28 |
29 | return (
30 |
38 | );
39 | };
40 |
41 | export default FilePath;
42 |
--------------------------------------------------------------------------------
/ChatGPT/src/components/Markdown/Editor.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import Editor from '@monaco-editor/react';
3 | import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
4 |
5 | import Markdown from '@/components/Markdown';
6 | import './index.scss';
7 |
8 | interface MarkdownEditorProps {
9 | value?: string;
10 | onChange?: (v: string) => void;
11 | mode?: string;
12 | }
13 |
14 | const MarkdownEditor: FC = ({ value = '', onChange, mode = 'split' }) => {
15 | const [content, setContent] = useState(value);
16 |
17 | useEffect(() => {
18 | setContent(value);
19 | onChange && onChange(value);
20 | }, [value]);
21 |
22 | const handleEdit = (e: any) => {
23 | setContent(e);
24 | onChange && onChange(e);
25 | };
26 |
27 | const isSplit = mode === 'split';
28 |
29 | return (
30 |
31 |
32 | {['md', 'split'].includes(mode) && (
33 |
34 |
35 |
36 | )}
37 | {isSplit && }
38 | {['doc', 'split'].includes(mode) && (
39 |
40 | {content}
41 |
42 | )}
43 |
44 |
45 | );
46 | };
47 |
48 | export default MarkdownEditor;
49 |
--------------------------------------------------------------------------------
/ChatGPT/src/components/Markdown/index.scss:
--------------------------------------------------------------------------------
1 | .markdown-body {
2 | height: 100%;
3 | overflow: auto;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial,
5 | sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
6 |
7 | &.edit-preview {
8 | padding: 10px;
9 | font-size: 14px;
10 | }
11 |
12 | pre,
13 | code {
14 | font-family: monospace, monospace;
15 | }
16 | }
17 |
18 | .md-main {
19 | height: calc(100vh - 130px);
20 | overflow: hidden;
21 | }
22 |
23 | .resize-handle {
24 | width: 0.25rem;
25 | transition: 250ms linear background-color;
26 | background-color: #7f8082;
27 | outline: none;
28 |
29 | &:hover,
30 | &[data-resize-handle-active] {
31 | background-color: #5194eb;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ChatGPT/src/components/Markdown/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import clsx from 'clsx';
3 | import ReactMarkdown from 'react-markdown';
4 | import remarkGfm from 'remark-gfm';
5 | import rehypeRaw from 'rehype-raw';
6 | import SyntaxHighlighter from 'react-syntax-highlighter';
7 | import agate from 'react-syntax-highlighter/dist/esm/styles/hljs/agate';
8 | import 'github-markdown-css';
9 |
10 | import './index.scss';
11 |
12 | interface MarkdownProps {
13 | children: string;
14 | className?: string;
15 | }
16 |
17 | const Markdown: FC = ({ children, className }) => {
18 | return (
19 |
20 |
21 |
39 | ) : (
40 |
41 | {children}
42 |
43 | );
44 | },
45 | }}
46 | />
47 |
48 |
49 | );
50 | };
51 |
52 | export default Markdown;
53 |
--------------------------------------------------------------------------------
/ChatGPT/src/components/SwitchOrigin/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { Form, Select, Tag, Tooltip, Switch } from 'antd';
4 | import { QuestionCircleOutlined } from '@ant-design/icons';
5 |
6 | import useJson from '@/hooks/useJson';
7 | import { DISABLE_AUTO_COMPLETE, CHAT_AWESOME_JSON } from '@/utils';
8 | interface SwitchOriginProps {
9 | name: string;
10 | }
11 |
12 | const SwitchOrigin: FC = ({ name }) => {
13 | const { json: list = [] } = useJson(CHAT_AWESOME_JSON);
14 | const form = Form.useFormInstance();
15 |
16 | const labelName = `(${name === 'main' ? 'Main' : 'SystemTray'})`;
17 | const dashboardName = `${name}_dashboard`;
18 | const originName = `${name}_origin`;
19 | const isEnable = Form.useWatch(dashboardName, form);
20 |
21 | let urlList = [{ title: 'ChatGPT', url: 'https://chat.openai.com', init: true }];
22 | if (Array.isArray(list)) {
23 | urlList = urlList.concat(list);
24 | }
25 |
26 | return (
27 | <>
28 |
31 | Dashboard {labelName}{' '}
32 |
35 |
36 | Set Dashboard as the application default window.
37 |
38 |
39 | If this is enabled, the Switch Origin {labelName}{' '}
40 | setting will be invalid.
41 |
42 |
43 | If you want to add a new URL to the dashboard, add it in the{' '}
44 | Awesome menu and make sure it is enabled.
45 |
46 |
47 | }
48 | >
49 |
50 |
51 |
52 | }
53 | name={dashboardName}
54 | valuePropName="checked"
55 | >
56 |
57 |
58 |
61 | Switch Origin {labelName}{' '}
62 |
65 |
66 | Set a single URL as the application default window.
67 |
68 |
69 | If you need to set a new URL as the application loading window, please add the
70 | URL in the Awesome menu and then select it.
71 |
72 |
73 | }
74 | >
75 |
76 |
77 |
78 | }
79 | name={originName}
80 | >
81 |
93 |
94 | >
95 | );
96 | };
97 |
98 | export default SwitchOrigin;
99 |
--------------------------------------------------------------------------------
/ChatGPT/src/components/Tags/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useRef, useState } from 'react';
2 | import { PlusOutlined } from '@ant-design/icons';
3 | import { Input, Tag } from 'antd';
4 | import type { InputRef } from 'antd';
5 |
6 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
7 |
8 | interface TagsProps {
9 | value?: string[];
10 | onChange?: (v: string[]) => void;
11 | addTxt?: string;
12 | max?: number;
13 | }
14 |
15 | const Tags: FC = ({ max = 99, value = [], onChange, addTxt = 'New Tag' }) => {
16 | const [tags, setTags] = useState(value);
17 | const [inputVisible, setInputVisible] = useState(false);
18 | const [inputValue, setInputValue] = useState('');
19 | const inputRef = useRef(null);
20 |
21 | useEffect(() => {
22 | setTags(value);
23 | }, [value]);
24 |
25 | useEffect(() => {
26 | if (inputVisible) {
27 | inputRef.current?.focus();
28 | }
29 | }, [inputVisible]);
30 |
31 | const handleClose = (removedTag: string) => {
32 | const newTags = tags.filter((tag) => tag !== removedTag);
33 | setTags(newTags);
34 | };
35 |
36 | const showInput = () => {
37 | setInputVisible(true);
38 | };
39 |
40 | const handleInputChange = (e: React.ChangeEvent) => {
41 | setInputValue(e.target.value);
42 | };
43 |
44 | const handleInputConfirm = () => {
45 | if (inputValue && tags.indexOf(inputValue) === -1) {
46 | const val = [...tags, inputValue];
47 | setTags(val);
48 | onChange && onChange(val);
49 | }
50 | setInputVisible(false);
51 | setInputValue('');
52 | };
53 |
54 | const forMap = (tag: string) => {
55 | const tagElem = (
56 | {
59 | e.preventDefault();
60 | handleClose(tag);
61 | }}
62 | >
63 | {tag}
64 |
65 | );
66 | return (
67 |
68 | {tagElem}
69 |
70 | );
71 | };
72 |
73 | const tagChild = tags.map(forMap);
74 |
75 | return (
76 | <>
77 | {tagChild}
78 | {inputVisible && (
79 |
90 | )}
91 | {!inputVisible && tags.length < max && (
92 |
93 | {addTxt}
94 |
95 | )}
96 | >
97 | );
98 | };
99 |
100 | export default Tags;
101 |
--------------------------------------------------------------------------------
/ChatGPT/src/hooks/useChatModel.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { clone } from 'lodash';
3 | import { invoke } from '@tauri-apps/api';
4 |
5 | import { CHAT_MODEL_JSON, CHAT_MODEL_CMD_JSON, readJSON, writeJSON } from '@/utils';
6 | import useInit from '@/hooks/useInit';
7 |
8 | export default function useChatModel(key: string, file = CHAT_MODEL_JSON) {
9 | const [modelJson, setModelJson] = useState>({});
10 |
11 | useInit(async () => {
12 | const data = await readJSON(file, {
13 | defaultVal: { name: 'ChatGPT Model', [key]: null },
14 | });
15 | setModelJson(data);
16 | });
17 |
18 | const modelSet = async (data: Record[] | Record) => {
19 | const oData = clone(modelJson);
20 | oData[key] = data;
21 | await writeJSON(file, oData);
22 | setModelJson(oData);
23 | };
24 |
25 | return { modelJson, modelSet, modelData: modelJson?.[key] || [] };
26 | }
27 |
28 | export function useCacheModel(file = '') {
29 | const [modelCacheJson, setModelCacheJson] = useState[]>([]);
30 |
31 | useEffect(() => {
32 | if (!file) return;
33 | (async () => {
34 | const data = await readJSON(file, { isRoot: true, isList: true });
35 | setModelCacheJson(data);
36 | })();
37 | }, [file]);
38 |
39 | const modelCacheSet = async (data: Record[], newFile = '') => {
40 | await writeJSON(newFile ? newFile : file, data, { isRoot: true });
41 | setModelCacheJson(data);
42 | await modelCacheCmd();
43 | };
44 |
45 | const modelCacheCmd = async () => {
46 | // Generate the `chat.model.cmd.json` file and refresh the page for the slash command to take effect.
47 | const list = await invoke('cmd_list');
48 | await writeJSON(CHAT_MODEL_CMD_JSON, {
49 | name: 'ChatGPT CMD',
50 | last_updated: Date.now(),
51 | data: list,
52 | });
53 | await invoke('window_reload', { label: 'core' });
54 | await invoke('window_reload', { label: 'tray' });
55 | };
56 |
57 | return { modelCacheJson, modelCacheSet, modelCacheCmd };
58 | }
59 |
--------------------------------------------------------------------------------
/ChatGPT/src/hooks/useColumns.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, useCallback } from 'react';
2 | import { Input } from 'antd';
3 |
4 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
5 |
6 | export default function useColumns(columns: any[] = []) {
7 | const [opType, setOpType] = useState('');
8 | const [opRecord, setRecord] = useState | null>(null);
9 | const [opTime, setNow] = useState(null);
10 | const [opExtra, setExtra] = useState(null);
11 |
12 | const handleRecord = useCallback((row: Record | null, type: string) => {
13 | setOpType(type);
14 | setRecord(row);
15 | setNow(Date.now());
16 | }, []);
17 |
18 | const resetRecord = useCallback(() => {
19 | setRecord(null);
20 | setOpType('');
21 | setNow(Date.now());
22 | }, []);
23 |
24 | const opNew = useCallback(() => handleRecord(null, 'new'), [handleRecord]);
25 |
26 | const cols = columns.map((i: any) => {
27 | if (i.render) {
28 | const opRender = i.render;
29 | i.render = (text: string, row: Record) => {
30 | return opRender(text, row, { setRecord: handleRecord, setExtra });
31 | };
32 | }
33 | return i;
34 | });
35 |
36 | return {
37 | opTime,
38 | opType,
39 | opNew,
40 | columns: cols,
41 | opRecord,
42 | setRecord: handleRecord,
43 | resetRecord,
44 | setExtra,
45 | opExtra,
46 | };
47 | }
48 |
49 | interface EditRowProps {
50 | rowKey: string;
51 | row: Record;
52 | actions: any;
53 | }
54 | export const EditRow: FC = ({ rowKey, row, actions }) => {
55 | const [isEdit, setEdit] = useState(false);
56 | const [val, setVal] = useState(row[rowKey] || '');
57 | const handleEdit = () => {
58 | setEdit(true);
59 | };
60 | const handleChange = (e: React.ChangeEvent) => {
61 | setVal(e.target.value);
62 | };
63 |
64 | const handleSave = () => {
65 | setEdit(false);
66 | row[rowKey] = val?.trim();
67 | actions?.setRecord(row, 'rowedit');
68 | };
69 |
70 | return isEdit ? (
71 |
78 | ) : (
79 |
80 | {val}
81 |
82 | );
83 | };
84 |
--------------------------------------------------------------------------------
/ChatGPT/src/hooks/useData.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { v4 } from 'uuid';
3 |
4 | export const safeKey = Symbol('chat-id');
5 |
6 | export default function useData(oData: any[]) {
7 | const [opData, setData] = useState([]);
8 |
9 | useEffect(() => {
10 | opInit(oData);
11 | }, []);
12 |
13 | const opAdd = (val: any) => {
14 | const v = [val, ...opData];
15 | setData(v);
16 | return v;
17 | };
18 |
19 | const opInit = (val: any[] = []) => {
20 | if (!val || !Array.isArray(val)) return;
21 | const nData = val.map((i) => ({ [safeKey]: v4(), ...i }));
22 | setData(nData);
23 | };
24 |
25 | const opRemove = (id: string) => {
26 | const nData = opData.filter((i) => i[safeKey] !== id);
27 | setData(nData);
28 | return nData;
29 | };
30 |
31 | const opRemoveItems = (ids: string[]) => {
32 | const nData = opData.filter((i) => !ids.includes(i[safeKey]));
33 | setData(nData);
34 | return nData;
35 | };
36 |
37 | const opReplace = (id: string, data: any) => {
38 | const nData = [...opData];
39 | const idx = opData.findIndex((v) => v[safeKey] === id);
40 | nData[idx] = data;
41 | setData(nData);
42 | return nData;
43 | };
44 |
45 | const opReplaceItems = (ids: string[], data: any) => {
46 | const nData = [...opData];
47 | let count = 0;
48 | for (let i = 0; i < nData.length; i++) {
49 | const v = nData[i];
50 | if (ids.includes(v[safeKey])) {
51 | count++;
52 | nData[i] = { ...v, ...data };
53 | }
54 | if (count === ids.length) break;
55 | }
56 | setData(nData);
57 | return nData;
58 | };
59 |
60 | return {
61 | opSafeKey: safeKey,
62 | opInit,
63 | opReplace,
64 | opAdd,
65 | opRemove,
66 | opRemoveItems,
67 | opData,
68 | opReplaceItems,
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/ChatGPT/src/hooks/useInit.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react';
2 |
3 | // fix: Two interface requests will be made in development mode
4 | export default function useInit(callback: () => void) {
5 | const isInit = useRef(true);
6 | useEffect(() => {
7 | if (isInit.current) {
8 | callback();
9 | isInit.current = false;
10 | }
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/ChatGPT/src/hooks/useJson.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | import { readJSON, writeJSON } from '@/utils';
4 | import useInit from '@/hooks/useInit';
5 |
6 | export default function useJson(file: string) {
7 | const [json, setData] = useState();
8 |
9 | const refreshJson = async () => {
10 | const data = await readJSON(file);
11 | setData(data);
12 | return data;
13 | };
14 |
15 | const updateJson = async (data: any) => {
16 | await writeJSON(file, data);
17 | await refreshJson();
18 | };
19 |
20 | useInit(refreshJson);
21 |
22 | return { json, refreshJson, updateJson };
23 | }
24 |
--------------------------------------------------------------------------------
/ChatGPT/src/hooks/useTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Table } from 'antd';
3 | import type { TableRowSelection } from 'antd/es/table/interface';
4 |
5 | import { safeKey } from '@/hooks/useData';
6 |
7 | type rowSelectionOptions = {
8 | key: 'id' | string;
9 | rowType: 'id' | 'row' | 'all';
10 | };
11 | export function useTableRowSelection(options?: Partial) {
12 | const { key = 'id', rowType = 'id' } = options || {};
13 | const [selectedRowKeys, setSelectedRowKeys] = useState([]);
14 | const [selectedRowIDs, setSelectedRowIDs] = useState([]);
15 | const [selectedRows, setSelectedRows] = useState[]>([]);
16 |
17 | const onSelectChange = (
18 | newSelectedRowKeys: React.Key[],
19 | newSelectedRows: Record[],
20 | ) => {
21 | const keys = newSelectedRows.map((i: any) => i[safeKey] || i[key]);
22 | setSelectedRowKeys(newSelectedRowKeys);
23 | if (rowType === 'id') {
24 | setSelectedRowIDs(keys);
25 | }
26 | if (rowType === 'row') {
27 | setSelectedRows(newSelectedRows);
28 | }
29 | if (rowType === 'all') {
30 | setSelectedRowIDs(keys);
31 | setSelectedRows(newSelectedRows);
32 | }
33 | };
34 |
35 | const rowReset = () => {
36 | setSelectedRowKeys([]);
37 | setSelectedRowIDs([]);
38 | setSelectedRows([]);
39 | };
40 |
41 | const rowSelection: TableRowSelection> = {
42 | selectedRowKeys,
43 | onChange: onSelectChange,
44 | selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE],
45 | };
46 |
47 | return { rowSelection, selectedRowIDs, selectedRows, rowReset };
48 | }
49 |
50 | export const TABLE_PAGINATION = {
51 | hideOnSinglePage: true,
52 | showSizeChanger: true,
53 | showQuickJumper: true,
54 | defaultPageSize: 10,
55 | pageSizeOptions: [5, 10, 15, 20],
56 | showTotal: (total: number) => Total {total} items,
57 | };
58 |
--------------------------------------------------------------------------------
/ChatGPT/src/icons/SplitIcon.tsx:
--------------------------------------------------------------------------------
1 | import Icon from '@ant-design/icons';
2 | import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
3 |
4 | interface IconProps {
5 | onClick: () => void;
6 | }
7 |
8 | export default function SplitIcon(props: Partial) {
9 | return (
10 | (
13 |
22 | )}
23 | />
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/ChatGPT/src/layout/index.scss:
--------------------------------------------------------------------------------
1 | .chat-logo {
2 | text-align: center;
3 | height: 48px;
4 |
5 | img {
6 | width: 44px;
7 | height: 44px;
8 | margin-top: 4px;
9 | }
10 | }
11 |
12 | .chat-info {
13 | text-align: center;
14 | font-weight: bold;
15 |
16 | .ant-tag {
17 | margin: 2px;
18 | }
19 | }
20 |
21 | .ant-layout-sider-trigger {
22 | user-select: none;
23 | -webkit-user-select: none;
24 | }
25 |
26 | .ant-layout-sider-children {
27 | overflow-y: auto;
28 | }
29 |
30 | .chat-container {
31 | padding: 20px;
32 | overflow: hidden;
33 | }
34 |
35 | .ant-menu {
36 | user-select: none;
37 | -webkit-user-select: none;
38 | }
39 |
40 | .ant-layout-footer {
41 | color: #666 !important;
42 | opacity: 0.7;
43 | }
44 |
--------------------------------------------------------------------------------
/ChatGPT/src/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Layout, Menu, Tooltip, ConfigProvider, theme, Tag } from 'antd';
3 | import { SyncOutlined } from '@ant-design/icons';
4 | import { useNavigate, useLocation } from 'react-router-dom';
5 | import { getName, getVersion } from '@tauri-apps/api/app';
6 | import { invoke } from '@tauri-apps/api';
7 |
8 | import useInit from '@/hooks/useInit';
9 | import Routes, { menuItems } from '@/routes';
10 | import './index.scss';
11 |
12 | const { Content, Footer, Sider } = Layout;
13 |
14 | export default function ChatLayout() {
15 | const [collapsed, setCollapsed] = useState(false);
16 | const [isDashboard, setDashboard] = useState(null);
17 | const [appInfo, setAppInfo] = useState>({});
18 | const location = useLocation();
19 | const [menuKey, setMenuKey] = useState(location.pathname);
20 | const go = useNavigate();
21 |
22 | useEffect(() => {
23 | if (location.search === '?type=control') {
24 | go('/settings');
25 | }
26 | if (location.search === '?type=preview') {
27 | go('/?type=preview');
28 | }
29 | setMenuKey(location.pathname);
30 | setDashboard(location.pathname === '/');
31 | }, [location.pathname]);
32 |
33 | useInit(async () => {
34 | setAppInfo({
35 | appName: await getName(),
36 | appVersion: await getVersion(),
37 | appTheme: await invoke('get_theme'),
38 | });
39 | });
40 |
41 | const checkAppUpdate = async () => {
42 | await invoke('run_check_update', { silent: false, hasMsg: true });
43 | };
44 |
45 | const isDark = appInfo.appTheme === 'dark';
46 |
47 | if (isDashboard === null) return null;
48 |
49 | return (
50 |
51 | {isDashboard ? (
52 |
53 | ) : (
54 |
55 | setCollapsed(value)}
60 | style={{
61 | overflow: 'auto',
62 | height: '100vh',
63 | position: 'fixed',
64 | left: 0,
65 | top: 0,
66 | bottom: 0,
67 | zIndex: 999,
68 | }}
69 | >
70 |
71 |

72 |
73 |
74 |
{appInfo.appName}
75 |
76 | {appInfo.appVersion}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
95 |
99 |
105 |
106 |
107 |
113 |
114 |
115 | )}
116 |
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/ChatGPT/src/main.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color: #2a2a2a;
8 | background-color: #f6f6f6;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | html,
18 | body,
19 | #root {
20 | padding: 0;
21 | margin: 0;
22 | height: 100%;
23 | }
24 |
25 | .ant-table-selection-col,
26 | .ant-table-selection-column {
27 | width: 50px !important;
28 | min-width: 50px !important;
29 | }
30 |
31 | .chat-prompts-val {
32 | display: inline-block;
33 | width: 100%;
34 | max-width: 300px;
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | display: -webkit-box;
38 | -webkit-line-clamp: 2;
39 | -webkit-box-orient: vertical;
40 | }
41 |
42 | .ellipsis-line {
43 | display: inline-block;
44 | width: 180px;
45 | overflow: hidden;
46 | text-overflow: ellipsis;
47 | white-space: nowrap;
48 | }
49 |
50 | .rowedit {
51 | padding: 2px 5px;
52 |
53 | &:hover {
54 | box-shadow: 0 0 2px rgba(237, 122, 60, 0.8);
55 | border-radius: 4px;
56 | }
57 | }
58 |
59 | .chat-add-btn {
60 | margin-bottom: 5px;
61 | }
62 |
63 | .chat-tags,
64 | .chat-prompts-tags {
65 | .ant-tag {
66 | margin: 2px;
67 | }
68 | }
69 |
70 | .chat-table-tip {
71 | > span {
72 | line-height: 16px;
73 | }
74 | }
75 |
76 | .chat-file-path {
77 | font-size: 12px;
78 | font-weight: 500;
79 | color: #888;
80 | margin-bottom: 3px;
81 | line-height: 16px;
82 |
83 | > div {
84 | max-width: 400px;
85 | overflow: hidden;
86 | text-overflow: ellipsis;
87 | white-space: nowrap;
88 | }
89 |
90 | span {
91 | display: inline-block;
92 | // background-color: #d8d8d8;
93 | color: #4096ff;
94 | padding: 0 8px;
95 | height: 20px;
96 | line-height: 20px;
97 | border-radius: 4px;
98 | cursor: pointer;
99 | text-decoration: underline;
100 | }
101 | }
102 |
103 | .chatico {
104 | cursor: pointer;
105 | }
106 |
107 | .awesome-tips {
108 | .ant-tag {
109 | cursor: pointer;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/ChatGPT/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode, Suspense } from 'react';
2 | import { BrowserRouter } from 'react-router-dom';
3 | import ReactDOM from 'react-dom/client';
4 |
5 | import Layout from '@/layout';
6 | import './main.scss';
7 |
8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
9 |
10 |
11 |
12 |
13 |
14 |
15 | ,
16 | );
17 |
--------------------------------------------------------------------------------
/ChatGPT/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import { useRoutes } from 'react-router-dom';
2 | import {
3 | SettingOutlined,
4 | BulbOutlined,
5 | SyncOutlined,
6 | FileSyncOutlined,
7 | UserOutlined,
8 | DownloadOutlined,
9 | FormOutlined,
10 | GlobalOutlined,
11 | InfoCircleOutlined,
12 | } from '@ant-design/icons';
13 | import type { MenuProps } from 'antd';
14 |
15 | import Settings from '@/view/settings';
16 | import About from '@/view/about';
17 | import Awesome from '@/view/awesome';
18 | import UserCustom from '@/view/model/UserCustom';
19 | import SyncPrompts from '@/view/model/SyncPrompts';
20 | import SyncCustom from '@/view/model/SyncCustom';
21 | import SyncRecord from '@/view/model/SyncRecord';
22 | import Download from '@/view/download';
23 | import Notes from '@/view/notes';
24 | import Markdown from '@/view/markdown';
25 | import Dashboard from '@/view/dashboard';
26 |
27 | export type ChatRouteMetaObject = {
28 | label: string;
29 | icon?: React.ReactNode;
30 | };
31 |
32 | type ChatRouteObject = {
33 | path: string;
34 | element?: JSX.Element;
35 | hideMenu?: boolean;
36 | meta?: ChatRouteMetaObject;
37 | children?: ChatRouteObject[];
38 | };
39 |
40 | export const routes: Array = [
41 | {
42 | path: '/settings',
43 | element: ,
44 | meta: {
45 | label: 'Settings',
46 | icon: ,
47 | },
48 | },
49 | {
50 | path: '/awesome',
51 | element: ,
52 | meta: {
53 | label: 'Awesome',
54 | icon: ,
55 | },
56 | },
57 | {
58 | path: '/notes',
59 | element: ,
60 | meta: {
61 | label: 'Notes',
62 | icon: ,
63 | },
64 | },
65 | {
66 | path: '/md/:id',
67 | element: ,
68 | hideMenu: true,
69 | },
70 | {
71 | path: '/model',
72 | meta: {
73 | label: 'Language Model',
74 | icon: ,
75 | },
76 | children: [
77 | {
78 | path: 'user-custom',
79 | element: ,
80 | meta: {
81 | label: 'User Custom',
82 | icon: ,
83 | },
84 | },
85 | // --- Sync
86 | {
87 | path: 'sync-prompts',
88 | element: ,
89 | meta: {
90 | label: 'Sync Prompts',
91 | icon: ,
92 | },
93 | },
94 | {
95 | path: 'sync-custom',
96 | element: ,
97 | meta: {
98 | label: 'Sync Custom',
99 | icon: ,
100 | },
101 | },
102 | {
103 | path: 'sync-custom/:id',
104 | element: ,
105 | hideMenu: true,
106 | },
107 | ],
108 | },
109 | {
110 | path: '/download',
111 | element: ,
112 | meta: {
113 | label: 'Download',
114 | icon: ,
115 | },
116 | },
117 | {
118 | path: '/about',
119 | element: ,
120 | meta: {
121 | label: 'About',
122 | icon: ,
123 | },
124 | },
125 | {
126 | path: '/',
127 | element: ,
128 | hideMenu: true,
129 | },
130 | ];
131 |
132 | type MenuItem = Required['items'][number];
133 | export const menuItems: MenuItem[] = routes
134 | .filter((j) => !j.hideMenu)
135 | .map((i) => ({
136 | ...i.meta,
137 | key: i.path || '',
138 | children: i?.children
139 | ?.filter((j) => !j.hideMenu)
140 | ?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || '' })),
141 | }));
142 |
143 | export default () => {
144 | return useRoutes(routes);
145 | };
146 |
--------------------------------------------------------------------------------
/ChatGPT/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { readTextFile, writeTextFile, exists, createDir } from '@tauri-apps/api/fs';
2 | import { homeDir, join, dirname } from '@tauri-apps/api/path';
3 | import dayjs from 'dayjs';
4 |
5 | export const APP_CONF_JSON = 'chat.conf.json';
6 | export const CHAT_MODEL_JSON = 'chat.model.json';
7 | export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json';
8 | export const CHAT_DOWNLOAD_JSON = 'chat.download.json';
9 | export const CHAT_AWESOME_JSON = 'chat.awesome.json';
10 | export const CHAT_NOTES_JSON = 'chat.notes.json';
11 | export const CHAT_PROMPTS_CSV = 'chat.prompts.csv';
12 | export const GITHUB_PROMPTS_CSV_URL =
13 | 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv';
14 | export const GITHUB_LOG_URL = 'https://raw.githubusercontent.com/lencx/ChatGPT/main/UPDATE_LOG.md';
15 |
16 | export const DISABLE_AUTO_COMPLETE = {
17 | autoCapitalize: 'off',
18 | autoComplete: 'off',
19 | spellCheck: false,
20 | };
21 |
22 | export const chatRoot = async () => {
23 | return join(await homeDir(), '.chatgpt');
24 | };
25 |
26 | export const chatModelPath = async (): Promise => {
27 | return join(await chatRoot(), CHAT_MODEL_JSON);
28 | };
29 |
30 | export const chatPromptsPath = async (): Promise => {
31 | return join(await chatRoot(), CHAT_PROMPTS_CSV);
32 | };
33 |
34 | type readJSONOpts = { defaultVal?: Record; isRoot?: boolean; isList?: boolean };
35 | export const readJSON = async (path: string, opts: readJSONOpts = {}) => {
36 | const { defaultVal = {}, isRoot = false, isList = false } = opts;
37 | const root = await chatRoot();
38 | let file = path;
39 |
40 | if (!isRoot) {
41 | file = await join(root, path);
42 | }
43 |
44 | if (!(await exists(file))) {
45 | if ((await dirname(file)) !== root) {
46 | await createDir(await dirname(file), { recursive: true });
47 | }
48 | await writeTextFile(
49 | file,
50 | isList
51 | ? '[]'
52 | : JSON.stringify(
53 | {
54 | name: 'ChatGPT',
55 | link: 'https://github.com/lencx/ChatGPT',
56 | ...defaultVal,
57 | },
58 | null,
59 | 2,
60 | ),
61 | );
62 | }
63 |
64 | try {
65 | return JSON.parse(await readTextFile(file));
66 | } catch (e) {
67 | return {};
68 | }
69 | };
70 |
71 | type writeJSONOpts = { dir?: string; isRoot?: boolean };
72 | export const writeJSON = async (
73 | path: string,
74 | data: Record,
75 | opts: writeJSONOpts = {},
76 | ) => {
77 | const { isRoot = false } = opts;
78 | const root = await chatRoot();
79 | let file = path;
80 |
81 | if (!isRoot) {
82 | file = await join(root, path);
83 | }
84 |
85 | if (isRoot && !(await exists(await dirname(file)))) {
86 | await createDir(await dirname(file), { recursive: true });
87 | }
88 |
89 | await writeTextFile(file, JSON.stringify(data, null, 2));
90 | };
91 |
92 | export const fmtDate = (date: any) => dayjs(date).format('YYYY-MM-DD HH:mm:ss');
93 |
94 | export const genCmd = (act: string) =>
95 | act
96 | .replace(/\s+|\/+/g, '_')
97 | .replace(/[^\d\w]/g, '')
98 | .toLocaleLowerCase();
99 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/about/index.scss:
--------------------------------------------------------------------------------
1 | .about {
2 | .log-tab {
3 | font-size: 14px;
4 |
5 | h2 {
6 | font-size: 16px;
7 | }
8 | }
9 |
10 | .markdown-body {
11 | background-color: unset;
12 | }
13 |
14 | .about-tab {
15 | .imgs {
16 | img {
17 | max-width: 200px;
18 | margin-bottom: 20px;
19 | }
20 | }
21 | }
22 |
23 | code {
24 | background-color: rgba(200, 200, 200, 0.4);
25 | padding: 2px 4px;
26 | border-radius: 5px;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/about/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { invoke } from '@tauri-apps/api';
3 | import { Tabs, Tag } from 'antd';
4 |
5 | import { GITHUB_LOG_URL } from '@/utils';
6 | import useInit from '@/hooks/useInit';
7 | import Markdown from '@/components/Markdown';
8 | import './index.scss';
9 |
10 | export default function About() {
11 | const [logContent, setLogContent] = useState('');
12 |
13 | useInit(async () => {
14 | const data = (await invoke('get_data', { url: GITHUB_LOG_URL })) || '';
15 | setLogContent(data as string);
16 | });
17 |
18 | return (
19 |
20 |
},
23 | { label: 'About ChatGPT', key: 'about', children:
},
24 | { label: 'Update Log', key: 'log', children:
},
25 | ]}
26 | />
27 |
28 | );
29 | }
30 |
31 | const AboutLangHelper = () =>{
32 | return (
33 |
34 |
35 | This is new function for learning languages, you can talk with ChatGPT with natural
36 | AI sound, get pronuciation assessment, memorize words with context, practice listening,
37 | so on, its based on ChatGPT Desktop@lencx, I've been developing this language helper on
38 | the shoulders of gaints, if it helps you, do not hesitate to star it, any problems
39 | you can follow my channel
40 | https://space.bilibili.com/33672855
41 | to get solutions and update,
42 | and if you have new ideas for it, please contact my email
43 | nslearning888@gmail.com
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | const AboutChatGPT = () => {
51 | return (
52 |
53 |
ChatGPT Desktop Application (Mac, Windows and Linux)
54 |
55 | 🕒 History versions:{' '}
56 |
57 | lencx/ChatGPT/releases
58 |
59 |
60 |
61 | It is just a wrapper for the
62 |
63 | {' '}
64 | OpenAI ChatGPT{' '}
65 |
66 | website, no other data transfer exists (you can check the{' '}
67 |
72 | {' '}
73 | source code{' '}
74 |
75 | ). The development and maintenance of this software has taken up a lot of my time. If it
76 | helps you, you can buy me a cup of coffee (Chinese users can use WeChat to scan the code),
77 | thanks!
78 |
79 |
80 |
81 |
85 | {' '}
86 |
87 |
91 |
92 |

96 |
97 | );
98 | };
99 |
100 | const LogTab = ({ content }: { content: string }) => {
101 | return (
102 |
111 | );
112 | };
113 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/awesome/Form.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, ForwardRefRenderFunction, useImperativeHandle, forwardRef } from 'react';
2 | import { Form, Input, Switch } from 'antd';
3 | import type { FormProps } from 'antd';
4 |
5 | import Tags from '@comps/Tags';
6 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
7 |
8 | interface AwesomeFormProps {
9 | record?: Record | null;
10 | }
11 |
12 | const initFormValue = {
13 | title: '',
14 | url: '',
15 | enable: true,
16 | tags: [],
17 | category: '',
18 | };
19 |
20 | const AwesomeForm: ForwardRefRenderFunction = ({ record }, ref) => {
21 | const [form] = Form.useForm();
22 | useImperativeHandle(ref, () => ({ form }));
23 |
24 | useEffect(() => {
25 | if (record) {
26 | form.setFieldsValue(record);
27 | }
28 | }, [record]);
29 |
30 | return (
31 |
37 |
38 |
39 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default forwardRef(AwesomeForm);
64 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/awesome/config.tsx:
--------------------------------------------------------------------------------
1 | import { Tag, Space, Popconfirm, Switch } from 'antd';
2 | import { open } from '@tauri-apps/api/shell';
3 |
4 | export const awesomeColumns = () => [
5 | {
6 | title: 'Title',
7 | dataIndex: 'title',
8 | fixed: 'left',
9 | key: 'title',
10 | width: 160,
11 | },
12 | {
13 | title: 'URL',
14 | dataIndex: 'url',
15 | key: 'url',
16 | width: 200,
17 | render: (v: string) => open(v)}>{v},
18 | },
19 | // {
20 | // title: 'Icon',
21 | // dataIndex: 'icon',
22 | // key: 'icon',
23 | // width: 120,
24 | // },
25 | {
26 | title: 'Enable',
27 | dataIndex: 'enable',
28 | key: 'enable',
29 | width: 80,
30 | render: (v: boolean = true, row: Record, action: Record) => (
31 | action.setRecord({ ...row, enable: v }, 'enable')} />
32 | ),
33 | },
34 | {
35 | title: 'Category',
36 | dataIndex: 'category',
37 | key: 'category',
38 | width: 120,
39 | render: (v: string) => {v},
40 | },
41 | {
42 | title: 'Tags',
43 | dataIndex: 'tags',
44 | key: 'tags',
45 | width: 150,
46 | render: (v: string[]) => (
47 |
48 | {v?.map((i) => (
49 | {i}
50 | ))}
51 |
52 | ),
53 | },
54 | {
55 | title: 'Action',
56 | fixed: 'right',
57 | width: 150,
58 | render: (_: any, row: any, actions: any) => {
59 | return (
60 |
61 | actions.setRecord(row, 'edit')}>Edit
62 | actions.setRecord(row, 'delete')}
65 | okText="Yes"
66 | cancelText="No"
67 | >
68 | Delete
69 |
70 |
71 | );
72 | },
73 | },
74 | ];
75 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/dashboard/index.scss:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | position: fixed;
3 | width: calc(100% - 30px);
4 | height: calc(100% - 30px);
5 | overflow-y: auto;
6 | padding: 15px;
7 |
8 | &-no-data {
9 | height: 100%;
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | text-align: center;
14 | flex-direction: column;
15 | font-weight: bold;
16 |
17 | .icon {
18 | color: #989898;
19 | font-size: 24px;
20 | }
21 |
22 | .txt {
23 | padding: 10px;
24 | font-size: 12px;
25 | line-height: 16px;
26 |
27 | a {
28 | color: #1677ff;
29 | cursor: pointer;
30 | }
31 | }
32 | }
33 |
34 | &.dark {
35 | background-color: #000;
36 | }
37 |
38 | &.has-top-dom {
39 | padding-top: 30px;
40 | }
41 |
42 | &.preview {
43 | padding-top: 15px;
44 | }
45 |
46 | .group-item {
47 | margin-bottom: 20px;
48 |
49 | .title {
50 | font-weight: bold;
51 | font-size: 18px;
52 | margin-bottom: 10px;
53 | }
54 |
55 | .item {
56 | .ant-card-body {
57 | padding: 10px;
58 | text-align: center;
59 | font-weight: 500;
60 | font-size: 14px;
61 | }
62 |
63 | span {
64 | display: block;
65 | height: 100%;
66 | width: 100%;
67 | overflow: hidden;
68 | text-overflow: ellipsis;
69 | white-space: nowrap;
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import clsx from 'clsx';
3 | import { useSearchParams } from 'react-router-dom';
4 | import { Row, Col, Card } from 'antd';
5 | import { InboxOutlined } from '@ant-design/icons';
6 | import { os, invoke } from '@tauri-apps/api';
7 |
8 | import useInit from '@/hooks/useInit';
9 | import useJson from '@/hooks/useJson';
10 | import { CHAT_AWESOME_JSON, APP_CONF_JSON, readJSON } from '@/utils';
11 | import './index.scss';
12 |
13 | export default function Dashboard() {
14 | const [params] = useSearchParams();
15 | const { json } = useJson[]>(CHAT_AWESOME_JSON);
16 | const [list, setList] = useState[]]>>();
17 | const [hasClass, setClass] = useState(false);
18 | const [theme, setTheme] = useState('');
19 |
20 | useInit(async () => {
21 | const getOS = await os.platform();
22 | const conf = await readJSON(APP_CONF_JSON);
23 | const appTheme = await invoke('get_theme');
24 | setTheme(appTheme as string);
25 | setClass(!conf?.titlebar && getOS === 'darwin');
26 | });
27 |
28 | useEffect(() => {
29 | if (!json) return;
30 | const categories = new Map();
31 |
32 | if (Array.isArray(json)) {
33 | json?.forEach((i) => {
34 | if (!i.enable) return;
35 | if (!categories.has(i.category)) {
36 | categories.set(i.category, []);
37 | }
38 | categories.get(i?.category).push(i);
39 | });
40 | setList(Array.from(categories) || []);
41 | } else {
42 | setList([]);
43 | }
44 | }, [JSON.stringify(json)]);
45 |
46 | const handleLink = async (item: Record) => {
47 | await invoke('wa_window', {
48 | label: btoa(item.url).replace(/[^a-zA-Z0-9]/g, ''),
49 | title: item.title,
50 | url: item.url,
51 | });
52 | };
53 |
54 | if (!list) return null;
55 | if (list?.length === 0) {
56 | return (
57 |
58 |
59 |
60 |
61 | No data
62 |
63 |
67 |
68 | );
69 | }
70 |
71 | return (
72 |
78 |
79 | {list.map((i) => {
80 | return (
81 |
82 |
83 |
84 | {i[1].map((j, idx) => {
85 | return (
86 |
94 | handleLink(j)}>
95 | {j?.title}
96 |
97 |
98 | );
99 | })}
100 |
101 |
102 |
103 | );
104 | })}
105 |
106 |
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/download/config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Tag, Space, Popconfirm } from 'antd';
3 | import { path, shell } from '@tauri-apps/api';
4 |
5 | import { EditRow } from '@/hooks/useColumns';
6 |
7 | import useInit from '@/hooks/useInit';
8 | import { fmtDate, chatRoot } from '@/utils';
9 |
10 | const colorMap: any = {
11 | pdf: 'blue',
12 | png: 'orange',
13 | };
14 |
15 | export const downloadColumns = () => [
16 | {
17 | title: 'Name',
18 | dataIndex: 'name',
19 | fixed: 'left',
20 | key: 'name',
21 | width: 240,
22 | render: (_: string, row: any, actions: any) => (
23 |
24 | ),
25 | },
26 | {
27 | title: 'Extension',
28 | dataIndex: 'ext',
29 | key: 'ext',
30 | width: 120,
31 | render: (v: string) => {v},
32 | },
33 | {
34 | title: 'Path',
35 | dataIndex: 'path',
36 | key: 'path',
37 | width: 200,
38 | render: (_: string, row: any) => ,
39 | },
40 | {
41 | title: 'Created',
42 | dataIndex: 'created',
43 | key: 'created',
44 | width: 150,
45 | render: fmtDate,
46 | },
47 | {
48 | title: 'Action',
49 | fixed: 'right',
50 | width: 150,
51 | render: (_: any, row: any, actions: any) => {
52 | return (
53 |
54 | actions.setRecord(row, 'preview')}>Preview
55 | actions.setRecord(row, 'delete')}
58 | okText="Yes"
59 | cancelText="No"
60 | >
61 | Delete
62 |
63 |
64 | );
65 | },
66 | },
67 | ];
68 |
69 | const RenderPath = ({ row }: any) => {
70 | const [filePath, setFilePath] = useState('');
71 | useInit(async () => {
72 | setFilePath(await getPath(row));
73 | });
74 | return shell.open(filePath)}>{filePath};
75 | };
76 |
77 | export const getPath = async (row: any) => {
78 | const isImg = ['png'].includes(row?.ext);
79 | return (
80 | (await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id)) + `.${row.ext}`
81 | );
82 | };
83 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/download/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Table, Modal, Popconfirm, Button, message } from 'antd';
3 | import { invoke, path, fs } from '@tauri-apps/api';
4 |
5 | import useJson from '@/hooks/useJson';
6 | import useData from '@/hooks/useData';
7 | import useColumns from '@/hooks/useColumns';
8 | import FilePath from '@/components/FilePath';
9 | import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
10 | import { chatRoot, CHAT_DOWNLOAD_JSON } from '@/utils';
11 | import { downloadColumns } from './config';
12 |
13 | function renderFile(buff: Uint8Array, type: string) {
14 | const renderType = {
15 | pdf: 'application/pdf',
16 | png: 'image/png',
17 | }[type];
18 | return URL.createObjectURL(new Blob([buff], { type: renderType }));
19 | }
20 |
21 | export default function Download() {
22 | const [source, setSource] = useState('');
23 | const [isVisible, setVisible] = useState(false);
24 | const { opData, opInit, opReplace, opSafeKey } = useData([]);
25 | const { columns, ...opInfo } = useColumns(downloadColumns());
26 | const { rowSelection, selectedRows, rowReset } = useTableRowSelection({ rowType: 'row' });
27 | const { json, refreshJson, updateJson } = useJson(CHAT_DOWNLOAD_JSON);
28 | const selectedItems = rowSelection.selectedRowKeys || [];
29 |
30 | useEffect(() => {
31 | if (!json || json.length <= 0) return;
32 | opInit(json);
33 | }, [json?.length]);
34 |
35 | useEffect(() => {
36 | if (!opInfo.opType) return;
37 | (async () => {
38 | const record = opInfo?.opRecord;
39 | const isImg = ['png'].includes(record?.ext);
40 | const file = await path.join(
41 | await chatRoot(),
42 | 'download',
43 | isImg ? 'img' : record?.ext,
44 | `${record?.id}.${record?.ext}`,
45 | );
46 | if (opInfo.opType === 'preview') {
47 | const data = await fs.readBinaryFile(file);
48 | const sourceData = renderFile(data, record?.ext);
49 | setSource(sourceData);
50 | setVisible(true);
51 | return;
52 | }
53 | if (opInfo.opType === 'delete') {
54 | await fs.removeFile(file);
55 | await handleRefresh();
56 | }
57 | if (opInfo.opType === 'rowedit') {
58 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
59 | await updateJson(data);
60 | message.success('Name has been changed!');
61 | }
62 | opInfo.resetRecord();
63 | })();
64 | }, [opInfo.opType]);
65 |
66 | const handleDelete = async () => {
67 | if (opData?.length === selectedRows.length) {
68 | const downloadDir = await path.join(await chatRoot(), 'download');
69 | await fs.removeDir(downloadDir, { recursive: true });
70 | await handleRefresh();
71 | message.success('All files have been cleared!');
72 | return;
73 | }
74 |
75 | const rows = selectedRows.map(async (i) => {
76 | const isImg = ['png'].includes(i?.ext);
77 | const file = await path.join(
78 | await chatRoot(),
79 | 'download',
80 | isImg ? 'img' : i?.ext,
81 | `${i?.id}.${i?.ext}`,
82 | );
83 | await fs.removeFile(file);
84 | return file;
85 | });
86 | Promise.all(rows).then(async () => {
87 | await handleRefresh();
88 | message.success('All files selected are cleared!');
89 | });
90 | };
91 |
92 | const handleRefresh = async () => {
93 | await invoke('download_list', { pathname: CHAT_DOWNLOAD_JSON, dir: 'download' });
94 | rowReset();
95 | const data = await refreshJson();
96 | opInit(data);
97 | };
98 |
99 | const handleCancel = () => {
100 | setVisible(false);
101 | opInfo.resetRecord();
102 | };
103 |
104 | return (
105 |
106 |
107 |
108 | {selectedItems.length > 0 && (
109 | <>
110 |
118 |
119 |
120 |
Selected {selectedItems.length} items
121 | >
122 | )}
123 |
124 |
125 |
126 |
134 |
{opInfo?.opRecord?.name || ''} }
137 | onCancel={handleCancel}
138 | footer={false}
139 | destroyOnClose
140 | >
141 |
142 |
143 |
144 | );
145 | }
146 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/markdown/index.scss:
--------------------------------------------------------------------------------
1 | .md-task {
2 | margin-bottom: 5px;
3 | display: flex;
4 | justify-content: space-between;
5 |
6 | .ant-breadcrumb-link {
7 | padding: 3px 5px;
8 | transition: all 300ms ease;
9 | border-radius: 4px;
10 | &:hover {
11 | color: rgba(0, 0, 0, 0.88);
12 | background-color: rgba(0, 0, 0, 0.06);
13 | cursor: pointer;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/markdown/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 | import { Breadcrumb } from 'antd';
4 | import { ArrowLeftOutlined } from '@ant-design/icons';
5 | import MarkdownEditor from '@/components/Markdown/Editor';
6 | import { fs, shell } from '@tauri-apps/api';
7 |
8 | import useInit from '@/hooks/useInit';
9 | import SplitIcon from '@/icons/SplitIcon';
10 | import { getPath } from '@/view/notes/config';
11 | import './index.scss';
12 |
13 | const modeMap: any = {
14 | 0: 'split',
15 | 1: 'md',
16 | 2: 'doc',
17 | };
18 |
19 | export default function Markdown() {
20 | const [filePath, setFilePath] = useState('');
21 | const [source, setSource] = useState('');
22 | const [previewMode, setPreviewMode] = useState(0);
23 | const location = useLocation();
24 | const state = location?.state;
25 |
26 | useInit(async () => {
27 | const file = await getPath(state);
28 | setFilePath(file);
29 | setSource(await fs.readTextFile(file));
30 | });
31 |
32 | const handleChange = async (v: string) => {
33 | await fs.writeTextFile(filePath, v);
34 | };
35 |
36 | const handlePreview = () => {
37 | let mode = previewMode + 1;
38 | if (mode > 2) mode = 0;
39 | setPreviewMode(mode);
40 | };
41 |
42 | return (
43 | <>
44 |
45 |
46 | history.go(-1)}>
47 |
48 |
49 | shell.open(filePath)}>{filePath}
50 |
51 |
52 |
53 |
54 |
55 |
56 | >
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncCustom/Form.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | useEffect,
3 | useState,
4 | ForwardRefRenderFunction,
5 | useImperativeHandle,
6 | forwardRef,
7 | } from 'react';
8 | import { Form, Input, Select, Tooltip } from 'antd';
9 | import { v4 } from 'uuid';
10 | import type { FormProps } from 'antd';
11 |
12 | import { DISABLE_AUTO_COMPLETE, chatRoot } from '@/utils';
13 | import useInit from '@/hooks/useInit';
14 |
15 | interface SyncFormProps {
16 | record?: Record | null;
17 | type: string;
18 | }
19 |
20 | const initFormValue = {
21 | act: '',
22 | enable: true,
23 | tags: [],
24 | prompt: '',
25 | };
26 |
27 | const SyncForm: ForwardRefRenderFunction = ({ record, type }, ref) => {
28 | const isDisabled = type === 'edit';
29 | const [form] = Form.useForm();
30 | useImperativeHandle(ref, () => ({ form }));
31 | const [root, setRoot] = useState('');
32 |
33 | useInit(async () => {
34 | setRoot(await chatRoot());
35 | });
36 |
37 | useEffect(() => {
38 | if (record) {
39 | form.setFieldsValue(record);
40 | }
41 | }, [record]);
42 |
43 | const pathOptions = (
44 |
45 |
50 |
51 | );
52 | const extOptions = (
53 |
54 |
58 |
59 | );
60 |
61 | const jsonTip = (
62 |
65 | {JSON.stringify(
66 | [
67 | { cmd: '', act: '', prompt: '' },
68 | { cmd: '', act: '', prompt: '' },
69 | ],
70 | null,
71 | 2,
72 | )}
73 |
74 | }
75 | >
76 | JSON
77 |
78 | );
79 |
80 | const csvTip = (
81 | {`"cmd","act","prompt"
84 | "cmd","act","prompt"
85 | "cmd","act","prompt"
86 | "cmd","act","prompt"`}
87 | }
88 | >
89 | CSV
90 |
91 | );
92 |
93 | return (
94 | <>
95 |
101 |
102 |
103 |
108 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | The file supports only {csvTip} and {jsonTip} formats.
122 |
123 |
124 | >
125 | );
126 | };
127 |
128 | export default forwardRef(SyncForm);
129 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncCustom/config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Tag, Space, Popconfirm } from 'antd';
3 | import { HistoryOutlined } from '@ant-design/icons';
4 | import { shell, path } from '@tauri-apps/api';
5 | import { Link } from 'react-router-dom';
6 |
7 | import useInit from '@/hooks/useInit';
8 | import { chatRoot, fmtDate } from '@/utils';
9 |
10 | export const syncColumns = () => [
11 | {
12 | title: 'Name',
13 | dataIndex: 'name',
14 | key: 'name',
15 | width: 100,
16 | },
17 | {
18 | title: 'Protocol',
19 | dataIndex: 'protocol',
20 | key: 'protocol',
21 | width: 80,
22 | render: (v: string) => {v},
23 | },
24 | {
25 | title: 'PATH',
26 | dataIndex: 'path',
27 | key: 'path',
28 | width: 180,
29 | render: (_: string, row: any) => ,
30 | },
31 | {
32 | title: 'Last updated',
33 | dataIndex: 'last_updated',
34 | key: 'last_updated',
35 | width: 140,
36 | render: (v: number) => (
37 |
38 |
39 | {v ? fmtDate(v) : ''}
40 |
41 | ),
42 | },
43 | {
44 | title: 'Action',
45 | fixed: 'right',
46 | width: 150,
47 | render: (_: any, row: any, actions: any) => {
48 | return (
49 |
50 | actions.setRecord(row, 'sync')}
54 | okText="Yes"
55 | cancelText="No"
56 | >
57 | Sync
58 |
59 | {row.last_updated && (
60 |
61 | View
62 |
63 | )}
64 | actions.setRecord(row, 'edit')}>Edit
65 | actions.setRecord(row, 'delete')}
68 | okText="Yes"
69 | cancelText="No"
70 | >
71 | Delete
72 |
73 |
74 | );
75 | },
76 | },
77 | ];
78 |
79 | const RenderPath = ({ row }: any) => {
80 | const [filePath, setFilePath] = useState('');
81 | useInit(async () => {
82 | setFilePath(await getPath(row));
83 | });
84 | return shell.open(filePath)}>{filePath};
85 | };
86 |
87 | export const getPath = async (row: any) => {
88 | if (!/^http/.test(row.protocol)) {
89 | return (await path.join(await chatRoot(), row.path)) + `.${row.ext}`;
90 | } else {
91 | return `${row.protocol}://${row.path}.${row.ext}`;
92 | }
93 | };
94 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncCustom/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef, useEffect } from 'react';
2 | import { Table, Modal, Button, message } from 'antd';
3 | import { invoke, path, fs } from '@tauri-apps/api';
4 |
5 | import useData from '@/hooks/useData';
6 | import useColumns from '@/hooks/useColumns';
7 | import { TABLE_PAGINATION } from '@/hooks/useTable';
8 | import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
9 | import { CHAT_MODEL_JSON, chatRoot, readJSON, genCmd } from '@/utils';
10 | import { syncColumns, getPath } from './config';
11 | import SyncForm from './Form';
12 |
13 | const fmtData = (data: Record[] = []) =>
14 | (Array.isArray(data) ? data : []).map((i) => ({
15 | ...i,
16 | cmd: i.cmd ? i.cmd : genCmd(i.act),
17 | tags: ['user-sync'],
18 | enable: true,
19 | }));
20 |
21 | export default function SyncCustom() {
22 | const [isVisible, setVisible] = useState(false);
23 | const { modelData, modelSet } = useChatModel('sync_custom', CHAT_MODEL_JSON);
24 | const { modelCacheCmd, modelCacheSet } = useCacheModel();
25 | const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]);
26 | const { columns, ...opInfo } = useColumns(syncColumns());
27 | const formRef = useRef(null);
28 |
29 | const hide = () => {
30 | setVisible(false);
31 | opInfo.resetRecord();
32 | };
33 |
34 | useEffect(() => {
35 | if (modelData.length <= 0) return;
36 | opInit(modelData);
37 | }, [modelData]);
38 |
39 | useEffect(() => {
40 | if (!opInfo.opType) return;
41 | if (opInfo.opType === 'sync') {
42 | const filename = `${opInfo?.opRecord?.id}.json`;
43 | handleSync(filename).then((isOk: boolean) => {
44 | opInfo.resetRecord();
45 | if (!isOk) return;
46 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], {
47 | ...opInfo?.opRecord,
48 | last_updated: Date.now(),
49 | });
50 | modelSet(data);
51 | opInfo.resetRecord();
52 | });
53 | }
54 | if (['edit', 'new'].includes(opInfo.opType)) {
55 | setVisible(true);
56 | }
57 | if (['delete'].includes(opInfo.opType)) {
58 | (async () => {
59 | try {
60 | const file = await path.join(
61 | await chatRoot(),
62 | 'cache_model',
63 | `${opInfo?.opRecord?.id}.json`,
64 | );
65 | await fs.removeFile(file);
66 | } catch (e) {}
67 | const data = opRemove(opInfo?.opRecord?.[opSafeKey]);
68 | modelSet(data);
69 | opInfo.resetRecord();
70 | modelCacheCmd();
71 | })();
72 | }
73 | }, [opInfo.opType, formRef]);
74 |
75 | const handleSync = async (filename: string) => {
76 | const record = opInfo?.opRecord;
77 | const isJson = /json$/.test(record?.ext);
78 | const file = await path.join(await chatRoot(), 'cache_model', filename);
79 | const filePath = await getPath(record);
80 |
81 | // https or http
82 | if (/^http/.test(record?.protocol)) {
83 | const data = await invoke('sync_user_prompts', { url: filePath, dataType: record?.ext });
84 | if (data) {
85 | await modelCacheSet(data as [], file);
86 | await modelCacheCmd();
87 | message.success('ChatGPT Prompts data has been synchronized!');
88 | return true;
89 | } else {
90 | message.error('ChatGPT Prompts data sync failed, please try again!');
91 | return false;
92 | }
93 | }
94 | // local
95 | if (isJson) {
96 | // parse json
97 | const data = await readJSON(filePath, { isRoot: true });
98 | await modelCacheSet(fmtData(data), file);
99 | } else {
100 | // parse csv
101 | const data = await fs.readTextFile(filePath);
102 | const list: Record[] = await invoke('parse_prompt', { data });
103 | await modelCacheSet(fmtData(list), file);
104 | }
105 | await modelCacheCmd();
106 | return true;
107 | };
108 |
109 | const handleOk = () => {
110 | formRef.current?.form?.validateFields().then((vals: Record) => {
111 | if (opInfo.opType === 'new') {
112 | const data = opAdd(vals);
113 | modelSet(data);
114 | message.success('Data added successfully');
115 | }
116 | if (opInfo.opType === 'edit') {
117 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals);
118 | modelSet(data);
119 | message.success('Data updated successfully');
120 | }
121 | hide();
122 | });
123 | };
124 |
125 | return (
126 |
127 |
135 |
143 |
151 |
152 |
153 |
154 | );
155 | }
156 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncPrompts/config.tsx:
--------------------------------------------------------------------------------
1 | import { Table, Switch, Tag } from 'antd';
2 |
3 | import { genCmd } from '@/utils';
4 |
5 | export const syncColumns = () => [
6 | {
7 | title: '/{cmd}',
8 | dataIndex: 'cmd',
9 | fixed: 'left',
10 | // width: 120,
11 | key: 'cmd',
12 | render: (_: string, row: Record) => (
13 | /{genCmd(row.act)}
14 | ),
15 | },
16 | {
17 | title: 'Act',
18 | dataIndex: 'act',
19 | key: 'act',
20 | // width: 200,
21 | },
22 | {
23 | title: 'Tags',
24 | dataIndex: 'tags',
25 | key: 'tags',
26 | // width: 150,
27 | render: () => chatgpt-prompts,
28 | },
29 | {
30 | title: 'Enable',
31 | dataIndex: 'enable',
32 | key: 'enable',
33 | // width: 80,
34 | render: (v: boolean = false, row: Record, action: Record) => (
35 | action.setRecord({ ...row, enable: v }, 'enable')} />
36 | ),
37 | },
38 | Table.EXPAND_COLUMN,
39 | {
40 | title: 'Prompt',
41 | dataIndex: 'prompt',
42 | key: 'prompt',
43 | // width: 300,
44 | render: (v: string) => {v},
45 | },
46 | ];
47 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncPrompts/index.scss:
--------------------------------------------------------------------------------
1 | .chat-table-tip,
2 | .chat-table-btns {
3 | display: flex;
4 | justify-content: space-between;
5 | }
6 |
7 | .chat-table-btns {
8 | margin-bottom: 5px;
9 |
10 | .num {
11 | margin-left: 10px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncPrompts/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Table, Button, Popconfirm } from 'antd';
3 | import { invoke, path } from '@tauri-apps/api';
4 |
5 | import useInit from '@/hooks/useInit';
6 | import useData from '@/hooks/useData';
7 | import useColumns from '@/hooks/useColumns';
8 | import FilePath from '@/components/FilePath';
9 | import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
10 | import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
11 | import { fmtDate, chatRoot } from '@/utils';
12 | import { syncColumns } from './config';
13 | import './index.scss';
14 |
15 | const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv';
16 |
17 | export default function SyncPrompts() {
18 | const { rowSelection, selectedRowIDs } = useTableRowSelection();
19 | const [jsonPath, setJsonPath] = useState('');
20 | const { modelJson, modelSet } = useChatModel('sync_prompts');
21 | const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
22 | const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
23 | const { columns, ...opInfo } = useColumns(syncColumns());
24 | const lastUpdated = modelJson?.sync_prompts?.last_updated;
25 | const selectedItems = rowSelection.selectedRowKeys || [];
26 |
27 | useInit(async () => {
28 | setJsonPath(await path.join(await chatRoot(), 'cache_model', 'chatgpt_prompts.json'));
29 | });
30 |
31 | useEffect(() => {
32 | if (modelCacheJson.length <= 0) return;
33 | opInit(modelCacheJson);
34 | }, [modelCacheJson.length]);
35 |
36 | const handleSync = async () => {
37 | const data = await invoke('sync_prompts', { time: Date.now() });
38 | if (data) {
39 | opInit(data as any[]);
40 | modelSet({
41 | id: 'chatgpt_prompts',
42 | last_updated: Date.now(),
43 | });
44 | }
45 | };
46 |
47 | useEffect(() => {
48 | if (opInfo.opType === 'enable') {
49 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
50 | modelCacheSet(data);
51 | }
52 | }, [opInfo.opTime]);
53 |
54 | const handleEnable = (isEnable: boolean) => {
55 | const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
56 | modelCacheSet(data);
57 | };
58 |
59 | return (
60 |
61 |
62 |
70 |
71 |
72 |
73 | {selectedItems.length > 0 && (
74 | <>
75 |
78 |
79 | Selected {selectedItems.length} items
80 | >
81 | )}
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | {lastUpdated && (
90 |
91 | Last updated on {fmtDate(lastUpdated)}
92 |
93 | )}
94 |
95 |
{record.prompt}
,
105 | }}
106 | />
107 |
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncRecord/config.tsx:
--------------------------------------------------------------------------------
1 | import { Switch, Tag, Table } from 'antd';
2 |
3 | import { genCmd } from '@/utils';
4 |
5 | export const syncColumns = () => [
6 | {
7 | title: '/{cmd}',
8 | dataIndex: 'cmd',
9 | fixed: 'left',
10 | // width: 120,
11 | key: 'cmd',
12 | render: (_: string, row: Record) => (
13 | /{row.cmd ? row.cmd : genCmd(row.act)}
14 | ),
15 | },
16 | {
17 | title: 'Act',
18 | dataIndex: 'act',
19 | key: 'act',
20 | // width: 200,
21 | },
22 | {
23 | title: 'Tags',
24 | dataIndex: 'tags',
25 | key: 'tags',
26 | // width: 150,
27 | render: (v: string[]) => (
28 |
29 | {v?.map((i) => (
30 | {i}
31 | ))}
32 |
33 | ),
34 | },
35 | {
36 | title: 'Enable',
37 | dataIndex: 'enable',
38 | key: 'enable',
39 | // width: 80,
40 | render: (v: boolean = false, row: Record, action: Record) => (
41 | action.setRecord({ ...row, enable: v }, 'enable')} />
42 | ),
43 | },
44 | Table.EXPAND_COLUMN,
45 | {
46 | title: 'Prompt',
47 | dataIndex: 'prompt',
48 | key: 'prompt',
49 | // width: 300,
50 | render: (v: string) => {v},
51 | },
52 | ];
53 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/SyncRecord/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 | import { ArrowLeftOutlined } from '@ant-design/icons';
4 | import { Table, Button } from 'antd';
5 | import { path } from '@tauri-apps/api';
6 |
7 | import useData from '@/hooks/useData';
8 | import useColumns from '@/hooks/useColumns';
9 | import FilePath from '@/components/FilePath';
10 | import { useCacheModel } from '@/hooks/useChatModel';
11 | import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
12 | import { getPath } from '@/view/model/SyncCustom/config';
13 | import { fmtDate, chatRoot } from '@/utils';
14 | import { syncColumns } from './config';
15 | import useInit from '@/hooks/useInit';
16 |
17 | export default function SyncRecord() {
18 | const location = useLocation();
19 | const [filePath, setFilePath] = useState('');
20 | const [jsonPath, setJsonPath] = useState('');
21 | const state = location?.state;
22 |
23 | const { rowSelection, selectedRowIDs } = useTableRowSelection();
24 | const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
25 | const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
26 | const { columns, ...opInfo } = useColumns(syncColumns());
27 |
28 | const selectedItems = rowSelection.selectedRowKeys || [];
29 |
30 | useInit(async () => {
31 | setFilePath(await getPath(state));
32 | setJsonPath(await path.join(await chatRoot(), 'cache_model', `${state?.id}.json`));
33 | });
34 |
35 | useEffect(() => {
36 | if (modelCacheJson.length <= 0) return;
37 | opInit(modelCacheJson);
38 | }, [modelCacheJson.length]);
39 |
40 | useEffect(() => {
41 | if (opInfo.opType === 'enable') {
42 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
43 | modelCacheSet(data);
44 | }
45 | }, [opInfo.opTime]);
46 |
47 | const handleEnable = (isEnable: boolean) => {
48 | const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
49 | modelCacheSet(data);
50 | };
51 |
52 | return (
53 |
54 |
55 |
56 | } onClick={() => history.back()} />
57 |
58 |
59 | {selectedItems.length > 0 && (
60 | <>
61 |
64 |
65 | Selected {selectedItems.length} items
66 | >
67 | )}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | {state?.last_updated && (
76 |
77 | Last updated on {fmtDate(state?.last_updated)}
78 |
79 | )}
80 |
81 |
{record.prompt}
,
91 | }}
92 | />
93 |
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/UserCustom/Form.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, ForwardRefRenderFunction, useImperativeHandle, forwardRef } from 'react';
2 | import { Form, Input, Switch } from 'antd';
3 | import type { FormProps } from 'antd';
4 |
5 | import Tags from '@comps/Tags';
6 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
7 |
8 | interface UserCustomFormProps {
9 | record?: Record | null;
10 | }
11 |
12 | const initFormValue = {
13 | act: '',
14 | enable: true,
15 | tags: [],
16 | prompt: '',
17 | };
18 |
19 | const UserCustomForm: ForwardRefRenderFunction = (
20 | { record },
21 | ref,
22 | ) => {
23 | const [form] = Form.useForm();
24 | useImperativeHandle(ref, () => ({ form }));
25 |
26 | useEffect(() => {
27 | if (record) {
28 | form.setFieldsValue(record);
29 | }
30 | }, [record]);
31 |
32 | return (
33 |
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default forwardRef(UserCustomForm);
66 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/UserCustom/config.tsx:
--------------------------------------------------------------------------------
1 | import { Tag, Switch, Space, Popconfirm, Table } from 'antd';
2 |
3 | export const modelColumns = () => [
4 | {
5 | title: '/{cmd}',
6 | dataIndex: 'cmd',
7 | fixed: 'left',
8 | width: 120,
9 | key: 'cmd',
10 | render: (v: string) => /{v},
11 | },
12 | {
13 | title: 'Act',
14 | dataIndex: 'act',
15 | key: 'act',
16 | width: 200,
17 | },
18 | {
19 | title: 'Tags',
20 | dataIndex: 'tags',
21 | key: 'tags',
22 | width: 150,
23 | render: (v: string[]) => (
24 |
25 | {v?.map((i) => (
26 | {i}
27 | ))}
28 |
29 | ),
30 | },
31 | {
32 | title: 'Enable',
33 | dataIndex: 'enable',
34 | key: 'enable',
35 | width: 80,
36 | render: (v: boolean = false, row: Record, action: Record) => (
37 | action.setRecord({ ...row, enable: v }, 'enable')} />
38 | ),
39 | },
40 | Table.EXPAND_COLUMN,
41 | {
42 | title: 'Prompt',
43 | dataIndex: 'prompt',
44 | key: 'prompt',
45 | width: 300,
46 | render: (v: string) => {v},
47 | },
48 | {
49 | title: 'Action',
50 | key: 'action',
51 | fixed: 'right',
52 | width: 120,
53 | render: (_: any, row: any, actions: any) => (
54 |
55 | actions.setRecord(row, 'edit')}>Edit
56 | actions.setRecord(row, 'delete')}
59 | okText="Yes"
60 | cancelText="No"
61 | >
62 | Delete
63 |
64 |
65 | ),
66 | },
67 | ];
68 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/model/UserCustom/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef, useEffect } from 'react';
2 | import { Table, Button, Modal, message } from 'antd';
3 | import { path } from '@tauri-apps/api';
4 |
5 | import useInit from '@/hooks/useInit';
6 | import useData from '@/hooks/useData';
7 | import useColumns from '@/hooks/useColumns';
8 | import FilePath from '@/components/FilePath';
9 | import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
10 | import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
11 | import { chatRoot, fmtDate } from '@/utils';
12 | import { modelColumns } from './config';
13 | import UserCustomForm from './Form';
14 |
15 | export default function UserCustom() {
16 | const { rowSelection, selectedRowIDs } = useTableRowSelection();
17 | const [isVisible, setVisible] = useState(false);
18 | const [jsonPath, setJsonPath] = useState('');
19 | const { modelJson, modelSet } = useChatModel('user_custom');
20 | const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
21 | const { opData, opInit, opReplaceItems, opAdd, opRemove, opReplace, opSafeKey } = useData([]);
22 | const { columns, ...opInfo } = useColumns(modelColumns());
23 | const lastUpdated = modelJson?.user_custom?.last_updated;
24 | const selectedItems = rowSelection.selectedRowKeys || [];
25 | const formRef = useRef(null);
26 |
27 | useInit(async () => {
28 | setJsonPath(await path.join(await chatRoot(), 'cache_model', 'user_custom.json'));
29 | });
30 |
31 | useEffect(() => {
32 | if (modelCacheJson.length <= 0) return;
33 | opInit(modelCacheJson);
34 | }, [modelCacheJson.length]);
35 |
36 | useEffect(() => {
37 | if (!opInfo.opType) return;
38 | if (['edit', 'new'].includes(opInfo.opType)) {
39 | setVisible(true);
40 | }
41 | if (['delete'].includes(opInfo.opType)) {
42 | const data = opRemove(opInfo?.opRecord?.[opSafeKey]);
43 | modelCacheSet(data);
44 | opInfo.resetRecord();
45 | }
46 | }, [opInfo.opType, formRef]);
47 |
48 | useEffect(() => {
49 | if (opInfo.opType === 'enable') {
50 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
51 | modelCacheSet(data);
52 | }
53 | }, [opInfo.opTime]);
54 |
55 | const handleEnable = (isEnable: boolean) => {
56 | const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
57 | modelCacheSet(data);
58 | };
59 |
60 | const hide = () => {
61 | setVisible(false);
62 | opInfo.resetRecord();
63 | };
64 |
65 | const handleOk = () => {
66 | formRef.current?.form?.validateFields().then(async (vals: Record) => {
67 | if (
68 | modelCacheJson.map((i: any) => i.cmd).includes(vals.cmd) &&
69 | opInfo?.opRecord?.cmd !== vals.cmd
70 | ) {
71 | message.warning(
72 | `"cmd: /${vals.cmd}" already exists, please change the "${vals.cmd}" name and resubmit.`,
73 | );
74 | return;
75 | }
76 | let data = [];
77 | switch (opInfo.opType) {
78 | case 'new':
79 | data = opAdd(vals);
80 | break;
81 | case 'edit':
82 | data = opReplace(opInfo?.opRecord?.[opSafeKey], vals);
83 | break;
84 | default:
85 | break;
86 | }
87 | await modelCacheSet(data);
88 | opInit(data);
89 | modelSet({
90 | id: 'user_custom',
91 | last_updated: Date.now(),
92 | });
93 | hide();
94 | });
95 | };
96 |
97 | const modalTitle = `${{ new: 'Create', edit: 'Edit' }[opInfo.opType]} Model`;
98 |
99 | return (
100 |
101 |
102 |
105 |
106 | {selectedItems.length > 0 && (
107 | <>
108 |
111 |
112 | Selected {selectedItems.length} items
113 | >
114 | )}
115 |
116 |
117 |
118 |
119 | {lastUpdated && (
120 |
121 | Last updated on {fmtDate(lastUpdated)}
122 |
123 | )}
124 |
125 |
{record.prompt}
,
135 | }}
136 | />
137 |
145 |
146 |
147 |
148 | );
149 | }
150 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/notes/config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { Space, Popconfirm } from 'antd';
4 | import { path, shell } from '@tauri-apps/api';
5 |
6 | import { EditRow } from '@/hooks/useColumns';
7 |
8 | import useInit from '@/hooks/useInit';
9 | import { fmtDate, chatRoot } from '@/utils';
10 |
11 | export const notesColumns = () => [
12 | {
13 | title: 'Name',
14 | dataIndex: 'name',
15 | fixed: 'left',
16 | key: 'name',
17 | width: 240,
18 | render: (_: string, row: any, actions: any) => (
19 |
20 | ),
21 | },
22 | {
23 | title: 'Path',
24 | dataIndex: 'path',
25 | key: 'path',
26 | width: 200,
27 | render: (_: string, row: any) => ,
28 | },
29 | {
30 | title: 'Created',
31 | dataIndex: 'created',
32 | key: 'created',
33 | width: 150,
34 | render: fmtDate,
35 | },
36 | {
37 | title: 'Action',
38 | fixed: 'right',
39 | width: 160,
40 | render: (_: any, row: any, actions: any) => {
41 | return (
42 |
43 | actions.setRecord(row, 'preview')}>Preview
44 |
45 | Edit
46 |
47 | actions.setRecord(row, 'delete')}
50 | okText="Yes"
51 | cancelText="No"
52 | >
53 | Delete
54 |
55 |
56 | );
57 | },
58 | },
59 | ];
60 |
61 | const RenderPath = ({ row }: any) => {
62 | const [filePath, setFilePath] = useState('');
63 | useInit(async () => {
64 | setFilePath(await getPath(row));
65 | });
66 | return shell.open(filePath)}>{filePath};
67 | };
68 |
69 | export const getPath = async (row: any) => {
70 | return (await path.join(await chatRoot(), 'notes', row.id)) + `.${row.ext}`;
71 | };
72 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/notes/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Table, Modal, Popconfirm, Button, message } from 'antd';
3 | import { invoke, path, fs } from '@tauri-apps/api';
4 |
5 | import useJson from '@/hooks/useJson';
6 | import useData from '@/hooks/useData';
7 | import useColumns from '@/hooks/useColumns';
8 | import Markdown from '@/components/Markdown';
9 | import FilePath from '@/components/FilePath';
10 | import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
11 | import { chatRoot, CHAT_NOTES_JSON } from '@/utils';
12 | import { notesColumns } from './config';
13 |
14 | export default function Notes() {
15 | const [source, setSource] = useState('');
16 | const [isVisible, setVisible] = useState(false);
17 | const { opData, opInit, opReplace, opSafeKey } = useData([]);
18 | const { columns, ...opInfo } = useColumns(notesColumns());
19 | const { rowSelection, selectedRows, rowReset } = useTableRowSelection({ rowType: 'row' });
20 | const { json, refreshJson, updateJson } = useJson(CHAT_NOTES_JSON);
21 | const selectedItems = rowSelection.selectedRowKeys || [];
22 |
23 | useEffect(() => {
24 | if (!json || json.length <= 0) return;
25 | opInit(json);
26 | }, [json?.length]);
27 |
28 | useEffect(() => {
29 | if (!opInfo.opType) return;
30 | (async () => {
31 | const record = opInfo?.opRecord;
32 | const file = await path.join(await chatRoot(), 'notes', `${record?.id}.${record?.ext}`);
33 | if (opInfo.opType === 'preview') {
34 | const data = await fs.readTextFile(file);
35 | setSource(data);
36 | setVisible(true);
37 | return;
38 | }
39 | if (opInfo.opType === 'delete') {
40 | await fs.removeFile(file);
41 | await handleRefresh();
42 | }
43 | if (opInfo.opType === 'rowedit') {
44 | const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
45 | await updateJson(data);
46 | message.success('Name has been changed!');
47 | }
48 | opInfo.resetRecord();
49 | })();
50 | }, [opInfo.opType]);
51 |
52 | const handleDelete = async () => {
53 | if (opData?.length === selectedRows.length) {
54 | const notesDir = await path.join(await chatRoot(), 'notes');
55 | await fs.removeDir(notesDir, { recursive: true });
56 | await handleRefresh();
57 | message.success('All files have been cleared!');
58 | return;
59 | }
60 |
61 | const rows = selectedRows.map(async (i) => {
62 | const file = await path.join(await chatRoot(), 'notes', `${i?.id}.${i?.ext}`);
63 | await fs.removeFile(file);
64 | return file;
65 | });
66 | Promise.all(rows).then(async () => {
67 | await handleRefresh();
68 | message.success('All files selected are cleared!');
69 | });
70 | };
71 |
72 | const handleRefresh = async () => {
73 | await invoke('download_list', { pathname: CHAT_NOTES_JSON, dir: 'notes' });
74 | rowReset();
75 | const data = await refreshJson();
76 | opInit(data);
77 | };
78 |
79 | const handleCancel = () => {
80 | setVisible(false);
81 | opInfo.resetRecord();
82 | };
83 |
84 | return (
85 |
86 |
87 |
88 | {selectedItems.length > 0 && (
89 | <>
90 |
98 |
99 |
100 |
Selected {selectedItems.length} items
101 | >
102 | )}
103 |
104 |
105 |
106 |
114 |
{opInfo?.opRecord?.name || ''} }
117 | onCancel={handleCancel}
118 | footer={false}
119 | destroyOnClose
120 | width={600}
121 | >
122 |
123 |
124 |
125 | );
126 | }
127 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/settings/General.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Form, Radio, Switch, Input, Tooltip, Select, Tag } from 'antd';
3 | import { QuestionCircleOutlined } from '@ant-design/icons';
4 | import { platform } from '@tauri-apps/api/os';
5 |
6 | import useInit from '@/hooks/useInit';
7 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
8 |
9 | export default function General() {
10 | const [platformInfo, setPlatform] = useState('');
11 | const [vlist, setVoices] = useState([]);
12 |
13 | useInit(async () => {
14 | setPlatform(await platform());
15 | speechSynthesis.addEventListener('voiceschanged', () => {
16 | const voices = speechSynthesis.getVoices();
17 | console.log(voices);
18 | setVoices(voices);
19 | });
20 | setVoices(speechSynthesis.getVoices());
21 | });
22 |
23 | return (
24 | <>
25 |
26 |
27 |
28 |
29 |
30 |
31 | {platformInfo === 'darwin' && (
32 |
33 |
34 |
35 | )}
36 | {platformInfo === 'darwin' && (
37 |
38 |
39 |
40 | )}
41 |
42 |
43 | Light
44 | Dark
45 | {['darwin', 'windows'].includes(platformInfo) && System}
46 |
47 |
48 | } name="auto_update">
49 |
50 | Prompt
51 | Silent
52 | {/*Disable*/}
53 |
54 |
55 | } name="global_shortcut">
56 |
57 |
58 |
59 |
69 |
70 | >
71 | );
72 | }
73 |
74 | const AutoUpdateLabel = () => {
75 | return (
76 |
77 | Auto Update{' '}
78 |
81 | Auto Update Policy
82 |
83 | Prompt: prompt to install
84 |
85 |
86 | Silent: install silently
87 |
88 | {/* Disable: disable auto update
*/}
89 |
90 | }
91 | >
92 |
93 |
94 |
95 | );
96 | };
97 |
98 | const GlobalShortcutLabel = () => {
99 | return (
100 |
111 | }
112 | >
113 |
114 |
115 |
116 | );
117 | };
118 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/settings/LangHelper.tsx:
--------------------------------------------------------------------------------
1 | import { useState} from 'react';
2 | import useInit from '@/hooks/useInit';
3 | import {Form,Switch,Select,Radio,Input} from 'antd'
4 | import { speakers, celebrity} from './Speakers';
5 | import { FormInstance } from 'antd/lib/form';
6 |
7 | export default function LangHelper({ form }: { form: FormInstance }) {
8 |
9 | const Speech_Type_List = ['web','ai','azure','iflytek'];
10 | // web, ai,azure, iflytek
11 | const Rec_Type_List = ['web','iflytek','ai'];
12 | //iflytek, speechsuper
13 | const Assess_Type_List = ['','iflytek','speechsuper'];
14 |
15 | const Special_Speech_Mode =['accent_per_sentence',"region_per_passage"]
16 | const [selectSpeech, setSelectSpeech] = useState('')
17 | const [selectRec,setSelectRec] = useState('');
18 | const [selectAssess,setSelectAssess] = useState('');
19 |
20 | useInit(() => {
21 | setSelectSpeech(form.getFieldValue('speech_type'));
22 | setSelectRec(form.getFieldValue('rec_type'));
23 | setSelectAssess(form.getFieldValue('assess_type'));
24 | });
25 | return (
26 | <>
27 |
28 |
29 |
30 |
31 |
40 |
41 |
42 |
51 |
52 |
53 |
62 |
63 | { selectSpeech === "ai" && (
64 |
65 |
89 |
90 | )}
91 | { selectSpeech === "ai" && (
92 |
93 |
94 | 1
95 | 2
96 | 3
97 |
98 |
99 | )}
100 | { selectRec === "iflytek" && (
101 |
102 |
103 |
104 | )}
105 | { selectRec === "iflytek" && (
106 |
107 |
108 |
109 | )}
110 | { selectRec === "iflytek" && (
111 |
112 |
113 | ch&en
114 | en
115 |
116 |
117 | )}
118 | { selectAssess === "iflytek" && (
119 |
120 |
121 |
122 | )}
123 | { selectAssess === "iflytek" && (
124 |
125 |
126 |
127 | )}
128 | { selectAssess === "iflytek" && (
129 |
130 |
131 |
132 | )}
133 | { selectAssess === "speechsuper" && (
134 |
135 |
136 |
137 | )}
138 | { selectAssess === "speechsuper" && (
139 |
140 |
141 |
142 | )}
143 | >
144 | );
145 | }
146 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/settings/MainWindow.tsx:
--------------------------------------------------------------------------------
1 | import { Form, Switch, Input, InputNumber, Tooltip } from 'antd';
2 | import { QuestionCircleOutlined } from '@ant-design/icons';
3 |
4 | import SwitchOrigin from '@/components/SwitchOrigin';
5 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
6 |
7 | const PopupSearchLabel = () => {
8 | return (
9 |
10 | Pop-up Search{' '}
11 |
14 |
15 | Generate images according to the content: Select the ChatGPT content with the mouse,
16 | no more than 400 characters. the DALL·E 2 button appears, and click to jump
17 | (Note: because the search content filled by the script cannot trigger the event
18 | directly, you need to enter a space in the input box to make the button clickable).
19 |
20 |
21 | The application is built using Tauri, and due to its security restrictions, some of
22 | the action buttons will not work, so we recommend going to your browser.
23 |
24 |
25 | }
26 | >
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | const MainCloseLabel = () => {
34 | return (
35 |
36 | Close Exit{' '}
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default function MainWindow() {
45 | return (
46 | <>
47 | } name="popup_search" valuePropName="checked">
48 |
49 |
50 | } name="main_close" valuePropName="checked">
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
66 |
67 | >
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/settings/Speakers.ts:
--------------------------------------------------------------------------------
1 |
2 | export const speakers: string[][] =[
3 | ['p225', 'F', 'English'],
4 | ['p226', 'M', 'English'],
5 | ['p227', 'M', 'English'],
6 | ['p228', 'F', 'English'],
7 | ['p229', 'F', 'English'],
8 | ['p230', 'F', 'English'],
9 | ['p231', 'F', 'English'],
10 | ['p232', 'M', 'English'],
11 | ['p233', 'F', 'English'],
12 | ['p234', 'F', 'Scottish'],
13 | ['p236', 'F', 'English'],
14 | ['p237', 'M', 'Scottish'],
15 | ['p238', 'F', 'NorthernIrish'],
16 | ['p239', 'F', 'English'],
17 | ['p240', 'F', 'English'],
18 | ['p241', 'M', 'Scottish'],
19 | ['p243', 'M', 'English'],
20 | ['p244', 'F', 'English'],
21 | ['p245', 'M', 'Irish'],
22 | ['p246', 'M', 'Scottish'],
23 | ['p247', 'M', 'Scottish'],
24 | ['p248', 'F', 'Indian'],
25 | ['p249', 'F', 'Scottish'],
26 | ['p250', 'F', 'English'],
27 | ['p251', 'M', 'Indian'],
28 | ['p252', 'M', 'Scottish'],
29 | ['p253', 'F', 'Welsh'],
30 | ['p254', 'M', 'English'],
31 | ['p255', 'M', 'Scottish'],
32 | ['p256', 'M', 'English'],
33 | ['p257', 'F', 'English'],
34 | ['p258', 'M', 'English'],
35 | ['p259', 'M', 'English'],
36 | ['p260', 'M', 'Scottish'],
37 | ['p261', 'F', 'NorthernIrish'],
38 | ['p262', 'F', 'Scottish'],
39 | ['p263', 'M', 'Scottish'],
40 | ['p264', 'F', 'Scottish'],
41 | ['p265', 'F', 'Scottish'],
42 | ['p266', 'F', 'Irish'],
43 | ['p267', 'F', 'English'],
44 | ['p268', 'F', 'English'],
45 | ['p269', 'F', 'English'],
46 | ['p270', 'M', 'English'],
47 | ['p271', 'M', 'Scottish'],
48 | ['p272', 'M', 'Scottish'],
49 | ['p273', 'M', 'English'],
50 | ['p274', 'M', 'English'],
51 | ['p275', 'M', 'Scottish'],
52 | ['p276', 'F', 'English'],
53 | ['p277', 'F', 'English'],
54 | ['p278', 'M', 'English'],
55 | ['p279', 'M', 'English'],
56 | ['p280', 'F', 'Unknown'],
57 | ['p281', 'M', 'Scottish'],
58 | ['p282', 'F', 'English'],
59 | ['p283', 'F', 'Irish'],
60 | ['p284', 'M', 'Scottish'],
61 | ['p285', 'M', 'Scottish'],
62 | ['p286', 'M', 'English'],
63 | ['p287', 'M', 'English'],
64 | ['p288', 'F', 'Irish'],
65 | ['p292', 'M', 'NorthernIrish'],
66 | ['p293', 'F', 'NorthernIrish'],
67 | ['p294', 'F', 'American'],
68 | ['p295', 'F', 'Irish'],
69 | ['p297', 'F', 'American'],
70 | ['p298', 'M', 'Irish'],
71 | ['p299', 'F', 'American'],
72 | ['p300', 'F', 'American'],
73 | ['p301', 'F', 'American'],
74 | ['p302', 'M', 'Canadian'],
75 | ['p303', 'F', 'Canadian'],
76 | ['p304', 'M', 'NorthernIrish'],
77 | ['p305', 'F', 'American'],
78 | ['p306', 'F', 'American'],
79 | ['p307', 'F', 'Canadian'],
80 | ['p308', 'F', 'American'],
81 | ['p310', 'F', 'American'],
82 | ['p311', 'M', 'American'],
83 | ['p312', 'F', 'Canadian'],
84 | ['p313', 'F', 'Irish'],
85 | ['p314', 'F', 'SouthAfrican'],
86 | ['p315', 'M', 'American'],
87 | ['p316', 'M', 'Canadian'],
88 | ['p317', 'F', 'Canadian'],
89 | ['p318', 'F', 'American'],
90 | ['p323', 'F', 'SouthAfrican'],
91 | ['p326', 'M', 'Australian'],
92 | ['p329', 'F', 'American'],
93 | ['p330', 'F', 'American'],
94 | ['p333', 'F', 'American'],
95 | ['p334', 'M', 'American'],
96 | ['p335', 'F', 'NewZealand'],
97 | ['p336', 'F', 'SouthAfrican'],
98 | ['p339', 'F', 'American'],
99 | ['p340', 'F', 'Irish'],
100 | ['p341', 'F', 'American'],
101 | ['p343', 'F', 'Canadian'],
102 | ['p345', 'M', 'American'],
103 | ['p347', 'M', 'SouthAfrican'],
104 | ['p351', 'F', 'NorthernIrish'],
105 | ['p360', 'M', 'American'],
106 | ['p361', 'F', 'American'],
107 | ['p362', 'F', 'American'],
108 | ['p363', 'M', 'Canadian'],
109 | ['p364', 'M', 'Irish'],
110 | ['p374', 'M', 'Australian'],
111 | ['p376', 'M', 'Indian'],
112 | ['s5', 'F', 'British'],
113 | ];
114 |
115 | export const celebrity:string[]= [
116 | "Downey",
117 | "Obama",
118 | ];
--------------------------------------------------------------------------------
/ChatGPT/src/view/settings/TrayWindow.tsx:
--------------------------------------------------------------------------------
1 | import { Form, Switch, Input, InputNumber, Tooltip } from 'antd';
2 | import { QuestionCircleOutlined } from '@ant-design/icons';
3 |
4 | import { DISABLE_AUTO_COMPLETE } from '@/utils';
5 | import SwitchOrigin from '@/components/SwitchOrigin';
6 |
7 | const UALabel = () => {
8 | return (
9 |
10 | User Agent (SystemTray){' '}
11 | For a better experience, we recommend using the Mobile User-Agent.}
13 | >
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default function TrayWindow() {
21 | return (
22 | <>
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | } name="ua_tray">
34 |
39 |
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/ChatGPT/src/view/settings/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useSearchParams } from 'react-router-dom';
3 | import { Form, Tabs, Space, Button, Popconfirm, message } from 'antd';
4 | import { invoke, dialog, process, path, shell } from '@tauri-apps/api';
5 | import { clone, omit, isEqual } from 'lodash';
6 |
7 | import useInit from '@/hooks/useInit';
8 | import FilePath from '@/components/FilePath';
9 | import { chatRoot, APP_CONF_JSON } from '@/utils';
10 | import General from './General';
11 | import MainWindow from './MainWindow';
12 | import TrayWindow from './TrayWindow';
13 | import LangHelper from './LangHelper'
14 |
15 | export default function Settings() {
16 | const [params] = useSearchParams();
17 | const [activeKey, setActiveKey] = useState('general');
18 | const [form] = Form.useForm();
19 | const [chatConf, setChatConf] = useState(null);
20 | const [filePath, setPath] = useState('');
21 | const key = params.get('type');
22 |
23 | useEffect(() => {
24 | setActiveKey(key ? key : 'general');
25 | }, [key]);
26 |
27 | useInit(async () => {
28 | setChatConf(await invoke('get_app_conf'));
29 | setPath(await path.join(await chatRoot(), APP_CONF_JSON));
30 | });
31 |
32 | useEffect(() => {
33 | form.setFieldsValue(clone(chatConf));
34 | }, [chatConf]);
35 |
36 | const onCancel = () => {
37 | form.setFieldsValue(chatConf);
38 | };
39 |
40 | const onReset = async () => {
41 | const chatData = await invoke('reset_app_conf');
42 | setChatConf(chatData);
43 | const isOk = await dialog.ask(`Configuration reset successfully, do you want to restart?`, {
44 | title: 'ChatGPT Preferences',
45 | });
46 | if (isOk) {
47 | process.relaunch();
48 | return;
49 | }
50 | message.success('Configuration reset successfully');
51 | };
52 |
53 | const onFinish = async (values: any) => {
54 | if (!isEqual(omit(chatConf, ['default_origin']), values)) {
55 | await invoke('form_confirm', { data: values, label: 'main' });
56 | const isOk = await dialog.ask(`Configuration saved successfully, do you want to restart?`, {
57 | title: 'ChatGPT Preferences',
58 | });
59 | if (isOk) {
60 | process.relaunch();
61 | return;
62 | }
63 | message.success('Configuration saved successfully');
64 | }
65 | };
66 |
67 | const handleTab = (v: string) => {
68 | setActiveKey(v);
69 | };
70 |
71 | return (
72 |
107 | }
108 | onConfirm={onReset}
109 | okText="Yes"
110 | cancelText="No"
111 | >
112 |
113 |
114 |
115 |
116 |
117 |
118 | );
119 | }
120 |
--------------------------------------------------------------------------------
/ChatGPT/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/ChatGPT/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["src/*"],
21 | "@view/*": ["src/view/*"],
22 | "@comps/*": ["src/components/*"],
23 | "@layout/*": ["src/layout/*"]
24 | }
25 | },
26 | "include": ["src"],
27 | "references": [{ "path": "./tsconfig.node.json" }]
28 | }
29 |
--------------------------------------------------------------------------------
/ChatGPT/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/ChatGPT/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 | import tsconfigPaths from 'vite-tsconfig-paths';
4 | import commonjs from 'vite-plugin-commonjs'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [tsconfigPaths(), react(),commonjs()],
9 |
10 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
11 | // prevent vite from obscuring rust errors
12 | clearScreen: false,
13 | // tauri expects a fixed port, fail if that port is not available
14 | server: {
15 | port: 1420,
16 | strictPort: true,
17 | },
18 | // to make use of `TAURI_DEBUG` and other env variables
19 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
20 | envPrefix: ['VITE_', 'TAURI_'],
21 | build: {
22 | // Tauri supports es2021
23 | target: ['es2021', 'chrome100', 'safari13'],
24 | // don't minify for debug builds
25 | minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
26 | // produce sourcemaps for debug builds
27 | sourcemap: !!process.env.TAURI_DEBUG,
28 | rollupOptions: {
29 | output: {
30 | manualChunks: {
31 | ant: ['antd'],
32 | antico: ['@ant-design/icons'],
33 | editor: ['@monaco-editor/react'],
34 | utils: ['lodash', 'uuid', 'dayjs', 'clsx'],
35 | rrr: ['react', 'react-dom', 'react-router-dom'],
36 | rm: ['react-markdown'],
37 | rsh: ['react-syntax-highlighter'],
38 | md: ['github-markdown-css', 'rehype-raw', 'remark-comment-config', 'remark-gfm'],
39 | },
40 | },
41 | },
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 NsLearning
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 |
--------------------------------------------------------------------------------
/LangHelper/Assess/SpeechSuper.py:
--------------------------------------------------------------------------------
1 | #_*_encoding:utf-8_*_
2 | import time
3 | import json
4 | import asyncio
5 | import hashlib
6 | import websockets
7 |
8 | #In order to try it out for free, you should email them.
9 | #API price: https://www.speechsuper.com/index.html#Pricing
10 |
11 | class SpeechSuper:
12 | def __init__(
13 | self,
14 | appKey: str,
15 | secretKey: str,
16 | audioPath: str,
17 | audioType: str = "wav",
18 | audioSampleRate: int = 16000,
19 | coreType: str = "sent.eval",
20 | baseUrl: str = "wss://api.speechsuper.com/"
21 | ) -> None:
22 | self.appKey = appKey
23 | self.secretKey = secretKey
24 | self.audioPath = audioPath
25 | self.audioType = audioType
26 | self.coreType = coreType
27 | self.audioSampleRate = audioSampleRate
28 | self.baseUrl = baseUrl
29 | self.scores = {
30 | "fluency":None,
31 | "rhythm":None,
32 | "overall":None,
33 | "pronunciation": None,
34 | "speed": None
35 | }
36 | # Send authentication
37 | async def initConnnct(self,websocket):
38 | t = time.time()
39 | timestamp = '%d' % int(round(t * 1000))
40 | connectStr = (self.appKey + timestamp + self.secretKey).encode("utf-8")
41 | connectSig = hashlib.sha1(connectStr).hexdigest()
42 |
43 | jconnect = {
44 | "cmd" : "connect",
45 | "param" : {
46 | "sdk": {
47 | "version": 16777472,
48 | "source": 4,
49 | "protocol": 1
50 | },
51 | "app": {
52 | "applicationId": self.appKey,
53 | "sig": connectSig,
54 | "timestamp": timestamp
55 | }
56 | }
57 | }
58 |
59 | param = json.dumps(jconnect)
60 | await websocket.send(param)
61 | return True
62 |
63 | # Send evaluation request
64 | async def startScore(self, websocket, request):
65 | # Send start request
66 | userId ="userId"
67 | timestamp = '%d' % int(round(time.time() * 1000))
68 | startStr = (self.appKey + timestamp + userId + self.secretKey).encode("utf-8")
69 | startSig = hashlib.sha1(startStr).hexdigest()
70 |
71 | startObj = {
72 | "cmd": "start",
73 | "param": {
74 | "app": {
75 | "userId": userId,
76 | "applicationId": self.appKey,
77 | "timestamp": timestamp,
78 | "sig": startSig
79 | },
80 | "audio": {
81 | "audioType": self.audioType,
82 | "channel": 1,
83 | "sampleBytes": 2,
84 | "sampleRate": self.audioSampleRate
85 | },
86 | "request": request
87 | }
88 | }
89 |
90 | startObj["param"]["request"]["tokenId"] = "tokenId"
91 |
92 | startStr = json.dumps(startObj)
93 | await websocket.send(startStr)
94 |
95 | # Send audio data
96 | f = open(self.audioPath, "rb")
97 | while True:
98 | data = f.read(1024)
99 | if not data:
100 | break
101 | await websocket.send(data)
102 | f.close()
103 |
104 | # Send stop request
105 | empty_arry = {
106 | "cmd": "stop",
107 | }
108 | empty_arry2 = json.dumps(empty_arry)
109 | await websocket.send(empty_arry2)
110 |
111 | return True
112 |
113 |
114 |
115 | async def main_logic(self,refText):
116 | #Establish connection
117 | async with websockets.connect(str(self.baseUrl) + str(self.coreType), ping_timeout = None) as websocket:
118 |
119 | await self.initConnnct(websocket)
120 |
121 | await self.startScore(websocket,{"coreType":self.coreType,"refText":refText})
122 |
123 | # Return result
124 | try:
125 | async for message in websocket:
126 | print('message===>' + message)
127 | message = json.loads(message)
128 | if message.get('errId'):
129 | print("error:", message['error'])
130 | else:
131 | self.scores["fluency"] = message["result"]["fluency"]
132 | self.scores["rhythm"] = message["result"]["rhythm"]
133 | self.scores["pronunciation"] = message["result"]["pronunciation"]
134 | self.scores["speed"] = message["result"]["speed"]
135 | self.scores["overall"] = message["result"]["overall"]
136 | print(self.scores)
137 | if websocket and websocket.open:
138 | await websocket.close()
139 | except websockets.exceptions.ConnectionClosedError:
140 | print('Close connection to websocket')
141 |
--------------------------------------------------------------------------------
/LangHelper/Assess/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Assess/__init__.py
--------------------------------------------------------------------------------
/LangHelper/Recognition/IflytekRec.py:
--------------------------------------------------------------------------------
1 |
2 | import hmac
3 | import base64
4 | import websocket
5 | import hashlib
6 | import json, time, threading,os
7 | from urllib.parse import quote
8 |
9 | class Recognition():
10 | def __init__(self, app_id, api_key):
11 |
12 | self.app_id = app_id
13 | self.api_key = api_key
14 | self.base_url = "ws://rtasr.xfyun.cn/v1/ws"
15 | self.ts = None
16 | self.signa = None
17 |
18 | self.ws = None
19 | self.trecv = None
20 | self.finished = False
21 | self.lang = 'en' #中文、中英混合识别:cn;英文:en;
22 | self.result = ""
23 |
24 | #update request url to adviod this error: illegal access|ts expired
25 | def update_request_parameter(self):
26 | self.ts = str(int(time.time()))
27 | tt = (self.app_id + self.ts).encode('utf-8')
28 | md5 = hashlib.md5()
29 | md5.update(tt)
30 | baseString = md5.hexdigest()
31 | baseString = bytes(baseString, encoding='utf-8')
32 | apiKey = self.api_key.encode('utf-8')
33 | signa = hmac.new(apiKey, baseString, hashlib.sha1).digest()
34 | signa = base64.b64encode(signa)
35 | self.signa = str(signa, 'utf-8')
36 |
37 | def set_language(self, lang):
38 | self.lang = 'cn' if (lang == 'cn') else 'en'
39 |
40 | def get_finish_state(self):
41 | return self.finished
42 |
43 | def start_connection(self):
44 | self.finished = False
45 | self.result = ""
46 | if self.ws:
47 | self.close()
48 | if self.trecv:
49 | self.trecv.join()
50 | self.update_request_parameter()
51 | Xunfeiurl = self.base_url + "?appid=" + self.app_id + "&ts=" + self.ts + "&signa=" + quote(self.signa) + "&lang=" + self.lang
52 | self.ws = websocket.create_connection(Xunfeiurl)
53 | self.trecv = threading.Thread(target=self.recv)
54 | self.trecv.start()
55 | print("connected sucessfully!")
56 |
57 | def check_connection(self):
58 | return self.ws and self.ws.connected
59 |
60 | def send(self, file_path, chunk):
61 | # two ways to send data, if chunk is not None, send data by chunk
62 | if chunk:
63 | if self.ws and self.ws.connected:
64 | self.ws.send(chunk)
65 | time.sleep(0.04) # 1280bytes /40ms
66 | else:
67 | print("can't send data to xunfei, not connected!")
68 | else:
69 | file_object = open(file_path, 'rb')
70 | try:
71 | index = 1
72 | while True:
73 | chunk = file_object.read(1280)
74 | if not chunk:
75 | break
76 | self.ws.send(chunk)
77 |
78 | index += 1
79 | time.sleep(0.04)
80 | finally:
81 | file_object.close()
82 |
83 | def send_endtag(self):
84 | self.ws.send(bytes(self.end_tag.encode('utf-8')))
85 | print("send end tag success")
86 |
87 | def recv(self):
88 | try:
89 | while self.ws.connected:
90 | result = str(self.ws.recv())
91 | if len(result) == 0:
92 | self.finished = True
93 | print("receive result end")
94 | break
95 | result_dict = json.loads(result)
96 | # 解析结果
97 | if result_dict["action"] == "started":
98 | print("handshake success, result: " + result)
99 |
100 | if result_dict["action"] == "result":
101 | #parse data
102 | data = json.loads(result_dict["data"])
103 | if data["cn"]["st"]["type"] == '0':
104 | stt_text = ""
105 | raw_rt = data["cn"]["st"]["rt"]
106 | for rt in raw_rt:
107 | raw_ws = rt['ws']
108 | for ws in raw_ws:
109 | raw_cw = ws['cw']
110 | for cw in raw_cw:
111 | stt_text += cw['w']
112 | print("rtasr result: " + stt_text)
113 | self.result += stt_text
114 | if result_dict["action"] == "error":
115 | print("rtasr error: " + result)
116 | self.ws.close()
117 | self.finished = True
118 | return
119 | except websocket.WebSocketConnectionClosedException:
120 | print("receive result end")
121 | self.finished = True
122 |
123 | def close(self):
124 | if self.ws:
125 | self.ws.close()
126 | self.finished = True
127 | print(" close connection manually")
128 |
129 | # test
130 | if __name__ == "__main__":
131 | rec = Recognition(app_id = "", api_key="")
132 | currentdir = os.path.dirname(os.path.abspath(__file__))
133 | pcmfile = os.path.join(currentdir,"../Resource/rec/realtime.pcm")
134 | # rec.set_language("cn") #中英识别,默认en
135 | rec.start_connection()
136 | rec.send(pcmfile,None)
137 | print(rec.result)
--------------------------------------------------------------------------------
/LangHelper/Recognition/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Recognition/__init__.py
--------------------------------------------------------------------------------
/LangHelper/Resource/cn/read_sentence_cn.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/cn/read_sentence_cn.mp3
--------------------------------------------------------------------------------
/LangHelper/Resource/cn/read_sentence_cn.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/cn/read_sentence_cn.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/cn/read_sentence_cn.txt:
--------------------------------------------------------------------------------
1 | 今天天气怎么样
--------------------------------------------------------------------------------
/LangHelper/Resource/cn/read_sentence_cn.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/cn/read_sentence_cn.wav
--------------------------------------------------------------------------------
/LangHelper/Resource/cn/read_syllable_cn.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/cn/read_syllable_cn.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/cn/read_syllable_cn.txt:
--------------------------------------------------------------------------------
1 | 好
--------------------------------------------------------------------------------
/LangHelper/Resource/en/oral_translation_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/oral_translation_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/oral_translation_en.txt:
--------------------------------------------------------------------------------
1 | [topic]
2 | 1. British People
3 | 1.1. British people usually say ""hello"" or ""nice to meet you"" and shake your hand when they meet you for the first time. They behave politely in public. They think it's rude to push in before others. They always queue. They are very polite at home as well. When in Rome, do as the Romans do. When we are in a strange place, we should do as the local people do.
4 | 1.2. For the first meeting, the English will usually say ""hello"" or ""nice to meet you"" and shake hands with you. In the public places, they behave themselves well; they think that jumping in the line is a rude behavior, so they always line up. They are often very polite at home. When we are in a strange place, do in Rome as Rome does. We should behave well as local people.
5 | 1.3. When they meet for the first time, the British usually say ""hello"" or ""nice to meet you"", and shake hands with each other. In public, they behave themselves appropriately. They think it is impolite to jump the queue, and they always wait in line patiently for their turns. They are also very polite at home. As the saying goes, ""when in Rome, do as the Romans do"". When we are in a strange place, we should act as the locals do.
6 | 1.4. When first meet, English are likely to say ""hello"" or ""nice to meet you"" and shake hands with you. They behave well in public. They usually line up because they think queue jumping is very impolite. And they are also very polite at home. There is an old saying ""Do in Rome as Rome does"". So when we are in a new place, we should behave ourselves as the locals do.
7 | 1.5. When meeting for the first time, Englishmen usually say ""hello"" or ""nice to meet you"" with a handshake. They behave themselves well in public places. They regard jumping a queue as one of the rude behavior, so they always queue up. They are also very polite at home. When in Rome, do as the Romans do. When we are in a strange place, we should behavior just like the local people.
8 | 1.6. For the first meeting, English people usually say ""Hello"" or ""Nice to meet you"" and shake hands with you. In the public place, they also act very decently. In their views, it is very impolite to cut in line. They have formed a habit to wait in a queue. At home, they are also very polite. When in a strange place, we should do in Rome as the Romans do. Moreover, it is also polite that we behave like the local people.
9 | 1.7. In first meeting, the English often say ""hi"" or ""nice to meet you!"" and then shake hands with you. In public occasions, they behave mannerly. They think jumping a queue is impolite and they always line up. Also, they are polite at home. When in Rome do as the Romans do. When we are in a strange land, we should behave like the natives.
10 | [lmtext]
11 | British People
12 | British people usually say ""hello"" or ""nice to meet you"" and shake your hand when they meet you for the first time. They behave politely in public. They think it's rude to push in before others. They always queue. They are very polite at home as well. When in Rome, do as the Romans do. When we are in a strange place, we should do as the local people do.
13 | For the first meeting, the English will usually say ""hello"" or ""nice to meet you"" and shake hands with you. In the public places, they behave themselves well; they think that jumping in the line is a rude behavior, so they always line up. They are often very polite at home. When we are in a strange place, do in Rome as Rome does. We should behave well as local people.
14 | When they meet for the first time, the British usually say ""hello"" or ""nice to meet you"", and shake hands with each other. In public, they behave themselves appropriately. They think it is impolite to jump the queue, and they always wait in line patiently for their turns. They are also very polite at home. As the saying goes, ""when in Rome, do as the Romans do"". When we are in a strange place, we should act as the locals do.
15 | When first meet, English are likely to say ""hello"" or ""nice to meet you"" and shake hands with you. They behave well in public. They usually line up because they think queue jumping is very impolite. And they are also very polite at home. There is an old saying ""Do in Rome as Rome does"". So when we are in a new place, we should behave ourselves as the locals do.
16 | When meeting for the first time, Englishmen usually say ""hello"" or ""nice to meet you"" with a handshake. They behave themselves well in public places. They regard jumping a queue as one of the rude behavior, so they always queue up. They are also very polite at home. When in Rome, do as the Romans do. When we are in a strange place, we should behavior just like the local people.
17 | For the first meeting, English people usually say ""Hello"" or ""Nice to meet you"" and shake hands with you. In the public place, they also act very decently. In their views, it is very impolite to cut in line. They have formed a habit to wait in a queue. At home, they are also very polite. When in a strange place, we should do in Rome as the Romans do. Moreover, it is also polite that we behave like the local people.
18 | In first meeting, the English often say ""hi"" or ""nice to meet you!"" and then shake hands with you. In public occasions, they behave mannerly. They think jumping a queue is impolite and they always line up. Also, they are polite at home. When in Rome do as the Romans do. When we are in a strange land, we should behave like the natives.
19 | [vocabulary]
20 | behavior /b ih 'hh ey v y ax/
21 | uncourteous /,ah n 'k er t ir s/
--------------------------------------------------------------------------------
/LangHelper/Resource/en/picture_talk_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/picture_talk_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/read_choice_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/read_choice_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/read_choice_en.txt:
--------------------------------------------------------------------------------
1 | [choice]
2 | 1. Snakes.
3 | 2. Children.
4 | 3. Cats.
5 | [keywords]
6 | cats
7 | [question]
8 | What did the woman dislike?
--------------------------------------------------------------------------------
/LangHelper/Resource/en/read_sentence_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/read_sentence_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/read_sentence_en.txt:
--------------------------------------------------------------------------------
1 | [content]
2 | there was a gentleman live near my house
--------------------------------------------------------------------------------
/LangHelper/Resource/en/read_word_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/read_word_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/read_word_en.txt:
--------------------------------------------------------------------------------
1 | [word]
2 | house
--------------------------------------------------------------------------------
/LangHelper/Resource/en/retell_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/retell_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/retell_en.txt:
--------------------------------------------------------------------------------
1 | [topic]
2 | 1. The Goose Thief
3 | 1.1. Tom went to primary school in the countryside. Near his classroom, there was a small pond where two geese were raised. Students were all fond of them. One day, when Tom passed the school kitchen, he heard the cooks talking about killing the geese for the teachers' Christmas dinner. Tom got angry, and said to himself, ""I won't let them be eaten!"" That night, Tom worked out a plan. He was going to hide them somewhere far away from the school. The next morning, Tom went to school in his father's big coat. During the break, he rushed to the pond. Without anyone around, he caught the geese and pushed them inside the coat. However, the geese were larger than he had thought, and they tried very hard to free themselves from the coat. The big noise caught the notice of the head teacher and the students, and they all ran to the pond. The head teacher asked for an explanation. Looking at the teacher with fear, Tom told the story and said, ""It is unfair to them. We all love them!"" The head teacher smiled and promised not to have them killed for the Christmas dinner.
4 | [keypoint]
5 | 1. Tom went to primary school in the countryside. Near his classroom, there was a small pond where two geese were raised.
6 | 2. Students were all fond of them.
7 | 3. One day, when Tom passed the school kitchen, he heard the cooks talking about killing the geese for the teachers' Christmas dinner.
8 | 4. Tom got angry, and said to himself, ""I won't let them be eaten!"" That night, Tom worked out a plan. He was going to hide them somewhere far away from the school.
9 | 5. The next morning, Tom went to school in his father's big coat. During the break, he rushed to the pond. Without anyone around, he caught the geese and pushed them inside the coat.
10 | 6. However, the geese were larger than he had thought, and they tried very hard to free themselves from the coat. The big noise caught the notice of the head teacher and the students,
11 | 7. They all ran to the pond.
12 | 8. The head teacher asked for an explanation.
13 | 9. Looking at the teacher with fear, Tom told the story and said, ""It is unfair to them. We all love them!""
14 | 10. The head teacher smiled and promised not to have them killed for the Christmas dinner.
15 | [lmtext]
16 | The Goose Thief
17 | Tom went to primary school in the countryside. Near his classroom, there was a small pond where two geese were raised. Students were all fond of them. One day, when Tom passed the school kitchen, he heard the cooks talking about killing the geese for the teachers' Christmas dinner. Tom got angry, and said to himself, ""I won't let them be eaten!"" That night, Tom worked out a plan. He was going to hide them somewhere far away from the school. The next morning, Tom went to school in his father's big coat. During the break, he rushed to the pond. Without anyone around, he caught the geese and pushed them inside the coat. However, the geese were larger than he had thought, and they tried very hard to free themselves from the coat. The big noise caught the notice of the head teacher and the students, and they all ran to the pond. The head teacher asked for an explanation. Looking at the teacher with fear, Tom told the story and said, ""It is unfair to them. We all love them!"" The head teacher smiled and promised not to have them killed for the Christmas dinner.
--------------------------------------------------------------------------------
/LangHelper/Resource/en/topic_en.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/en/topic_en.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/en/topic_en.txt:
--------------------------------------------------------------------------------
1 | [topic]
2 | 1. The Goose Thief
3 | 1.1. Tom went to primary school in the countryside. Near his classroom, there was a small pond where two geese were raised. Students were all fond of them. One day, when Tom passed the school kitchen, he heard the cooks talking about killing the geese for the teachers' Christmas dinner. Tom got angry, and said to himself, ""I won't let them be eaten!"" That night, Tom worked out a plan. He was going to hide them somewhere far away from the school. The next morning, Tom went to school in his father's big coat. During the break, he rushed to the pond. Without anyone around, he caught the geese and pushed them inside the coat. However, the geese were larger than he had thought, and they tried very hard to free themselves from the coat. The big noise caught the notice of the head teacher and the students, and they all ran to the pond. The head teacher asked for an explanation. Looking at the teacher with fear, Tom told the story and said, ""It is unfair to them. We all love them!"" The head teacher smiled and promised not to have them killed for the Christmas dinner.
4 | [keypoint]
5 | 1. Tom went to primary school in the countryside. Near his classroom, there was a small pond where two geese were raised.
6 | 2. Students were all fond of them.
7 | 3. One day, when Tom passed the school kitchen, he heard the cooks talking about killing the geese for the teachers' Christmas dinner.
8 | 4. Tom got angry, and said to himself, ""I won't let them be eaten!"" That night, Tom worked out a plan. He was going to hide them somewhere far away from the school.
9 | 5. The next morning, Tom went to school in his father's big coat. During the break, he rushed to the pond. Without anyone around, he caught the geese and pushed them inside the coat.
10 | 6. However, the geese were larger than he had thought, and they tried very hard to free themselves from the coat. The big noise caught the notice of the head teacher and the students,
11 | 7. They all ran to the pond.
12 | 8. The head teacher asked for an explanation.
13 | 9. Looking at the teacher with fear, Tom told the story and said, ""It is unfair to them. We all love them!""
14 | 10. The head teacher smiled and promised not to have them killed for the Christmas dinner.
15 | [lmtext]
16 | The Goose Thief
17 | Tom went to primary school in the countryside. Near his classroom, there was a small pond where two geese were raised. Students were all fond of them. One day, when Tom passed the school kitchen, he heard the cooks talking about killing the geese for the teachers' Christmas dinner. Tom got angry, and said to himself, ""I won't let them be eaten!"" That night, Tom worked out a plan. He was going to hide them somewhere far away from the school. The next morning, Tom went to school in his father's big coat. During the break, he rushed to the pond. Without anyone around, he caught the geese and pushed them inside the coat. However, the geese were larger than he had thought, and they tried very hard to free themselves from the coat. The big noise caught the notice of the head teacher and the students, and they all ran to the pond. The head teacher asked for an explanation. Looking at the teacher with fear, Tom told the story and said, ""It is unfair to them. We all love them!"" The head teacher smiled and promised not to have them killed for the Christmas dinner.
--------------------------------------------------------------------------------
/LangHelper/Resource/rec/realtime.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NsLearning/LangHelper/acb30ca60b2311c545d0006d2a319ea6a5a9cedb/LangHelper/Resource/rec/realtime.pcm
--------------------------------------------------------------------------------
/LangHelper/Resource/speaker-info.txt:
--------------------------------------------------------------------------------
1 | ID AGE GENDER ACCENTS REGION COMMENTS
2 | p225 23 F English Southern England
3 | p226 22 M English Surrey
4 | p227 38 M English Cumbria
5 | p228 22 F English Southern England
6 | p229 23 F English Southern England
7 | p230 22 F English Stockton-on-tees
8 | p231 23 F English Southern England
9 | p232 23 M English Southern England
10 | p233 23 F English Staffordshire
11 | p234 22 F Scottish West Dumfries
12 | p236 23 F English Manchester
13 | p237 22 M Scottish Fife
14 | p238 22 F NorthernIrish Belfast
15 | p239 22 F English SW England
16 | p240 21 F English Southern England
17 | p241 21 M Scottish Perth
18 | p243 22 M English London
19 | p244 22 F English Manchester
20 | p245 25 M Irish Dublin
21 | p246 22 M Scottish Selkirk
22 | p247 22 M Scottish Argyll
23 | p248 23 F Indian
24 | p249 22 F Scottish Aberdeen
25 | p250 22 F English SE England
26 | p251 26 M Indian
27 | p252 22 M Scottish Edinburgh
28 | p253 22 F Welsh Cardiff
29 | p254 21 M English Surrey
30 | p255 19 M Scottish Galloway
31 | p256 24 M English Birmingham
32 | p257 24 F English Southern England
33 | p258 22 M English Southern England
34 | p259 23 M English Nottingham
35 | p260 21 M Scottish Orkney
36 | p261 26 F NorthernIrish Belfast
37 | p262 23 F Scottish Edinburgh
38 | p263 22 M Scottish Aberdeen
39 | p264 23 F Scottish West Lothian
40 | p265 23 F Scottish Ross
41 | p266 22 F Irish Athlone
42 | p267 23 F English Yorkshire
43 | p268 23 F English Southern England
44 | p269 20 F English Newcastle
45 | p270 21 M English Yorkshire
46 | p271 19 M Scottish Fife
47 | p272 23 M Scottish Edinburgh
48 | p273 23 M English Suffolk
49 | p274 22 M English Essex
50 | p275 23 M Scottish Midlothian
51 | p276 24 F English Oxford
52 | p277 23 F English NE England
53 | p278 22 M English Cheshire
54 | p279 23 M English Leicester
55 | p280 25 F Unknown France (mic2 files unavailable)
56 | p281 29 M Scottish Edinburgh
57 | p282 23 F English Newcastle
58 | p283 24 F Irish Cork
59 | p284 20 M Scottish Fife
60 | p285 21 M Scottish Edinburgh
61 | p286 23 M English Newcastle
62 | p287 23 M English York
63 | p288 22 F Irish Dublin
64 | p292 23 M NorthernIrish Belfast
65 | p293 22 F NorthernIrish Belfast
66 | p294 33 F American San Francisco
67 | p295 23 F Irish Dublin
68 | p297 20 F American New York
69 | p298 19 M Irish Tipperary
70 | p299 25 F American California
71 | p300 23 F American California
72 | p301 23 F American North Carolina
73 | p302 20 M Canadian Montreal
74 | p303 24 F Canadian Toronto
75 | p304 22 M NorthernIrish Belfast
76 | p305 19 F American Philadelphia
77 | p306 21 F American New York
78 | p307 23 F Canadian Ontario
79 | p308 18 F American Alabama
80 | p310 21 F American Tennessee
81 | p311 21 M American Iowa
82 | p312 19 F Canadian Hamilton
83 | p313 24 F Irish County Down
84 | p314 26 F SouthAfrican Cape Town
85 | p315 18 M American New England (Text and mic2 files unavailable)
86 | p316 20 M Canadian Alberta
87 | p317 23 F Canadian Hamilton
88 | p318 32 F American Napa
89 | p323 19 F SouthAfrican Pretoria
90 | p326 26 M Australian English Sydney
91 | p329 23 F American
92 | p330 26 F American
93 | p333 19 F American Indiana
94 | p334 18 M American Chicago
95 | p335 25 F NewZealand English
96 | p336 18 F SouthAfrican Johannesburg
97 | p339 21 F American Pennsylvania
98 | p340 18 F Irish Dublin
99 | p341 26 F American Ohio
100 | p343 27 F Canadian Alberta
101 | p345 22 M American Florida
102 | p347 26 M SouthAfrican Johannesburg
103 | p351 21 F NorthernIrish Derry
104 | p360 19 M American New Jersey
105 | p361 19 F American New Jersey
106 | p362 29 F American
107 | p363 22 M Canadian Toronto
108 | p364 23 M Irish Donegal
109 | p374 28 M Australian English
110 | p376 22 M Indian
111 | s5 22 F British (new speaker, more data would be released for training a speaker-dependent model)
112 |
--------------------------------------------------------------------------------
/LangHelper/Resource/tts_models--en--vctk--vits/speaker_ids.json:
--------------------------------------------------------------------------------
1 | {
2 | "ED\n": 0,
3 | "p225": 1,
4 | "p226": 2,
5 | "p227": 3,
6 | "p228": 4,
7 | "p229": 5,
8 | "p230": 6,
9 | "p231": 7,
10 | "p232": 8,
11 | "p233": 9,
12 | "p234": 10,
13 | "p236": 11,
14 | "p237": 12,
15 | "p238": 13,
16 | "p239": 14,
17 | "p240": 15,
18 | "p241": 16,
19 | "p243": 17,
20 | "p244": 18,
21 | "p245": 19,
22 | "p246": 20,
23 | "p247": 21,
24 | "p248": 22,
25 | "p249": 23,
26 | "p250": 24,
27 | "p251": 25,
28 | "p252": 26,
29 | "p253": 27,
30 | "p254": 28,
31 | "p255": 29,
32 | "p256": 30,
33 | "p257": 31,
34 | "p258": 32,
35 | "p259": 33,
36 | "p260": 34,
37 | "p261": 35,
38 | "p262": 36,
39 | "p263": 37,
40 | "p264": 38,
41 | "p265": 39,
42 | "p266": 40,
43 | "p267": 41,
44 | "p268": 42,
45 | "p269": 43,
46 | "p270": 44,
47 | "p271": 45,
48 | "p272": 46,
49 | "p273": 47,
50 | "p274": 48,
51 | "p275": 49,
52 | "p276": 50,
53 | "p277": 51,
54 | "p278": 52,
55 | "p279": 53,
56 | "p280": 54,
57 | "p281": 55,
58 | "p282": 56,
59 | "p283": 57,
60 | "p284": 58,
61 | "p285": 59,
62 | "p286": 60,
63 | "p287": 61,
64 | "p288": 62,
65 | "p292": 63,
66 | "p293": 64,
67 | "p294": 65,
68 | "p295": 66,
69 | "p297": 67,
70 | "p298": 68,
71 | "p299": 69,
72 | "p300": 70,
73 | "p301": 71,
74 | "p302": 72,
75 | "p303": 73,
76 | "p304": 74,
77 | "p305": 75,
78 | "p306": 76,
79 | "p307": 77,
80 | "p308": 78,
81 | "p310": 79,
82 | "p311": 80,
83 | "p312": 81,
84 | "p313": 82,
85 | "p314": 83,
86 | "p316": 84,
87 | "p317": 85,
88 | "p318": 86,
89 | "p323": 87,
90 | "p326": 88,
91 | "p329": 89,
92 | "p330": 90,
93 | "p333": 91,
94 | "p334": 92,
95 | "p335": 93,
96 | "p336": 94,
97 | "p339": 95,
98 | "p340": 96,
99 | "p341": 97,
100 | "p343": 98,
101 | "p345": 99,
102 | "p347": 100,
103 | "p351": 101,
104 | "p360": 102,
105 | "p361": 103,
106 | "p362": 104,
107 | "p363": 105,
108 | "p364": 106,
109 | "p374": 107,
110 | "p376": 108
111 | }
--------------------------------------------------------------------------------
/LangHelper/recoder.py:
--------------------------------------------------------------------------------
1 | import pyaudio
2 | import os
3 | import wave
4 |
5 | class VoiceRecorder:
6 | def __init__(self, root_path):
7 |
8 | self.root_path = root_path # path to save cache recording file
9 | self.audio = pyaudio.PyAudio()
10 | self.stream = None
11 | self.chunks = [] #.pcm data byte chunks
12 | self.audio_format = pyaudio.paInt16
13 | self.rate = 16000
14 | self.channels = 1
15 |
16 | def start(self):
17 | print("start recording!")
18 | self.stream = self.audio.open(
19 | format= self.audio_format,
20 | channels= self.channels,
21 | rate=self.rate,
22 | input=True,
23 | frames_per_buffer=1280
24 | )
25 | self.chunks = []
26 |
27 | def stop(self):
28 | print("stop recording!")
29 | if self.stream:
30 | self.stream.stop_stream()
31 | self.stream.close()
32 | self.stream = None
33 | file_path = os.path.join(self.root_path,"realtime.pcm")
34 | self.save_to_pcm(file_path,self.chunks)
35 | file_path = os.path.join(self.root_path,"realtime.wav")
36 | self.save_to_wav(file_path, self.chunks)
37 |
38 | def read_audio(self):
39 | if not self.stream:
40 | raise Exception("Stream not open. Call start() first.")
41 | while True:
42 | data = self.stream.read(1280)
43 | yield data
44 |
45 | def save_to_pcm(self, filename, frames):
46 | with open(filename, "wb") as f:
47 | f.write(b"".join(frames))
48 |
49 | def save_to_wav(self,filename,frames):
50 | wf = wave.open(filename, 'wb')
51 | wf.setnchannels(self.channels)
52 | sampwidth = self.audio.get_sample_size(self.audio_format)
53 | print("sampwidth --->", sampwidth)
54 | wf.setsampwidth(sampwidth)
55 | wf.setframerate(self.rate)
56 | wf.writeframes(b''.join(frames))
57 | wf.close()
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## ✨ Features / 功能
4 | 1. talk to ChatGPT / 口语对话
5 |
6 | - support different speech types, web, AI with hundreds accents (VCTK corpus), will support azure, iflytek. [100多种口音演示](https://www.bilibili.com/video/BV1Q84y1P7nK/?spm_id_from=333.999.0.0&vd_source=21f2f45d40a5b4fec0f1ea075e50b356)
7 |
8 | - support two recognition tech, web and iflytek. [双语识别演示](https://www.bilibili.com/video/BV11L411C7G2/?spm_id_from=333.999.0.0&vd_source=21f2f45d40a5b4fec0f1ea075e50b356)
9 |
10 | - talk to any celebrities, such as Obama, Taylor, Downey...[奥巴马、霉霉、小罗伯特唐尼实时语音演示](https://www.bilibili.com/video/BV1Am4y127rp/)
11 |
12 | - will offer interface for loading your TTS model.
13 |
14 |
15 | 2. Speaking Assessment / 发音评分
16 |
17 | integrate two Assessment API, speechsuper and iflytek now, open to get better algorithms to perfect the feature for IELTS/TOEFL test. [口语评分演示](https://www.bilibili.com/video/BV1Ch41137en/?spm_id_from=333.999.0.0&vd_source=21f2f45d40a5b4fec0f1ea075e50b356) + [IELTS spoken test prompts](#ielts)
18 |
19 | ## 📦 Install
20 | ### windows
21 |
22 | [ChatGPT and LangHelper](https://github.com/NsLearning/LangHelper/releases/tag/V0.01.2) + [espeak-ng 装x86版的](https://github.com/espeak-ng/espeak-ng/releases/tag/1.51). ChatGPT and LangHelper 都是免安装的,espeak-ng 一路默认安装即可。[视频教程](https://www.bilibili.com/video/BV1f24y1c7qm/?vd_source=21f2f45d40a5b4fec0f1ea075e50b356) + [文字教程及注意事项](#instructions)
23 |
24 | 目前要使用AI发音得装ChatGPT desktop for Langhelper + LangHelper + espeak-ng,因为跑AI用到pytorch一些库比较大,放弃了把python程序打包成bin文件集成到ChatGPT desktop for Langhelper, espeak-ng是语音合成的必须依赖。
25 | - ChatGPT desktop for Langhelper(改版后的chatgpt桌面应用), windows 4月初更新那版好像会查杀,允许就好(可以查源代码, 没有任何其他有害脚本注入)。
26 | - LangHelper(AI发音及其他辅助程序, LangHelper文件下的python程序)
27 | - espeak-ng (开源的文本转语音库)
28 |
29 | ## instructions
30 |
31 | 1. 解压压缩包,一个Langhelper文件夹(内含langhelper.exe),一个chatgpt.exe, 分别打开。langhelper 会等待chatgpt配置完成后才能工作,chatgpt在-> preferences -> control center ->
32 | setting -> LangHelper 下设置语音相关功能, 没有配置API,就不要选其他的speech type 和recognition,设置完成后点击submit后, 有个restart提示点击yes. 这是langhelper窗口会出现Complete init -> start conversation,表示初始化完成。
33 |
34 | 2. 对话不能正常AI发音,先在setting -> LangHelper->Audition text 同行点击try测试是否能发音,文本为空则会默认合成"you are gorgeous, i love you". 看langhlper界面是什么提示,error:Obama, 就是表示Obama口音不能用,其他就是可能环境没配置好。入群发问请给出详细的错误截图信息。以下为合成How can I assist you today? 正常信息提示:
35 | > Text splitted to sentences.
36 | ['How can I assist you today?']
37 | Hello! p241
38 | > Text splitted to sentences.
39 | ['Hello!']
40 | > Processing time: 0.8514664173126221
41 | > Real-time factor: 0.5273829916220033
42 | > Processing time: 0.6864285469055176
43 | > Real-time factor: 0.6285610240559246
44 |
45 | 3. 语音识别,需先点一次speech recognition, 待不再需要识别后再点一次结束识别。
46 |
47 | ## IELTS
48 | How to set up prompts? Preferences -> control center ->Language Model -> User Custom ->Add model, add your custom prompts, here is my presetting ITELTS prompt for reference:
49 | > I want you to ask me some questions for simulating IELTS speaking test, non offical but give score of reference,when you ask me a question, I'll respond you my spoken text and prounciation scores which come from speech recognition and assessment tech, please remeber that you should ask me question one by one it means that you should offer another question after I give you response text which including prouncation scores, do not give questions one time because I do not want to respond it by one time, after all questions finished , you can combine the score to assess my answer, please give me the final score of IELTS speaking test.so let's start first question.
50 |
51 | ## Mission
52 | 11.22 OpenAI已出完全免费的语音对话功能,非PLUS也可以用:https://twitter.com/OpenAI/status/1727065166188274145
53 |
--------------------------------------------------------------------------------