Improve Export/Import looks and behavior - Fixes #375

This commit is contained in:
Enrico Ros
2024-02-02 16:01:30 -08:00
parent a93d9aab08
commit b80afca458
8 changed files with 139 additions and 82 deletions
+3 -1
View File
@@ -300,7 +300,9 @@ export function AppChat() {
const handleConversationImportDialog = React.useCallback(() => setTradeConfig({ dir: 'import' }), []);
const handleConversationExport = React.useCallback((conversationId: DConversationId | null) => setTradeConfig({ dir: 'export', conversationId }), []);
const handleConversationExport = React.useCallback((conversationId: DConversationId | null, exportAll: boolean) => {
setTradeConfig({ dir: 'export', conversationId, exportAll });
}, []);
const handleConversationBranch = React.useCallback((conversationId: DConversationId, messageId: string | null): DConversationId | null => {
showNextTitle.current = true;
@@ -4,8 +4,8 @@ import { shallow } from 'zustand/shallow';
import { Box, IconButton, ListDivider, ListItem, ListItemButton, ListItemDecorator, Tooltip } from '@mui/joy';
import AddIcon from '@mui/icons-material/Add';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import FileDownloadIcon from '@mui/icons-material/FileDownload';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined';
import FolderIcon from '@mui/icons-material/Folder';
import FolderOpenOutlinedIcon from '@mui/icons-material/FolderOpenOutlined';
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
@@ -87,7 +87,7 @@ function ChatDrawer(props: {
disableNewButton: boolean,
onConversationActivate: (conversationId: DConversationId) => void,
onConversationDelete: (conversationId: DConversationId, bypassConfirmation: boolean) => void,
onConversationExportDialog: (conversationId: DConversationId | null) => void,
onConversationExportDialog: (conversationId: DConversationId | null, exportAll: boolean) => void,
onConversationImportDialog: () => void,
onConversationNew: () => void,
onConversationsDeleteAll: () => void,
@@ -300,15 +300,15 @@ function ChatDrawer(props: {
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<ListItemButton onClick={props.onConversationImportDialog} sx={{ flex: 1 }}>
<ListItemDecorator>
<FileUploadIcon />
<FileUploadOutlinedIcon />
</ListItemDecorator>
Import
{/*<OpenAIIcon sx={{ ml: 'auto' }} />*/}
</ListItemButton>
<ListItemButton disabled={!nonEmptyChats} onClick={() => props.onConversationExportDialog(props.activeConversationId)} sx={{ flex: 1 }}>
<ListItemButton disabled={!nonEmptyChats} onClick={() => props.onConversationExportDialog(props.activeConversationId, true)} sx={{ flex: 1 }}>
<ListItemDecorator>
<FileDownloadIcon />
<FileDownloadOutlinedIcon />
</ListItemDecorator>
Export
</ListItemButton>
@@ -57,7 +57,7 @@ function ChatDrawerItem(props: {
bottomBarBasis: number,
onConversationActivate: (conversationId: DConversationId, closeMenu: boolean) => void,
onConversationDelete: (conversationId: DConversationId) => void,
onConversationExport: (conversationId: DConversationId) => void,
onConversationExport: (conversationId: DConversationId, exportAll: boolean) => void,
onConversationFolderChange: (folderChangeRequest: FolderChangeRequest) => void,
}) {
@@ -88,7 +88,7 @@ function ChatDrawerItem(props: {
const handleConversationExport = React.useCallback((event: React.MouseEvent) => {
event.stopPropagation();
conversationId && onConversationExport(conversationId);
conversationId && onConversationExport(conversationId, false);
}, [conversationId, onConversationExport]);
@@ -277,7 +277,7 @@ function ChatDrawerItem(props: {
<Divider orientation='vertical' sx={{ my: 1, opacity: 0.5 }} />
<Tooltip disableInteractive title='Export'>
<Tooltip disableInteractive title='Export Chat'>
<FadeInButton size='sm' onClick={handleConversationExport}>
<FileDownloadOutlinedIcon />
</FadeInButton>
+78 -42
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { Box, Button, Typography } from '@mui/joy';
import { Box, Button, Grid, Typography } from '@mui/joy';
import DoneIcon from '@mui/icons-material/Done';
import FileDownloadIcon from '@mui/icons-material/FileDownload';
@@ -13,7 +13,11 @@ import { PublishExport } from './publish/PublishExport';
import { downloadAllConversationsJson, downloadConversation } from './trade.client';
export type ExportConfig = { dir: 'export', conversationId: DConversationId | null };
export type ExportConfig = {
dir: 'export',
conversationId: DConversationId | null,
exportAll: boolean,
};
/**
@@ -29,6 +33,9 @@ export function ExportChats(props: { config: ExportConfig, onClose: () => void }
// external state
const enableSharing = backendCaps().hasDB;
// derived state
const { exportAll } = props.config;
// download chats
@@ -61,57 +68,86 @@ export function ExportChats(props: { config: ExportConfig, onClose: () => void }
return <>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'center', py: 1 }}>
<Grid container spacing={3}>
<Typography level='title-sm'>
Share / Download <strong>current chat</strong>:
</Typography>
{/* Current Chat */}
<Grid xs={12} md={6} sx={{ display: 'flex', alignItems: 'flex-start', py: 2 }}>
<Box sx={{ display: 'grid', gap: 1, mx: 'auto' }}>
<Button variant='soft' disabled={!hasConversation}
color={downloadedJSONState === 'ok' ? 'success' : downloadedJSONState === 'fail' ? 'warning' : 'primary'}
endDecorator={downloadedJSONState === 'ok' ? <DoneIcon /> : downloadedJSONState === 'fail' ? '✘' : <FileDownloadIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleDownloadConversationJSON}>
Download · JSON
</Button>
{exportAll && (
<Typography level='body-sm'>
Download or share <strong>this chat</strong>:
</Typography>
)}
<Button variant='soft' disabled={!hasConversation}
color={downloadedMarkdownState === 'ok' ? 'success' : downloadedMarkdownState === 'fail' ? 'warning' : 'primary'}
endDecorator={downloadedMarkdownState === 'ok' ? <DoneIcon /> : downloadedMarkdownState === 'fail' ? '✘' : <FileDownloadIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleDownloadConversationMarkdown}>
Export · Markdown
</Button>
<Button
variant='soft' disabled={!hasConversation}
color={downloadedJSONState === 'ok' ? 'success' : downloadedJSONState === 'fail' ? 'warning' : 'primary'}
endDecorator={downloadedJSONState === 'ok' ? <DoneIcon /> : downloadedJSONState === 'fail' ? '✘' : <FileDownloadIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleDownloadConversationJSON}
>
Download · JSON
</Button>
{enableSharing && (
<ChatLinkExport
conversationId={props.config.conversationId}
enableSharing={enableSharing}
onClose={props.onClose}
/>
)}
<Button
variant='soft' disabled={!hasConversation}
color={downloadedMarkdownState === 'ok' ? 'success' : downloadedMarkdownState === 'fail' ? 'warning' : 'primary'}
endDecorator={downloadedMarkdownState === 'ok' ? <DoneIcon /> : downloadedMarkdownState === 'fail' ? '✘' : <FileDownloadIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleDownloadConversationMarkdown}
>
Export · Markdown
</Button>
<PublishExport
conversationId={props.config.conversationId}
onClose={props.onClose}
/>
{enableSharing && (
<ChatLinkExport
conversationId={props.config.conversationId}
enableSharing={enableSharing}
onClose={props.onClose}
/>
)}
<PublishExport
conversationId={props.config.conversationId}
onClose={props.onClose}
/>
{/*<Button variant='soft' size='md' disabled sx={{ minWidth: 240, justifyContent: 'space-between', fontWeight: 400 }}>*/}
{/* Publish to ShareGPT*/}
{/*</Button>*/}
{/*<Button*/}
{/* variant='soft'*/}
{/* endDecorator={<ExitToAppIcon />}*/}
{/* sx={{ minWidth: 240, justifyContent: 'space-between' }}*/}
{/*>*/}
{/* Share Copy · ShareGPT*/}
{/*</Button>*/}
<Typography level='title-sm' sx={{ mt: 2 }}>
Store / Transfer <strong>all chats</strong>:
</Typography>
<Button variant='soft' size='md'
</Box>
</Grid>
{/* All Chats */}
{exportAll && (
<Grid xs={12} md={6} sx={{ display: 'flex', alignItems: 'flex-start', py: 2 }}>
<Box sx={{ display: 'grid', gap: 1, mx: 'auto' }}>
<Typography level='body-sm'>
Backup or transfer <strong>all chats</strong>:
</Typography>
<Button
variant='soft'
color={downloadedAllState === 'ok' ? 'success' : downloadedAllState === 'fail' ? 'warning' : 'primary'}
endDecorator={downloadedAllState === 'ok' ? <DoneIcon /> : downloadedAllState === 'fail' ? '✘' : <FileDownloadIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleDownloadAllConversationsJSON}>
Download All · JSON
</Button>
</Box>
onClick={handleDownloadAllConversationsJSON}
>
Download All · JSON
</Button>
</Box>
</Grid>
)}
</Grid>
</>;
}
+15 -9
View File
@@ -146,23 +146,29 @@ export function ImportChats(props: { onConversationActivate: (conversationId: DC
return <>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'center', py: 1 }}>
<Box sx={{ display: 'grid', gap: 1, mx: 'auto' }}>
<Typography level='body-sm'>
Select where to import from
Select where to <strong>import from</strong>:
</Typography>
<Button variant='soft' size='md' endDecorator={<FileUploadIcon />} sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleImportFromFiles}>
Upload JSON
<Button
variant='soft' endDecorator={<FileUploadIcon />} sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleImportFromFiles}
>
{Brand.Title.Base} · JSON
</Button>
{!chatGptEdit && (
<Button variant='soft' size='md' endDecorator={<OpenAIIcon />} sx={{ minWidth: 240, justifyContent: 'space-between' }}
color={chatGptEdit ? 'neutral' : 'primary'}
onClick={handleChatGptToggleShown}>
ChatGPT shared link
<Button
variant='soft' endDecorator={<OpenAIIcon />} sx={{ minWidth: 240, justifyContent: 'space-between' }}
color={chatGptEdit ? 'neutral' : 'primary'}
onClick={handleChatGptToggleShown}
>
ChatGPT · Shared Link
</Button>
)}
</Box>
{/* [chatgpt] data & controls */}
+16 -7
View File
@@ -1,7 +1,5 @@
import * as React from 'react';
import { Divider } from '@mui/joy';
import { DConversationId } from '~/common/state/store-chats';
import { GoodModal } from '~/common/components/GoodModal';
@@ -12,11 +10,22 @@ export type TradeConfig = ImportConfig | ExportConfig;
export function TradeModal(props: { config: TradeConfig, onConversationActivate: (conversationId: DConversationId) => void, onClose: () => void }) {
return (
<GoodModal title={<><b>{props.config.dir === 'import' ? 'Import ' : props.config.dir === 'export' ? 'Export ' : ''}</b> conversations</>} open onClose={props.onClose}>
<Divider />
{props.config.dir === 'import' && <ImportChats onConversationActivate={props.onConversationActivate} onClose={props.onClose} />}
{props.config.dir === 'export' && <ExportChats config={props.config} onClose={props.onClose} />}
<Divider />
<GoodModal
open onClose={props.onClose}
dividers
title={<>
<b>{props.config.dir === 'import' ? 'Import ' : props.config.dir === 'export' ? 'Export ' : ''}</b> {(props.config.dir === 'export' && !props.config.exportAll) ? 'conversation' : 'conversations'}
</>}
>
{props.config.dir === 'import' && (
<ImportChats onConversationActivate={props.onConversationActivate} onClose={props.onClose} />
)}
{props.config.dir === 'export' && (
<ExportChats config={props.config} onClose={props.onClose} />
)}
</GoodModal>
);
}
+9 -7
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Badge, Button } from '@mui/joy';
import DoneIcon from '@mui/icons-material/Done';
import IosShareIcon from '@mui/icons-material/IosShare';
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
import { Brand } from '~/common/app.config';
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
@@ -119,12 +119,14 @@ export function ChatLinkExport(props: {
return <>
<Badge color='danger' invisible={!chatLinkBadge}>
<Button variant='soft' disabled={!hasConversation || isUploading}
loading={isUploading}
color={linkPutResult ? 'success' : 'primary'}
endDecorator={linkPutResult ? <DoneIcon /> : <IosShareIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleConfirm}>
<Button
variant='soft' disabled={!hasConversation || isUploading}
loading={isUploading}
color={linkPutResult ? 'success' : 'primary'}
endDecorator={linkPutResult ? <DoneIcon /> : <ExitToAppIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handleConfirm}
>
Share Link · {Brand.Title.Base}
</Button>
</Badge>
+9 -7
View File
@@ -79,13 +79,15 @@ export function PublishExport(props: {
return <>
<Button variant='soft' disabled={!hasConversation || publishUploading}
loading={publishUploading}
color={publishResponse ? 'success' : 'primary'}
endDecorator={<ExitToAppIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handlePublishConversation}>
Share Link · Paste.gg
<Button
variant='soft' disabled={!hasConversation || publishUploading}
loading={publishUploading}
color={publishResponse ? 'success' : 'primary'}
endDecorator={<ExitToAppIcon />}
sx={{ minWidth: 240, justifyContent: 'space-between' }}
onClick={handlePublishConversation}
>
Share Copy · Paste.gg
</Button>
{/* [publish] confirmation */}