feat: chatgpt-prompts sync

This commit is contained in:
lencx
2022-12-19 02:56:53 +08:00
parent 02fb4dd3b7
commit c54aec88c0
21 changed files with 367 additions and 23 deletions

View File

@@ -4,20 +4,20 @@ import { clone } from 'lodash';
import { CHAT_MODEL_JSON, readJSON, writeJSON } from '@/utils';
import useInit from '@/hooks/useInit';
export default function useChatModel() {
export default function useChatModel(key: string) {
const [modelJson, setModelJson] = useState<Record<string, any>>({});
useInit(async () => {
const data = await readJSON(CHAT_MODEL_JSON, { name: 'ChatGPT Model', data: [] });
const data = await readJSON(CHAT_MODEL_JSON, { name: 'ChatGPT Model', [key]: [] });
setModelJson(data);
});
const modelSet = async (data: Record<string, any>[]) => {
const oData = clone(modelJson);
oData.data = data;
oData[key] = data;
await writeJSON(CHAT_MODEL_JSON, oData);
setModelJson(oData);
}
return { modelJson, modelSet, modelData: modelJson?.data || [] }
return { modelJson, modelSet, modelData: modelJson?.[key] || [] }
}

View File

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

View File

@@ -8,6 +8,11 @@
}
}
.ant-layout-sider-trigger {
user-select: none;
-webkit-user-select: none;
}
.chat-container {
padding: 20px;
overflow: hidden;

27
src/layout/index.tsx vendored
View File

@@ -17,13 +17,32 @@ const ChatLayout: FC<ChatLayoutProps> = ({ children }) => {
const go = useNavigate();
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider theme="light" collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
<Layout style={{ minHeight: '100vh' }} hasSider>
<Sider
theme="light"
collapsible
collapsed={collapsed}
onCollapse={(value) => setCollapsed(value)}
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
zIndex: 999,
}}
>
<div className="chat-logo"><img src="/logo.png" /></div>
<Menu defaultSelectedKeys={[location.pathname]} mode="vertical" items={menuItems} onClick={(i) => go(i.key)} />
</Sider>
<Layout className="chat-layout">
<Content className="chat-container">
<Layout className="chat-layout" style={{ marginLeft: collapsed ? 80 : 200, transition: 'margin-left 300ms ease-out' }}>
<Content
className="chat-container"
style={{
overflow: 'inherit'
}}
>
<Routes />
</Content>
<Footer style={{ textAlign: 'center' }}>

10
src/routes.tsx vendored
View File

@@ -2,12 +2,14 @@ import { useRoutes } from 'react-router-dom';
import {
DesktopOutlined,
BulbOutlined,
SyncOutlined,
} from '@ant-design/icons';
import type { RouteObject } from 'react-router-dom';
import type { MenuProps } from 'antd';
import General from '@view/General';
import LanguageModel from '@/view/LanguageModel';
import SyncPrompts from '@/view/SyncPrompts';
export type ChatRouteObject = {
label: string;
@@ -31,6 +33,14 @@ export const routes: Array<RouteObject & { meta: ChatRouteObject }> = [
icon: <BulbOutlined />,
},
},
{
path: '/sync-prompts',
element: <SyncPrompts />,
meta: {
label: 'Sync Prompts',
icon: <SyncOutlined />,
},
},
];
type MenuItem = Required<MenuProps>['items'][number];

14
src/utils.ts vendored
View File

@@ -1,7 +1,10 @@
import { readTextFile, writeTextFile, exists } from '@tauri-apps/api/fs';
import { homeDir, join } from '@tauri-apps/api/path';
import dayjs from 'dayjs';
export const CHAT_MODEL_JSON = 'chat.model.json';
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 DISABLE_AUTO_COMPLETE = {
autoCapitalize: 'off',
autoComplete: 'off',
@@ -12,10 +15,14 @@ export const chatRoot = async () => {
return join(await homeDir(), '.chatgpt')
}
export const chatModelPath = async () => {
export const chatModelPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_MODEL_JSON);
}
export const chatPromptsPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_PROMPTS_CSV);
}
export const readJSON = async (path: string, defaultVal = {}) => {
const root = await chatRoot();
const file = await join(root, path);
@@ -24,7 +31,6 @@ export const readJSON = async (path: string, defaultVal = {}) => {
writeTextFile(file, JSON.stringify({
name: 'ChatGPT',
link: 'https://github.com/lencx/ChatGPT/blob/main/chat.model.md',
data: null,
...defaultVal,
}, null, 2))
}
@@ -40,4 +46,6 @@ export const writeJSON = async (path: string, data: Record<string, any>) => {
const root = await chatRoot();
const file = await join(root, path);
await writeTextFile(file, JSON.stringify(data, null, 2));
}
}
export const fmtDate = (date: any) => dayjs(date).format('YYYY-MM-DD HH:mm:ss');

View File

@@ -14,7 +14,7 @@ import './index.scss';
export default function LanguageModel() {
const [isVisible, setVisible] = useState(false);
const [modelPath, setChatModelPath] = useState('');
const { modelData, modelSet } = useChatModel();
const { modelData, modelSet } = useChatModel('user_custom');
const { opData, opAdd, opRemove, opReplace, opSafeKey } = useData(modelData);
const { columns, ...opInfo } = useColumns(modelColumns());
const formRef = useRef<any>(null);

45
src/view/SyncPrompts/config.tsx vendored Normal file
View File

@@ -0,0 +1,45 @@
import { Tag } from 'antd';
export const genCmd = (act: string) => act.replace(/\s+|\/+/g, '_').replace(/[^\d\w]/g, '').toLocaleLowerCase();
export const modelColumns = () => [
{
title: '/{cmd}',
dataIndex: 'cmd',
fixed: 'left',
// width: 120,
key: 'cmd',
render: (_: string, row: Record<string, string>) => (
<Tag color="#2a2a2a">/{genCmd(row.act)}</Tag>
),
},
{
title: 'Act',
dataIndex: 'act',
key: 'act',
// width: 200,
},
{
title: 'Tags',
dataIndex: 'tags',
key: 'tags',
// width: 150,
render: () => <Tag>chatgpt-prompts</Tag>,
},
// {
// title: 'Enable',
// dataIndex: 'enable',
// key: 'enable',
// width: 80,
// render: (v: boolean = false) => <Switch checked={v} disabled />,
// },
{
title: 'Prompt',
dataIndex: 'prompt',
key: 'prompt',
// width: 300,
// render: (v: string) => (
// <Tooltip overlayInnerStyle={{ width: 350 }} title={v}><span className="chat-prompts-val">{v}</span></Tooltip>
// ),
},
];

28
src/view/SyncPrompts/index.scss vendored Normal file
View File

@@ -0,0 +1,28 @@
.chat-prompts-tags {
.ant-tag {
margin: 2px;
}
}
.add-btn {
margin-bottom: 5px;
}
.chat-model-path {
font-size: 12px;
font-weight: bold;
color: #888;
margin-bottom: 5px;
span {
display: inline-block;
// background-color: #d8d8d8;
color: #4096ff;
padding: 0 8px;
height: 20px;
line-height: 20px;
border-radius: 4px;
cursor: pointer;
text-decoration: underline;
}
}

71
src/view/SyncPrompts/index.tsx vendored Normal file
View File

@@ -0,0 +1,71 @@
import { useState } from 'react';
import { Table, Button, message } from 'antd';
import { invoke } from '@tauri-apps/api';
import { fetch, ResponseType } from '@tauri-apps/api/http';
import { writeTextFile, readTextFile } from '@tauri-apps/api/fs';
import useColumns from '@/hooks/useColumns';
import useChatModel from '@/hooks/useChatModel';
import { fmtDate, chatPromptsPath, GITHUB_PROMPTS_CSV_URL } from '@/utils';
import { modelColumns, genCmd } from './config';
import './index.scss';
import useInit from '@/hooks/useInit';
const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv';
export default function LanguageModel() {
const [loading, setLoading] = useState(false);
const [lastUpdated, setLastUpdated] = useState();
const { modelSet } = useChatModel('sys_sync_prompts');
const [tableData, setTableData] = useState<Record<string, string>[]>([]);
const { columns, ...opInfo } = useColumns(modelColumns());
useInit(async () => {
const filename = await chatPromptsPath();
const data = await readTextFile(filename);
const list: Record<string, string>[] = await invoke('parse_prompt', { data });
const fileData: Record<string, any> = await invoke('metadata', { path: filename });
setLastUpdated(fileData.accessedAtMs);
setTableData(list);
})
const handleSync = async () => {
setLoading(true);
const res = await fetch(GITHUB_PROMPTS_CSV_URL, {
method: 'GET',
responseType: ResponseType.Text,
});
const data = (res.data || '') as string;
// const content = data.replace(/"(\s+)?,(\s+)?"/g, '","');
await writeTextFile(await chatPromptsPath(), data);
const list: Record<string, string>[] = await invoke('parse_prompt', { data });
setTableData(list);
modelSet(list.map(i => ({ cmd: genCmd(i.act), enable: true, tags: ['chatgpt-prompts'], ...i })));
setLoading(false);
setLastUpdated(fmtDate(Date.now()) as any);
message.success('ChatGPT Prompts data synchronization completed!');
};
return (
<div>
<Button type="primary" loading={loading} onClick={handleSync}>Sync</Button>
{lastUpdated && <span style={{ marginLeft: 10, color: '#999' }}>Last updated on {fmtDate(lastUpdated)}</span>}
<div className="chat-model-path">URL: <a href={promptsURL} target="_blank">{promptsURL}</a></div>
<Table
key={lastUpdated}
rowKey="act"
columns={columns}
scroll={{ x: 'auto' }}
dataSource={tableData}
pagination={{
hideOnSinglePage: true,
showSizeChanger: true,
showQuickJumper: true,
defaultPageSize: 5,
pageSizeOptions: [5, 10, 15, 20],
showTotal: (total) => <span>Total {total} items</span>,
}}
/>
</div>
)
}