fixed ocr/tts... caching
This commit is contained in:
@@ -10,8 +10,16 @@ import { readSttConfig } from '@@/api/server-settings/stt';
|
|||||||
import { readOcrConfig } from '@@/api/server-settings/ocr';
|
import { readOcrConfig } from '@@/api/server-settings/ocr';
|
||||||
|
|
||||||
const DEFAULT_HOME_DIRS = ['Downloads', 'Documents', 'Music', 'Video', 'Pictures', 'Onboarding'];
|
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');
|
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) {
|
async function seedHomeDir(homeDir: string) {
|
||||||
for (const dir of DEFAULT_HOME_DIRS) {
|
for (const dir of DEFAULT_HOME_DIRS) {
|
||||||
const target = join(homeDir, dir);
|
const target = join(homeDir, dir);
|
||||||
@@ -60,6 +68,11 @@ router.get('/ls', async (ctx) => {
|
|||||||
await mkdir(absPath, { recursive: true });
|
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[];
|
let names: string[];
|
||||||
try {
|
try {
|
||||||
names = await readdir(absPath);
|
names = await readdir(absPath);
|
||||||
@@ -234,7 +247,7 @@ router.get('/transcode', async (ctx) => {
|
|||||||
|
|
||||||
const userDataDir = getUserDataDir(user.email);
|
const userDataDir = getUserDataDir(user.email);
|
||||||
const { dir, name } = parsePath(relPath);
|
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);
|
const cacheAbs = resolve(userDataDir, cacheRel);
|
||||||
|
|
||||||
if (!existsSync(cacheAbs)) {
|
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
|
// 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) => {
|
router.post('/save-result', async (ctx) => {
|
||||||
const user = ctx.get('user');
|
const user = ctx.get('user');
|
||||||
@@ -328,7 +341,12 @@ router.post('/save-result', async (ctx) => {
|
|||||||
const prefix = CACHE_PREFIXES.find((p) => cachedPath.startsWith(p));
|
const prefix = CACHE_PREFIXES.find((p) => cachedPath.startsWith(p));
|
||||||
if (!prefix) throw errors.BAD_REQUEST('Not a cached result path');
|
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 userDataDir = getUserDataDir(user.email);
|
||||||
const srcAbs = resolve(userDataDir, cachedPath);
|
const srcAbs = resolve(userDataDir, cachedPath);
|
||||||
if (!existsSync(srcAbs)) throw errors.BAD_REQUEST('Cached file not found');
|
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 userDataDir = getUserDataDir(user.email);
|
||||||
const { dir, name } = parsePath(filePath.replace(/^\/+/, ''));
|
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);
|
const cacheAbs = resolve(userDataDir, cacheRel);
|
||||||
|
|
||||||
if (existsSync(cacheAbs)) {
|
if (existsSync(cacheAbs)) {
|
||||||
@@ -409,7 +427,7 @@ router.post('/ocr', async (ctx) => {
|
|||||||
|
|
||||||
const userDataDir = getUserDataDir(user.email);
|
const userDataDir = getUserDataDir(user.email);
|
||||||
const { dir, name } = parsePath(filePath.replace(/^\/+/, ''));
|
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);
|
const cacheAbs = resolve(userDataDir, cacheRel);
|
||||||
|
|
||||||
if (existsSync(cacheAbs)) {
|
if (existsSync(cacheAbs)) {
|
||||||
@@ -486,7 +504,7 @@ router.post('/extract-audio', async (ctx) => {
|
|||||||
|
|
||||||
const userDataDir = getUserDataDir(user.email);
|
const userDataDir = getUserDataDir(user.email);
|
||||||
const { dir, name } = parsePath(filePath.replace(/^\/+/, ''));
|
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);
|
const cacheAbs = resolve(userDataDir, cacheRel);
|
||||||
|
|
||||||
if (existsSync(cacheAbs)) {
|
if (existsSync(cacheAbs)) {
|
||||||
@@ -610,7 +628,7 @@ router.post('/transcribe', async (ctx) => {
|
|||||||
|
|
||||||
const userDataDir = getUserDataDir(user.email);
|
const userDataDir = getUserDataDir(user.email);
|
||||||
const { dir, name } = parsePath(filePath.replace(/^\/+/, ''));
|
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);
|
const cacheAbs = resolve(userDataDir, cacheRel);
|
||||||
|
|
||||||
if (existsSync(cacheAbs)) {
|
if (existsSync(cacheAbs)) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useFilesAPI, type DirEntry } from '../../../hooks/useFilesAPI';
|
|||||||
import { useTasks, type TaskSummary } from '../useTasks';
|
import { useTasks, type TaskSummary } from '../useTasks';
|
||||||
import { useUserState } from 'state/useUserState';
|
import { useUserState } from 'state/useUserState';
|
||||||
import { useAuth } from 'hooks/useAuth';
|
import { useAuth } from 'hooks/useAuth';
|
||||||
|
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||||
|
|
||||||
type DefaultSort = {
|
type DefaultSort = {
|
||||||
field: 'name' | 'size' | 'type' | 'date';
|
field: 'name' | 'size' | 'type' | 'date';
|
||||||
@@ -76,13 +77,15 @@ export const useFileBrowserApp = (basePath: string, rootOverride?: string, initi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [refreshSignal] = usePanelChannel<number>('files:refresh-signal', 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (basePath !== '/' && !currentPath.startsWith(basePath)) {
|
if (basePath !== '/' && !currentPath.startsWith(basePath)) {
|
||||||
setCurrentPath(basePath);
|
setCurrentPath(basePath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refresh();
|
refresh();
|
||||||
}, [currentPath, homeRoot]);
|
}, [currentPath, homeRoot, refreshSignal]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelected(new Set());
|
setSelected(new Set());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useFilesAPI } from '../../hooks/useFilesAPI';
|
import { useFilesAPI } from '../../hooks/useFilesAPI';
|
||||||
|
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { getFileType } from './file-types';
|
import { getFileType } from './file-types';
|
||||||
import { FileViewerContext } from './FileViewerContext';
|
import { FileViewerContext } from './FileViewerContext';
|
||||||
@@ -35,6 +36,7 @@ export const FileViewerProvider = ({
|
|||||||
const [saveResultLoading, setSaveResultLoading] = useState(false);
|
const [saveResultLoading, setSaveResultLoading] = useState(false);
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const files = useFilesAPI(root);
|
const files = useFilesAPI(root);
|
||||||
|
const [, setRefreshSignal] = usePanelChannel<number>('files:refresh-signal', 0);
|
||||||
const fileType = getFileType(fileName);
|
const fileType = getFileType(fileName);
|
||||||
const editable = fileType === 'markdown' || fileType === 'code' || fileType === 'text';
|
const editable = fileType === 'markdown' || fileType === 'code' || fileType === 'text';
|
||||||
|
|
||||||
@@ -114,9 +116,7 @@ export const FileViewerProvider = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isCachedResult =
|
const isCachedResult = root === 'user-data' && filePath.startsWith('cache/');
|
||||||
root === 'user-data' &&
|
|
||||||
(filePath.startsWith('ocr/') || filePath.startsWith('tts/') || filePath.startsWith('transcriptions/') || filePath.startsWith('audio/'));
|
|
||||||
|
|
||||||
const handleSaveResult = isCachedResult
|
const handleSaveResult = isCachedResult
|
||||||
? async () => {
|
? async () => {
|
||||||
@@ -124,6 +124,7 @@ export const FileViewerProvider = ({
|
|||||||
try {
|
try {
|
||||||
const { savedPath } = await files.saveResult(filePath);
|
const { savedPath } = await files.saveResult(filePath);
|
||||||
const savedName = savedPath.split('/').pop() ?? savedPath;
|
const savedName = savedPath.split('/').pop() ?? savedPath;
|
||||||
|
setRefreshSignal((n) => n + 1);
|
||||||
toast.success(`Saved as "${savedName}"`);
|
toast.success(`Saved as "${savedName}"`);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to save file');
|
toast.error('Failed to save file');
|
||||||
|
|||||||
@@ -49,19 +49,19 @@ export const useFilesAPI = (root: string = 'home') => {
|
|||||||
client.post(withRoot('/file-browser/download-video'), { url, path, audioOnly }),
|
client.post(withRoot('/file-browser/download-video'), { url, path, audioOnly }),
|
||||||
|
|
||||||
tts: (path: string) =>
|
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) =>
|
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) =>
|
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) =>
|
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) =>
|
extract: (path: string) =>
|
||||||
client.post<{ extractedPath: string }>(withRoot('/file-browser/extract'), { path }),
|
client.post<{ extractedPath: string }>('/file-browser/extract', { path, root }),
|
||||||
|
|
||||||
saveResult: (path: string) =>
|
saveResult: (path: string) =>
|
||||||
client.post<{ savedPath: string }>('/file-browser/save-result', { path }),
|
client.post<{ savedPath: string }>('/file-browser/save-result', { path }),
|
||||||
|
|||||||
Reference in New Issue
Block a user