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

chore: sync

This commit is contained in:
lencx 2022-12-23 15:27:05 +08:00
parent 2be560e69a
commit 389e00a5e0
17 changed files with 260 additions and 252 deletions

View File

@ -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"

View File

@ -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<String>,
pub enable: bool,
}
#[command]
pub fn cmd_list() -> Vec<ModelRecord> {
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<ModelRecord> = 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
}

View File

@ -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 = `<div>${result.map(itemDom).join('')}</div>`;
window.__CHAT_MODEL_CMD__ = result[0]?.prompt.trim();
modelDom.innerHTML = `<div>${data.map(itemDom).join('')}</div>`;
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 = `<div>${result.map(itemDom).join('')}</div>`;
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 = '';
}
}, {

View File

@ -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)

View File

@ -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<Record<string, any>>([]);
const [modelJson, setModelJson] = useState<Record<string, any>>({});
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<string, any>[]) => {
const modelSet = async (data: Record<string, any>[]|Record<string, any>) => {
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<Record<string, any>[]>([]);
export function useCacheModel(file = '') {
const [modelCacheJson, setModelCacheJson] = useState<Record<string, any>[]>([]);
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<string, any>[]) => {
await writeJSON(file, data, { isRoot: true });
await invoke('window_reload', { label: 'core' });
setModelJson(data);
const modelCacheSet = async (data: Record<string, any>[], 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 };
}

View File

@ -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);
};

View File

@ -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(() => {

41
src/main.scss vendored
View File

@ -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;
}
}

15
src/utils.ts vendored
View File

@ -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<string> => {
return join(await chatRoot(), CHAT_MODEL_JSON);
}
export const chatModelSyncPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_MODEL_SYNC_JSON);
}
// export const chatModelSyncPath = async (): Promise<string> => {
// return join(await chatRoot(), CHAT_MODEL_SYNC_JSON);
// }
export const chatPromptsPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_PROMPTS_CSV);
}
type readJSONOpts = { defaultVal?: Record<string, any>, isRoot?: boolean };
type readJSONOpts = { defaultVal?: Record<string, any>, 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,

View File

@ -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;
}
}

View File

@ -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<string, any>[]) => 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<any>(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<string, string>[] = 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<string, string>[] = 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 (
<div>
<Button
className="add-btn"
className="chat-add-btn"
type="primary"
onClick={opInfo.opNew}
>

View File

@ -1,13 +1,3 @@
.chat-prompts-tags {
.ant-tag {
margin: 2px;
}
}
.add-btn {
margin-bottom: 5px;
}
.chat-table-tip, .chat-table-btns {
display: flex;
justify-content: space-between;
@ -20,22 +10,3 @@
margin-left: 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;
}
}

View File

@ -1,14 +1,13 @@
import { useEffect, useState } from 'react';
import { Table, Button, message, Popconfirm } from 'antd';
import { invoke } from '@tauri-apps/api';
import { fetch, ResponseType } from '@tauri-apps/api/http';
import { writeTextFile } from '@tauri-apps/api/fs';
import { invoke, http, path, shell } from '@tauri-apps/api';
import useColumns from '@/hooks/useColumns';
import useInit from '@/hooks/useInit';
import useData from '@/hooks/useData';
import useChatModel from '@/hooks/useChatModel';
import useColumns from '@/hooks/useColumns';
import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
import { fmtDate, chatPromptsPath, GITHUB_PROMPTS_CSV_URL, genCmd } from '@/utils';
import { fmtDate, chatRoot, GITHUB_PROMPTS_CSV_URL, genCmd } from '@/utils';
import { syncColumns } from './config';
import './index.scss';
@ -16,36 +15,39 @@ const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/promp
export default function SyncPrompts() {
const { rowSelection, selectedRowIDs } = useTable();
const [lastUpdated, setLastUpdated] = useState();
const { modelJson, modelSet } = useChatModel('sys_sync_prompts');
const [jsonPath, setJsonPath] = useState('');
const { modelJson, modelSet } = useChatModel('sync_prompts');
const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
const { columns, ...opInfo } = useColumns(syncColumns());
const lastUpdated = modelJson?.sync_prompts?.last_updated;
const selectedItems = rowSelection.selectedRowKeys || [];
useInit(async () => {
setJsonPath(await path.join(await chatRoot(), 'cache_model', 'chatgpt_prompts.json'));
});
useEffect(() => {
if (!modelJson?.sys_sync_prompts) return;
opInit(modelJson?.sys_sync_prompts);
if (lastUpdated) return;
(async () => {
const fileData: Record<string, any> = await invoke('metadata', { path: await chatPromptsPath() });
setLastUpdated(fileData.accessedAtMs);
})();
}, [modelJson?.sys_sync_prompts])
if (modelCacheJson.length <= 0) return;
opInit(modelCacheJson);
}, [modelCacheJson.length]);
const handleSync = async () => {
const res = await fetch(GITHUB_PROMPTS_CSV_URL, {
const res = await http.fetch(GITHUB_PROMPTS_CSV_URL, {
method: 'GET',
responseType: ResponseType.Text,
responseType: http.ResponseType.Text,
});
const data = (res.data || '') as string;
if (res.ok) {
// const content = data.replace(/"(\s+)?,(\s+)?"/g, '","');
await writeTextFile(await chatPromptsPath(), data);
const list: Record<string, string>[] = await invoke('parse_prompt', { data });
opInit(list);
modelSet(list.map(i => ({ ...i, cmd: i.cmd ? i.cmd : genCmd(i.act), enable: true, tags: ['chatgpt-prompts'] })));
setLastUpdated(fmtDate(Date.now()) as any);
const fmtList = list.map(i => ({ ...i, cmd: i.cmd ? i.cmd : genCmd(i.act), enable: true, tags: ['chatgpt-prompts'] }));
await modelCacheSet(fmtList);
opInit(fmtList);
modelSet({
id: 'chatgpt_prompts',
last_updated: Date.now(),
});
message.success('ChatGPT Prompts data has been synchronized!');
} else {
message.error('ChatGPT Prompts data sync failed, please try again!');
@ -55,13 +57,13 @@ export default function SyncPrompts() {
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 (
@ -87,7 +89,10 @@ export default function SyncPrompts() {
</Popconfirm>
</div>
<div className="chat-table-tip">
<span className="chat-model-path">URL: <a href={promptsURL} target="_blank" title={promptsURL}>f/awesome-chatgpt-prompts/prompts.csv</a></span>
<div className="chat-sync-path">
<div>PATH: <a onClick={() => shell.open(promptsURL)} target="_blank" title={promptsURL}>f/awesome-chatgpt-prompts/prompts.csv</a></div>
<div>CACHE: <a onClick={() => shell.open(jsonPath)} target="_blank" title={jsonPath}>{jsonPath}</a></div>
</div>
{lastUpdated && <span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>Last updated on {fmtDate(lastUpdated)}</span>}
</div>
<Table

View File

@ -1,42 +0,0 @@
// .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;
}
}

View File

@ -12,7 +12,6 @@ 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();
@ -21,7 +20,7 @@ export default function SyncRecord() {
const state = location?.state;
const { rowSelection, selectedRowIDs } = useTable();
const { modelJson, modelSet } = useCacheModel(jsonPath);
const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
const { columns, ...opInfo } = useColumns(syncColumns());
@ -29,24 +28,24 @@ export default function SyncRecord() {
useInit(async () => {
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 (

View File

@ -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;
}
}

View File

@ -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<any>(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<string, any>) => {
if (modelData.map((i: any) => i.cmd).includes(vals.cmd) && opInfo?.opRecord?.cmd !== vals.cmd) {
.then(async (vals: Record<string, any>) => {
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 (
<div>
<Button className="add-btn" type="primary" onClick={opInfo.opNew}>Add Model</Button>
<div className="chat-model-path">PATH: <span onClick={handleOpenFile}>{modelPath}</span></div>
<div className="chat-table-btns">
<Button className="chat-add-btn" type="primary" onClick={opInfo.opNew}>Add Model</Button>
<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-model-path">PATH: <span onClick={handleOpenFile}>{modelPath}</span></div> */}
<div className="chat-table-tip">
<div className="chat-sync-path">
<div>CACHE: <a onClick={() => shell.open(jsonPath)} title={jsonPath}>{jsonPath}</a></div>
</div>
{lastUpdated && <span style={{ marginLeft: 10, color: '#888', fontSize: 12 }}>Last updated on {fmtDate(lastUpdated)}</span>}
</div>
<Table
key={opInfo.opExtra}
key={lastUpdated}
rowKey="cmd"
columns={columns}
scroll={{ x: 'auto' }}
dataSource={opData}
rowSelection={rowSelection}
pagination={TABLE_PAGINATION}
/>
<Modal