diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8c4540d..1d1b9a9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,11 +19,12 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.2.2", features = ["api-all", "devtools", "system-tray", "updater"] } tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] } -tokio = { version = "1.23.0", features = ["macros"] } log = "0.4.17" csv = "1.1.6" thiserror = "1.0.38" -reqwest = "0.11.13" +walkdir = "2.3.2" +# tokio = { version = "1.23.0", features = ["macros"] } +# reqwest = "0.11.13" [dependencies.tauri-plugin-log] git = "https://github.com/tauri-apps/tauri-plugin-log" diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index 6ae243b..25784a4 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -66,8 +66,8 @@ pub fn open_file(path: PathBuf) { } #[command] -pub fn get_chat_model() -> serde_json::Value { - let path = utils::chat_root().join("chat.model.json"); +pub fn get_chat_model_cmd() -> serde_json::Value { + let path = utils::chat_root().join("chat.model.cmd.json"); let content = fs::read_to_string(path).unwrap_or_else(|_| r#"{"data":[]}"#.to_string()); serde_json::from_str(&content).unwrap() } @@ -98,3 +98,33 @@ pub fn window_reload(app: AppHandle, label: &str) { .eval("window.location.reload()") .unwrap(); } + + +use walkdir::WalkDir; +use utils::chat_root; + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct ModelRecord { + pub cmd: String, + pub act: String, + pub prompt: String, + pub tags: Vec, + pub enable: bool, +} + +#[command] +pub fn cmd_list() -> Vec { + let mut list = vec![]; + for entry in WalkDir::new(chat_root().join("cache_model")).into_iter().filter_map(|e| e.ok()) { + let file = fs::read_to_string(entry.path().display().to_string()); + if let Ok(v) = file { + let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); + let enable_list = data.into_iter() + .filter(|v| v.enable); + list.extend(enable_list) + } + } + // dbg!(&list); + list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); + list +} diff --git a/src-tauri/src/assets/cmd.js b/src-tauri/src/assets/cmd.js index 186a85e..f26d520 100644 --- a/src-tauri/src/assets/cmd.js +++ b/src-tauri/src/assets/cmd.js @@ -61,10 +61,8 @@ function init() { } async function cmdTip() { - const chatModelJson = await invoke('get_chat_model') || {}; - const user_custom = chatModelJson.user_custom || []; - const sys_sync_prompts = chatModelJson.sys_sync_prompts || []; - const data = [...user_custom, ...sys_sync_prompts]; + const chatModelJson = await invoke('get_chat_model_cmd') || {}; + const data = chatModelJson.data; if (data.length <= 0) return; const modelDom = document.createElement('div'); @@ -82,18 +80,43 @@ async function cmdTip() { // Enter a command starting with `/` and press a space to automatically fill `chatgpt prompt`. // If more than one command appears in the search results, the first one will be used by default. searchInput.addEventListener('keydown', (event) => { - if (!window.__CHAT_MODEL_CMD__) { + if (!window.__CHAT_MODEL_CMD_PROMPT__) { return; } - if (event.keyCode === 13 && window.__CHAT_MODEL_CMD__) { - searchInput.value = window.__CHAT_MODEL_CMD__; + // feat: https://github.com/lencx/ChatGPT/issues/54 + if (event.keyCode === 9) { + const strGroup = window.__CHAT_MODEL_CMD_PROMPT__.match(/\{([^{}]*)\}/) || []; + + if (strGroup[1]) { + searchInput.value = `/${window.__CHAT_MODEL_CMD__}` + `{${strGroup[1]}}` + ' |-> '; + window.__CHAT_MODEL_VAR__ = true; + } + event.preventDefault(); + } + + if (window.__CHAT_MODEL_VAR__ && event.keyCode === 9) { + const data = searchInput.value.split('|->'); + if (data[1]) { + window.__CHAT_MODEL_CMD_PROMPT__ = window.__CHAT_MODEL_CMD_PROMPT__?.replace(/\{([^{}]*)\}/, `{${data[1]?.trim()}}`); + // searchInput.value = window.__CHAT_MODEL_CMD_PROMPT__; + } + // event.preventDefault(); + } + + // send + if (event.keyCode === 13 && window.__CHAT_MODEL_CMD_PROMPT__) { + searchInput.value = window.__CHAT_MODEL_CMD_PROMPT__; modelDom.innerHTML = ''; + delete window.__CHAT_MODEL_CMD_PROMPT__; delete window.__CHAT_MODEL_CMD__; + delete window.__CHAT_MODEL_VAR__; } }); searchInput.addEventListener('input', (event) => { + if (window.__CHAT_MODEL_VAR__) return; + const query = searchInput.value; if (!query || !/^\//.test(query)) { modelDom.innerHTML = ''; @@ -102,18 +125,20 @@ async function cmdTip() { // all cmd result if (query === '/') { - const result = data.filter(i => i.enable); - modelDom.innerHTML = `
${result.map(itemDom).join('')}
`; - window.__CHAT_MODEL_CMD__ = result[0]?.prompt.trim(); + modelDom.innerHTML = `
${data.map(itemDom).join('')}
`; + window.__CHAT_MODEL_CMD_PROMPT__ = data[0]?.prompt.trim(); + window.__CHAT_MODEL_CMD__ = data[0]?.cmd.trim(); return; } - const result = data.filter(i => i.enable && new RegExp(query.substring(1)).test(i.cmd)); + const result = data.filter(i => new RegExp(query.substring(1)).test(i.cmd)); if (result.length > 0) { modelDom.innerHTML = `
${result.map(itemDom).join('')}
`; - window.__CHAT_MODEL_CMD__ = result[0]?.prompt.trim(); + window.__CHAT_MODEL_CMD_PROMPT__ = result[0]?.prompt.trim(); + window.__CHAT_MODEL_CMD__ = result[0]?.cmd.trim(); } else { modelDom.innerHTML = ''; + delete window.__CHAT_MODEL_CMD_PROMPT__; delete window.__CHAT_MODEL_CMD__; } }, { @@ -136,7 +161,7 @@ async function cmdTip() { const val = decodeURIComponent(item.getAttribute('data-prompt')); searchInput.value = val; document.querySelector('form textarea').focus(); - window.__CHAT_MODEL_CMD__ = val; + window.__CHAT_MODEL_CMD_PROMPT__ = val; modelDom.innerHTML = ''; } }, { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 22841b3..b628ae7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -15,8 +15,7 @@ use tauri_plugin_log::{ LogTarget, LoggerBuilder, }; -#[tokio::main] -async fn main() { +fn main() { ChatConfJson::init(); let chat_conf = ChatConfJson::get_chat_conf(); let context = tauri::generate_context!(); @@ -58,9 +57,10 @@ async fn main() { cmd::form_confirm, cmd::form_msg, cmd::open_file, - cmd::get_chat_model, + cmd::get_chat_model_cmd, cmd::parse_prompt, cmd::window_reload, + cmd::cmd_list, fs_extra::metadata, ]) .setup(setup::init) diff --git a/src/hooks/useChatModel.ts b/src/hooks/useChatModel.ts index 52c6f8e..45a816a 100644 --- a/src/hooks/useChatModel.ts +++ b/src/hooks/useChatModel.ts @@ -2,46 +2,52 @@ import { useState, useEffect } from 'react'; import { clone } from 'lodash'; import { invoke } from '@tauri-apps/api'; -import { CHAT_MODEL_JSON, readJSON, writeJSON } from '@/utils'; +import { CHAT_MODEL_JSON, CHAT_MODEL_CMD_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, { - defaultVal: { name: 'ChatGPT Model', [key]: [] }, + defaultVal: { name: 'ChatGPT Model', [key]: null }, }); setModelJson(data); }); - const modelSet = async (data: Record[]) => { + const modelSet = async (data: Record[]|Record) => { const oData = clone(modelJson); oData[key] = data; await writeJSON(file, oData); - await invoke('window_reload', { label: 'core' }); setModelJson(oData); } return { modelJson, modelSet, modelData: modelJson?.[key] || [] }; } -export function useCacheModel(file: string) { - const [modelJson, setModelJson] = useState[]>([]); +export function useCacheModel(file = '') { + const [modelCacheJson, setModelCacheJson] = useState[]>([]); useEffect(() => { if (!file) return; (async () => { - const data = await readJSON(file, { isRoot: true }); - setModelJson(data); + const data = await readJSON(file, { isRoot: true, isList: true }); + setModelCacheJson(data); })(); }, [file]); - const modelSet = async (data: Record[]) => { - await writeJSON(file, data, { isRoot: true }); - await invoke('window_reload', { label: 'core' }); - setModelJson(data); + const modelCacheSet = async (data: Record[], newFile = '') => { + await writeJSON(newFile ? newFile : file, data, { isRoot: true }); + setModelCacheJson(data); + await modelCacheCmd(); } - return { modelJson, modelSet }; + const modelCacheCmd = async () => { + // Generate the `chat.model.cmd.json` file and refresh the page for the slash command to take effect. + const list = await invoke('cmd_list'); + await writeJSON(CHAT_MODEL_CMD_JSON, { name: 'ChatGPT CMD', last_updated: Date.now(), data: list }); + await invoke('window_reload', { label: 'core' }); + }; + + return { modelCacheJson, modelCacheSet, modelCacheCmd }; } \ No newline at end of file diff --git a/src/hooks/useData.ts b/src/hooks/useData.ts index 8722399..39b52f1 100644 --- a/src/hooks/useData.ts +++ b/src/hooks/useData.ts @@ -17,6 +17,9 @@ export default function useData(oData: any[]) { }; const opInit = (val: any[] = []) => { + if (!val || !Array.isArray(val)) return; + console.log('«20» /src/hooks/useData.ts ~> ', val); + const nData = val.map(i => ({ [safeKey]: v4(), ...i })); setData(nData); }; diff --git a/src/hooks/useEvent.ts b/src/hooks/useEvent.ts index 79286da..d230fc4 100644 --- a/src/hooks/useEvent.ts +++ b/src/hooks/useEvent.ts @@ -5,7 +5,7 @@ import useChatModel from '@/hooks/useChatModel'; import { GITHUB_PROMPTS_CSV_URL, chatPromptsPath, genCmd } from '@/utils'; export default function useEvent() { - const { modelSet } = useChatModel('sys_sync_prompts'); + const { modelSet } = useChatModel('sync_prompts'); // Using `emit` and `listen` will be triggered multiple times in development mode. // So here we use `eval` to call `__sync_prompt` useInit(() => { diff --git a/src/main.scss b/src/main.scss index be0111b..4e5b8c6 100644 --- a/src/main.scss +++ b/src/main.scss @@ -22,4 +22,45 @@ html, body { .ant-table-selection-column { width: 50px !important; min-width: 50px !important; +} + +.chat-prompts-val { + display: inline-block; + width: 100%; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} + +.chat-add-btn { + margin-bottom: 5px; +} + +.chat-prompts-tags { + .ant-tag { + margin: 2px; + } +} + +.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/utils.ts b/src/utils.ts index ff92424..cc15f27 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,7 +3,7 @@ import { homeDir, join, dirname } from '@tauri-apps/api/path'; import dayjs from 'dayjs'; export const CHAT_MODEL_JSON = 'chat.model.json'; -export const CHAT_MODEL_SYNC_JSON = 'chat.model.sync.json'; +export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.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 = { @@ -20,22 +20,23 @@ export const chatModelPath = async (): Promise => { return join(await chatRoot(), CHAT_MODEL_JSON); } -export const chatModelSyncPath = async (): Promise => { - return join(await chatRoot(), CHAT_MODEL_SYNC_JSON); -} +// export const chatModelSyncPath = async (): Promise => { +// return join(await chatRoot(), CHAT_MODEL_SYNC_JSON); +// } export const chatPromptsPath = async (): Promise => { return join(await chatRoot(), CHAT_PROMPTS_CSV); } -type readJSONOpts = { defaultVal?: Record, isRoot?: boolean }; +type readJSONOpts = { defaultVal?: Record, isRoot?: boolean, isList?: boolean }; export const readJSON = async (path: string, opts: readJSONOpts = {}) => { - const { defaultVal = {}, isRoot = false } = opts; + const { defaultVal = {}, isRoot = false, isList = false } = opts; const root = await chatRoot(); const file = await join(isRoot ? '' : root, path); if (!await exists(file)) { - writeTextFile(file, JSON.stringify({ + await createDir(await dirname(file), { recursive: true }); + await writeTextFile(file, isList ? '[]' : JSON.stringify({ name: 'ChatGPT', link: 'https://github.com/lencx/ChatGPT', ...defaultVal, diff --git a/src/view/model/SyncCustom/index.scss b/src/view/model/SyncCustom/index.scss deleted file mode 100644 index 2a6f666..0000000 --- a/src/view/model/SyncCustom/index.scss +++ /dev/null @@ -1,28 +0,0 @@ -.chat-prompts-tags { - .ant-tag { - margin: 2px; - } -} - -.add-btn { - margin-bottom: 10px; -} - -.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; - } -} \ No newline at end of file diff --git a/src/view/model/SyncCustom/index.tsx b/src/view/model/SyncCustom/index.tsx index 6f6a81d..6f487ae 100644 --- a/src/view/model/SyncCustom/index.tsx +++ b/src/view/model/SyncCustom/index.tsx @@ -3,19 +3,19 @@ import { Table, Modal, Button, message } from 'antd'; import { invoke, http, path, fs } from '@tauri-apps/api'; import useData from '@/hooks/useData'; -import useChatModel from '@/hooks/useChatModel'; +import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import useColumns from '@/hooks/useColumns'; import { TABLE_PAGINATION } from '@/hooks/useTable'; -import { CHAT_MODEL_SYNC_JSON, chatRoot, writeJSON, readJSON, genCmd } from '@/utils'; +import { CHAT_MODEL_JSON, chatRoot, readJSON, genCmd } from '@/utils'; import { syncColumns, getPath } from './config'; import SyncForm from './Form'; -import './index.scss'; const setTag = (data: Record[]) => data.map((i) => ({ ...i, tags: ['user-sync'], enable: true })) export default function SyncCustom() { const [isVisible, setVisible] = useState(false); - const { modelData, modelSet } = useChatModel('sync_url', CHAT_MODEL_SYNC_JSON); + const { modelData, modelSet } = useChatModel('sync_custom', CHAT_MODEL_JSON); + const { modelCacheCmd, modelCacheSet } = useCacheModel(); const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]); const { columns, ...opInfo } = useColumns(syncColumns()); const formRef = useRef(null); @@ -53,7 +53,7 @@ export default function SyncCustom() { const handleSync = async (filename: string) => { const record = opInfo?.opRecord; const isJson = /json$/.test(record?.ext); - const file = await path.join(await chatRoot(), 'cache_sync', filename); + const file = await path.join(await chatRoot(), 'cache_model', filename); const filePath = await getPath(record); // https or http @@ -65,13 +65,14 @@ export default function SyncCustom() { if (res.ok) { if (isJson) { // parse json - writeJSON(file, setTag(Array.isArray(res?.data) ? res?.data : []), { isRoot: true, dir: 'cache_sync' }); + await modelCacheSet(setTag(Array.isArray(res?.data) ? res?.data : []), file); } else { // parse csv const list: Record[] = await invoke('parse_prompt', { data: res?.data }); const fmtList = list.map(i => ({ ...i, cmd: i.cmd ? i.cmd : genCmd(i.act), enable: true, tags: ['user-sync'] })); - await writeJSON(file, fmtList, { isRoot: true, dir: 'cache_sync' }); + await modelCacheSet(fmtList, file); } + await modelCacheCmd(); message.success('ChatGPT Prompts data has been synchronized!'); } else { message.error('ChatGPT Prompts data sync failed, please try again!'); @@ -82,14 +83,15 @@ export default function SyncCustom() { if (isJson) { // parse json const data = await readJSON(filePath, { isRoot: true }); - await writeJSON(file, setTag(Array.isArray(data) ? data : []), { isRoot: true, dir: 'cache_sync' }); + await modelCacheSet(setTag(Array.isArray(data) ? data : []), file); } else { // parse csv const data = await fs.readTextFile(filePath); const list: Record[] = await invoke('parse_prompt', { data }); const fmtList = list.map(i => ({ ...i, cmd: i.cmd ? i.cmd : genCmd(i.act), enable: true, tags: ['user-sync'] })); - await writeJSON(file, fmtList, { isRoot: true, dir: 'cache_sync' }); + await modelCacheSet(fmtList, file); } + await modelCacheCmd(); }; const handleOk = () => { @@ -109,7 +111,7 @@ export default function SyncCustom() { return (
{ setFilePath(await getPath(state)); - setJsonPath(await path.join(await chatRoot(), 'cache_sync', `${state?.id}.json`)); + setJsonPath(await path.join(await chatRoot(), 'cache_model', `${state?.id}.json`)); }) useEffect(() => { - if (modelJson.length <= 0) return; - opInit(modelJson); - }, [modelJson.length]); + if (modelCacheJson.length <= 0) return; + opInit(modelCacheJson); + }, [modelCacheJson.length]); useEffect(() => { if (opInfo.opType === 'enable') { const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); - modelSet(data); + modelCacheSet(data); } }, [opInfo.opTime]); const handleEnable = (isEnable: boolean) => { const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) - modelSet(data); + modelCacheSet(data); }; return ( diff --git a/src/view/model/UserCustom/index.scss b/src/view/model/UserCustom/index.scss deleted file mode 100644 index f4be422..0000000 --- a/src/view/model/UserCustom/index.scss +++ /dev/null @@ -1,39 +0,0 @@ -.chat-prompts-val { - display: inline-block; - width: 100%; - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; -} - -.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; - } -} \ No newline at end of file diff --git a/src/view/model/UserCustom/index.tsx b/src/view/model/UserCustom/index.tsx index 019e9da..ed8bbad 100644 --- a/src/view/model/UserCustom/index.tsx +++ b/src/view/model/UserCustom/index.tsx @@ -1,29 +1,36 @@ import { useState, useRef, useEffect } from 'react'; import { Table, Button, Modal, message } from 'antd'; -import { invoke } from '@tauri-apps/api'; +import { shell, path } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; import useData from '@/hooks/useData'; -import useChatModel from '@/hooks/useChatModel'; +import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import useColumns from '@/hooks/useColumns'; -import { TABLE_PAGINATION } from '@/hooks/useTable'; -import { chatModelPath } from '@/utils'; +import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; +import { chatRoot, fmtDate } from '@/utils'; import { modelColumns } from './config'; import UserCustomForm from './Form'; -import './index.scss'; export default function LanguageModel() { + const { rowSelection, selectedRowIDs } = useTable(); const [isVisible, setVisible] = useState(false); - const [modelPath, setChatModelPath] = useState(''); - const { modelData, modelSet } = useChatModel('user_custom'); - const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]); + const [jsonPath, setJsonPath] = useState(''); + const { modelJson, modelSet } = useChatModel('user_custom'); + const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath); + const { opData, opInit, opReplaceItems, opAdd, opRemove, opReplace, opSafeKey } = useData([]); const { columns, ...opInfo } = useColumns(modelColumns()); + const lastUpdated = modelJson?.user_custom?.last_updated; + const selectedItems = rowSelection.selectedRowKeys || []; const formRef = useRef(null); + useInit(async () => { + setJsonPath(await path.join(await chatRoot(), 'cache_model', 'user_custom.json')); + }); + useEffect(() => { - if (modelData.length <= 0) return; - opInit(modelData); - }, [modelData]); + if (modelCacheJson.length <= 0) return; + opInit(modelCacheJson); + }, [modelCacheJson.length]); useEffect(() => { if (!opInfo.opType) return; @@ -32,7 +39,7 @@ export default function LanguageModel() { } if (['delete'].includes(opInfo.opType)) { const data = opRemove(opInfo?.opRecord?.[opSafeKey]); - modelSet(data); + modelCacheSet(data); opInfo.resetRecord(); } }, [opInfo.opType, formRef]); @@ -40,14 +47,22 @@ export default function LanguageModel() { useEffect(() => { if (opInfo.opType === 'enable') { const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); - modelSet(data); + modelCacheSet(data); } }, [opInfo.opTime]) - useInit(async () => { - const path = await chatModelPath(); - setChatModelPath(path); - }) + + useEffect(() => { + if (opInfo.opType === 'enable') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); + modelCacheSet(data); + } + }, [opInfo.opTime]); + + const handleEnable = (isEnable: boolean) => { + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + modelCacheSet(data); + }; const hide = () => { setVisible(false); @@ -56,8 +71,8 @@ export default function LanguageModel() { const handleOk = () => { formRef.current?.form?.validateFields() - .then((vals: Record) => { - if (modelData.map((i: any) => i.cmd).includes(vals.cmd) && opInfo?.opRecord?.cmd !== vals.cmd) { + .then(async (vals: Record) => { + if (modelCacheJson.map((i: any) => i.cmd).includes(vals.cmd) && opInfo?.opRecord?.cmd !== vals.cmd) { message.warning(`"cmd: /${vals.cmd}" already exists, please change the "${vals.cmd}" name and resubmit.`); return; } @@ -67,28 +82,46 @@ export default function LanguageModel() { case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; default: break; } - modelSet(data); - opInfo.setExtra(Date.now()); + await modelCacheSet(data); + opInit(data); + modelSet({ + id: 'user_custom', + last_updated: Date.now(), + }); hide(); }) }; - const handleOpenFile = () => { - invoke('open_file', { path: modelPath }); - }; - const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} Model`; return (
- -
PATH: {modelPath}
+
+ +
+ {selectedItems.length > 0 && ( + <> + + + Selected {selectedItems.length} items + + )} +
+
+ {/*
PATH: {modelPath}
*/} +
+ + {lastUpdated && Last updated on {fmtDate(lastUpdated)}} +