open ocr/tts/transcribe results in dual panels from file viewer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ type FileViewerProviderProps = {
|
|||||||
root?: string;
|
root?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
onOpenFile?: (filePath: string, root: string) => void;
|
onOpenFile?: (filePath: string, root: string) => void;
|
||||||
|
onReplaceView?: (viewPath: string, viewRoot: string, ephemeralPath: string, ephemeralRoot: string) => void;
|
||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
@@ -22,6 +23,7 @@ export const FileViewerProvider = ({
|
|||||||
root = 'home',
|
root = 'home',
|
||||||
content: directContent,
|
content: directContent,
|
||||||
onOpenFile,
|
onOpenFile,
|
||||||
|
onReplaceView,
|
||||||
autoPlay = false,
|
autoPlay = false,
|
||||||
children,
|
children,
|
||||||
}: FileViewerProviderProps) => {
|
}: FileViewerProviderProps) => {
|
||||||
@@ -71,8 +73,9 @@ export const FileViewerProvider = ({
|
|||||||
const handleReadAloud = async () => {
|
const handleReadAloud = async () => {
|
||||||
setTtsLoading(true);
|
setTtsLoading(true);
|
||||||
try {
|
try {
|
||||||
const { audioPath, audioRoot } = await files.tts(filePath);
|
const { audioPath, audioRoot } = await files.tts(filePath, { saveNextTo: true });
|
||||||
onOpenFile?.(audioPath, audioRoot);
|
setRefreshSignal((n) => n + 1);
|
||||||
|
onReplaceView?.(filePath, root, audioPath, audioRoot);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to generate speech audio');
|
toast.error('Failed to generate speech audio');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -83,8 +86,9 @@ export const FileViewerProvider = ({
|
|||||||
const handleOcr = async () => {
|
const handleOcr = async () => {
|
||||||
setOcrLoading(true);
|
setOcrLoading(true);
|
||||||
try {
|
try {
|
||||||
const { ocrPath, ocrRoot } = await files.ocr(filePath);
|
const { ocrPath, ocrRoot } = await files.ocr(filePath, { saveNextTo: true });
|
||||||
onOpenFile?.(ocrPath, ocrRoot);
|
setRefreshSignal((n) => n + 1);
|
||||||
|
onReplaceView?.(filePath, root, ocrPath, ocrRoot);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to extract text from image');
|
toast.error('Failed to extract text from image');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -95,8 +99,9 @@ export const FileViewerProvider = ({
|
|||||||
const handleTranscribe = async () => {
|
const handleTranscribe = async () => {
|
||||||
setTranscribeLoading(true);
|
setTranscribeLoading(true);
|
||||||
try {
|
try {
|
||||||
const { transcriptionPath, transcriptionRoot } = await files.transcribe(filePath);
|
const { transcriptionPath, transcriptionRoot } = await files.transcribe(filePath, { saveNextTo: true });
|
||||||
onOpenFile?.(transcriptionPath, transcriptionRoot);
|
setRefreshSignal((n) => n + 1);
|
||||||
|
onReplaceView?.(filePath, root, transcriptionPath, transcriptionRoot);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to transcribe audio');
|
toast.error('Failed to transcribe audio');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
import { type ReactNode, useCallback } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||||
import { FileViewerProvider } from '../../apps/FileViewer';
|
import { FileViewerProvider } from '../../apps/FileViewer';
|
||||||
import { EmbeddableChat } from '../../apps/Chat/EmbeddableChat';
|
import { EmbeddableChat } from '../../apps/Chat/EmbeddableChat';
|
||||||
|
|
||||||
|
type SetSearchParams = ReturnType<typeof useSearchParams>[1];
|
||||||
|
|
||||||
|
const onReplaceView = (setSearchParams: SetSearchParams) =>
|
||||||
|
(viewPath: string, viewRoot: string, ephemeralPath: string, ephemeralRoot: string) => {
|
||||||
|
setSearchParams((prev) => {
|
||||||
|
const next = new URLSearchParams(prev);
|
||||||
|
next.set('view', viewPath);
|
||||||
|
next.set('ephemeral', ephemeralPath);
|
||||||
|
next.set('ephemeralRoot', ephemeralRoot);
|
||||||
|
next.delete('ephemeral2');
|
||||||
|
next.delete('ephemeral2Root');
|
||||||
|
next.delete('ephemeral2Auto');
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export function ViewerProvider({ children }: { children: ReactNode }) {
|
export function ViewerProvider({ children }: { children: ReactNode }) {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const viewPath = searchParams.get('view') ?? '';
|
const viewPath = searchParams.get('view') ?? '';
|
||||||
@@ -19,7 +36,7 @@ export function ViewerProvider({ children }: { children: ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileViewerProvider filePath={viewPath} fileName={fileName} onOpenFile={onOpenFile}>
|
<FileViewerProvider filePath={viewPath} fileName={fileName} onOpenFile={onOpenFile} onReplaceView={onReplaceView(setSearchParams)}>
|
||||||
{children}
|
{children}
|
||||||
</FileViewerProvider>
|
</FileViewerProvider>
|
||||||
);
|
);
|
||||||
@@ -42,21 +59,21 @@ export function EphemeralProvider({ children }: { children: ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileViewerProvider filePath={ephemeralPath} fileName={fileName} root={ephemeralRoot} onOpenFile={onOpenFile}>
|
<FileViewerProvider filePath={ephemeralPath} fileName={fileName} root={ephemeralRoot} onOpenFile={onOpenFile} onReplaceView={onReplaceView(setSearchParams)}>
|
||||||
{children}
|
{children}
|
||||||
</FileViewerProvider>
|
</FileViewerProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Ephemeral2Provider({ children }: { children: ReactNode }) {
|
export function Ephemeral2Provider({ children }: { children: ReactNode }) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const ephemeral2Path = searchParams.get('ephemeral2') ?? '';
|
const ephemeral2Path = searchParams.get('ephemeral2') ?? '';
|
||||||
const ephemeral2Root = searchParams.get('ephemeral2Root') ?? 'home';
|
const ephemeral2Root = searchParams.get('ephemeral2Root') ?? 'home';
|
||||||
const ephemeral2Auto = searchParams.get('ephemeral2Auto') === '1';
|
const ephemeral2Auto = searchParams.get('ephemeral2Auto') === '1';
|
||||||
const fileName = ephemeral2Path.split('/').pop() ?? '';
|
const fileName = ephemeral2Path.split('/').pop() ?? '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileViewerProvider filePath={ephemeral2Path} fileName={fileName} root={ephemeral2Root} autoPlay={ephemeral2Auto}>
|
<FileViewerProvider filePath={ephemeral2Path} fileName={fileName} root={ephemeral2Root} autoPlay={ephemeral2Auto} onReplaceView={onReplaceView(setSearchParams)}>
|
||||||
{children}
|
{children}
|
||||||
</FileViewerProvider>
|
</FileViewerProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user