1
0
mirror of https://github.com/lencx/ChatGPT.git synced 2024-10-01 01:06:13 -04:00

chore: add pretty

This commit is contained in:
lencx 2023-01-22 18:18:36 +08:00
parent 1ba356a91f
commit bc39dcdd72
56 changed files with 776 additions and 535 deletions

View File

@ -1,26 +1,26 @@
name: "🕷️ Bug report" name: '🕷️ Bug report'
description: "report bugs" description: 'report bugs'
title: "[Bug]" title: '[Bug]'
labels: labels:
- "bug" - 'bug'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!" value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!'
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
## Bug report ## Bug report
Please fill in the following information to help us reproduce the bug: Please fill in the following information to help us reproduce the bug:
- type: input - type: input
id: version id: version
attributes: attributes:
label: Version label: Version
description: "Please specify the version of ChatGPT you are using, a newer version may have fixed the bug you encountered.Check the [UPDATE_LOG](https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md) for more information." description: 'Please specify the version of ChatGPT you are using, a newer version may have fixed the bug you encountered.Check the [UPDATE_LOG](https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md) for more information.'
placeholder: "e.g. v0.1.0" placeholder: 'e.g. v0.1.0'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: bug id: bug
attributes: attributes:
label: Bug description label: Bug description
@ -28,16 +28,16 @@ body:
Please describe the bug here,if possible, please provide a minimal example to reproduce the bug.The content of `~/.chatgpt/chatgpt.log` may be helpful if you encounter a crash. Please describe the bug here,if possible, please provide a minimal example to reproduce the bug.The content of `~/.chatgpt/chatgpt.log` may be helpful if you encounter a crash.
validations: validations:
required: true required: true
- type: input - type: input
id: OS id: OS
attributes: attributes:
label: OS label: OS
description: "Please specify the OS you are using." description: 'Please specify the OS you are using.'
placeholder: "e.g. Ubuntu 22.04" placeholder: 'e.g. Ubuntu 22.04'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: environment id: environment
attributes: attributes:
label: Environment label: Environment
description: "If you think your environment may be related to the problem, please describe it here." description: 'If you think your environment may be related to the problem, please describe it here.'

View File

@ -1,37 +1,37 @@
name: "❌ Build error report" name: '❌ Build error report'
description: "report errors when building by yourself" description: 'report errors when building by yourself'
title: "[Build Error]" title: '[Build Error]'
labels: labels:
- "build error" - 'build error'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!" value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!'
- type: markdown - type: markdown
attributes: attributes:
value: "Please make sure to build from the source code with the latest version of ChatGPT." value: 'Please make sure to build from the source code with the latest version of ChatGPT.'
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
## Build error report ## Build error report
Please fill in the following information to help us reproduce the bug: Please fill in the following information to help us reproduce the bug:
- type: textarea - type: textarea
id: error id: error
attributes: attributes:
label: Error message label: Error message
description: "Please paste the error message here." description: 'Please paste the error message here.'
validations: validations:
required: true required: true
- type: input - type: input
id: OS id: OS
attributes: attributes:
label: OS label: OS
description: "Please specify the OS you are using." description: 'Please specify the OS you are using.'
placeholder: "e.g. Ubuntu 22.04" placeholder: 'e.g. Ubuntu 22.04'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: environment id: environment
attributes: attributes:
label: Environment label: Environment
description: "If you think your environment may be related to the problem, please describe it here." description: 'If you think your environment may be related to the problem, please describe it here.'

View File

@ -1,19 +1,19 @@
name: "📚 Documentation Issue" name: '📚 Documentation Issue'
description: "report documentation issues, typos welcome!" description: 'report documentation issues, typos welcome!'
title: "[Doc]" title: '[Doc]'
labels: labels:
- "documentation" - 'documentation'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one." value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one.'
- type: textarea - type: textarea
id: doc-description id: doc-description
attributes: attributes:
label: "Provide a description of requested docs changes" label: 'Provide a description of requested docs changes'
description: "Briefly describe the requested docs changes." description: 'Briefly describe the requested docs changes.'
validations: validations:
required: true required: true
- type: markdown - type: markdown
attributes: attributes:
value: Please limit one request per issue. value: Please limit one request per issue.

View File

@ -1,34 +1,34 @@
name: "⭐ Feature or enhancement request" name: '⭐ Feature or enhancement request'
description: "suggest new features or enhancements" description: 'suggest new features or enhancements'
title: "[Feature]" title: '[Feature]'
labels: labels:
- "enhancement" - 'enhancement'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one." value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one.'
- type: textarea - type: textarea
id: feature-description id: feature-description
attributes: attributes:
label: "Feature description" label: 'Feature description'
description: "Describe the feature or enhancements you'd like to see." description: "Describe the feature or enhancements you'd like to see."
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: motivation id: motivation
attributes: attributes:
label: "Motivation" label: 'Motivation'
description: "Describe the motivation for this feature or enhancement." description: 'Describe the motivation for this feature or enhancement.'
- type: textarea - type: textarea
id: alternatives id: alternatives
attributes: attributes:
label: "Alternatives" label: 'Alternatives'
description: "Describe any alternatives you've considered." description: "Describe any alternatives you've considered."
- type: textarea - type: textarea
id: additional-context id: additional-context
attributes: attributes:
label: "Additional context" label: 'Additional context'
description: "Add any other context or screenshots about the feature request here." description: 'Add any other context or screenshots about the feature request here.'
- type: markdown - type: markdown
attributes: attributes:
value: Please limit one request per issue. value: Please limit one request per issue.

View File

@ -1,34 +1,34 @@
name: "⚠️ Security&Privacy issue" name: '⚠️ Security&Privacy issue'
description: "Report security or privacy issues" description: 'Report security or privacy issues'
title: "[Security]" title: '[Security]'
labels: labels:
- "security" - 'security'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one." value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one.'
- type: textarea - type: textarea
id: security-description id: security-description
attributes: attributes:
label: "Description" label: 'Description'
description: "Describe the security or privacy issue." description: 'Describe the security or privacy issue.'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: motivation id: motivation
attributes: attributes:
label: "Motivation" label: 'Motivation'
description: "Describe the motivation for this security or privacy issue." description: 'Describe the motivation for this security or privacy issue.'
- type: textarea - type: textarea
id: alternatives id: alternatives
attributes: attributes:
label: "Alternatives" label: 'Alternatives'
description: "Describe any alternatives you've considered." description: "Describe any alternatives you've considered."
- type: textarea - type: textarea
id: additional-context id: additional-context
attributes: attributes:
label: "Additional context" label: 'Additional context'
description: "Add any other context or screenshots about the security or privacy issue here." description: 'Add any other context or screenshots about the security or privacy issue here.'
- type: markdown - type: markdown
attributes: attributes:
value: Please limit one request per issue. value: Please limit one request per issue.

45
.prettierignore Normal file
View File

@ -0,0 +1,45 @@
package-lock.json
node_modules/
yarn.lock
*.lock
casks/
# rust
src-tauri/
target/
Cargo.lock
*.toml
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
assets/**
public/**
.gitattributes
.gitignore
.prettierignore
LICENSE

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"printWidth": 100
}

View File

@ -10,6 +10,7 @@
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases) [![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr) [![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
[![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_) [![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
<!-- [![lencx](https://img.shields.io/twitter/follow/lencx_.svg?style=social)](https://twitter.com/lencx_) --> <!-- [![lencx](https://img.shields.io/twitter/follow/lencx_.svg?style=social)](https://twitter.com/lencx_) -->
<a href="https://www.buymeacoffee.com/lencx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a> <a href="https://www.buymeacoffee.com/lencx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>
@ -25,6 +26,7 @@
- [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): - [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi):
- 使用 [winget](https://winstall.app/apps/lencx.ChatGPT): - 使用 [winget](https://winstall.app/apps/lencx.ChatGPT):
```bash ```bash
# install the latest version # install the latest version
winget install --id=lencx.ChatGPT -e winget install --id=lencx.ChatGPT -e

View File

@ -10,6 +10,7 @@
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases) [![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr) [![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
[![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_) [![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
<!-- [![lencx](https://img.shields.io/twitter/follow/lencx_.svg?style=social)](https://twitter.com/lencx_) --> <!-- [![lencx](https://img.shields.io/twitter/follow/lencx_.svg?style=social)](https://twitter.com/lencx_) -->
<!-- [![中文版 badge](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-Traditional%20Chinese-blue)](./README-ZH.md) --> <!-- [![中文版 badge](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-Traditional%20Chinese-blue)](./README-ZH.md) -->
@ -27,6 +28,7 @@
- [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): Direct download installer - [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): Direct download installer
- Use [winget](https://winstall.app/apps/lencx.ChatGPT): - Use [winget](https://winstall.app/apps/lencx.ChatGPT):
```bash ```bash
# install the latest version # install the latest version
winget install --id=lencx.ChatGPT -e winget install --id=lencx.ChatGPT -e
@ -174,6 +176,7 @@ Currently, only json and csv are supported for synchronizing custom files, and t
## 📌 TODO ## 📌 TODO
<!-- - Web access capability ([#20](https://github.com/lencx/ChatGPT/issues/20)) --> <!-- - Web access capability ([#20](https://github.com/lencx/ChatGPT/issues/20)) -->
- `Control Center` enhancement - `Control Center` enhancement
- `Pop-up Search` enhancement - `Pop-up Search` enhancement
- ... - ...

View File

@ -11,15 +11,18 @@ fix: slash command does not work
## v0.9.0 ## v0.9.0
fix: fix:
- export button does not work - export button does not work
feat: feat:
- add an export markdown button - add an export markdown button
- `Control Center` adds `Notes` and `Download` menus for managing exported chat files (Markdown, PNG, PDF). `Notes` supports markdown previews. - `Control Center` adds `Notes` and `Download` menus for managing exported chat files (Markdown, PNG, PDF). `Notes` supports markdown previews.
## v0.8.1 ## v0.8.1
fix: fix:
- export button keeps blinking - export button keeps blinking
- export button in the old chat does not work - export button in the old chat does not work
- disable export sharing links because it is a security risk - disable export sharing links because it is a security risk
@ -27,22 +30,26 @@ fix:
## v0.8.0 ## v0.8.0
feat: feat:
- theme enhancement (Light, Dark, System) - theme enhancement (Light, Dark, System)
- automatic updates support `silent` settings - automatic updates support `silent` settings
- pop-up search: select the ChatGPT content with the mouse, the `DALL·E 2` button appears, and click to jump (note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable). - pop-up search: select the ChatGPT content with the mouse, the `DALL·E 2` button appears, and click to jump (note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable).
fix: fix:
- close the main window and hide it in the tray (windows systems) - close the main window and hide it in the tray (windows systems)
## v0.7.4 ## v0.7.4
fix: fix:
- trying to resolve linux errors: `error while loading shared libraries` - trying to resolve linux errors: `error while loading shared libraries`
- customize global shortcuts (`Menu -> Preferences -> Control Center -> General -> Global Shortcut`) - customize global shortcuts (`Menu -> Preferences -> Control Center -> General -> Global Shortcut`)
## v0.7.3 ## v0.7.3
chore: chore:
- optimize slash command style - optimize slash command style
- optimize tray menu icon and button icons - optimize tray menu icon and button icons
- global shortcuts to the chatgpt app (mac: `Command + Shift + O`, windows: `Ctrl + Shift + O`) - global shortcuts to the chatgpt app (mac: `Command + Shift + O`, windows: `Ctrl + Shift + O`)
@ -54,6 +61,7 @@ fix: some windows systems cannot start the application
## v0.7.1 ## v0.7.1
fix: fix:
- some windows systems cannot start the application - some windows systems cannot start the application
- windows and linux add about menu (show version information) - windows and linux add about menu (show version information)
- the tray icon is indistinguishable from the background in dark mode on window and linux - the tray icon is indistinguishable from the background in dark mode on window and linux
@ -61,10 +69,12 @@ fix:
## v0.7.0 ## v0.7.0
fix: fix:
- mac m1 copy/paste does not work on some system versions - mac m1 copy/paste does not work on some system versions
- optimize the save chat log button to a small icon, the tray window no longer provides a save chat log button (the buttons causes the input area to become larger and the content area to become smaller) - optimize the save chat log button to a small icon, the tray window no longer provides a save chat log button (the buttons causes the input area to become larger and the content area to become smaller)
feat: feat:
- use the keyboard `⇧` (arrow up) and `⇩` (arrow down) keys to select the slash command - use the keyboard `⇧` (arrow up) and `⇩` (arrow down) keys to select the slash command
<!-- - global shortcuts to the chatgpt app (mac: command+shift+o, windows: ctrl+shift+o) --> <!-- - global shortcuts to the chatgpt app (mac: command+shift+o, windows: ctrl+shift+o) -->
@ -77,6 +87,7 @@ fix: sync failure on windows
fix: path not allowed on the configured scope fix: path not allowed on the configured scope
feat: feat:
- optimize the generated pdf file size - optimize the generated pdf file size
- menu added `Sync Prompts` - menu added `Sync Prompts`
- `Control Center` added `Sync Custom` - `Control Center` added `Sync Custom`
@ -86,6 +97,7 @@ feat:
## v0.6.0 ## v0.6.0
fix: fix:
- windows show Chinese when upgrading - windows show Chinese when upgrading
## v0.5.1 ## v0.5.1
@ -103,11 +115,13 @@ add chatgpt log (path: `~/.chatgpt/chatgpt.log`)
## v0.4.1 ## v0.4.1
fix: fix:
- tray window style optimization - tray window style optimization
## v0.4.0 ## v0.4.0
feat: feat:
- customize the ChatGPT prompts command (https://github.com/lencx/ChatGPT#-announcement) - customize the ChatGPT prompts command (https://github.com/lencx/ChatGPT#-announcement)
- menu enhancement: hide application icons from the Dock (support macOS only) - menu enhancement: hide application icons from the Dock (support macOS only)
@ -116,12 +130,14 @@ feat:
fix: can't open ChatGPT fix: can't open ChatGPT
feat: menu enhancement feat: menu enhancement
- the control center of ChatGPT application - the control center of ChatGPT application
- open the configuration file directory - open the configuration file directory
## v0.2.2 ## v0.2.2
feat: feat:
- menu: go to config - menu: go to config
## v0.2.1 ## v0.2.1
@ -131,12 +147,14 @@ feat: menu optimization
## v0.2.0 ## v0.2.0
feat: menu enhancement feat: menu enhancement
- customize user-agent to prevent security detection interception - customize user-agent to prevent security detection interception
- clear all chatgpt configuration files - clear all chatgpt configuration files
## v0.1.8 ## v0.1.8
feat: feat:
- menu enhancement: theme, titlebar - menu enhancement: theme, titlebar
- modify website address - modify website address
@ -147,6 +165,7 @@ feat: tray window
## v0.1.6 ## v0.1.6
feat: feat:
- stay on top - stay on top
- export ChatGPT history - export ChatGPT history
@ -157,6 +176,7 @@ fix: mac can't use shortcut keys
## v0.1.4 ## v0.1.4
feat: feat:
- beautify icons - beautify icons
- add system tray menu - add system tray menu

View File

@ -12,8 +12,11 @@
"fix:tray": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon-light.png\" --json.tauri_systemTray_iconAsTemplate=false", "fix:tray": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon-light.png\" --json.tauri_systemTray_iconAsTemplate=false",
"fix:tray:mac": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon.png\" --json.tauri_systemTray_iconAsTemplate=true", "fix:tray:mac": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon.png\" --json.tauri_systemTray_iconAsTemplate=true",
"download": "node ./scripts/download.js", "download": "node ./scripts/download.js",
"fmt:rs": "cargo fmt",
"tr": "tr", "tr": "tr",
"tauri": "tauri" "tauri": "tauri",
"prettier": "prettier -c --write '**/*'",
"pretty-quick": "pretty-quick"
}, },
"license": "MIT", "license": "MIT",
"author": "lencx <cxin1314@gmail.com>", "author": "lencx <cxin1314@gmail.com>",
@ -32,6 +35,11 @@
"type": "git", "type": "git",
"url": "https://github.com/lencx/ChatGPT" "url": "https://github.com/lencx/ChatGPT"
}, },
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.8.0", "@ant-design/icons": "^4.8.0",
"@monaco-editor/react": "^4.4.6", "@monaco-editor/react": "^4.4.6",
@ -59,6 +67,9 @@
"@types/react-syntax-highlighter": "^15.5.6", "@types/react-syntax-highlighter": "^15.5.6",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.0.0",
"husky": "^8.0.3",
"prettier": "^2.8.3",
"pretty-quick": "^3.1.3",
"sass": "^1.56.2", "sass": "^1.56.2",
"typescript": "^4.9.4", "typescript": "^4.9.4",
"vite": "^4.0.0", "vite": "^4.0.0",

View File

@ -22,15 +22,20 @@ const FilePath: FC<FilePathProps> = ({ className, label = 'PATH', paths = '', ur
setPath(url); setPath(url);
return; return;
} }
setPath(await path.join(await chatRoot(), ...paths.split('/').filter(i => !!i))); setPath(await path.join(await chatRoot(), ...paths.split('/').filter((i) => !!i)));
})() })();
}, [url, paths]) }, [url, paths]);
return ( return (
<div className={clsx(className, 'chat-file-path')}> <div className={clsx(className, 'chat-file-path')}>
<div>{label}: <a onClick={() => shell.open(filePath)} title={filePath}>{content ? content : filePath}</a></div> <div>
{label}:{' '}
<a onClick={() => shell.open(filePath)} title={filePath}>
{content ? content : filePath}
</a>
</div>
</div> </div>
); );
} };
export default FilePath; export default FilePath;

View File

@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import Editor from "@monaco-editor/react"; import Editor from '@monaco-editor/react';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import Markdown from '@/components/Markdown'; import Markdown from '@/components/Markdown';
@ -17,12 +17,12 @@ const MarkdownEditor: FC<MarkdownEditorProps> = ({ value = '', onChange, mode =
useEffect(() => { useEffect(() => {
setContent(value); setContent(value);
onChange && onChange(value); onChange && onChange(value);
}, [value]) }, [value]);
const handleEdit = (e: any) => { const handleEdit = (e: any) => {
setContent(e); setContent(e);
onChange && onChange(e); onChange && onChange(e);
} };
const isSplit = mode === 'split'; const isSplit = mode === 'split';
@ -31,11 +31,7 @@ const MarkdownEditor: FC<MarkdownEditorProps> = ({ value = '', onChange, mode =
<PanelGroup direction="horizontal"> <PanelGroup direction="horizontal">
{['md', 'split'].includes(mode) && ( {['md', 'split'].includes(mode) && (
<Panel> <Panel>
<Editor <Editor language="markdown" value={content} onChange={handleEdit} />
language="markdown"
value={content}
onChange={handleEdit}
/>
</Panel> </Panel>
)} )}
{isSplit && <PanelResizeHandle className="resize-handle" />} {isSplit && <PanelResizeHandle className="resize-handle" />}
@ -46,7 +42,7 @@ const MarkdownEditor: FC<MarkdownEditorProps> = ({ value = '', onChange, mode =
)} )}
</PanelGroup> </PanelGroup>
</div> </div>
) );
}; };
export default MarkdownEditor; export default MarkdownEditor;

View File

@ -1,15 +1,16 @@
.markdown-body { .markdown-body {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial,
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
&.edit-preview { &.edit-preview {
padding: 10px; padding: 10px;
font-size: 14px; font-size: 14px;
} }
pre, code { pre,
code {
font-family: monospace, monospace; font-family: monospace, monospace;
} }
} }

View File

@ -13,7 +13,6 @@ interface MarkdownProps {
} }
const Markdown: FC<MarkdownProps> = ({ children, className }) => { const Markdown: FC<MarkdownProps> = ({ children, className }) => {
return ( return (
<div className={clsx(className, 'markdown-body')}> <div className={clsx(className, 'markdown-body')}>
<div> <div>
@ -22,8 +21,8 @@ const Markdown: FC<MarkdownProps> = ({ children, className }) => {
linkTarget="_blank" linkTarget="_blank"
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
components={{ components={{
code({node, inline, className, children, ...props}) { code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '') const match = /language-(\w+)/.exec(className || '');
return !inline && match ? ( return !inline && match ? (
<SyntaxHighlighter <SyntaxHighlighter
children={String(children).replace(/\n$/, '')} children={String(children).replace(/\n$/, '')}
@ -38,13 +37,13 @@ const Markdown: FC<MarkdownProps> = ({ children, className }) => {
<code className={className} {...props}> <code className={className} {...props}>
{children} {children}
</code> </code>
) );
} },
}} }}
/> />
</div> </div>
</div> </div>
) );
} };
export default Markdown; export default Markdown;

View File

@ -20,7 +20,7 @@ const Tags: FC<TagsProps> = ({ max = 99, value = [], onChange, addTxt = 'New Tag
useEffect(() => { useEffect(() => {
setTags(value); setTags(value);
}, [value]) }, [value]);
useEffect(() => { useEffect(() => {
if (inputVisible) { if (inputVisible) {

View File

@ -15,12 +15,12 @@ export default function useChatModel(key: string, file = CHAT_MODEL_JSON) {
setModelJson(data); setModelJson(data);
}); });
const modelSet = async (data: Record<string, any>[]|Record<string, any>) => { const modelSet = async (data: Record<string, any>[] | Record<string, any>) => {
const oData = clone(modelJson); const oData = clone(modelJson);
oData[key] = data; oData[key] = data;
await writeJSON(file, oData); await writeJSON(file, oData);
setModelJson(oData); setModelJson(oData);
} };
return { modelJson, modelSet, modelData: modelJson?.[key] || [] }; return { modelJson, modelSet, modelData: modelJson?.[key] || [] };
} }
@ -40,12 +40,16 @@ export function useCacheModel(file = '') {
await writeJSON(newFile ? newFile : file, data, { isRoot: true }); await writeJSON(newFile ? newFile : file, data, { isRoot: true });
setModelCacheJson(data); setModelCacheJson(data);
await modelCacheCmd(); await modelCacheCmd();
} };
const modelCacheCmd = async () => { const modelCacheCmd = async () => {
// Generate the `chat.model.cmd.json` file and refresh the page for the slash command to take effect. // Generate the `chat.model.cmd.json` file and refresh the page for the slash command to take effect.
const list = await invoke('cmd_list'); const list = await invoke('cmd_list');
await writeJSON(CHAT_MODEL_CMD_JSON, { name: 'ChatGPT CMD', last_updated: Date.now(), data: list }); await writeJSON(CHAT_MODEL_CMD_JSON, {
name: 'ChatGPT CMD',
last_updated: Date.now(),
data: list,
});
await invoke('window_reload', { label: 'core' }); await invoke('window_reload', { label: 'core' });
await invoke('window_reload', { label: 'tray' }); await invoke('window_reload', { label: 'tray' });
}; };

View File

@ -5,7 +5,7 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils';
export default function useColumns(columns: any[] = []) { export default function useColumns(columns: any[] = []) {
const [opType, setOpType] = useState(''); const [opType, setOpType] = useState('');
const [opRecord, setRecord] = useState<Record<string|symbol, any> | null>(null); const [opRecord, setRecord] = useState<Record<string | symbol, any> | null>(null);
const [opTime, setNow] = useState<number | null>(null); const [opTime, setNow] = useState<number | null>(null);
const [opExtra, setExtra] = useState<any>(null); const [opExtra, setExtra] = useState<any>(null);
@ -58,17 +58,16 @@ export const EditRow: FC<EditRowProps> = ({ rowKey, row, actions }) => {
setEdit(true); setEdit(true);
}; };
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setVal(e.target.value) setVal(e.target.value);
}; };
const handleSave = () => { const handleSave = () => {
setEdit(false); setEdit(false);
row[rowKey] = val?.trim(); row[rowKey] = val?.trim();
actions?.setRecord(row, 'rowedit') actions?.setRecord(row, 'rowedit');
}; };
return isEdit return isEdit ? (
? (
<Input <Input
value={val} value={val}
autoFocus autoFocus
@ -76,8 +75,9 @@ export const EditRow: FC<EditRowProps> = ({ rowKey, row, actions }) => {
{...DISABLE_AUTO_COMPLETE} {...DISABLE_AUTO_COMPLETE}
onPressEnter={handleSave} onPressEnter={handleSave}
/> />
) ) : (
: ( <div className="rowedit" onClick={handleEdit}>
<div className='rowedit' onClick={handleEdit}>{val}</div> {val}
</div>
); );
}; };

View File

@ -8,7 +8,7 @@ export default function useData(oData: any[]) {
useEffect(() => { useEffect(() => {
opInit(oData); opInit(oData);
}, []) }, []);
const opAdd = (val: any) => { const opAdd = (val: any) => {
const v = [val, ...opData]; const v = [val, ...opData];
@ -18,19 +18,19 @@ export default function useData(oData: any[]) {
const opInit = (val: any[] = []) => { const opInit = (val: any[] = []) => {
if (!val || !Array.isArray(val)) return; if (!val || !Array.isArray(val)) return;
const nData = val.map(i => ({ [safeKey]: v4(), ...i })); const nData = val.map((i) => ({ [safeKey]: v4(), ...i }));
setData(nData); setData(nData);
}; };
const opRemove = (id: string) => { const opRemove = (id: string) => {
const nData = opData.filter(i => i[safeKey] !== id); const nData = opData.filter((i) => i[safeKey] !== id);
setData(nData); setData(nData);
return nData; return nData;
}; };
const opReplace = (id: string, data: any) => { const opReplace = (id: string, data: any) => {
const nData = [...opData]; const nData = [...opData];
const idx = opData.findIndex(v => v[safeKey] === id); const idx = opData.findIndex((v) => v[safeKey] === id);
nData[idx] = data; nData[idx] = data;
setData(nData); setData(nData);
return nData; return nData;

View File

@ -8,5 +8,5 @@ export default function useInit(callback: () => void) {
callback(); callback();
isInit.current = false; isInit.current = false;
} }
}) });
} }

View File

@ -7,14 +7,17 @@ import { safeKey } from '@/hooks/useData';
type rowSelectionOptions = { type rowSelectionOptions = {
key: 'id' | string; key: 'id' | string;
rowType: 'id' | 'row' | 'all'; rowType: 'id' | 'row' | 'all';
} };
export function useTableRowSelection(options: Partial<rowSelectionOptions> = {}) { export function useTableRowSelection(options: Partial<rowSelectionOptions> = {}) {
const { key = 'id', rowType = 'id' } = options; const { key = 'id', rowType = 'id' } = options;
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRowIDs, setSelectedRowIDs] = useState<string[]>([]); const [selectedRowIDs, setSelectedRowIDs] = useState<string[]>([]);
const [selectedRows, setSelectedRows] = useState<Record<string|symbol, any>[]>([]); const [selectedRows, setSelectedRows] = useState<Record<string | symbol, any>[]>([]);
const onSelectChange = (newSelectedRowKeys: React.Key[], newSelectedRows: Record<string|symbol, any>[]) => { const onSelectChange = (
newSelectedRowKeys: React.Key[],
newSelectedRows: Record<string | symbol, any>[],
) => {
const keys = newSelectedRows.map((i: any) => i[safeKey] || i[key]); const keys = newSelectedRows.map((i: any) => i[safeKey] || i[key]);
setSelectedRowKeys(newSelectedRowKeys); setSelectedRowKeys(newSelectedRowKeys);
if (rowType === 'id') { if (rowType === 'id') {
@ -38,11 +41,7 @@ export function useTableRowSelection(options: Partial<rowSelectionOptions> = {})
const rowSelection: TableRowSelection<Record<string, any>> = { const rowSelection: TableRowSelection<Record<string, any>> = {
selectedRowKeys, selectedRowKeys,
onChange: onSelectChange, onChange: onSelectChange,
selections: [ selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE],
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
],
}; };
return { rowSelection, selectedRowIDs, selectedRows, rowReset }; return { rowSelection, selectedRowIDs, selectedRows, rowReset };

View File

@ -1,4 +1,4 @@
import Icon from "@ant-design/icons"; import Icon from '@ant-design/icons';
import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
interface IconProps { interface IconProps {
@ -6,12 +6,20 @@ interface IconProps {
} }
export default function SplitIcon(props: Partial<CustomIconComponentProps & IconProps>) { export default function SplitIcon(props: Partial<CustomIconComponentProps & IconProps>) {
return <Icon return (
<Icon
{...props} {...props}
component={() => ( component={() => (
<svg className="chatico" viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor"> <svg
className="chatico"
viewBox="0 0 1024 1024"
width="1em"
height="1em"
fill="currentColor"
>
<path d="M252.068571 906.496h520.283429c89.581714 0 134.144-44.562286 134.144-132.845714V250.331429c0-88.283429-44.562286-132.845714-134.144-132.845715H252.068571c-89.142857 0-134.582857 44.141714-134.582857 132.845715V773.668571c0 88.704 45.44 132.845714 134.582857 132.845715z m1.28-68.992c-42.843429 0-66.852571-22.710857-66.852571-67.291429V253.805714c0-44.580571 24.009143-67.291429 66.852571-67.291428h222.866286v651.008z m517.723429-651.008c42.422857 0 66.432 22.710857 66.432 67.291429V770.194286c0 44.580571-24.009143 67.291429-66.432 67.291428H548.205714V186.496z" /> <path d="M252.068571 906.496h520.283429c89.581714 0 134.144-44.562286 134.144-132.845714V250.331429c0-88.283429-44.562286-132.845714-134.144-132.845715H252.068571c-89.142857 0-134.582857 44.141714-134.582857 132.845715V773.668571c0 88.704 45.44 132.845714 134.582857 132.845715z m1.28-68.992c-42.843429 0-66.852571-22.710857-66.852571-67.291429V253.805714c0-44.580571 24.009143-67.291429 66.852571-67.291428h222.866286v651.008z m517.723429-651.008c42.422857 0 66.432 22.710857 66.432 67.291429V770.194286c0 44.580571-24.009143 67.291429-66.432 67.291428H548.205714V186.496z" />
</svg> </svg>
)} )}
/> />
);
} }

34
src/layout/index.tsx vendored
View File

@ -21,21 +21,21 @@ export default function ChatLayout() {
setAppInfo({ setAppInfo({
appName: await getName(), appName: await getName(),
appVersion: await getVersion(), appVersion: await getVersion(),
appTheme: await invoke("get_theme"), appTheme: await invoke('get_theme'),
});
}); });
})
const checkAppUpdate = async () => { const checkAppUpdate = async () => {
await invoke('run_check_update', { silent: false, hasMsg: true }); await invoke('run_check_update', { silent: false, hasMsg: true });
} };
const isDark = appInfo.appTheme === "dark"; const isDark = appInfo.appTheme === 'dark';
return ( return (
<ConfigProvider theme={{ algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm }}> <ConfigProvider theme={{ algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm }}>
<Layout style={{ minHeight: '100vh' }} hasSider> <Layout style={{ minHeight: '100vh' }} hasSider>
<Sider <Sider
theme={isDark ? "dark" : "light"} theme={isDark ? 'dark' : 'light'}
collapsible collapsible
collapsed={collapsed} collapsed={collapsed}
onCollapse={(value) => setCollapsed(value)} onCollapse={(value) => setCollapsed(value)}
@ -49,13 +49,17 @@ export default function ChatLayout() {
zIndex: 999, zIndex: 999,
}} }}
> >
<div className="chat-logo"><img src="/logo.png" /></div> <div className="chat-logo">
<img src="/logo.png" />
</div>
<div className="chat-info"> <div className="chat-info">
<Tag>{appInfo.appName}</Tag> <Tag>{appInfo.appName}</Tag>
<Tag> <Tag>
<span style={{ marginRight: 5 }}>{appInfo.appVersion}</span> <span style={{ marginRight: 5 }}>{appInfo.appVersion}</span>
<Tooltip title="click to check update"> <Tooltip title="click to check update">
<a onClick={checkAppUpdate}><SyncOutlined /></a> <a onClick={checkAppUpdate}>
<SyncOutlined />
</a>
</Tooltip> </Tooltip>
</Tag> </Tag>
</div> </div>
@ -63,27 +67,33 @@ export default function ChatLayout() {
<Menu <Menu
defaultSelectedKeys={[location.pathname]} defaultSelectedKeys={[location.pathname]}
mode="inline" mode="inline"
theme={ appInfo.appTheme === "dark" ? "dark" : "light" } theme={appInfo.appTheme === 'dark' ? 'dark' : 'light'}
inlineIndent={12} inlineIndent={12}
items={menuItems} items={menuItems}
// defaultOpenKeys={['/model']} // defaultOpenKeys={['/model']}
onClick={(i) => go(i.key)} onClick={(i) => go(i.key)}
/> />
</Sider> </Sider>
<Layout className="chat-layout" style={{ marginLeft: collapsed ? 80 : 200, transition: 'margin-left 300ms ease-out' }}> <Layout
className="chat-layout"
style={{ marginLeft: collapsed ? 80 : 200, transition: 'margin-left 300ms ease-out' }}
>
<Content <Content
className="chat-container" className="chat-container"
style={{ style={{
overflow: 'inherit' overflow: 'inherit',
}} }}
> >
<Routes /> <Routes />
</Content> </Content>
<Footer style={{ textAlign: 'center' }}> <Footer style={{ textAlign: 'center' }}>
<a href="https://github.com/lencx/chatgpt" target="_blank">ChatGPT Desktop Application</a> ©2022 Created by lencx <a href="https://github.com/lencx/chatgpt" target="_blank">
ChatGPT Desktop Application
</a>{' '}
©2022 Created by lencx
</Footer> </Footer>
</Layout> </Layout>
</Layout> </Layout>
</ConfigProvider> </ConfigProvider>
); );
}; }

3
src/main.scss vendored
View File

@ -14,7 +14,8 @@
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
html, body { html,
body {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }

4
src/main.tsx vendored
View File

@ -9,8 +9,8 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode> <StrictMode>
<Suspense fallback={null}> <Suspense fallback={null}>
<BrowserRouter> <BrowserRouter>
<Layout/> <Layout />
</BrowserRouter> </BrowserRouter>
</Suspense> </Suspense>
</StrictMode> </StrictMode>,
); );

8
src/routes.tsx vendored
View File

@ -23,7 +23,7 @@ import Markdown from '@/view/markdown';
export type ChatRouteMetaObject = { export type ChatRouteMetaObject = {
label: string; label: string;
icon?: React.ReactNode, icon?: React.ReactNode;
}; };
type ChatRouteObject = { type ChatRouteObject = {
@ -32,7 +32,7 @@ type ChatRouteObject = {
hideMenu?: boolean; hideMenu?: boolean;
meta?: ChatRouteMetaObject; meta?: ChatRouteMetaObject;
children?: ChatRouteObject[]; children?: ChatRouteObject[];
} };
export const routes: Array<ChatRouteObject> = [ export const routes: Array<ChatRouteObject> = [
{ {
@ -116,12 +116,12 @@ export const routes: Array<ChatRouteObject> = [
type MenuItem = Required<MenuProps>['items'][number]; type MenuItem = Required<MenuProps>['items'][number];
export const menuItems: MenuItem[] = routes export const menuItems: MenuItem[] = routes
.filter((j) => !j.hideMenu) .filter((j) => !j.hideMenu)
.map(i => ({ .map((i) => ({
...i.meta, ...i.meta,
key: i.path || '', key: i.path || '',
children: i?.children children: i?.children
?.filter((j) => !j.hideMenu) ?.filter((j) => !j.hideMenu)
?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || ''})), ?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || '' })),
})); }));
export default () => { export default () => {

54
src/utils.ts vendored
View File

@ -9,27 +9,28 @@ export const CHAT_DOWNLOAD_JSON = 'chat.download.json';
export const CHAT_AWESOME_JSON = 'chat.awesome.json'; export const CHAT_AWESOME_JSON = 'chat.awesome.json';
export const CHAT_NOTES_JSON = 'chat.notes.json'; export const CHAT_NOTES_JSON = 'chat.notes.json';
export const CHAT_PROMPTS_CSV = 'chat.prompts.csv'; export const CHAT_PROMPTS_CSV = 'chat.prompts.csv';
export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv'; export const GITHUB_PROMPTS_CSV_URL =
'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv';
export const DISABLE_AUTO_COMPLETE = { export const DISABLE_AUTO_COMPLETE = {
autoCapitalize: 'off', autoCapitalize: 'off',
autoComplete: 'off', autoComplete: 'off',
spellCheck: false spellCheck: false,
}; };
export const chatRoot = async () => { export const chatRoot = async () => {
return join(await homeDir(), '.chatgpt') return join(await homeDir(), '.chatgpt');
} };
export const chatModelPath = async (): Promise<string> => { export const chatModelPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_MODEL_JSON); return join(await chatRoot(), CHAT_MODEL_JSON);
} };
export const chatPromptsPath = async (): Promise<string> => { export const chatPromptsPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_PROMPTS_CSV); return join(await chatRoot(), CHAT_PROMPTS_CSV);
} };
type readJSONOpts = { defaultVal?: Record<string, any>, isRoot?: boolean, isList?: boolean }; type readJSONOpts = { defaultVal?: Record<string, any>; isRoot?: boolean; isList?: boolean };
export const readJSON = async (path: string, opts: readJSONOpts = {}) => { export const readJSON = async (path: string, opts: readJSONOpts = {}) => {
const { defaultVal = {}, isRoot = false, isList = false } = opts; const { defaultVal = {}, isRoot = false, isList = false } = opts;
const root = await chatRoot(); const root = await chatRoot();
@ -39,26 +40,39 @@ export const readJSON = async (path: string, opts: readJSONOpts = {}) => {
file = await join(root, path); file = await join(root, path);
} }
if (!await exists(file)) { if (!(await exists(file))) {
if (await dirname(file) !== root) { if ((await dirname(file)) !== root) {
await createDir(await dirname(file), { recursive: true }); await createDir(await dirname(file), { recursive: true });
} }
await writeTextFile(file, isList ? '[]' : JSON.stringify({ await writeTextFile(
file,
isList
? '[]'
: JSON.stringify(
{
name: 'ChatGPT', name: 'ChatGPT',
link: 'https://github.com/lencx/ChatGPT', link: 'https://github.com/lencx/ChatGPT',
...defaultVal, ...defaultVal,
}, null, 2)) },
null,
2,
),
);
} }
try { try {
return JSON.parse(await readTextFile(file)); return JSON.parse(await readTextFile(file));
} catch(e) { } catch (e) {
return {}; return {};
} }
} };
type writeJSONOpts = { dir?: string, isRoot?: boolean }; type writeJSONOpts = { dir?: string; isRoot?: boolean };
export const writeJSON = async (path: string, data: Record<string, any>, opts: writeJSONOpts = {}) => { export const writeJSON = async (
path: string,
data: Record<string, any>,
opts: writeJSONOpts = {},
) => {
const { isRoot = false } = opts; const { isRoot = false } = opts;
const root = await chatRoot(); const root = await chatRoot();
let file = path; let file = path;
@ -67,13 +81,17 @@ export const writeJSON = async (path: string, data: Record<string, any>, opts: w
file = await join(root, path); file = await join(root, path);
} }
if (isRoot && !await exists(await dirname(file))) { if (isRoot && !(await exists(await dirname(file)))) {
await createDir(await dirname(file), { recursive: true }); await createDir(await dirname(file), { recursive: true });
} }
await writeTextFile(file, JSON.stringify(data, null, 2)); await writeTextFile(file, JSON.stringify(data, null, 2));
} };
export const fmtDate = (date: any) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'); export const fmtDate = (date: any) => dayjs(date).format('YYYY-MM-DD HH:mm:ss');
export const genCmd = (act: string) => act.replace(/\s+|\/+/g, '_').replace(/[^\d\w]/g, '').toLocaleLowerCase(); export const genCmd = (act: string) =>
act
.replace(/\s+|\/+/g, '_')
.replace(/[^\d\w]/g, '')
.toLocaleLowerCase();

View File

@ -6,7 +6,7 @@ import Tags from '@comps/Tags';
import { DISABLE_AUTO_COMPLETE } from '@/utils'; import { DISABLE_AUTO_COMPLETE } from '@/utils';
interface AwesomeFormProps { interface AwesomeFormProps {
record?: Record<string|symbol, any> | null; record?: Record<string | symbol, any> | null;
} }
const initFormValue = { const initFormValue = {
@ -28,11 +28,7 @@ const AwesomeForm: ForwardRefRenderFunction<FormProps, AwesomeFormProps> = ({ re
}, [record]); }, [record]);
return ( return (
<Form <Form form={form} labelCol={{ span: 4 }} initialValues={initFormValue}>
form={form}
labelCol={{ span: 4 }}
initialValues={initFormValue}
>
<Form.Item <Form.Item
label="Title" label="Title"
name="title" name="title"
@ -57,7 +53,7 @@ const AwesomeForm: ForwardRefRenderFunction<FormProps, AwesomeFormProps> = ({ re
<Switch /> <Switch />
</Form.Item> </Form.Item>
</Form> </Form>
) );
} };
export default forwardRef(AwesomeForm); export default forwardRef(AwesomeForm);

View File

@ -34,7 +34,7 @@ export const awesomeColumns = () => [
dataIndex: 'category', dataIndex: 'category',
key: 'category', key: 'category',
width: 120, width: 120,
render: (v: string) => <Tag color="geekblue">{v}</Tag> render: (v: string) => <Tag color="geekblue">{v}</Tag>,
}, },
{ {
title: 'Tags', title: 'Tags',
@ -42,7 +42,11 @@ export const awesomeColumns = () => [
key: 'tags', key: 'tags',
width: 150, width: 150,
render: (v: string[]) => ( render: (v: string[]) => (
<span className="chat-tags">{v?.map(i => <Tag key={i}>{i}</Tag>)}</span> <span className="chat-tags">
{v?.map((i) => (
<Tag key={i}>{i}</Tag>
))}
</span>
), ),
}, },
{ {
@ -62,7 +66,7 @@ export const awesomeColumns = () => [
<a>Delete</a> <a>Delete</a>
</Popconfirm> </Popconfirm>
</Space> </Space>
) );
} },
} },
]; ];

View File

@ -34,7 +34,8 @@ export default function Awesome() {
updateJson(data); updateJson(data);
opInfo.resetRecord(); opInfo.resetRecord();
} }
}, [opInfo.opType, formRef]); }, [opInfo.opType,
formRef]);
const hide = () => { const hide = () => {
setVisible(false); setVisible(false);
@ -46,15 +47,12 @@ export default function Awesome() {
const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
updateJson(data); updateJson(data);
} }
}, [opInfo.opTime]) }, [opInfo.opTime]);
const handleDelete = () => { const handleDelete = () => {};
};
const handleOk = () => { const handleOk = () => {
formRef.current?.form?.validateFields() formRef.current?.form?.validateFields().then(async (vals: Record<string, any>) => {
.then(async (vals: Record<string, any>) => {
if (opInfo.opType === 'new') { if (opInfo.opType === 'new') {
const data = opAdd(vals); const data = opAdd(vals);
await updateJson(data); await updateJson(data);
@ -67,24 +65,28 @@ export default function Awesome() {
message.success('Data updated successfully'); message.success('Data updated successfully');
} }
hide(); hide();
}) });
}; };
const handleEnable = (isEnable: boolean) => { const handleEnable = (isEnable: boolean) => {
const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
updateJson(data); updateJson(data);
}; };
const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} URL`; const modalTitle = `${{ new: 'Create', edit: 'Edit' }[opInfo.opType]} URL`;
return ( return (
<div> <div>
<div className="chat-table-btns"> <div className="chat-table-btns">
<Button className="chat-add-btn" type="primary" onClick={opInfo.opNew}>Add URL</Button> <Button className="chat-add-btn" type="primary" onClick={opInfo.opNew}>
Add URL
</Button>
<div> <div>
{selectedItems.length > 0 && ( {selectedItems.length > 0 && (
<> <>
<Button type="primary" onClick={() => handleEnable(true)}>Enable</Button> <Button type="primary" onClick={() => handleEnable(true)}>
Enable
</Button>
<Button onClick={() => handleEnable(false)}>Disable</Button> <Button onClick={() => handleEnable(false)}>Disable</Button>
<Popconfirm <Popconfirm
overlayStyle={{ width: 250 }} overlayStyle={{ width: 250 }}
@ -121,5 +123,5 @@ export default function Awesome() {
<AwesomeForm ref={formRef} record={opInfo?.opRecord} /> <AwesomeForm ref={formRef} record={opInfo?.opRecord} />
</Modal> </Modal>
</div> </div>
) );
} }

View File

@ -10,7 +10,7 @@ import { fmtDate, chatRoot } from '@/utils';
const colorMap: any = { const colorMap: any = {
pdf: 'blue', pdf: 'blue',
png: 'orange', png: 'orange',
} };
export const downloadColumns = () => [ export const downloadColumns = () => [
{ {
@ -61,20 +61,22 @@ export const downloadColumns = () => [
<a>Delete</a> <a>Delete</a>
</Popconfirm> </Popconfirm>
</Space> </Space>
) );
} },
} },
]; ];
const RenderPath = ({ row }: any) => { const RenderPath = ({ row }: any) => {
const [filePath, setFilePath] = useState(''); const [filePath, setFilePath] = useState('');
useInit(async () => { useInit(async () => {
setFilePath(await getPath(row)); setFilePath(await getPath(row));
}) });
return <a onClick={() => shell.open(filePath)}>{filePath}</a>; return <a onClick={() => shell.open(filePath)}>{filePath}</a>;
}; };
export const getPath = async (row: any) => { export const getPath = async (row: any) => {
const isImg = ['png'].includes(row?.ext); const isImg = ['png'].includes(row?.ext);
return await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id) + `.${row.ext}`; return (
} (await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id)) + `.${row.ext}`
);
};

View File

@ -37,7 +37,12 @@ export default function Download() {
(async () => { (async () => {
const record = opInfo?.opRecord; const record = opInfo?.opRecord;
const isImg = ['png'].includes(record?.ext); const isImg = ['png'].includes(record?.ext);
const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : record?.ext, `${record?.id}.${record?.ext}`); const file = await path.join(
await chatRoot(),
'download',
isImg ? 'img' : record?.ext,
`${record?.id}.${record?.ext}`,
);
if (opInfo.opType === 'preview') { if (opInfo.opType === 'preview') {
const data = await fs.readBinaryFile(file); const data = await fs.readBinaryFile(file);
const sourceData = renderFile(data, record?.ext); const sourceData = renderFile(data, record?.ext);
@ -55,8 +60,8 @@ export default function Download() {
message.success('Name has been changed!'); message.success('Name has been changed!');
} }
opInfo.resetRecord(); opInfo.resetRecord();
})() })();
}, [opInfo.opType]) }, [opInfo.opType]);
const handleDelete = async () => { const handleDelete = async () => {
if (opData?.length === selectedRows.length) { if (opData?.length === selectedRows.length) {
@ -69,10 +74,15 @@ export default function Download() {
const rows = selectedRows.map(async (i) => { const rows = selectedRows.map(async (i) => {
const isImg = ['png'].includes(i?.ext); const isImg = ['png'].includes(i?.ext);
const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : i?.ext, `${i?.id}.${i?.ext}`); const file = await path.join(
await chatRoot(),
'download',
isImg ? 'img' : i?.ext,
`${i?.id}.${i?.ext}`,
);
await fs.removeFile(file); await fs.removeFile(file);
return file; return file;
}) });
Promise.all(rows).then(async () => { Promise.all(rows).then(async () => {
await handleRefresh(); await handleRefresh();
message.success('All files selected are cleared!'); message.success('All files selected are cleared!');
@ -131,5 +141,5 @@ export default function Download() {
<img style={{ maxWidth: '100%' }} src={source} /> <img style={{ maxWidth: '100%' }} src={source} />
</Modal> </Modal>
</div> </div>
) );
} }

View File

@ -1,4 +1,3 @@
.md-task { .md-task {
margin-bottom: 5px; margin-bottom: 5px;
display: flex; display: flex;

View File

@ -14,7 +14,7 @@ const modeMap: any = {
0: 'split', 0: 'split',
1: 'md', 1: 'md',
2: 'doc', 2: 'doc',
} };
export default function Markdown() { export default function Markdown() {
const [filePath, setFilePath] = useState(''); const [filePath, setFilePath] = useState('');
@ -26,8 +26,8 @@ export default function Markdown() {
useInit(async () => { useInit(async () => {
const file = await getPath(state); const file = await getPath(state);
setFilePath(file); setFilePath(file);
setSource(await fs.readTextFile(file)) setSource(await fs.readTextFile(file));
}) });
const handleChange = async (v: string) => { const handleChange = async (v: string) => {
await fs.writeTextFile(filePath, v); await fs.writeTextFile(filePath, v);
@ -46,9 +46,7 @@ export default function Markdown() {
<Breadcrumb.Item onClick={() => history.go(-1)}> <Breadcrumb.Item onClick={() => history.go(-1)}>
<ArrowLeftOutlined /> <ArrowLeftOutlined />
</Breadcrumb.Item> </Breadcrumb.Item>
<Breadcrumb.Item onClick={() => shell.open(filePath)}> <Breadcrumb.Item onClick={() => shell.open(filePath)}>{filePath}</Breadcrumb.Item>
{filePath}
</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<div> <div>
<SplitIcon onClick={handlePreview} style={{ fontSize: 18, color: 'rgba(0,0,0,0.5)' }} /> <SplitIcon onClick={handlePreview} style={{ fontSize: 18, color: 'rgba(0,0,0,0.5)' }} />

View File

@ -1,4 +1,10 @@
import { useEffect, useState, ForwardRefRenderFunction, useImperativeHandle, forwardRef } from 'react'; import {
useEffect,
useState,
ForwardRefRenderFunction,
useImperativeHandle,
forwardRef,
} from 'react';
import { Form, Input, Select, Tooltip } from 'antd'; import { Form, Input, Select, Tooltip } from 'antd';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import type { FormProps } from 'antd'; import type { FormProps } from 'antd';
@ -7,7 +13,7 @@ import { DISABLE_AUTO_COMPLETE, chatRoot } from '@/utils';
import useInit from '@/hooks/useInit'; import useInit from '@/hooks/useInit';
interface SyncFormProps { interface SyncFormProps {
record?: Record<string|symbol, any> | null; record?: Record<string | symbol, any> | null;
type: string; type: string;
} }
@ -54,10 +60,18 @@ const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record,
const jsonTip = ( const jsonTip = (
<Tooltip <Tooltip
title={<pre>{JSON.stringify([ title={
<pre>
{JSON.stringify(
[
{ cmd: '', act: '', prompt: '' }, { cmd: '', act: '', prompt: '' },
{ cmd: '', act: '', prompt: '' }, { cmd: '', act: '', prompt: '' },
], null, 2)}</pre>} ],
null,
2,
)}
</pre>
}
> >
<a>JSON</a> <a>JSON</a>
</Tooltip> </Tooltip>
@ -65,10 +79,12 @@ const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record,
const csvTip = ( const csvTip = (
<Tooltip <Tooltip
title={<pre>{`"cmd","act","prompt" title={
<pre>{`"cmd","act","prompt"
"cmd","act","prompt" "cmd","act","prompt"
"cmd","act","prompt" "cmd","act","prompt"
"cmd","act","prompt"`}</pre>} "cmd","act","prompt"`}</pre>
}
> >
<a>CSV</a> <a>CSV</a>
</Tooltip> </Tooltip>
@ -76,11 +92,7 @@ const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record,
return ( return (
<> <>
<Form <Form form={form} labelCol={{ span: 4 }} initialValues={initFormValue}>
form={form}
labelCol={{ span: 4 }}
initialValues={initFormValue}
>
<Form.Item <Form.Item
label="Name" label="Name"
name="name" name="name"
@ -100,13 +112,17 @@ const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record,
{...DISABLE_AUTO_COMPLETE} {...DISABLE_AUTO_COMPLETE}
/> />
</Form.Item> </Form.Item>
<Form.Item style={{ display: 'none' }} name="id" initialValue={v4().replace(/-/g, '')}><input /></Form.Item> <Form.Item style={{ display: 'none' }} name="id" initialValue={v4().replace(/-/g, '')}>
<input />
</Form.Item>
</Form> </Form>
<div className="tip"> <div className="tip">
<p>The file supports only {csvTip} and {jsonTip} formats.</p> <p>
The file supports only {csvTip} and {jsonTip} formats.
</p>
</div> </div>
</> </>
) );
} };
export default forwardRef(SyncForm); export default forwardRef(SyncForm);

View File

@ -26,7 +26,7 @@ export const syncColumns = () => [
dataIndex: 'path', dataIndex: 'path',
key: 'path', key: 'path',
width: 180, width: 180,
render: (_: string, row: any) => <RenderPath row={row} /> render: (_: string, row: any) => <RenderPath row={row} />,
}, },
{ {
title: 'Last updated', title: 'Last updated',
@ -36,7 +36,7 @@ export const syncColumns = () => [
render: (v: number) => ( render: (v: number) => (
<div> <div>
<HistoryOutlined style={{ marginRight: 5, color: v ? '#52c41a' : '#ff4d4f' }} /> <HistoryOutlined style={{ marginRight: 5, color: v ? '#52c41a' : '#ff4d4f' }} />
{ v ? fmtDate(v) : ''} {v ? fmtDate(v) : ''}
</div> </div>
), ),
}, },
@ -56,7 +56,11 @@ export const syncColumns = () => [
> >
<a>Sync</a> <a>Sync</a>
</Popconfirm> </Popconfirm>
{row.last_updated && <Link to={`${row.id}`} state={row}>View</Link>} {row.last_updated && (
<Link to={`${row.id}`} state={row}>
View
</Link>
)}
<a onClick={() => actions.setRecord(row, 'edit')}>Edit</a> <a onClick={() => actions.setRecord(row, 'edit')}>Edit</a>
<Popconfirm <Popconfirm
title="Are you sure to delete this path?" title="Are you sure to delete this path?"
@ -67,23 +71,23 @@ export const syncColumns = () => [
<a>Delete</a> <a>Delete</a>
</Popconfirm> </Popconfirm>
</Space> </Space>
) );
} },
} },
]; ];
const RenderPath = ({ row }: any) => { const RenderPath = ({ row }: any) => {
const [filePath, setFilePath] = useState(''); const [filePath, setFilePath] = useState('');
useInit(async () => { useInit(async () => {
setFilePath(await getPath(row)); setFilePath(await getPath(row));
}) });
return <a onClick={() => shell.open(filePath)}>{filePath}</a> return <a onClick={() => shell.open(filePath)}>{filePath}</a>;
}; };
export const getPath = async (row: any) => { export const getPath = async (row: any) => {
if (!/^http/.test(row.protocol)) { if (!/^http/.test(row.protocol)) {
return await path.join(await chatRoot(), row.path) + `.${row.ext}`; return (await path.join(await chatRoot(), row.path)) + `.${row.ext}`;
} else { } else {
return `${row.protocol}://${row.path}.${row.ext}`; return `${row.protocol}://${row.path}.${row.ext}`;
} }
} };

View File

@ -10,7 +10,13 @@ import { CHAT_MODEL_JSON, chatRoot, readJSON, genCmd } from '@/utils';
import { syncColumns, getPath } from './config'; import { syncColumns, getPath } from './config';
import SyncForm from './Form'; import SyncForm from './Form';
const fmtData = (data: Record<string, any>[] = []) => (Array.isArray(data) ? data : []).map((i) => ({ ...i, cmd: i.cmd ? i.cmd : genCmd(i.act), tags: ['user-sync'], enable: true })); const fmtData = (data: Record<string, any>[] = []) =>
(Array.isArray(data) ? data : []).map((i) => ({
...i,
cmd: i.cmd ? i.cmd : genCmd(i.act),
tags: ['user-sync'],
enable: true,
}));
export default function SyncCustom() { export default function SyncCustom() {
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
@ -37,7 +43,10 @@ export default function SyncCustom() {
handleSync(filename).then((isOk: boolean) => { handleSync(filename).then((isOk: boolean) => {
opInfo.resetRecord(); opInfo.resetRecord();
if (!isOk) return; if (!isOk) return;
const data = opReplace(opInfo?.opRecord?.[opSafeKey], { ...opInfo?.opRecord, last_updated: Date.now() }); const data = opReplace(opInfo?.opRecord?.[opSafeKey], {
...opInfo?.opRecord,
last_updated: Date.now(),
});
modelSet(data); modelSet(data);
opInfo.resetRecord(); opInfo.resetRecord();
}); });
@ -48,9 +57,13 @@ export default function SyncCustom() {
if (['delete'].includes(opInfo.opType)) { if (['delete'].includes(opInfo.opType)) {
(async () => { (async () => {
try { try {
const file = await path.join(await chatRoot(), 'cache_model', `${opInfo?.opRecord?.id}.json`); const file = await path.join(
await chatRoot(),
'cache_model',
`${opInfo?.opRecord?.id}.json`,
);
await fs.removeFile(file); await fs.removeFile(file);
} catch(e) {} } catch (e) {}
const data = opRemove(opInfo?.opRecord?.[opSafeKey]); const data = opRemove(opInfo?.opRecord?.[opSafeKey]);
modelSet(data); modelSet(data);
opInfo.resetRecord(); opInfo.resetRecord();
@ -94,8 +107,7 @@ export default function SyncCustom() {
}; };
const handleOk = () => { const handleOk = () => {
formRef.current?.form?.validateFields() formRef.current?.form?.validateFields().then((vals: Record<string, any>) => {
.then((vals: Record<string, any>) => {
if (opInfo.opType === 'new') { if (opInfo.opType === 'new') {
const data = opAdd(vals); const data = opAdd(vals);
modelSet(data); modelSet(data);
@ -107,16 +119,12 @@ export default function SyncCustom() {
message.success('Data updated successfully'); message.success('Data updated successfully');
} }
hide(); hide();
}) });
}; };
return ( return (
<div> <div>
<Button <Button className="chat-add-btn" type="primary" onClick={opInfo.opNew}>
className="chat-add-btn"
type="primary"
onClick={opInfo.opNew}
>
Add PATH Add PATH
</Button> </Button>
<Table <Table
@ -138,5 +146,5 @@ export default function SyncCustom() {
<SyncForm ref={formRef} record={opInfo?.opRecord} type={opInfo.opType} /> <SyncForm ref={formRef} record={opInfo?.opRecord} type={opInfo.opType} />
</Modal> </Modal>
</div> </div>
) );
} }

View File

@ -41,8 +41,6 @@ export const syncColumns = () => [
dataIndex: 'prompt', dataIndex: 'prompt',
key: 'prompt', key: 'prompt',
// width: 300, // width: 300,
render: (v: string) => ( render: (v: string) => <span className="chat-prompts-val">{v}</span>,
<span className="chat-prompts-val">{v}</span>
),
}, },
]; ];

View File

@ -1,4 +1,5 @@
.chat-table-tip, .chat-table-btns { .chat-table-tip,
.chat-table-btns {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }

View File

@ -52,7 +52,7 @@ export default function SyncPrompts() {
}, [opInfo.opTime]); }, [opInfo.opTime]);
const handleEnable = (isEnable: boolean) => { const handleEnable = (isEnable: boolean) => {
const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
modelCacheSet(data); modelCacheSet(data);
}; };
@ -72,7 +72,9 @@ export default function SyncPrompts() {
<div> <div>
{selectedItems.length > 0 && ( {selectedItems.length > 0 && (
<> <>
<Button type="primary" onClick={() => handleEnable(true)}>Enable</Button> <Button type="primary" onClick={() => handleEnable(true)}>
Enable
</Button>
<Button onClick={() => handleEnable(false)}>Disable</Button> <Button onClick={() => handleEnable(false)}>Disable</Button>
<span className="num">Selected {selectedItems.length} items</span> <span className="num">Selected {selectedItems.length} items</span>
</> </>
@ -84,7 +86,11 @@ export default function SyncPrompts() {
<FilePath url={promptsURL} content="f/awesome-chatgpt-prompts/prompts.csv" /> <FilePath url={promptsURL} content="f/awesome-chatgpt-prompts/prompts.csv" />
<FilePath label="CACHE" paths="cache_model/chatgpt_prompts.json" /> <FilePath label="CACHE" paths="cache_model/chatgpt_prompts.json" />
</div> </div>
{lastUpdated && <span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>Last updated on {fmtDate(lastUpdated)}</span>} {lastUpdated && (
<span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>
Last updated on {fmtDate(lastUpdated)}
</span>
)}
</div> </div>
<Table <Table
key={lastUpdated} key={lastUpdated}
@ -94,8 +100,10 @@ export default function SyncPrompts() {
dataSource={opData} dataSource={opData}
rowSelection={rowSelection} rowSelection={rowSelection}
pagination={TABLE_PAGINATION} pagination={TABLE_PAGINATION}
expandable={{expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>}} expandable={{
expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>,
}}
/> />
</div> </div>
) );
} }

View File

@ -25,7 +25,11 @@ export const syncColumns = () => [
key: 'tags', key: 'tags',
// width: 150, // width: 150,
render: (v: string[]) => ( render: (v: string[]) => (
<span className="chat-prompts-tags">{v?.map(i => <Tag key={i}>{i}</Tag>)}</span> <span className="chat-prompts-tags">
{v?.map((i) => (
<Tag key={i}>{i}</Tag>
))}
</span>
), ),
}, },
{ {
@ -43,8 +47,6 @@ export const syncColumns = () => [
dataIndex: 'prompt', dataIndex: 'prompt',
key: 'prompt', key: 'prompt',
// width: 300, // width: 300,
render: (v: string) => ( render: (v: string) => <span className="chat-prompts-val">{v}</span>,
<span className="chat-prompts-val">{v}</span>
),
}, },
]; ];

View File

@ -30,7 +30,7 @@ export default function SyncRecord() {
useInit(async () => { useInit(async () => {
setFilePath(await getPath(state)); setFilePath(await getPath(state));
setJsonPath(await path.join(await chatRoot(), 'cache_model', `${state?.id}.json`)); setJsonPath(await path.join(await chatRoot(), 'cache_model', `${state?.id}.json`));
}) });
useEffect(() => { useEffect(() => {
if (modelCacheJson.length <= 0) return; if (modelCacheJson.length <= 0) return;
@ -45,7 +45,7 @@ export default function SyncRecord() {
}, [opInfo.opTime]); }, [opInfo.opTime]);
const handleEnable = (isEnable: boolean) => { const handleEnable = (isEnable: boolean) => {
const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
modelCacheSet(data); modelCacheSet(data);
}; };
@ -58,7 +58,9 @@ export default function SyncRecord() {
<div> <div>
{selectedItems.length > 0 && ( {selectedItems.length > 0 && (
<> <>
<Button type="primary" onClick={() => handleEnable(true)}>Enable</Button> <Button type="primary" onClick={() => handleEnable(true)}>
Enable
</Button>
<Button onClick={() => handleEnable(false)}>Disable</Button> <Button onClick={() => handleEnable(false)}>Disable</Button>
<span className="num">Selected {selectedItems.length} items</span> <span className="num">Selected {selectedItems.length} items</span>
</> </>
@ -70,7 +72,11 @@ export default function SyncRecord() {
<FilePath url={filePath} /> <FilePath url={filePath} />
<FilePath label="CACHE" paths={`cache_model/${state?.id}.json`} /> <FilePath label="CACHE" paths={`cache_model/${state?.id}.json`} />
</div> </div>
{state?.last_updated && <span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>Last updated on {fmtDate(state?.last_updated)}</span>} {state?.last_updated && (
<span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>
Last updated on {fmtDate(state?.last_updated)}
</span>
)}
</div> </div>
<Table <Table
key="prompt" key="prompt"
@ -80,8 +86,10 @@ export default function SyncRecord() {
dataSource={opData} dataSource={opData}
rowSelection={rowSelection} rowSelection={rowSelection}
pagination={TABLE_PAGINATION} pagination={TABLE_PAGINATION}
expandable={{expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>}} expandable={{
expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>,
}}
/> />
</div> </div>
) );
} }

View File

@ -6,7 +6,7 @@ import Tags from '@comps/Tags';
import { DISABLE_AUTO_COMPLETE } from '@/utils'; import { DISABLE_AUTO_COMPLETE } from '@/utils';
interface UserCustomFormProps { interface UserCustomFormProps {
record?: Record<string|symbol, any> | null; record?: Record<string | symbol, any> | null;
} }
const initFormValue = { const initFormValue = {
@ -16,7 +16,10 @@ const initFormValue = {
prompt: '', prompt: '',
}; };
const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> = ({ record }, ref) => { const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> = (
{ record },
ref,
) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
useImperativeHandle(ref, () => ({ form })); useImperativeHandle(ref, () => ({ form }));
@ -27,11 +30,7 @@ const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> =
}, [record]); }, [record]);
return ( return (
<Form <Form form={form} labelCol={{ span: 4 }} initialValues={initFormValue}>
form={form}
labelCol={{ span: 4 }}
initialValues={initFormValue}
>
<Form.Item <Form.Item
label="/{cmd}" label="/{cmd}"
name="cmd" name="cmd"
@ -60,7 +59,7 @@ const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> =
<Input.TextArea rows={4} placeholder="Please enter a prompt" {...DISABLE_AUTO_COMPLETE} /> <Input.TextArea rows={4} placeholder="Please enter a prompt" {...DISABLE_AUTO_COMPLETE} />
</Form.Item> </Form.Item>
</Form> </Form>
) );
} };
export default forwardRef(UserCustomForm); export default forwardRef(UserCustomForm);

View File

@ -7,7 +7,7 @@ export const modelColumns = () => [
fixed: 'left', fixed: 'left',
width: 120, width: 120,
key: 'cmd', key: 'cmd',
render: (v: string) => <Tag color="#2a2a2a">/{v}</Tag> render: (v: string) => <Tag color="#2a2a2a">/{v}</Tag>,
}, },
{ {
title: 'Act', title: 'Act',
@ -21,7 +21,11 @@ export const modelColumns = () => [
key: 'tags', key: 'tags',
width: 150, width: 150,
render: (v: string[]) => ( render: (v: string[]) => (
<span className="chat-prompts-tags">{v?.map(i => <Tag key={i}>{i}</Tag>)}</span> <span className="chat-prompts-tags">
{v?.map((i) => (
<Tag key={i}>{i}</Tag>
))}
</span>
), ),
}, },
{ {
@ -39,9 +43,7 @@ export const modelColumns = () => [
dataIndex: 'prompt', dataIndex: 'prompt',
key: 'prompt', key: 'prompt',
width: 300, width: 300,
render: (v: string) => ( render: (v: string) => <span className="chat-prompts-val">{v}</span>,
<span className="chat-prompts-val">{v}</span>
),
}, },
{ {
title: 'Action', title: 'Action',
@ -61,5 +63,5 @@ export const modelColumns = () => [
</Popconfirm> </Popconfirm>
</Space> </Space>
), ),
} },
]; ];

View File

@ -50,10 +50,10 @@ export default function LanguageModel() {
const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
modelCacheSet(data); modelCacheSet(data);
} }
}, [opInfo.opTime]) }, [opInfo.opTime]);
const handleEnable = (isEnable: boolean) => { const handleEnable = (isEnable: boolean) => {
const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) const data = opReplaceItems(selectedRowIDs, { enable: isEnable });
modelCacheSet(data); modelCacheSet(data);
}; };
@ -63,17 +63,26 @@ export default function LanguageModel() {
}; };
const handleOk = () => { const handleOk = () => {
formRef.current?.form?.validateFields() formRef.current?.form?.validateFields().then(async (vals: Record<string, any>) => {
.then(async (vals: Record<string, any>) => { if (
if (modelCacheJson.map((i: any) => i.cmd).includes(vals.cmd) && opInfo?.opRecord?.cmd !== vals.cmd) { modelCacheJson.map((i: any) => i.cmd).includes(vals.cmd) &&
message.warning(`"cmd: /${vals.cmd}" already exists, please change the "${vals.cmd}" name and resubmit.`); opInfo?.opRecord?.cmd !== vals.cmd
) {
message.warning(
`"cmd: /${vals.cmd}" already exists, please change the "${vals.cmd}" name and resubmit.`,
);
return; return;
} }
let data = []; let data = [];
switch (opInfo.opType) { switch (opInfo.opType) {
case 'new': data = opAdd(vals); break; case 'new':
case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; data = opAdd(vals);
default: break; break;
case 'edit':
data = opReplace(opInfo?.opRecord?.[opSafeKey], vals);
break;
default:
break;
} }
await modelCacheSet(data); await modelCacheSet(data);
opInit(data); opInit(data);
@ -82,19 +91,23 @@ export default function LanguageModel() {
last_updated: Date.now(), last_updated: Date.now(),
}); });
hide(); hide();
}) });
}; };
const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} Model`; const modalTitle = `${{ new: 'Create', edit: 'Edit' }[opInfo.opType]} Model`;
return ( return (
<div> <div>
<div className="chat-table-btns"> <div className="chat-table-btns">
<Button className="chat-add-btn" type="primary" onClick={opInfo.opNew}>Add Model</Button> <Button className="chat-add-btn" type="primary" onClick={opInfo.opNew}>
Add Model
</Button>
<div> <div>
{selectedItems.length > 0 && ( {selectedItems.length > 0 && (
<> <>
<Button type="primary" onClick={() => handleEnable(true)}>Enable</Button> <Button type="primary" onClick={() => handleEnable(true)}>
Enable
</Button>
<Button onClick={() => handleEnable(false)}>Disable</Button> <Button onClick={() => handleEnable(false)}>Disable</Button>
<span className="num">Selected {selectedItems.length} items</span> <span className="num">Selected {selectedItems.length} items</span>
</> </>
@ -103,7 +116,11 @@ export default function LanguageModel() {
</div> </div>
<div className="chat-table-tip"> <div className="chat-table-tip">
<FilePath label="CACHE" paths="cache_model/user_custom.json" /> <FilePath label="CACHE" paths="cache_model/user_custom.json" />
{lastUpdated && <span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>Last updated on {fmtDate(lastUpdated)}</span>} {lastUpdated && (
<span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>
Last updated on {fmtDate(lastUpdated)}
</span>
)}
</div> </div>
<Table <Table
key={lastUpdated} key={lastUpdated}
@ -113,7 +130,9 @@ export default function LanguageModel() {
dataSource={opData} dataSource={opData}
rowSelection={rowSelection} rowSelection={rowSelection}
pagination={TABLE_PAGINATION} pagination={TABLE_PAGINATION}
expandable={{expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>}} expandable={{
expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>,
}}
/> />
<Modal <Modal
open={isVisible} open={isVisible}
@ -126,5 +145,5 @@ export default function LanguageModel() {
<UserCustomForm record={opInfo?.opRecord} ref={formRef} /> <UserCustomForm record={opInfo?.opRecord} ref={formRef} />
</Modal> </Modal>
</div> </div>
) );
} }

View File

@ -41,7 +41,9 @@ export const notesColumns = () => [
return ( return (
<Space> <Space>
<a onClick={() => actions.setRecord(row, 'preview')}>Preview</a> <a onClick={() => actions.setRecord(row, 'preview')}>Preview</a>
<Link to={`/md/${row.id}`} state={row}>Edit</Link> <Link to={`/md/${row.id}`} state={row}>
Edit
</Link>
<Popconfirm <Popconfirm
title="Are you sure to delete this file?" title="Are you sure to delete this file?"
onConfirm={() => actions.setRecord(row, 'delete')} onConfirm={() => actions.setRecord(row, 'delete')}
@ -51,20 +53,20 @@ export const notesColumns = () => [
<a>Delete</a> <a>Delete</a>
</Popconfirm> </Popconfirm>
</Space> </Space>
) );
} },
} },
]; ];
const RenderPath = ({ row }: any) => { const RenderPath = ({ row }: any) => {
const [filePath, setFilePath] = useState(''); const [filePath, setFilePath] = useState('');
useInit(async () => { useInit(async () => {
setFilePath(await getPath(row)); setFilePath(await getPath(row));
}) });
return <a onClick={() => shell.open(filePath)}>{filePath}</a>; return <a onClick={() => shell.open(filePath)}>{filePath}</a>;
}; };
export const getPath = async (row: any) => { export const getPath = async (row: any) => {
const isImg = ['png'].includes(row?.ext); const isImg = ['png'].includes(row?.ext);
return await path.join(await chatRoot(), 'notes', row.id) + `.${row.ext}`; return (await path.join(await chatRoot(), 'notes', row.id)) + `.${row.ext}`;
} };

View File

@ -46,8 +46,8 @@ export default function Notes() {
message.success('Name has been changed!'); message.success('Name has been changed!');
} }
opInfo.resetRecord(); opInfo.resetRecord();
})() })();
}, [opInfo.opType]) }, [opInfo.opType]);
const handleDelete = async () => { const handleDelete = async () => {
if (opData?.length === selectedRows.length) { if (opData?.length === selectedRows.length) {
@ -62,7 +62,7 @@ export default function Notes() {
const file = await path.join(await chatRoot(), 'notes', `${i?.id}.${i?.ext}`); const file = await path.join(await chatRoot(), 'notes', `${i?.id}.${i?.ext}`);
await fs.removeFile(file); await fs.removeFile(file);
return file; return file;
}) });
Promise.all(rows).then(async () => { Promise.all(rows).then(async () => {
await handleRefresh(); await handleRefresh();
message.success('All files selected are cleared!'); message.success('All files selected are cleared!');
@ -122,5 +122,5 @@ export default function Notes() {
<Markdown children={source} /> <Markdown children={source} />
</Modal> </Modal>
</div> </div>
) );
} }

View File

@ -9,37 +9,47 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils';
const AutoUpdateLabel = () => { const AutoUpdateLabel = () => {
return ( return (
<span> <span>
Auto Update Auto Update{' '}
{' '} <Tooltip
<Tooltip title={( title={
<div> <div>
<div>Auto Update Policy</div> <div>Auto Update Policy</div>
<div><strong>Prompt</strong>: prompt to install</div> <div>
<div><strong>Silent</strong>: install silently</div> <strong>Prompt</strong>: prompt to install
</div>
<div>
<strong>Silent</strong>: install silently
</div>
{/* <div><strong>Disable</strong>: disable auto update</div> */} {/* <div><strong>Disable</strong>: disable auto update</div> */}
</div> </div>
)}><QuestionCircleOutlined style={{ color: '#1677ff' }} /></Tooltip> }
>
<QuestionCircleOutlined style={{ color: '#1677ff' }} />
</Tooltip>
</span> </span>
) );
} };
const GlobalShortcutLabel = () => { const GlobalShortcutLabel = () => {
return ( return (
<div> <div>
Global Shortcut Global Shortcut{' '}
{' '} <Tooltip
<Tooltip title={( title={
<div> <div>
<div>Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q</div> <div>Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q</div>
<div style={{ margin: '10px 0'}}>If empty, the shortcut is disabled.</div> <div style={{ margin: '10px 0' }}>If empty, the shortcut is disabled.</div>
<a href="https://tauri.app/v1/api/js/globalshortcut" target="_blank">https://tauri.app/v1/api/js/globalshortcut</a> <a href="https://tauri.app/v1/api/js/globalshortcut" target="_blank">
https://tauri.app/v1/api/js/globalshortcut
</a>
</div> </div>
)}> }
>
<QuestionCircleOutlined style={{ color: '#1677ff' }} /> <QuestionCircleOutlined style={{ color: '#1677ff' }} />
</Tooltip> </Tooltip>
</div> </div>
) );
} };
export default function General() { export default function General() {
const [platformInfo, setPlatform] = useState(''); const [platformInfo, setPlatform] = useState('');
@ -62,9 +72,7 @@ export default function General() {
<Radio.Group> <Radio.Group>
<Radio value="Light">Light</Radio> <Radio value="Light">Light</Radio>
<Radio value="Dark">Dark</Radio> <Radio value="Dark">Dark</Radio>
{["darwin", "windows"].includes(platformInfo) && ( {['darwin', 'windows'].includes(platformInfo) && <Radio value="System">System</Radio>}
<Radio value="System">System</Radio>
)}
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label={<AutoUpdateLabel />} name="auto_update"> <Form.Item label={<AutoUpdateLabel />} name="auto_update">
@ -78,5 +86,5 @@ export default function General() {
<Input placeholder="CmdOrCtrl+Shift+O" {...DISABLE_AUTO_COMPLETE} /> <Input placeholder="CmdOrCtrl+Shift+O" {...DISABLE_AUTO_COMPLETE} />
</Form.Item> </Form.Item>
</> </>
) );
} }

View File

@ -9,25 +9,39 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils';
const OriginLabel = ({ url }: { url: string }) => { const OriginLabel = ({ url }: { url: string }) => {
return ( return (
<span> <span>
Switch Origin <Tooltip title={`Default: ${url}`}><QuestionCircleOutlined style={{ color: '#1677ff' }} /></Tooltip> Switch Origin{' '}
<Tooltip title={`Default: ${url}`}>
<QuestionCircleOutlined style={{ color: '#1677ff' }} />
</Tooltip>
</span> </span>
) );
} };
const PopupSearchLabel = () => { const PopupSearchLabel = () => {
return ( return (
<span> <span>
Pop-up Search Pop-up Search{' '}
{' '} <Tooltip
<Tooltip title={( title={
<div> <div>
<div style={{ marginBottom: 10 }}>Generate images according to the content: Select the ChatGPT content with the mouse, no more than 400 characters. the <b>DALL·E 2</b> button appears, and click to jump (Note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable).</div> <div style={{ marginBottom: 10 }}>
<div>The application is built using Tauri, and due to its security restrictions, some of the action buttons will not work, so we recommend going to your browser.</div> Generate images according to the content: Select the ChatGPT content with the mouse,
no more than 400 characters. the <b>DALL·E 2</b> button appears, and click to jump
(Note: because the search content filled by the script cannot trigger the event
directly, you need to enter a space in the input box to make the button clickable).
</div> </div>
)}><QuestionCircleOutlined style={{ color: '#1677ff' }} /></Tooltip> <div>
The application is built using Tauri, and due to its security restrictions, some of
the action buttons will not work, so we recommend going to your browser.
</div>
</div>
}
>
<QuestionCircleOutlined style={{ color: '#1677ff' }} />
</Tooltip>
</span> </span>
) );
} };
export default function General() { export default function General() {
const [chatConf, setChatConf] = useState<any>(null); const [chatConf, setChatConf] = useState<any>(null);
@ -46,8 +60,12 @@ export default function General() {
<Input placeholder="https://chat.openai.com" {...DISABLE_AUTO_COMPLETE} /> <Input placeholder="https://chat.openai.com" {...DISABLE_AUTO_COMPLETE} />
</Form.Item> </Form.Item>
<Form.Item label="User Agent (Main)" name="ua_window"> <Form.Item label="User Agent (Main)" name="ua_window">
<Input.TextArea autoSize={{ minRows: 4, maxRows: 4 }} {...DISABLE_AUTO_COMPLETE} placeholder="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" /> <Input.TextArea
autoSize={{ minRows: 4, maxRows: 4 }}
{...DISABLE_AUTO_COMPLETE}
placeholder="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
/>
</Form.Item> </Form.Item>
</> </>
) );
} }

View File

@ -9,8 +9,12 @@ export default function General() {
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item label="User Agent (SystemTray)" name="ua_tray"> <Form.Item label="User Agent (SystemTray)" name="ua_tray">
<Input.TextArea autoSize={{ minRows: 4, maxRows: 4 }} {...DISABLE_AUTO_COMPLETE} placeholder="Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" /> <Input.TextArea
autoSize={{ minRows: 4, maxRows: 4 }}
{...DISABLE_AUTO_COMPLETE}
placeholder="Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
/>
</Form.Item> </Form.Item>
</> </>
) );
} }

View File

@ -21,7 +21,7 @@ export default function Settings() {
useEffect(() => { useEffect(() => {
form.setFieldsValue(clone(chatConf)); form.setFieldsValue(clone(chatConf));
}, [chatConf]) }, [chatConf]);
const onCancel = () => { const onCancel = () => {
form.setFieldsValue(chatConf); form.setFieldsValue(chatConf);
@ -31,7 +31,7 @@ export default function Settings() {
const chatData = await invoke('reset_chat_conf'); const chatData = await invoke('reset_chat_conf');
setChatConf(chatData); setChatConf(chatData);
const isOk = await dialog.ask(`Configuration reset successfully, whether to restart?`, { const isOk = await dialog.ask(`Configuration reset successfully, whether to restart?`, {
title: 'ChatGPT Preferences' title: 'ChatGPT Preferences',
}); });
if (isOk) { if (isOk) {
process.relaunch(); process.relaunch();
@ -44,7 +44,7 @@ export default function Settings() {
if (!isEqual(omit(chatConf, ['default_origin']), values)) { if (!isEqual(omit(chatConf, ['default_origin']), values)) {
await invoke('form_confirm', { data: values, label: 'main' }); await invoke('form_confirm', { data: values, label: 'main' });
const isOk = await dialog.ask(`Configuration saved successfully, whether to restart?`, { const isOk = await dialog.ask(`Configuration saved successfully, whether to restart?`, {
title: 'ChatGPT Preferences' title: 'ChatGPT Preferences',
}); });
if (isOk) { if (isOk) {
process.relaunch(); process.relaunch();
@ -75,11 +75,15 @@ export default function Settings() {
<Form.Item> <Form.Item>
<Space size={20}> <Space size={20}>
<Button onClick={onCancel}>Cancel</Button> <Button onClick={onCancel}>Cancel</Button>
<Button type="primary" htmlType="submit">Submit</Button> <Button type="primary" htmlType="submit">
<Button type="dashed" onClick={onReset}>Reset to defaults</Button> Submit
</Button>
<Button type="dashed" onClick={onReset}>
Reset to defaults
</Button>
</Space> </Space>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>
) );
} }

View File

@ -20,7 +20,7 @@
"@/*": ["src/*"], "@/*": ["src/*"],
"@view/*": ["src/view/*"], "@view/*": ["src/view/*"],
"@comps/*": ["src/components/*"], "@comps/*": ["src/components/*"],
"@layout/*": ["src/layout/*"], "@layout/*": ["src/layout/*"]
} }
}, },
"include": ["src"], "include": ["src"],

View File

@ -1,6 +1,6 @@
import { defineConfig } from "vite"; import { defineConfig } from 'vite';
import react from "@vitejs/plugin-react"; import react from '@vitejs/plugin-react';
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@ -16,12 +16,12 @@ export default defineConfig({
}, },
// to make use of `TAURI_DEBUG` and other env variables // to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ["VITE_", "TAURI_"], envPrefix: ['VITE_', 'TAURI_'],
build: { build: {
// Tauri supports es2021 // Tauri supports es2021
target: ["es2021", "chrome100", "safari13"], target: ['es2021', 'chrome100', 'safari13'],
// don't minify for debug builds // don't minify for debug builds
minify: !process.env.TAURI_DEBUG ? "esbuild" : false, minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// produce sourcemaps for debug builds // produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG, sourcemap: !!process.env.TAURI_DEBUG,
}, },