From 6e8ee69311f8976e45767a1b3679a94a03d61193 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Wed, 25 Feb 2026 00:16:52 +0000 Subject: [PATCH] fixed ocr/tts... caching --- src/servers/api/file-browser/router.ts | 32 +++++++++++++++---- .../FileBrowserApp/useFileBrowserApp.ts | 5 ++- .../apps/FileViewer/FileViewerProvider.tsx | 7 ++-- .../officerdev/src/hooks/useFilesAPI.ts | 10 +++--- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/servers/api/file-browser/router.ts b/src/servers/api/file-browser/router.ts index 0bc4fa43..be2c1ecc 100644 --- a/src/servers/api/file-browser/router.ts +++ b/src/servers/api/file-browser/router.ts @@ -10,8 +10,16 @@ import { readSttConfig } from '@@/api/server-settings/stt'; import { readOcrConfig } from '@@/api/server-settings/ocr'; const DEFAULT_HOME_DIRS = ['Downloads', 'Documents', 'Music', 'Video', 'Pictures', 'Onboarding']; +const OLD_CACHE_DIRS = ['ocr', 'tts', 'transcriptions', 'audio', 'video']; const ONBOARDING_SEED = join(DATA_PATH, 'Onboarding'); +async function cleanOldCacheDirs(userDataDir: string) { + for (const dir of OLD_CACHE_DIRS) { + const target = join(userDataDir, dir); + if (existsSync(target)) await rm(target, { recursive: true, force: true }); + } +} + async function seedHomeDir(homeDir: string) { for (const dir of DEFAULT_HOME_DIRS) { const target = join(homeDir, dir); @@ -60,6 +68,11 @@ router.get('/ls', async (ctx) => { await mkdir(absPath, { recursive: true }); } + // Remove old top-level cache dirs (migrated to cache/ prefix) + if (ctx.req.query('root') === 'user-data') { + await cleanOldCacheDirs(rootDir); + } + let names: string[]; try { names = await readdir(absPath); @@ -234,7 +247,7 @@ router.get('/transcode', async (ctx) => { const userDataDir = getUserDataDir(user.email); const { dir, name } = parsePath(relPath); - const cacheRel = dir ? `video/${dir}/${name}.mp4` : `video/${name}.mp4`; + const cacheRel = dir ? `cache/video/${dir}/${name}.mp4` : `cache/video/${name}.mp4`; const cacheAbs = resolve(userDataDir, cacheRel); if (!existsSync(cacheAbs)) { @@ -318,7 +331,7 @@ router.get('/transcode-audio', async (ctx) => { }); // Save a cached result (ocr/tts/transcriptions/audio) next to the original file -const CACHE_PREFIXES = ['ocr/', 'tts/', 'transcriptions/', 'audio/']; +const CACHE_PREFIXES = ['cache/ocr/', 'cache/tts/', 'cache/transcriptions/', 'cache/audio/', 'cache/video/']; router.post('/save-result', async (ctx) => { const user = ctx.get('user'); @@ -328,7 +341,12 @@ router.post('/save-result', async (ctx) => { const prefix = CACHE_PREFIXES.find((p) => cachedPath.startsWith(p)); if (!prefix) throw errors.BAD_REQUEST('Not a cached result path'); - const relativePath = cachedPath.slice(prefix.length); + let relativePath = cachedPath.slice(prefix.length); + // Strip any nested cache prefixes (e.g. tts/ocr/photos/file.mp3 → photos/file.mp3) + let nested: string | undefined; + while ((nested = CACHE_PREFIXES.find((p) => relativePath.startsWith(p)))) { + relativePath = relativePath.slice(nested.length); + } const userDataDir = getUserDataDir(user.email); const srcAbs = resolve(userDataDir, cachedPath); if (!existsSync(srcAbs)) throw errors.BAD_REQUEST('Cached file not found'); @@ -357,7 +375,7 @@ router.post('/tts', async (ctx) => { const userDataDir = getUserDataDir(user.email); const { dir, name } = parsePath(filePath.replace(/^\/+/, '')); - const cacheRel = dir ? `tts/${dir}/${name}.mp3` : `tts/${name}.mp3`; + const cacheRel = dir ? `cache/tts/${dir}/${name}.mp3` : `cache/tts/${name}.mp3`; const cacheAbs = resolve(userDataDir, cacheRel); if (existsSync(cacheAbs)) { @@ -409,7 +427,7 @@ router.post('/ocr', async (ctx) => { const userDataDir = getUserDataDir(user.email); const { dir, name } = parsePath(filePath.replace(/^\/+/, '')); - const cacheRel = dir ? `ocr/${dir}/${name}.md` : `ocr/${name}.md`; + const cacheRel = dir ? `cache/ocr/${dir}/${name}.md` : `cache/ocr/${name}.md`; const cacheAbs = resolve(userDataDir, cacheRel); if (existsSync(cacheAbs)) { @@ -486,7 +504,7 @@ router.post('/extract-audio', async (ctx) => { const userDataDir = getUserDataDir(user.email); const { dir, name } = parsePath(filePath.replace(/^\/+/, '')); - const cacheRel = dir ? `audio/${dir}/${name}.mp3` : `audio/${name}.mp3`; + const cacheRel = dir ? `cache/audio/${dir}/${name}.mp3` : `cache/audio/${name}.mp3`; const cacheAbs = resolve(userDataDir, cacheRel); if (existsSync(cacheAbs)) { @@ -610,7 +628,7 @@ router.post('/transcribe', async (ctx) => { const userDataDir = getUserDataDir(user.email); const { dir, name } = parsePath(filePath.replace(/^\/+/, '')); - const cacheRel = dir ? `transcriptions/${dir}/${name}.md` : `transcriptions/${name}.md`; + const cacheRel = dir ? `cache/transcriptions/${dir}/${name}.md` : `cache/transcriptions/${name}.md`; const cacheAbs = resolve(userDataDir, cacheRel); if (existsSync(cacheAbs)) { diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts index 03f8be4a..3d299430 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts @@ -5,6 +5,7 @@ import { useFilesAPI, type DirEntry } from '../../../hooks/useFilesAPI'; import { useTasks, type TaskSummary } from '../useTasks'; import { useUserState } from 'state/useUserState'; import { useAuth } from 'hooks/useAuth'; +import { usePanelChannel } from 'hooks/usePanelChannel'; type DefaultSort = { field: 'name' | 'size' | 'type' | 'date'; @@ -76,13 +77,15 @@ export const useFileBrowserApp = (basePath: string, rootOverride?: string, initi } }; + const [refreshSignal] = usePanelChannel('files:refresh-signal', 0); + useEffect(() => { if (basePath !== '/' && !currentPath.startsWith(basePath)) { setCurrentPath(basePath); return; } refresh(); - }, [currentPath, homeRoot]); + }, [currentPath, homeRoot, refreshSignal]); useEffect(() => { setSelected(new Set()); diff --git a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx index c6e055e9..37aa1669 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import type { ReactNode } from 'react'; import { useFilesAPI } from '../../hooks/useFilesAPI'; +import { usePanelChannel } from 'hooks/usePanelChannel'; import { toast } from 'sonner'; import { getFileType } from './file-types'; import { FileViewerContext } from './FileViewerContext'; @@ -35,6 +36,7 @@ export const FileViewerProvider = ({ const [saveResultLoading, setSaveResultLoading] = useState(false); const [editing, setEditing] = useState(false); const files = useFilesAPI(root); + const [, setRefreshSignal] = usePanelChannel('files:refresh-signal', 0); const fileType = getFileType(fileName); const editable = fileType === 'markdown' || fileType === 'code' || fileType === 'text'; @@ -114,9 +116,7 @@ export const FileViewerProvider = ({ } }; - const isCachedResult = - root === 'user-data' && - (filePath.startsWith('ocr/') || filePath.startsWith('tts/') || filePath.startsWith('transcriptions/') || filePath.startsWith('audio/')); + const isCachedResult = root === 'user-data' && filePath.startsWith('cache/'); const handleSaveResult = isCachedResult ? async () => { @@ -124,6 +124,7 @@ export const FileViewerProvider = ({ try { const { savedPath } = await files.saveResult(filePath); const savedName = savedPath.split('/').pop() ?? savedPath; + setRefreshSignal((n) => n + 1); toast.success(`Saved as "${savedName}"`); } catch { toast.error('Failed to save file'); diff --git a/src/workspaces/officerdev/src/hooks/useFilesAPI.ts b/src/workspaces/officerdev/src/hooks/useFilesAPI.ts index 5df3719c..a68cfbf5 100644 --- a/src/workspaces/officerdev/src/hooks/useFilesAPI.ts +++ b/src/workspaces/officerdev/src/hooks/useFilesAPI.ts @@ -49,19 +49,19 @@ export const useFilesAPI = (root: string = 'home') => { client.post(withRoot('/file-browser/download-video'), { url, path, audioOnly }), tts: (path: string) => - client.post<{ audioPath: string; audioRoot: string }>(withRoot('/file-browser/tts'), { path }), + client.post<{ audioPath: string; audioRoot: string }>('/file-browser/tts', { path, root }), ocr: (path: string) => - client.post<{ ocrPath: string; ocrRoot: string }>(withRoot('/file-browser/ocr'), { path }), + client.post<{ ocrPath: string; ocrRoot: string }>('/file-browser/ocr', { path, root }), transcribe: (path: string) => - client.post<{ transcriptionPath: string; transcriptionRoot: string }>(withRoot('/file-browser/transcribe'), { path }), + client.post<{ transcriptionPath: string; transcriptionRoot: string }>('/file-browser/transcribe', { path, root }), extractAudio: (path: string) => - client.post<{ audioPath: string; audioRoot: string }>(withRoot('/file-browser/extract-audio'), { path }), + client.post<{ audioPath: string; audioRoot: string }>('/file-browser/extract-audio', { path, root }), extract: (path: string) => - client.post<{ extractedPath: string }>(withRoot('/file-browser/extract'), { path }), + client.post<{ extractedPath: string }>('/file-browser/extract', { path, root }), saveResult: (path: string) => client.post<{ savedPath: string }>('/file-browser/save-result', { path }),