mirror of
https://github.com/lencx/ChatGPT.git
synced 2024-10-01 01:06:13 -04:00
refactor: prompts
This commit is contained in:
parent
c53524b472
commit
882593479b
11
README.md
11
README.md
@ -5,22 +5,13 @@
|
||||
</p>
|
||||
|
||||
[![English badge](https://img.shields.io/badge/%E8%8B%B1%E6%96%87-English-blue)](./README.md)
|
||||
[![简体中文 badge](https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-Simplified%20Chinese-blue)](./README-ZH_CN.md)\
|
||||
![visitor](https://visitor-badge.glitch.me/badge?page_id=lencx.chatgpt)
|
||||
[![简体中文 badge](https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-Simplified%20Chinese-blue)](./README-ZH_CN.md)
|
||||
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
|
||||
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
|
||||
[![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
|
||||
|
||||
<!-- [![lencx](https://img.shields.io/twitter/follow/lencx_.svg?style=social)](https://twitter.com/lencx_) -->
|
||||
|
||||
<!-- [![中文版 badge](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-Traditional%20Chinese-blue)](./README-ZH.md) -->
|
||||
|
||||
<a href="https://www.buymeacoffee.com/lencx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>
|
||||
|
||||
**🛑 URGENT NOTICE: A hacker has been found to take advantage of the heat of `lencx/ChatGPT` to plant a Trojan horse after the fork project and rebuild the installer. If you have friends around you who are using this desktop application, please remind them not to download unknown links freely. Now the project will remove other installation ways and only provide this download link https://github.com/lencx/ChatGPT/releases**
|
||||
|
||||
**🛑 紧急通知:目前发现有黑客利用 `lencx/ChatGPT` 的热度,在 fork 项目后植入木马,重新构建安装程序。如果你身边有朋友正在使用此桌面应用,请提醒 TA 们不要随意下载不明链接。现在项目将删除其他安装途径,仅提供此下载链接 https://github.com/lencx/ChatGPT/releases**
|
||||
|
||||
---
|
||||
|
||||
**It is an unofficial project intended for personal learning and research purposes only. During the time that the ChatGPT desktop application was open-sourced, it received a lot of attention, and I would like to thank everyone for their support. However, as things have developed, there are two issues that seriously affect the project's next development plan:**
|
||||
|
@ -9,6 +9,8 @@ use std::{collections::HashMap, fs, path::PathBuf, vec};
|
||||
use tauri::{api, command, AppHandle, Manager};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::fs_extra::Error;
|
||||
|
||||
#[command]
|
||||
pub fn get_chat_prompt_cmd() -> serde_json::Value {
|
||||
let path = utils::app_root().join("chat.prompt.cmd.json");
|
||||
@ -24,23 +26,28 @@ pub struct PromptBaseRecord {
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn parse_prompt(data: String) -> Vec<PromptBaseRecord> {
|
||||
pub fn parse_prompt(data: String) -> Option<Vec<PromptBaseRecord>> {
|
||||
let mut rdr = csv::Reader::from_reader(data.as_bytes());
|
||||
let mut list = vec![];
|
||||
for result in rdr.deserialize() {
|
||||
let record: PromptBaseRecord = result.unwrap_or_else(|err| {
|
||||
error!("parse_prompt: {}", err);
|
||||
PromptBaseRecord {
|
||||
cmd: None,
|
||||
act: "".to_string(),
|
||||
prompt: "".to_string(),
|
||||
|
||||
for result in rdr.deserialize::<PromptBaseRecord>() {
|
||||
match result {
|
||||
Ok(record) => {
|
||||
if !record.act.is_empty() {
|
||||
list.push(record);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("parse_prompt: {}", err);
|
||||
}
|
||||
});
|
||||
if !record.act.is_empty() {
|
||||
list.push(record);
|
||||
}
|
||||
}
|
||||
list
|
||||
|
||||
if list.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(list)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
@ -165,87 +172,89 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<PromptRecord>
|
||||
.unwrap();
|
||||
|
||||
if let Some(v) = res {
|
||||
let data = parse_prompt(v)
|
||||
.iter()
|
||||
.map(move |i| PromptRecord {
|
||||
cmd: if i.cmd.is_some() {
|
||||
i.cmd.clone().unwrap()
|
||||
} else {
|
||||
utils::gen_cmd(i.act.clone())
|
||||
},
|
||||
act: i.act.clone(),
|
||||
prompt: i.prompt.clone(),
|
||||
tags: vec!["chatgpt-prompts".to_string()],
|
||||
enable: true,
|
||||
})
|
||||
.collect::<Vec<PromptRecord>>();
|
||||
|
||||
let data2 = data.clone();
|
||||
|
||||
let prompts = utils::app_root().join("chat.prompt.json");
|
||||
let prompt_cmd = utils::app_root().join("chat.prompt.cmd.json");
|
||||
let chatgpt_prompts = utils::app_root()
|
||||
.join("cache_prompts")
|
||||
.join("chatgpt_prompts.json");
|
||||
|
||||
if !utils::exists(&prompts) {
|
||||
fs::write(
|
||||
&prompts,
|
||||
serde_json::json!({
|
||||
"name": "ChatGPT Prompts",
|
||||
"link": "https://github.com/lencx/ChatGPT"
|
||||
if let Some(data) = parse_prompt(v) {
|
||||
let transformed_data = data
|
||||
.iter()
|
||||
.map(|i| PromptRecord {
|
||||
cmd: if let Some(cmd) = &i.cmd {
|
||||
cmd.clone()
|
||||
} else {
|
||||
utils::gen_cmd(i.act.clone())
|
||||
},
|
||||
act: i.act.clone(),
|
||||
prompt: i.prompt.clone(),
|
||||
tags: vec!["chatgpt-prompts".to_string()],
|
||||
enable: true,
|
||||
})
|
||||
.to_string(),
|
||||
.collect::<Vec<PromptRecord>>();
|
||||
|
||||
let data2 = transformed_data;
|
||||
|
||||
let prompts = utils::app_root().join("chat.prompt.json");
|
||||
let prompt_cmd = utils::app_root().join("chat.prompt.cmd.json");
|
||||
let chatgpt_prompts = utils::app_root()
|
||||
.join("cache_prompts")
|
||||
.join("chatgpt_prompts.json");
|
||||
|
||||
if !utils::exists(&prompts) {
|
||||
fs::write(
|
||||
&prompts,
|
||||
serde_json::json!({
|
||||
"name": "ChatGPT Prompts",
|
||||
"link": "https://github.com/lencx/ChatGPT"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// chatgpt_prompts.json
|
||||
fs::write(
|
||||
chatgpt_prompts,
|
||||
serde_json::to_string_pretty(&data).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let cmd_data = cmd_list();
|
||||
|
||||
// chat.prompt.cmd.json
|
||||
fs::write(
|
||||
prompt_cmd,
|
||||
serde_json::to_string_pretty(&serde_json::json!({
|
||||
"name": "ChatGPT CMD",
|
||||
"last_updated": time,
|
||||
"data": cmd_data,
|
||||
}))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut kv = HashMap::new();
|
||||
kv.insert(
|
||||
"sync_prompts".to_string(),
|
||||
serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }),
|
||||
);
|
||||
let prompts_data = utils::merge(
|
||||
&serde_json::from_str(&fs::read_to_string(&prompts).unwrap()).unwrap(),
|
||||
&kv,
|
||||
);
|
||||
|
||||
// chat.prompt.json
|
||||
fs::write(
|
||||
prompts,
|
||||
serde_json::to_string_pretty(&prompts_data).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// refresh window
|
||||
api::dialog::message(
|
||||
app.get_window("core").as_ref(),
|
||||
"Sync Prompts",
|
||||
"Prompts data has been synchronized!",
|
||||
);
|
||||
window::cmd::window_reload(app.clone(), "core");
|
||||
window::cmd::window_reload(app, "tray");
|
||||
|
||||
return Some(data2);
|
||||
}
|
||||
|
||||
// chatgpt_prompts.json
|
||||
fs::write(
|
||||
chatgpt_prompts,
|
||||
serde_json::to_string_pretty(&data).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let cmd_data = cmd_list();
|
||||
|
||||
// chat.prompt.cmd.json
|
||||
fs::write(
|
||||
prompt_cmd,
|
||||
serde_json::to_string_pretty(&serde_json::json!({
|
||||
"name": "ChatGPT CMD",
|
||||
"last_updated": time,
|
||||
"data": cmd_data,
|
||||
}))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut kv = HashMap::new();
|
||||
kv.insert(
|
||||
"sync_prompts".to_string(),
|
||||
serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }),
|
||||
);
|
||||
let prompts_data = utils::merge(
|
||||
&serde_json::from_str(&fs::read_to_string(&prompts).unwrap()).unwrap(),
|
||||
&kv,
|
||||
);
|
||||
|
||||
// chat.prompt.json
|
||||
fs::write(
|
||||
prompts,
|
||||
serde_json::to_string_pretty(&prompts_data).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// refresh window
|
||||
api::dialog::message(
|
||||
app.get_window("core").as_ref(),
|
||||
"Sync Prompts",
|
||||
"ChatGPT Prompts data has been synchronized!",
|
||||
);
|
||||
window::cmd::window_reload(app.clone(), "core");
|
||||
window::cmd::window_reload(app, "tray");
|
||||
|
||||
return Some(data2);
|
||||
}
|
||||
|
||||
None
|
||||
@ -260,37 +269,40 @@ pub async fn sync_user_prompts(url: String, data_type: String) -> Option<Vec<Pro
|
||||
});
|
||||
|
||||
if let Some(v) = res {
|
||||
let data;
|
||||
if data_type == "csv" {
|
||||
let data: Option<Vec<PromptBaseRecord>> = if data_type == "csv" {
|
||||
info!("chatgpt_http_csv_parse");
|
||||
data = parse_prompt(v);
|
||||
parse_prompt(v)
|
||||
} else if data_type == "json" {
|
||||
info!("chatgpt_http_json_parse");
|
||||
data = serde_json::from_str(&v).unwrap_or_else(|err| {
|
||||
error!("chatgpt_http_json_parse: {}", err);
|
||||
vec![]
|
||||
});
|
||||
match serde_json::from_str::<Vec<PromptBaseRecord>>(&v) {
|
||||
Ok(parsed) => Some(parsed),
|
||||
Err(err) => {
|
||||
error!("chatgpt_http_json_parse: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("chatgpt_http_unknown_type");
|
||||
data = vec![];
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(base_records) = data {
|
||||
let data = base_records
|
||||
.iter()
|
||||
.map(|i| PromptRecord {
|
||||
cmd: i
|
||||
.cmd
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::gen_cmd(i.act.clone())),
|
||||
act: i.act.clone(),
|
||||
prompt: i.prompt.clone(),
|
||||
tags: vec!["user-sync".to_string()],
|
||||
enable: true,
|
||||
})
|
||||
.collect::<Vec<PromptRecord>>();
|
||||
|
||||
return Some(data);
|
||||
}
|
||||
|
||||
let data = data
|
||||
.iter()
|
||||
.map(move |i| PromptRecord {
|
||||
cmd: if i.cmd.is_some() {
|
||||
i.cmd.clone().unwrap()
|
||||
} else {
|
||||
utils::gen_cmd(i.act.clone())
|
||||
},
|
||||
act: i.act.clone(),
|
||||
prompt: i.prompt.clone(),
|
||||
tags: vec!["user-sync".to_string()],
|
||||
enable: true,
|
||||
})
|
||||
.collect::<Vec<PromptRecord>>();
|
||||
|
||||
return Some(data);
|
||||
}
|
||||
|
||||
None
|
||||
|
12
src/hooks/useChatPrompt.ts
vendored
12
src/hooks/useChatPrompt.ts
vendored
@ -18,11 +18,21 @@ export default function useChatPrompt(key: string, file = CHAT_PROMPT_JSON) {
|
||||
const promptSet = async (data: Record<string, any>[] | Record<string, any>) => {
|
||||
const oData = clone(promptJson);
|
||||
oData[key] = data;
|
||||
|
||||
await writeJSON(file, oData);
|
||||
setPromptJson(oData);
|
||||
};
|
||||
|
||||
return { promptJson, promptSet, promptData: promptJson?.[key] || [] };
|
||||
const promptUpdate = async (id: string, field: string, value: any) => {
|
||||
const oData = clone(promptJson);
|
||||
const idx = oData[key].findIndex((v: any) => v.id === id);
|
||||
oData[key][idx][field] = value;
|
||||
|
||||
await writeJSON(file, oData);
|
||||
setPromptJson(oData);
|
||||
};
|
||||
|
||||
return { promptJson, promptSet, promptUpdate, promptData: promptJson?.[key] || [] };
|
||||
}
|
||||
|
||||
export function useCachePrompt(file = '') {
|
||||
|
100
src/view/prompts/SyncCustom/Form.tsx
vendored
100
src/view/prompts/SyncCustom/Form.tsx
vendored
@ -5,17 +5,15 @@ import {
|
||||
useImperativeHandle,
|
||||
forwardRef,
|
||||
} from 'react';
|
||||
import { Form, Input, Radio, Upload, Tooltip, message } from 'antd';
|
||||
import { Form, Input, Radio, Upload, Tooltip, Button, message } from 'antd';
|
||||
import { v4 } from 'uuid';
|
||||
import { InboxOutlined } from '@ant-design/icons';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import type { FormProps, RadioChangeEvent, UploadProps, UploadFile } from 'antd';
|
||||
|
||||
import { DISABLE_AUTO_COMPLETE, chatRoot } from '@/utils';
|
||||
// import useInit from '@/hooks/useInit';
|
||||
|
||||
interface SyncFormProps {
|
||||
record?: Record<string | symbol, any> | null;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const initFormValue = {
|
||||
@ -25,43 +23,18 @@ const initFormValue = {
|
||||
protocol: 'https',
|
||||
};
|
||||
|
||||
const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record, type }, ref) => {
|
||||
// const isDisabled = type === 'edit';
|
||||
const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
useImperativeHandle(ref, () => ({ form }));
|
||||
// const [root, setRoot] = useState('');
|
||||
const [protocol, setProtocol] = useState('https');
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
|
||||
// useInit(async () => {
|
||||
// setRoot(await chatRoot());
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
if (record) {
|
||||
form.setFieldsValue(record);
|
||||
}
|
||||
}, [record]);
|
||||
|
||||
// const pathOptions = (
|
||||
// <Form.Item noStyle name="protocol" initialValue="https">
|
||||
// <Select disabled={isDisabled}>
|
||||
// <Select.Option value="local">{root}</Select.Option>
|
||||
// <Select.Option value="http">http://</Select.Option>
|
||||
// <Select.Option value="https">https://</Select.Option>
|
||||
// </Select>
|
||||
// </Form.Item>
|
||||
// );
|
||||
|
||||
// const extOptions = (
|
||||
// <Form.Item noStyle name="ext" initialValue="json">
|
||||
// <Select disabled={isDisabled}>
|
||||
// <Select.Option value="csv">.csv</Select.Option>
|
||||
// <Select.Option value="json">.json</Select.Option>
|
||||
// </Select>
|
||||
// </Form.Item>
|
||||
// );
|
||||
|
||||
const jsonTip = (
|
||||
<Tooltip
|
||||
title={
|
||||
@ -135,49 +108,50 @@ const SyncForm: ForwardRefRenderFunction<FormProps, SyncFormProps> = ({ record,
|
||||
<Radio value="local">local</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<div style={{ marginLeft: 30, color: '#888' }}>
|
||||
<p>
|
||||
<b>.ext</b>: The file supports only {csvTip} and {jsonTip} formats.
|
||||
</p>
|
||||
</div>
|
||||
{['http', 'https'].includes(protocol) && (
|
||||
<Form.Item
|
||||
label="URL"
|
||||
name="url"
|
||||
rules={[
|
||||
{ required: true, message: 'Please enter the URL!' },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || /\.json$|\.csv$/.test(getFieldValue('url'))) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('The file supports only .csv and .json formats'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
style={{ height: 200 }}
|
||||
>
|
||||
<Input
|
||||
placeholder="your_path/file_name.ext"
|
||||
addonBefore={`${protocol}://`}
|
||||
// addonAfter={extOptions}
|
||||
{...DISABLE_AUTO_COMPLETE}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div style={{ height: 180 }}>
|
||||
<Form.Item
|
||||
label="URL"
|
||||
name="url"
|
||||
rules={[
|
||||
{ required: true, message: 'Please enter the URL!' },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || /\.json$|\.csv$/.test(getFieldValue('url'))) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error('The file supports only .csv and .json formats'),
|
||||
);
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="your_path/file_name.ext"
|
||||
addonBefore={`${protocol}://`}
|
||||
{...DISABLE_AUTO_COMPLETE}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div style={{ marginLeft: 80, color: '#888' }}>
|
||||
<p>
|
||||
<b>.ext</b>: only {csvTip} or {jsonTip} file formats are supported.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{protocol === 'local' && (
|
||||
<Form.Item
|
||||
name="file"
|
||||
label="File"
|
||||
rules={[{ required: true, message: 'Please select a file!' }]}
|
||||
style={{ height: 200 }}
|
||||
style={{ height: 168 }}
|
||||
>
|
||||
<Upload.Dragger {...uploadOptions}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||
<p className="ant-upload-hint">
|
||||
Only {csvTip} or {jsonTip} file formats are supported.
|
||||
</p>
|
||||
<p className="ant-upload-text">Click or drag file to this area to upload</p>
|
||||
<p className="ant-upload-hint">Only .json or .csv files are supported.</p>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
29
src/view/prompts/SyncCustom/config.tsx
vendored
29
src/view/prompts/SyncCustom/config.tsx
vendored
@ -4,6 +4,7 @@ import { HistoryOutlined } from '@ant-design/icons';
|
||||
import { shell, path } from '@tauri-apps/api';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { EditRow } from '@/hooks/useColumns';
|
||||
import useInit from '@/hooks/useInit';
|
||||
import { chatRoot, fmtDate } from '@/utils';
|
||||
|
||||
@ -13,6 +14,9 @@ export const syncColumns = () => [
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
render: (_: string, row: any, actions: any) => (
|
||||
<EditRow rowKey="name" row={row} actions={actions} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Protocol',
|
||||
@ -47,21 +51,22 @@ export const syncColumns = () => [
|
||||
render: (_: any, row: any, actions: any) => {
|
||||
return (
|
||||
<Space>
|
||||
<Popconfirm
|
||||
overlayStyle={{ width: 250 }}
|
||||
title="Sync will overwrite the previous data, confirm to sync?"
|
||||
onConfirm={() => actions.setRecord(row, 'sync')}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<a>Sync</a>
|
||||
</Popconfirm>
|
||||
{row.protocol !== 'local' && (
|
||||
<Popconfirm
|
||||
overlayStyle={{ width: 250 }}
|
||||
title="Sync will overwrite the previous data, confirm to sync?"
|
||||
onConfirm={() => actions.setRecord(row, 'sync')}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<a>Sync</a>
|
||||
</Popconfirm>
|
||||
)}
|
||||
{row.last_updated && (
|
||||
<Link to={`${row.id}`} state={row}>
|
||||
View
|
||||
</Link>
|
||||
)}
|
||||
<a onClick={() => actions.setRecord(row, 'edit')}>Edit</a>
|
||||
<Popconfirm
|
||||
title="Are you sure to delete this path?"
|
||||
onConfirm={() => actions.setRecord(row, 'delete')}
|
||||
@ -86,8 +91,8 @@ const RenderPath = ({ row }: any) => {
|
||||
|
||||
export const getPath = async (row: any) => {
|
||||
if (!/^http/.test(row.protocol)) {
|
||||
return (await path.join(await chatRoot(), row.path)) + `.${row.ext}`;
|
||||
return await path.join(await chatRoot(), 'cache_prompts', `${row.id}.json`);
|
||||
} else {
|
||||
return `${row.protocol}://${row.path}.${row.ext}`;
|
||||
return `${row.protocol}://${row.url}`;
|
||||
}
|
||||
};
|
||||
|
165
src/view/prompts/SyncCustom/index.tsx
vendored
165
src/view/prompts/SyncCustom/index.tsx
vendored
@ -1,12 +1,13 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Table, Modal, Button, message } from 'antd';
|
||||
import { invoke, path, fs } from '@tauri-apps/api';
|
||||
import { invoke, path, fs, shell } from '@tauri-apps/api';
|
||||
|
||||
import useData from '@/hooks/useData';
|
||||
import useInit from '@/hooks/useInit';
|
||||
import useColumns from '@/hooks/useColumns';
|
||||
import { TABLE_PAGINATION } from '@/hooks/useTable';
|
||||
import useChatPrompt, { useCachePrompt } from '@/hooks/useChatPrompt';
|
||||
import { CHAT_PROMPT_JSON, chatRoot, readJSON, genCmd } from '@/utils';
|
||||
import { CHAT_PROMPT_JSON, chatRoot, genCmd } from '@/utils';
|
||||
import { syncColumns, getPath } from './config';
|
||||
import SyncForm from './Form';
|
||||
|
||||
@ -19,8 +20,9 @@ const fmtData = (data: Record<string, any>[] = []) =>
|
||||
}));
|
||||
|
||||
export default function SyncCustom() {
|
||||
const [logPath, setLogPath] = useState('');
|
||||
const [isVisible, setVisible] = useState(false);
|
||||
const { promptData, promptSet } = useChatPrompt('sync_custom', CHAT_PROMPT_JSON);
|
||||
const { promptData, promptSet, promptUpdate } = useChatPrompt('sync_custom', CHAT_PROMPT_JSON);
|
||||
const { promptCacheCmd, promptCacheSet } = useCachePrompt();
|
||||
const { opData, opInit, opAdd, opRemove, opReplace, opSafeKey } = useData([]);
|
||||
const { columns, ...opInfo } = useColumns(syncColumns());
|
||||
@ -31,6 +33,11 @@ export default function SyncCustom() {
|
||||
opInfo.resetRecord();
|
||||
};
|
||||
|
||||
useInit(async () => {
|
||||
const filePath = await path.join(await chatRoot(), 'chatgpt.log');
|
||||
setLogPath(filePath);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (promptData.length <= 0) return;
|
||||
opInit(promptData);
|
||||
@ -38,24 +45,19 @@ export default function SyncCustom() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!opInfo.opType) return;
|
||||
if (opInfo.opType === 'sync') {
|
||||
const filename = `${opInfo?.opRecord?.id}.json`;
|
||||
handleSync(filename).then((isOk: boolean) => {
|
||||
(async () => {
|
||||
if (opInfo.opType === 'sync') {
|
||||
handleSync();
|
||||
}
|
||||
if (opInfo.opType === 'rowedit') {
|
||||
await promptUpdate(opInfo?.opRecord?.id, 'name', opInfo?.opRecord?.name);
|
||||
message.success('Name has been changed');
|
||||
opInfo.resetRecord();
|
||||
if (!isOk) return;
|
||||
const data = opReplace(opInfo?.opRecord?.[opSafeKey], {
|
||||
...opInfo?.opRecord,
|
||||
last_updated: Date.now(),
|
||||
});
|
||||
promptSet(data);
|
||||
opInfo.resetRecord();
|
||||
});
|
||||
}
|
||||
if (['edit', 'new'].includes(opInfo.opType)) {
|
||||
setVisible(true);
|
||||
}
|
||||
if (['delete'].includes(opInfo.opType)) {
|
||||
(async () => {
|
||||
}
|
||||
if (['edit', 'new'].includes(opInfo.opType)) {
|
||||
setVisible(true);
|
||||
}
|
||||
if (['delete'].includes(opInfo.opType)) {
|
||||
try {
|
||||
const file = await path.join(
|
||||
await chatRoot(),
|
||||
@ -68,63 +70,117 @@ export default function SyncCustom() {
|
||||
promptSet(data);
|
||||
opInfo.resetRecord();
|
||||
promptCacheCmd();
|
||||
})();
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [opInfo.opType, formRef]);
|
||||
|
||||
const handleSync = async (filename: string) => {
|
||||
const handleSync = async () => {
|
||||
const filename = `${opInfo?.opRecord?.id}.json`;
|
||||
const record = opInfo?.opRecord;
|
||||
const isJson = /json$/.test(record?.ext);
|
||||
const file = await path.join(await chatRoot(), 'cache_prompts', filename);
|
||||
const filePath = await getPath(record);
|
||||
|
||||
// https or http
|
||||
if (/^http/.test(record?.protocol)) {
|
||||
const data = await invoke('sync_user_prompts', { url: filePath, dataType: record?.ext });
|
||||
const isJson = /json$/.test(record?.url);
|
||||
const file = await path.join(await chatRoot(), 'cache_prompts', filename);
|
||||
const filePath = await getPath(record);
|
||||
|
||||
const data = await invoke('sync_user_prompts', {
|
||||
url: filePath,
|
||||
dataType: isJson ? 'json' : 'csv',
|
||||
});
|
||||
if (data) {
|
||||
await promptCacheSet(data as [], file);
|
||||
await promptCacheCmd();
|
||||
message.success('ChatGPT Prompts data has been synchronized!');
|
||||
return true;
|
||||
message.success('Prompts successfully synchronized');
|
||||
const data2 = opReplace(opInfo?.opRecord?.[opSafeKey], {
|
||||
...opInfo?.opRecord,
|
||||
last_updated: Date.now(),
|
||||
});
|
||||
promptSet(data2);
|
||||
opInfo.resetRecord();
|
||||
} else {
|
||||
message.error('ChatGPT Prompts data sync failed, please try again!');
|
||||
return false;
|
||||
message.error(
|
||||
'Prompts synchronization failed, please try again (click to "View Log" for more details)',
|
||||
);
|
||||
}
|
||||
}
|
||||
// local
|
||||
if (isJson) {
|
||||
// parse json
|
||||
const data = await readJSON(filePath, { isRoot: true });
|
||||
await promptCacheSet(fmtData(data), file);
|
||||
} else {
|
||||
// parse csv
|
||||
const data = await fs.readTextFile(filePath);
|
||||
const list: Record<string, string>[] = await invoke('parse_prompt', { data });
|
||||
await promptCacheSet(fmtData(list), file);
|
||||
opInfo.resetRecord();
|
||||
};
|
||||
|
||||
const parseLocal = async (file: File): Promise<[boolean, any[] | null]> => {
|
||||
if (file) {
|
||||
const fileData = await readFile(file);
|
||||
const isJSON = /json$/.test(file.name);
|
||||
|
||||
if (isJSON) {
|
||||
// parse json
|
||||
try {
|
||||
const jsonData = JSON.parse(fileData);
|
||||
return [true, jsonData];
|
||||
} catch (e) {
|
||||
message.error('JSON parse error, please check your file');
|
||||
return [false, null];
|
||||
}
|
||||
} else {
|
||||
// parse csv
|
||||
const list: Record<string, string>[] | null = await invoke('parse_prompt', {
|
||||
data: fileData,
|
||||
});
|
||||
if (!list) {
|
||||
message.error('CSV parse error, please check your file');
|
||||
return [false, null];
|
||||
} else {
|
||||
return [true, list];
|
||||
}
|
||||
}
|
||||
}
|
||||
await promptCacheCmd();
|
||||
return true;
|
||||
|
||||
message.error('File parsing exception');
|
||||
return [false, null];
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
formRef.current?.form?.validateFields().then(async (vals: Record<string, any>) => {
|
||||
const file = await readFile(vals?.file?.file?.originFileObj);
|
||||
vals.file = file;
|
||||
|
||||
if (opInfo.opType === 'new') {
|
||||
const data = opAdd(vals);
|
||||
promptSet(data);
|
||||
message.success('Data added successfully');
|
||||
if (vals.protocol !== 'local') {
|
||||
// http or https
|
||||
delete vals.file;
|
||||
const data = opAdd(vals);
|
||||
await promptSet(data);
|
||||
hide();
|
||||
opInfo.setRecord(data[0], 'sync');
|
||||
message.success('Data added successfully');
|
||||
} else {
|
||||
const file = vals?.file?.file?.originFileObj;
|
||||
const data = opAdd(vals);
|
||||
const parseData = await parseLocal(file);
|
||||
|
||||
if (parseData[0]) {
|
||||
const id = data[0].id;
|
||||
const filePath = await path.join(await chatRoot(), 'cache_prompts', `${id}.json`);
|
||||
data[0].last_updated = Date.now();
|
||||
await promptSet(data);
|
||||
await promptCacheSet(fmtData(parseData[1] as []), filePath);
|
||||
await promptCacheCmd();
|
||||
hide();
|
||||
message.success('Data added successfully');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opInfo.opType === 'edit') {
|
||||
delete vals.file;
|
||||
const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals);
|
||||
promptSet(data);
|
||||
hide();
|
||||
message.success('Data updated successfully');
|
||||
}
|
||||
hide();
|
||||
});
|
||||
};
|
||||
|
||||
const handleLog = () => {
|
||||
shell.open(logPath);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
@ -135,9 +191,12 @@ export default function SyncCustom() {
|
||||
>
|
||||
Add Prompt
|
||||
</Button>
|
||||
<Button style={{ marginBottom: 10 }} onClick={handleLog}>
|
||||
View Log
|
||||
</Button>
|
||||
<Table
|
||||
key="id"
|
||||
rowKey="name"
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
scroll={{ x: 800 }}
|
||||
dataSource={opData}
|
||||
@ -151,13 +210,13 @@ export default function SyncCustom() {
|
||||
destroyOnClose
|
||||
maskClosable={false}
|
||||
>
|
||||
<SyncForm ref={formRef} record={opInfo?.opRecord} type={opInfo.opType} />
|
||||
<SyncForm ref={formRef} record={opInfo?.opRecord} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function readFile(file: File) {
|
||||
function readFile(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e: any) => resolve(e.target.result);
|
||||
|
6
src/view/prompts/SyncRecord/index.tsx
vendored
6
src/view/prompts/SyncRecord/index.tsx
vendored
@ -28,7 +28,11 @@ export default function SyncRecord() {
|
||||
const selectedItems = rowSelection.selectedRowKeys || [];
|
||||
|
||||
useInit(async () => {
|
||||
setFilePath(await getPath(state));
|
||||
if (state.protocol === 'local') {
|
||||
setFilePath('');
|
||||
} else {
|
||||
setFilePath(await getPath(state));
|
||||
}
|
||||
setJsonPath(await path.join(await chatRoot(), 'cache_prompts', `${state?.id}.json`));
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user