mirror of
https://github.com/FranP-code/ChatGPT.git
synced 2025-10-13 00:13:25 +00:00
chore: export
This commit is contained in:
@@ -16,18 +16,18 @@ tauri-build = {version = "1.2.1", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.66"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
tauri = { version = "1.2.3", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] }
|
|
||||||
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
|
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
regex = "1.7.0"
|
regex = "1.7.0"
|
||||||
tokio = { version = "1.23.0", features = ["macros"] }
|
|
||||||
reqwest = "0.11.13"
|
reqwest = "0.11.13"
|
||||||
wry = "0.23.4"
|
wry = "0.24.1"
|
||||||
dark-light = "1.0.0"
|
dark-light = "1.0.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tokio = { version = "1.23.0", features = ["macros"] }
|
||||||
|
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
|
||||||
|
tauri = { version = "1.2.3", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] }
|
||||||
[dependencies.tauri-plugin-log]
|
[dependencies.tauri-plugin-log]
|
||||||
git = "https://github.com/lencx/tauri-plugin-log"
|
git = "https://github.com/lencx/tauri-plugin-log"
|
||||||
branch = "dev"
|
branch = "dev"
|
||||||
@@ -36,6 +36,8 @@ features = ["colored"]
|
|||||||
git = "https://github.com/lencx/tauri-plugin-autostart"
|
git = "https://github.com/lencx/tauri-plugin-autostart"
|
||||||
branch = "dev"
|
branch = "dev"
|
||||||
|
|
||||||
|
# sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::window,
|
app::{fs_extra, window},
|
||||||
conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL},
|
conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL},
|
||||||
utils,
|
utils::{self, chat_root, create_file},
|
||||||
};
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::{collections::HashMap, fs, path::PathBuf};
|
use regex::Regex;
|
||||||
|
use std::{collections::HashMap, fs, path::PathBuf, vec};
|
||||||
use tauri::{api, command, AppHandle, Manager, Theme};
|
use tauri::{api, command, AppHandle, Manager, Theme};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -35,14 +36,16 @@ pub fn fullscreen(app: AppHandle) {
|
|||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
|
pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
|
||||||
let path = api::path::download_dir().unwrap().join(name);
|
let path = chat_root().join(PathBuf::from(name));
|
||||||
|
create_file(&path).unwrap();
|
||||||
fs::write(&path, blob).unwrap();
|
fs::write(&path, blob).unwrap();
|
||||||
utils::open_file(path);
|
utils::open_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub fn save_file(_app: AppHandle, name: String, content: String) {
|
pub fn save_file(_app: AppHandle, name: String, content: String) {
|
||||||
let path = api::path::download_dir().unwrap().join(name);
|
let path = chat_root().join(PathBuf::from(name));
|
||||||
|
create_file(&path).unwrap();
|
||||||
fs::write(&path, content).unwrap();
|
fs::write(&path, content).unwrap();
|
||||||
utils::open_file(path);
|
utils::open_file(path);
|
||||||
}
|
}
|
||||||
@@ -174,6 +177,76 @@ pub fn cmd_list() -> Vec<ModelRecord> {
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
pub struct FileMetadata {
|
||||||
|
pub name: String,
|
||||||
|
pub ext: String,
|
||||||
|
pub created: u64,
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub fn download_list(filename: Option<String>, id: Option<String>) {
|
||||||
|
info!("download_list");
|
||||||
|
let download_path = chat_root().join("chat.download.json");
|
||||||
|
let content = fs::read_to_string(&download_path).unwrap_or_else(|err| {
|
||||||
|
info!("download_list_error: {}", err);
|
||||||
|
fs::write(&download_path, "[]").unwrap();
|
||||||
|
"[]".to_string()
|
||||||
|
});
|
||||||
|
let mut list = serde_json::from_str::<Vec<serde_json::Value>>(&content)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
info!("download_list_parse_error: {}", err);
|
||||||
|
vec![]
|
||||||
|
});
|
||||||
|
|
||||||
|
let list2 = &list;
|
||||||
|
let mut my_hashmap = HashMap::new();
|
||||||
|
utils::vec_to_hashmap(list2.clone().into_iter(), "id", &mut my_hashmap);
|
||||||
|
|
||||||
|
for entry in WalkDir::new(utils::chat_root().join("download"))
|
||||||
|
.into_iter()
|
||||||
|
.filter_entry(|e| !utils::is_hidden(e))
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
let metadata = entry.metadata().unwrap();
|
||||||
|
if metadata.is_file() {
|
||||||
|
let file_path = entry.path().display().to_string();
|
||||||
|
let re = Regex::new(r"(?P<id>[\d\w]+).(?P<ext>\w+)$").unwrap();
|
||||||
|
let caps = re.captures(&file_path).unwrap();
|
||||||
|
let fid = &caps["id"];
|
||||||
|
let fext = &caps["ext"];
|
||||||
|
|
||||||
|
let mut file_data = FileMetadata {
|
||||||
|
name: fid.to_string(),
|
||||||
|
id: fid.to_string(),
|
||||||
|
ext: fext.to_string(),
|
||||||
|
created: fs_extra::system_time_to_ms(metadata.created()),
|
||||||
|
};
|
||||||
|
if my_hashmap.get(fid).is_some() && filename.is_some() && id.is_some() {
|
||||||
|
if let Some(ref v) = id {
|
||||||
|
if fid == v {
|
||||||
|
if let Some(ref v2) = filename {
|
||||||
|
file_data.name = v2.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.push(serde_json::to_value(file_data).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!(&list);
|
||||||
|
|
||||||
|
list.sort_by(|a, b| {
|
||||||
|
let a1 = a.get("created").unwrap().as_u64().unwrap();
|
||||||
|
let b1 = b.get("created").unwrap().as_u64().unwrap();
|
||||||
|
a1.cmp(&b1).reverse()
|
||||||
|
});
|
||||||
|
|
||||||
|
fs::write(download_path, serde_json::to_string_pretty(&list).unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>> {
|
pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>> {
|
||||||
let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app))
|
let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app))
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ struct UnixMetadata {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
accessed_at_ms: u64,
|
accessed_at_ms: u64,
|
||||||
created_at_ms: u64,
|
pub created_at_ms: u64,
|
||||||
modified_at_ms: u64,
|
modified_at_ms: u64,
|
||||||
is_dir: bool,
|
is_dir: bool,
|
||||||
is_file: bool,
|
is_file: bool,
|
||||||
@@ -74,7 +74,7 @@ pub struct Metadata {
|
|||||||
file_attributes: u32,
|
file_attributes: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn system_time_to_ms(time: std::io::Result<SystemTime>) -> u64 {
|
pub fn system_time_to_ms(time: std::io::Result<SystemTime>) -> u64 {
|
||||||
time.map(|t| {
|
time.map(|t| {
|
||||||
let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
|
let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
|
||||||
duration_since_epoch.as_millis() as u64
|
duration_since_epoch.as_millis() as u64
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ async fn main() {
|
|||||||
trace: Color::Cyan,
|
trace: Color::Cyan,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cmd::download_list(None, None);
|
||||||
|
|
||||||
let chat_conf = ChatConfJson::get_chat_conf();
|
let chat_conf = ChatConfJson::get_chat_conf();
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
@@ -73,6 +75,7 @@ async fn main() {
|
|||||||
cmd::window_reload,
|
cmd::window_reload,
|
||||||
cmd::dalle2_window,
|
cmd::dalle2_window,
|
||||||
cmd::cmd_list,
|
cmd::cmd_list,
|
||||||
|
cmd::download_list,
|
||||||
fs_extra::metadata,
|
fs_extra::metadata,
|
||||||
])
|
])
|
||||||
.setup(setup::init)
|
.setup(setup::init)
|
||||||
|
|||||||
44
src-tauri/src/scripts/export.js
vendored
44
src-tauri/src/scripts/export.js
vendored
@@ -81,6 +81,7 @@ function shouldAddButtons(actionsArea) {
|
|||||||
function removeButtons() {
|
function removeButtons() {
|
||||||
const downloadButton = document.getElementById("download-png-button");
|
const downloadButton = document.getElementById("download-png-button");
|
||||||
const downloadPdfButton = document.getElementById("download-pdf-button");
|
const downloadPdfButton = document.getElementById("download-pdf-button");
|
||||||
|
const downloadMdButton = document.getElementById("download-markdown-button");
|
||||||
// const downloadHtmlButton = document.getElementById("download-html-button");
|
// const downloadHtmlButton = document.getElementById("download-html-button");
|
||||||
if (downloadButton) {
|
if (downloadButton) {
|
||||||
downloadButton.remove();
|
downloadButton.remove();
|
||||||
@@ -88,6 +89,9 @@ function removeButtons() {
|
|||||||
if (downloadPdfButton) {
|
if (downloadPdfButton) {
|
||||||
downloadPdfButton.remove();
|
downloadPdfButton.remove();
|
||||||
}
|
}
|
||||||
|
if (downloadPdfButton) {
|
||||||
|
downloadMdButton.remove();
|
||||||
|
}
|
||||||
// if (downloadHtmlButton) {
|
// if (downloadHtmlButton) {
|
||||||
// downloadHtmlButton.remove();
|
// downloadHtmlButton.remove();
|
||||||
// }
|
// }
|
||||||
@@ -95,6 +99,18 @@ function removeButtons() {
|
|||||||
|
|
||||||
function addActionsButtons(actionsArea, TryAgainButton) {
|
function addActionsButtons(actionsArea, TryAgainButton) {
|
||||||
const downloadButton = TryAgainButton.cloneNode(true);
|
const downloadButton = TryAgainButton.cloneNode(true);
|
||||||
|
// Export markdown
|
||||||
|
const exportMd = TryAgainButton.cloneNode(true);
|
||||||
|
exportMd.id = "download-markdown-button";
|
||||||
|
downloadButton.setAttribute("share-ext", "true");
|
||||||
|
exportMd.title = "Export Markdown";
|
||||||
|
exportMd.innerHTML = setIcon('md');
|
||||||
|
exportMd.onclick = () => {
|
||||||
|
exportMarkdown();
|
||||||
|
};
|
||||||
|
actionsArea.appendChild(exportMd);
|
||||||
|
|
||||||
|
// Generate PNG
|
||||||
downloadButton.id = "download-png-button";
|
downloadButton.id = "download-png-button";
|
||||||
downloadButton.setAttribute("share-ext", "true");
|
downloadButton.setAttribute("share-ext", "true");
|
||||||
// downloadButton.innerText = "Generate PNG";
|
// downloadButton.innerText = "Generate PNG";
|
||||||
@@ -104,6 +120,8 @@ function addActionsButtons(actionsArea, TryAgainButton) {
|
|||||||
downloadThread();
|
downloadThread();
|
||||||
};
|
};
|
||||||
actionsArea.appendChild(downloadButton);
|
actionsArea.appendChild(downloadButton);
|
||||||
|
|
||||||
|
// Generate PDF
|
||||||
const downloadPdfButton = TryAgainButton.cloneNode(true);
|
const downloadPdfButton = TryAgainButton.cloneNode(true);
|
||||||
downloadPdfButton.id = "download-pdf-button";
|
downloadPdfButton.id = "download-pdf-button";
|
||||||
downloadButton.setAttribute("share-ext", "true");
|
downloadButton.setAttribute("share-ext", "true");
|
||||||
@@ -126,17 +144,11 @@ function addActionsButtons(actionsArea, TryAgainButton) {
|
|||||||
// sendRequest();
|
// sendRequest();
|
||||||
// };
|
// };
|
||||||
// actionsArea.appendChild(exportHtml);
|
// actionsArea.appendChild(exportHtml);
|
||||||
const exportMd = TryAgainButton.cloneNode(true);
|
}
|
||||||
exportMd.id = "download-markdown-button";
|
|
||||||
downloadButton.setAttribute("share-ext", "true");
|
async function exportMarkdown() {
|
||||||
// exportHtml.innerText = "Share Link";
|
const data = ExportMD.turndown(document.querySelector("main div>div>div").innerHTML);
|
||||||
exportMd.title = "Download Markdown";
|
await invoke('save_file', { name: `notes/${Date.now().toString(36)}.md`, content: data });
|
||||||
exportMd.innerHTML = setIcon('md');
|
|
||||||
exportMd.onclick = () => {
|
|
||||||
const data = ExportMD.turndown(document.querySelector("main div>div>div").innerHTML);
|
|
||||||
invoke('save_file', { name: `chatgpt-${Date.now()}.md`, content: data });
|
|
||||||
};
|
|
||||||
actionsArea.appendChild(exportMd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadThread({ as = Format.PNG } = {}) {
|
function downloadThread({ as = Format.PNG } = {}) {
|
||||||
@@ -162,16 +174,17 @@ function downloadThread({ as = Format.PNG } = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleImg(imgData) {
|
async function handleImg(imgData) {
|
||||||
const binaryData = atob(imgData.split("base64,")[1]);
|
const binaryData = atob(imgData.split("base64,")[1]);
|
||||||
const data = [];
|
const data = [];
|
||||||
for (let i = 0; i < binaryData.length; i++) {
|
for (let i = 0; i < binaryData.length; i++) {
|
||||||
data.push(binaryData.charCodeAt(i));
|
data.push(binaryData.charCodeAt(i));
|
||||||
}
|
}
|
||||||
invoke('download', { name: `chatgpt-${Date.now()}.png`, blob: data });
|
await invoke('download', { name: `download/img/${Date.now().toString(36)}.png`, blob: data });
|
||||||
|
await invoke('download_list');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePdf(imgData, canvas, pixelRatio) {
|
async function handlePdf(imgData, canvas, pixelRatio) {
|
||||||
const { jsPDF } = window.jspdf;
|
const { jsPDF } = window.jspdf;
|
||||||
const orientation = canvas.width > canvas.height ? "l" : "p";
|
const orientation = canvas.width > canvas.height ? "l" : "p";
|
||||||
var pdf = new jsPDF(orientation, "pt", [
|
var pdf = new jsPDF(orientation, "pt", [
|
||||||
@@ -183,7 +196,8 @@ function handlePdf(imgData, canvas, pixelRatio) {
|
|||||||
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
|
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
|
||||||
|
|
||||||
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
|
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
|
||||||
invoke('download', { name: `chatgpt-${Date.now()}.pdf`, blob: Array.from(new Uint8Array(data)) });
|
await invoke('download', { name: `download/pdf/${Date.now().toString(36)}.pdf`, blob: Array.from(new Uint8Array(data)) });
|
||||||
|
await invoke('download_list');
|
||||||
}
|
}
|
||||||
|
|
||||||
class Elements {
|
class Elements {
|
||||||
|
|||||||
@@ -230,3 +230,23 @@ pub async fn silent_install(app: AppHandle<Wry>, update: UpdateResponse<Wry>) ->
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_hidden(entry: &walkdir::DirEntry) -> bool {
|
||||||
|
entry
|
||||||
|
.file_name()
|
||||||
|
.to_str()
|
||||||
|
.map(|s| s.starts_with('.'))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec_to_hashmap(
|
||||||
|
vec: impl Iterator<Item = serde_json::Value>,
|
||||||
|
key: &str,
|
||||||
|
map: &mut HashMap<String, serde_json::Value>,
|
||||||
|
) {
|
||||||
|
for v in vec {
|
||||||
|
if let Some(kval) = v.get(key).and_then(serde_json::Value::as_str) {
|
||||||
|
map.insert(kval.to_string(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
7
src/view/download/config.tsx
vendored
7
src/view/download/config.tsx
vendored
@@ -36,3 +36,10 @@ export const syncColumns = () => [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// {
|
||||||
|
// id: '',
|
||||||
|
// name: '',
|
||||||
|
// type: '.png',
|
||||||
|
// created: '2022.01.01',
|
||||||
|
// }
|
||||||
10
src/view/download/index.tsx
vendored
10
src/view/download/index.tsx
vendored
@@ -5,17 +5,21 @@ import { path, shell } from '@tauri-apps/api';
|
|||||||
import useInit from '@/hooks/useInit';
|
import useInit from '@/hooks/useInit';
|
||||||
import useColumns from '@/hooks/useColumns';
|
import useColumns from '@/hooks/useColumns';
|
||||||
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
|
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
|
||||||
import { chatRoot } from '@/utils';
|
import { chatRoot, readJSON } from '@/utils';
|
||||||
import { syncColumns } from './config';
|
import { syncColumns } from './config';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function SyncPrompts() {
|
export default function SyncPrompts() {
|
||||||
const { rowSelection, selectedRowIDs } = useTable();
|
const { rowSelection, selectedRowIDs } = useTable();
|
||||||
const [downloadPath, setDownloadPath] = useState('');
|
const [downloadPath, setDownloadPath] = useState('');
|
||||||
|
const [downloadData, setDownloadData] = useState([]);
|
||||||
const { columns, ...opInfo } = useColumns(syncColumns());
|
const { columns, ...opInfo } = useColumns(syncColumns());
|
||||||
|
|
||||||
useInit(async () => {
|
useInit(async () => {
|
||||||
setDownloadPath(await path.join(await chatRoot(), 'download'));
|
const file = await path.join(await chatRoot(), 'chat.download.json');
|
||||||
|
setDownloadPath(file);
|
||||||
|
const data = await readJSON(file, { isRoot: true, isList: true });
|
||||||
|
setDownloadData(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,7 +33,7 @@ export default function SyncPrompts() {
|
|||||||
rowKey="name"
|
rowKey="name"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
scroll={{ x: 'auto' }}
|
scroll={{ x: 'auto' }}
|
||||||
dataSource={[]}
|
dataSource={downloadData}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={TABLE_PAGINATION}
|
pagination={TABLE_PAGINATION}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user