mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
ERC: Save As
This commit is contained in:
@@ -98,6 +98,32 @@ const GuessedMimeLookupTable: Record<string, GuessedMimeInfo> = {
|
||||
'application/x-bzip2': { ext: ['bz2'], dt: 'other' },
|
||||
};
|
||||
|
||||
const MdTitleToMimeLookupTable: Record<string, GuessedMimeType> = {
|
||||
'typescript': 'text/x-typescript',
|
||||
'ts': 'text/x-typescript',
|
||||
'tsx': 'text/x-typescript',
|
||||
'javascript': 'text/javascript',
|
||||
'js': 'text/javascript',
|
||||
'jsx': 'text/javascript',
|
||||
'python': 'text/x-python',
|
||||
'py': 'text/x-python',
|
||||
'json': 'application/json',
|
||||
'html': 'text/html',
|
||||
'htm': 'text/html',
|
||||
'css': 'text/css',
|
||||
'md': 'text/markdown',
|
||||
'markdown': 'text/markdown',
|
||||
'csv': 'text/csv',
|
||||
'tsv': 'text/csv',
|
||||
'xml': 'text/xml',
|
||||
'pdf': 'application/pdf',
|
||||
'doc': 'application/msword',
|
||||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xls': 'application/vnd.ms-excel',
|
||||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'ppt': 'application/vnd.ms-powerpoint',
|
||||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
};
|
||||
|
||||
export function reverseLookupMimeType(fileExtension: string): GuessedMimeType | null {
|
||||
for (const [mimeType, { ext }] of Object.entries(GuessedMimeLookupTable)) {
|
||||
@@ -107,6 +133,15 @@ export function reverseLookupMimeType(fileExtension: string): GuessedMimeType |
|
||||
return null;
|
||||
}
|
||||
|
||||
export function reverseLookupMdTitle(mdTitle: string): { mimeType: GuessedMimeType, extension: string | null } | null {
|
||||
const guessedMimeType = MdTitleToMimeLookupTable[mdTitle] || null;
|
||||
if (guessedMimeType) {
|
||||
const { ext } = GuessedMimeLookupTable[guessedMimeType];
|
||||
return { mimeType: guessedMimeType, extension: (ext ? ext[0] : null) || null };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function guessInputContentTypeFromMime(mimeType: GuessedMimeType): GuessedMimeContents {
|
||||
return GuessedMimeLookupTable[mimeType]?.dt ?? 'plain';
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ export function EnhancedRenderCode(props: {
|
||||
{contextMenuAnchor && (
|
||||
<EnhancedRenderCodeMenu
|
||||
anchor={contextMenuAnchor}
|
||||
code={props.code} title={props.title}
|
||||
onClose={handleCloseContextMenu}
|
||||
isCollapsed={isCodeCollapsed}
|
||||
onToggleCollapse={handleToggleCodeCollapse}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { fileSave } from 'browser-fs-access';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import { Box, ListDivider, ListItemDecorator, MenuItem } from '@mui/joy';
|
||||
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import SaveAsIcon from '@mui/icons-material/SaveAs';
|
||||
|
||||
import { CloseableMenu } from '~/common/components/CloseableMenu';
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { isLiveFileSupported } from '~/common/livefile/store-live-file';
|
||||
import { reverseLookupMdTitle, reverseLookupMimeType } from '~/common/attachment-drafts/attachment.mimetypes';
|
||||
import { useLabsDevMode, useUXLabsStore } from '~/common/state/store-ux-labs';
|
||||
import { useOverlayComponents } from '~/common/layout/overlays/useOverlayComponents';
|
||||
|
||||
@@ -20,6 +23,8 @@ import { getCodeCollapseManager } from './codeCollapseManager';
|
||||
*/
|
||||
export function EnhancedRenderCodeMenu(props: {
|
||||
anchor: HTMLElement,
|
||||
title: string,
|
||||
code: string,
|
||||
onClose: () => void,
|
||||
isCollapsed: boolean,
|
||||
onToggleCollapse: () => void,
|
||||
@@ -46,6 +51,33 @@ export function EnhancedRenderCodeMenu(props: {
|
||||
getCodeCollapseManager().triggerCollapseAll(false);
|
||||
}, []);
|
||||
|
||||
const handleSaveAs = React.useCallback(async () => {
|
||||
// guess the mimetype from the markdown title
|
||||
let mimeType = 'text/plain';
|
||||
let extension = '';
|
||||
const hasExtension = props.title.includes('.');
|
||||
if (hasExtension) {
|
||||
extension = props.title.split('.').pop()!;
|
||||
mimeType = reverseLookupMimeType(extension) || 'text/plain';
|
||||
} else {
|
||||
const data = reverseLookupMdTitle(props.title);
|
||||
if (data?.extension)
|
||||
extension = data.extension;
|
||||
if (data?.mimeType)
|
||||
mimeType = data.mimeType;
|
||||
}
|
||||
|
||||
// content to be saved
|
||||
const blob = new Blob([props.code], { type: mimeType });
|
||||
|
||||
// save content
|
||||
await fileSave(blob, {
|
||||
fileName: props.title || undefined,
|
||||
extensions: extension ? [`.${extension}`] : undefined,
|
||||
mimeTypes: mimeType ? [mimeType] : undefined,
|
||||
}).catch(() => null);
|
||||
}, [props.code, props.title]);
|
||||
|
||||
const toggleEnhanceCodeBlocks = React.useCallback(() => {
|
||||
// turn blocks on (may not even be called, ever)
|
||||
if (!labsEnhanceCodeBlocks) {
|
||||
@@ -69,6 +101,7 @@ export function EnhancedRenderCodeMenu(props: {
|
||||
|
||||
const liveFileSupported = isLiveFileSupported();
|
||||
|
||||
|
||||
return (
|
||||
<CloseableMenu
|
||||
open={true} dense
|
||||
@@ -89,7 +122,10 @@ export function EnhancedRenderCodeMenu(props: {
|
||||
|
||||
<ListDivider />
|
||||
|
||||
{/* TODO: add Download here */}
|
||||
<MenuItem onClick={handleSaveAs}>
|
||||
<ListItemDecorator><SaveAsIcon /></ListItemDecorator>
|
||||
Save As ...
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={toggleEnhanceCodeLiveFile} disabled={!liveFileSupported}>
|
||||
<ListItemDecorator>{(labsEnhanceCodeLiveFile && liveFileSupported) && <CheckRoundedIcon />}</ListItemDecorator>
|
||||
|
||||
@@ -135,7 +135,7 @@ export async function downloadAllJsonV1B() {
|
||||
fileName: `backup_chats_${window?.location?.hostname || 'all'}_${payload.conversations.length}_${prettyTimestampForFilenames(false)}.agi.json`,
|
||||
// mimeTypes: ['application/json', 'application/big-agi'],
|
||||
extensions: ['.json'],
|
||||
});
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +172,7 @@ export async function downloadSingleChat(conversation: DConversation, format: 'j
|
||||
await fileSave(blob, {
|
||||
fileName: `conversation_${fileTitle}_${prettyTimestampForFilenames(false)}.agi${extension}`,
|
||||
extensions: [extension],
|
||||
});
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user