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

chore: sync record

This commit is contained in:
lencx 2022-12-23 02:23:36 +08:00
parent 6abe7c783e
commit 2be560e69a
11 changed files with 237 additions and 29 deletions

View File

@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { clone } from 'lodash'; import { clone } from 'lodash';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
@ -6,7 +6,7 @@ import { CHAT_MODEL_JSON, readJSON, writeJSON } from '@/utils';
import useInit from '@/hooks/useInit'; import useInit from '@/hooks/useInit';
export default function useChatModel(key: string, file = CHAT_MODEL_JSON) { export default function useChatModel(key: string, file = CHAT_MODEL_JSON) {
const [modelJson, setModelJson] = useState<Record<string, any>>({}); const [modelJson, setModelJson] = useState<Record<string, any>>([]);
useInit(async () => { useInit(async () => {
const data = await readJSON(file, { const data = await readJSON(file, {
@ -23,5 +23,25 @@ export default function useChatModel(key: string, file = CHAT_MODEL_JSON) {
setModelJson(oData); setModelJson(oData);
} }
return { modelJson, modelSet, modelData: modelJson?.[key] || [] } return { modelJson, modelSet, modelData: modelJson?.[key] || [] };
}
export function useCacheModel(file: string) {
const [modelJson, setModelJson] = useState<Record<string, any>[]>([]);
useEffect(() => {
if (!file) return;
(async () => {
const data = await readJSON(file, { isRoot: true });
setModelJson(data);
})();
}, [file]);
const modelSet = async (data: Record<string, any>[]) => {
await writeJSON(file, data, { isRoot: true });
await invoke('window_reload', { label: 'core' });
setModelJson(data);
}
return { modelJson, modelSet };
} }

32
src/routes.tsx vendored
View File

@ -11,7 +11,8 @@ import type { MenuProps } from 'antd';
import General from '@view/General'; import General from '@view/General';
import UserCustom from '@/view/model/UserCustom'; import UserCustom from '@/view/model/UserCustom';
import SyncPrompts from '@/view/model/SyncPrompts'; import SyncPrompts from '@/view/model/SyncPrompts';
import SyncMore from '@/view/model/SyncMore'; import SyncCustom from '@/view/model/SyncCustom';
import SyncRecord from '@/view/model/SyncRecord';
export type ChatRouteMetaObject = { export type ChatRouteMetaObject = {
label: string; label: string;
@ -21,7 +22,8 @@ export type ChatRouteMetaObject = {
type ChatRouteObject = { type ChatRouteObject = {
path: string; path: string;
element?: JSX.Element; element?: JSX.Element;
meta: ChatRouteMetaObject; hideMenu?: boolean;
meta?: ChatRouteMetaObject;
children?: ChatRouteObject[]; children?: ChatRouteObject[];
} }
@ -58,24 +60,32 @@ export const routes: Array<ChatRouteObject> = [
}, },
}, },
{ {
path: 'sync-more', path: 'sync-custom',
element: <SyncMore />, element: <SyncCustom />,
meta: { meta: {
label: 'Sync More', label: 'Sync Custom',
icon: <FileSyncOutlined />, icon: <FileSyncOutlined />,
}, },
}, },
{
path: 'sync-custom/:id',
element: <SyncRecord />,
hideMenu: true,
},
] ]
}, },
]; ];
type MenuItem = Required<MenuProps>['items'][number]; type MenuItem = Required<MenuProps>['items'][number];
export const menuItems: MenuItem[] = routes.map(i => ({ export const menuItems: MenuItem[] = routes
...i.meta, .filter((j) => !j.hideMenu)
key: i.path || '', .map(i => ({
children: i?.children?.map((j) => ...i.meta,
({ ...j.meta, key: `${i.path}/${j.path}` || ''})), key: i.path || '',
})); children: i?.children
?.filter((j) => !j.hideMenu)
?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || ''})),
}));
export default () => { export default () => {
return useRoutes(routes); return useRoutes(routes);

View File

@ -1,11 +1,13 @@
import { useState } from 'react'; import { useState } from 'react';
import { Tag, Space, Popconfirm } from 'antd'; import { Tag, Space, Popconfirm } from 'antd';
import { HistoryOutlined } from '@ant-design/icons';
import { shell, path } from '@tauri-apps/api'; import { shell, path } from '@tauri-apps/api';
import { Link } from 'react-router-dom';
import useInit from '@/hooks/useInit'; import useInit from '@/hooks/useInit';
import { chatRoot, fmtDate } from '@/utils'; import { chatRoot, fmtDate } from '@/utils';
export const pathColumns = () => [ export const syncColumns = () => [
{ {
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
@ -31,16 +33,22 @@ export const pathColumns = () => [
dataIndex: 'last_updated', dataIndex: 'last_updated',
key: 'last_updated', key: 'last_updated',
width: 140, width: 140,
render: fmtDate, render: (v: number) => (
<div style={{ textAlign: 'center' }}>
<HistoryOutlined style={{ marginRight: 5, color: v ? '#52c41a' : '#ff4d4f' }} />
{ v ? fmtDate(v) : ''}
</div>
),
}, },
{ {
title: 'Action', title: 'Action',
fixed: 'right', fixed: 'right',
width: 140, width: 150,
render: (_: any, row: any, actions: any) => { render: (_: any, row: any, actions: any) => {
return ( return (
<Space> <Space>
<a onClick={() => actions.setRecord(row, 'sync')}>Sync</a> <a onClick={() => actions.setRecord(row, 'sync')}>Sync</a>
{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?"

View File

@ -7,17 +7,17 @@ import useChatModel from '@/hooks/useChatModel';
import useColumns from '@/hooks/useColumns'; import useColumns from '@/hooks/useColumns';
import { TABLE_PAGINATION } from '@/hooks/useTable'; import { TABLE_PAGINATION } from '@/hooks/useTable';
import { CHAT_MODEL_SYNC_JSON, chatRoot, writeJSON, readJSON, genCmd } from '@/utils'; import { CHAT_MODEL_SYNC_JSON, chatRoot, writeJSON, readJSON, genCmd } from '@/utils';
import { pathColumns, getPath } from './config'; import { syncColumns, getPath } from './config';
import SyncForm from './Form'; import SyncForm from './Form';
import './index.scss'; import './index.scss';
const setTag = (data: Record<string, any>[]) => data.map((i) => ({ ...i, tags: ['user-sync'], enable: true })) const setTag = (data: Record<string, any>[]) => data.map((i) => ({ ...i, tags: ['user-sync'], enable: true }))
export default function SyncMore() { export default function SyncCustom() {
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const { modelData, modelSet } = useChatModel('sync_url', CHAT_MODEL_SYNC_JSON); const { modelData, modelSet } = useChatModel('sync_url', CHAT_MODEL_SYNC_JSON);
const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]); const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]);
const { columns, ...opInfo } = useColumns(pathColumns()); const { columns, ...opInfo } = useColumns(syncColumns());
const formRef = useRef<any>(null); const formRef = useRef<any>(null);
const hide = () => { const hide = () => {
@ -36,8 +36,6 @@ export default function SyncMore() {
const filename = `${opInfo?.opRecord?.id}.json`; const filename = `${opInfo?.opRecord?.id}.json`;
handleSync(filename).then(() => { handleSync(filename).then(() => {
const data = opReplace(opInfo?.opRecord?.[opSafeKey], { ...opInfo?.opRecord, last_updated: Date.now() }); const data = opReplace(opInfo?.opRecord?.[opSafeKey], { ...opInfo?.opRecord, last_updated: Date.now() });
console.log('«38» /model/SyncMore/index.tsx ~> ', data);
modelSet(data); modelSet(data);
opInfo.resetRecord(); opInfo.resetRecord();
}); });
@ -103,10 +101,7 @@ export default function SyncMore() {
case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break;
default: break; default: break;
} }
console.log('«95» /model/SyncMore/index.tsx ~> ', data);
modelSet(data); modelSet(data);
opInfo.setExtra(Date.now());
hide(); hide();
}) })
}; };

View File

@ -2,7 +2,7 @@ import { Switch, Tag, Tooltip } from 'antd';
import { genCmd } from '@/utils'; import { genCmd } from '@/utils';
export const modelColumns = () => [ export const syncColumns = () => [
{ {
title: '/{cmd}', title: '/{cmd}',
dataIndex: 'cmd', dataIndex: 'cmd',

View File

@ -9,17 +9,17 @@ import useData from '@/hooks/useData';
import useChatModel from '@/hooks/useChatModel'; import useChatModel from '@/hooks/useChatModel';
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
import { fmtDate, chatPromptsPath, GITHUB_PROMPTS_CSV_URL, genCmd } from '@/utils'; import { fmtDate, chatPromptsPath, GITHUB_PROMPTS_CSV_URL, genCmd } from '@/utils';
import { modelColumns } from './config'; import { syncColumns } from './config';
import './index.scss'; import './index.scss';
const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv'; const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv';
export default function LanguageModel() { export default function SyncPrompts() {
const { rowSelection, selectedRowIDs } = useTable(); const { rowSelection, selectedRowIDs } = useTable();
const [lastUpdated, setLastUpdated] = useState(); const [lastUpdated, setLastUpdated] = useState();
const { modelJson, modelSet } = useChatModel('sys_sync_prompts'); const { modelJson, modelSet } = useChatModel('sys_sync_prompts');
const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]); const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
const { columns, ...opInfo } = useColumns(modelColumns()); const { columns, ...opInfo } = useColumns(syncColumns());
const selectedItems = rowSelection.selectedRowKeys || []; const selectedItems = rowSelection.selectedRowKeys || [];

47
src/view/model/SyncRecord/config.tsx vendored Normal file
View File

@ -0,0 +1,47 @@
import { Switch, Tag, Tooltip } from 'antd';
import { genCmd } from '@/utils';
export const syncColumns = () => [
{
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, row: Record<string, any>, action: Record<string, any>) => (
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
),
},
{
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>
),
},
];

42
src/view/model/SyncRecord/index.scss vendored Normal file
View File

@ -0,0 +1,42 @@
// .chat-prompts-tags {
// .ant-tag {
// margin: 2px;
// }
// }
// .add-btn {
// margin-bottom: 5px;
// }
// .chat-table-tip, .chat-table-btns {
// display: flex;
// justify-content: space-between;
// }
// .chat-table-btns {
// margin-bottom: 5px;
// .num {
// margin-left: 10px;
// }
// }
.chat-sync-path {
font-size: 12px;
font-weight: 500;
color: #888;
margin-bottom: 5px;
line-height: 16px;
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;
}
}

86
src/view/model/SyncRecord/index.tsx vendored Normal file
View File

@ -0,0 +1,86 @@
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { Table, Button } from 'antd';
import { shell, path } from '@tauri-apps/api';
import useColumns from '@/hooks/useColumns';
import useData from '@/hooks/useData';
import { useCacheModel } from '@/hooks/useChatModel';
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
import { fmtDate, chatRoot } from '@/utils';
import { getPath } from '@/view/model/SyncCustom/config';
import { syncColumns } from './config';
import useInit from '@/hooks/useInit';
import './index.scss';
export default function SyncRecord() {
const location = useLocation();
const [filePath, setFilePath] = useState('');
const [jsonPath, setJsonPath] = useState('');
const state = location?.state;
const { rowSelection, selectedRowIDs } = useTable();
const { modelJson, modelSet } = useCacheModel(jsonPath);
const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
const { columns, ...opInfo } = useColumns(syncColumns());
const selectedItems = rowSelection.selectedRowKeys || [];
useInit(async () => {
setFilePath(await getPath(state));
setJsonPath(await path.join(await chatRoot(), 'cache_sync', `${state?.id}.json`));
})
useEffect(() => {
if (modelJson.length <= 0) return;
opInit(modelJson);
}, [modelJson.length]);
useEffect(() => {
if (opInfo.opType === 'enable') {
const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
modelSet(data);
}
}, [opInfo.opTime]);
const handleEnable = (isEnable: boolean) => {
const data = opReplaceItems(selectedRowIDs, { enable: isEnable })
modelSet(data);
};
return (
<div>
<div className="chat-table-btns">
<div>
<Button shape="round" icon={<ArrowLeftOutlined />} onClick={() => history.back()} />
</div>
<div>
{selectedItems.length > 0 && (
<>
<Button type="primary" onClick={() => handleEnable(true)}>Enable</Button>
<Button onClick={() => handleEnable(false)}>Disable</Button>
<span className="num">Selected {selectedItems.length} items</span>
</>
)}
</div>
</div>
<div className="chat-table-tip">
<div className="chat-sync-path">
<div>PATH: <a onClick={() => shell.open(filePath)} target="_blank" title={filePath}>{filePath}</a></div>
<div>CACHE: <a onClick={() => shell.open(jsonPath)} target="_blank" title={jsonPath}>{jsonPath}</a></div>
</div>
{state?.last_updated && <span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>Last updated on {fmtDate(state?.last_updated)}</span>}
</div>
<Table
key="prompt"
rowKey="act"
columns={columns}
scroll={{ x: 'auto' }}
dataSource={opData}
rowSelection={rowSelection}
pagination={TABLE_PAGINATION}
/>
</div>
)
}