diff --git a/src/hooks/useChatModel.ts b/src/hooks/useChatModel.ts index c7cdbe5..52c6f8e 100644 --- a/src/hooks/useChatModel.ts +++ b/src/hooks/useChatModel.ts @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { clone } from 'lodash'; import { invoke } from '@tauri-apps/api'; @@ -6,7 +6,7 @@ import { CHAT_MODEL_JSON, readJSON, writeJSON } from '@/utils'; import useInit from '@/hooks/useInit'; export default function useChatModel(key: string, file = CHAT_MODEL_JSON) { - const [modelJson, setModelJson] = useState>({}); + const [modelJson, setModelJson] = useState>([]); useInit(async () => { const data = await readJSON(file, { @@ -23,5 +23,25 @@ export default function useChatModel(key: string, file = CHAT_MODEL_JSON) { setModelJson(oData); } - return { modelJson, modelSet, modelData: modelJson?.[key] || [] } + return { modelJson, modelSet, modelData: modelJson?.[key] || [] }; +} + +export function useCacheModel(file: string) { + const [modelJson, setModelJson] = useState[]>([]); + + useEffect(() => { + if (!file) return; + (async () => { + const data = await readJSON(file, { isRoot: true }); + setModelJson(data); + })(); + }, [file]); + + const modelSet = async (data: Record[]) => { + await writeJSON(file, data, { isRoot: true }); + await invoke('window_reload', { label: 'core' }); + setModelJson(data); + } + + return { modelJson, modelSet }; } \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index eff592e..d7b0819 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -11,7 +11,8 @@ import type { MenuProps } from 'antd'; import General from '@view/General'; import UserCustom from '@/view/model/UserCustom'; 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 = { label: string; @@ -21,7 +22,8 @@ export type ChatRouteMetaObject = { type ChatRouteObject = { path: string; element?: JSX.Element; - meta: ChatRouteMetaObject; + hideMenu?: boolean; + meta?: ChatRouteMetaObject; children?: ChatRouteObject[]; } @@ -58,24 +60,32 @@ export const routes: Array = [ }, }, { - path: 'sync-more', - element: , + path: 'sync-custom', + element: , meta: { - label: 'Sync More', + label: 'Sync Custom', icon: , }, }, + { + path: 'sync-custom/:id', + element: , + hideMenu: true, + }, ] }, ]; type MenuItem = Required['items'][number]; -export const menuItems: MenuItem[] = routes.map(i => ({ - ...i.meta, - key: i.path || '', - children: i?.children?.map((j) => - ({ ...j.meta, key: `${i.path}/${j.path}` || ''})), -})); +export const menuItems: MenuItem[] = routes + .filter((j) => !j.hideMenu) + .map(i => ({ + ...i.meta, + key: i.path || '', + children: i?.children + ?.filter((j) => !j.hideMenu) + ?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || ''})), + })); export default () => { return useRoutes(routes); diff --git a/src/view/model/SyncMore/Form.tsx b/src/view/model/SyncCustom/Form.tsx similarity index 100% rename from src/view/model/SyncMore/Form.tsx rename to src/view/model/SyncCustom/Form.tsx diff --git a/src/view/model/SyncMore/config.tsx b/src/view/model/SyncCustom/config.tsx similarity index 79% rename from src/view/model/SyncMore/config.tsx rename to src/view/model/SyncCustom/config.tsx index 345ceff..2c214d9 100644 --- a/src/view/model/SyncMore/config.tsx +++ b/src/view/model/SyncCustom/config.tsx @@ -1,11 +1,13 @@ import { useState } from 'react'; import { Tag, Space, Popconfirm } from 'antd'; +import { HistoryOutlined } from '@ant-design/icons'; import { shell, path } from '@tauri-apps/api'; +import { Link } from 'react-router-dom'; import useInit from '@/hooks/useInit'; import { chatRoot, fmtDate } from '@/utils'; -export const pathColumns = () => [ +export const syncColumns = () => [ { title: 'Name', dataIndex: 'name', @@ -31,16 +33,22 @@ export const pathColumns = () => [ dataIndex: 'last_updated', key: 'last_updated', width: 140, - render: fmtDate, + render: (v: number) => ( +
+ + { v ? fmtDate(v) : ''} +
+ ), }, { title: 'Action', fixed: 'right', - width: 140, + width: 150, render: (_: any, row: any, actions: any) => { return ( actions.setRecord(row, 'sync')}>Sync + {row.last_updated && View} actions.setRecord(row, 'edit')}>Edit []) => data.map((i) => ({ ...i, tags: ['user-sync'], enable: true })) -export default function SyncMore() { +export default function SyncCustom() { const [isVisible, setVisible] = useState(false); const { modelData, modelSet } = useChatModel('sync_url', CHAT_MODEL_SYNC_JSON); const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]); - const { columns, ...opInfo } = useColumns(pathColumns()); + const { columns, ...opInfo } = useColumns(syncColumns()); const formRef = useRef(null); const hide = () => { @@ -36,8 +36,6 @@ export default function SyncMore() { const filename = `${opInfo?.opRecord?.id}.json`; handleSync(filename).then(() => { const data = opReplace(opInfo?.opRecord?.[opSafeKey], { ...opInfo?.opRecord, last_updated: Date.now() }); - console.log('«38» /model/SyncMore/index.tsx ~> ', data); - modelSet(data); opInfo.resetRecord(); }); @@ -103,10 +101,7 @@ export default function SyncMore() { case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; default: break; } - console.log('«95» /model/SyncMore/index.tsx ~> ', data); - modelSet(data); - opInfo.setExtra(Date.now()); hide(); }) }; diff --git a/src/view/model/SyncPrompts/config.tsx b/src/view/model/SyncPrompts/config.tsx index f14a058..32fbf6a 100644 --- a/src/view/model/SyncPrompts/config.tsx +++ b/src/view/model/SyncPrompts/config.tsx @@ -2,7 +2,7 @@ import { Switch, Tag, Tooltip } from 'antd'; import { genCmd } from '@/utils'; -export const modelColumns = () => [ +export const syncColumns = () => [ { title: '/{cmd}', dataIndex: 'cmd', diff --git a/src/view/model/SyncPrompts/index.tsx b/src/view/model/SyncPrompts/index.tsx index d6cabd6..5e19963 100644 --- a/src/view/model/SyncPrompts/index.tsx +++ b/src/view/model/SyncPrompts/index.tsx @@ -9,17 +9,17 @@ import useData from '@/hooks/useData'; import useChatModel from '@/hooks/useChatModel'; import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; import { fmtDate, chatPromptsPath, GITHUB_PROMPTS_CSV_URL, genCmd } from '@/utils'; -import { modelColumns } from './config'; +import { syncColumns } from './config'; import './index.scss'; 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 [lastUpdated, setLastUpdated] = useState(); const { modelJson, modelSet } = useChatModel('sys_sync_prompts'); const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]); - const { columns, ...opInfo } = useColumns(modelColumns()); + const { columns, ...opInfo } = useColumns(syncColumns()); const selectedItems = rowSelection.selectedRowKeys || []; diff --git a/src/view/model/SyncRecord/config.tsx b/src/view/model/SyncRecord/config.tsx new file mode 100644 index 0000000..32fbf6a --- /dev/null +++ b/src/view/model/SyncRecord/config.tsx @@ -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) => ( + /{genCmd(row.act)} + ), + }, + { + title: 'Act', + dataIndex: 'act', + key: 'act', + // width: 200, + }, + { + title: 'Tags', + dataIndex: 'tags', + key: 'tags', + // width: 150, + render: () => chatgpt-prompts, + }, + { + title: 'Enable', + dataIndex: 'enable', + key: 'enable', + // width: 80, + render: (v: boolean = false, row: Record, action: Record) => ( + action.setRecord({ ...row, enable: v }, 'enable')} /> + ), + }, + { + title: 'Prompt', + dataIndex: 'prompt', + key: 'prompt', + // width: 300, + render: (v: string) => ( + {v} + ), + }, +]; diff --git a/src/view/model/SyncRecord/index.scss b/src/view/model/SyncRecord/index.scss new file mode 100644 index 0000000..531795f --- /dev/null +++ b/src/view/model/SyncRecord/index.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/view/model/SyncRecord/index.tsx b/src/view/model/SyncRecord/index.tsx new file mode 100644 index 0000000..a772a1b --- /dev/null +++ b/src/view/model/SyncRecord/index.tsx @@ -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 ( +
+
+
+
+
+ {selectedItems.length > 0 && ( + <> + + + Selected {selectedItems.length} items + + )} +
+
+
+ + {state?.last_updated && Last updated on {fmtDate(state?.last_updated)}} +
+ + + ) +} \ No newline at end of file