mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-11 06:00:15 -07:00
Improve Export/Import looks and behavior - Fixes #375
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
</>;
|
||||
}
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user