From 7bfa36e5cf90252a96168be1d2afd5ab4ec9eba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 5 Mar 2026 00:42:02 +0000 Subject: [PATCH] open ocr/tts/transcribe results in dual panels from file viewer Co-Authored-By: Claude Opus 4.6 --- .../apps/FileViewer/FileViewerProvider.tsx | 17 +++++++----- .../hooks/useFileViewerPanels/Providers.tsx | 27 +++++++++++++++---- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx index 37aa1669..71a35045 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx @@ -12,6 +12,7 @@ type FileViewerProviderProps = { root?: string; content?: string; onOpenFile?: (filePath: string, root: string) => void; + onReplaceView?: (viewPath: string, viewRoot: string, ephemeralPath: string, ephemeralRoot: string) => void; autoPlay?: boolean; children: ReactNode; }; @@ -22,6 +23,7 @@ export const FileViewerProvider = ({ root = 'home', content: directContent, onOpenFile, + onReplaceView, autoPlay = false, children, }: FileViewerProviderProps) => { @@ -71,8 +73,9 @@ export const FileViewerProvider = ({ const handleReadAloud = async () => { setTtsLoading(true); try { - const { audioPath, audioRoot } = await files.tts(filePath); - onOpenFile?.(audioPath, audioRoot); + const { audioPath, audioRoot } = await files.tts(filePath, { saveNextTo: true }); + setRefreshSignal((n) => n + 1); + onReplaceView?.(filePath, root, audioPath, audioRoot); } catch { toast.error('Failed to generate speech audio'); } finally { @@ -83,8 +86,9 @@ export const FileViewerProvider = ({ const handleOcr = async () => { setOcrLoading(true); try { - const { ocrPath, ocrRoot } = await files.ocr(filePath); - onOpenFile?.(ocrPath, ocrRoot); + const { ocrPath, ocrRoot } = await files.ocr(filePath, { saveNextTo: true }); + setRefreshSignal((n) => n + 1); + onReplaceView?.(filePath, root, ocrPath, ocrRoot); } catch { toast.error('Failed to extract text from image'); } finally { @@ -95,8 +99,9 @@ export const FileViewerProvider = ({ const handleTranscribe = async () => { setTranscribeLoading(true); try { - const { transcriptionPath, transcriptionRoot } = await files.transcribe(filePath); - onOpenFile?.(transcriptionPath, transcriptionRoot); + const { transcriptionPath, transcriptionRoot } = await files.transcribe(filePath, { saveNextTo: true }); + setRefreshSignal((n) => n + 1); + onReplaceView?.(filePath, root, transcriptionPath, transcriptionRoot); } catch { toast.error('Failed to transcribe audio'); } finally { diff --git a/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx b/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx index fb44299a..59f75276 100644 --- a/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx +++ b/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx @@ -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 { usePanelChannel } from 'hooks/usePanelChannel'; import { FileViewerProvider } from '../../apps/FileViewer'; import { EmbeddableChat } from '../../apps/Chat/EmbeddableChat'; +type SetSearchParams = ReturnType[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 }) { const [searchParams, setSearchParams] = useSearchParams(); const viewPath = searchParams.get('view') ?? ''; @@ -19,7 +36,7 @@ export function ViewerProvider({ children }: { children: ReactNode }) { }; return ( - + {children} ); @@ -42,21 +59,21 @@ export function EphemeralProvider({ children }: { children: ReactNode }) { }; return ( - + {children} ); } export function Ephemeral2Provider({ children }: { children: ReactNode }) { - const [searchParams] = useSearchParams(); + const [searchParams, setSearchParams] = useSearchParams(); const ephemeral2Path = searchParams.get('ephemeral2') ?? ''; const ephemeral2Root = searchParams.get('ephemeral2Root') ?? 'home'; const ephemeral2Auto = searchParams.get('ephemeral2Auto') === '1'; const fileName = ephemeral2Path.split('/').pop() ?? ''; return ( - + {children} );