save ocr/tts/transcription results next to source file from context menu

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 23:52:57 +00:00
co-authored by Claude Opus 4.6
parent aa0207436c
commit 6a6ed5487d
3 changed files with 85 additions and 15 deletions
+70 -3
View File
@@ -391,7 +391,7 @@ router.post('/save-result', async (ctx) => {
// Text-to-speech with caching
router.post('/tts', async (ctx) => {
const user = ctx.get('user');
const { path: filePath, root } = ctx.get('body') as { path: string; root?: string };
const { path: filePath, root, saveNextTo } = ctx.get('body') as { path: string; root?: string; saveNextTo?: boolean };
if (!filePath) throw errors.BAD_REQUEST('path is required');
const rootDir = getRootDir(user, root);
@@ -411,7 +411,22 @@ router.post('/tts', async (ctx) => {
const cacheRel = dir ? `cache/tts/${voicePrefix}${dir}/${name}.mp3` : `cache/tts/${voicePrefix}${name}.mp3`;
const cacheAbs = resolve(userDataDir, cacheRel);
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.mp3` : `${name}.mp3`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
if (existsSync(siblingAbs)) {
return ctx.json({ audioPath: `/${siblingRel}`, audioRoot: root ?? 'home', cached: true });
}
}
if (existsSync(cacheAbs)) {
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.mp3` : `${name}.mp3`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
const cached = await readFile(cacheAbs);
await Bun.write(siblingAbs, cached);
return ctx.json({ audioPath: `/${siblingRel}`, audioRoot: root ?? 'home', cached: true });
}
return ctx.json({ audioPath: cacheRel, audioRoot: 'user-data' });
}
@@ -441,6 +456,13 @@ router.post('/tts', async (ctx) => {
const buffer = await res.arrayBuffer();
await Bun.write(cacheAbs, buffer);
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.mp3` : `${name}.mp3`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
await Bun.write(siblingAbs, buffer);
return ctx.json({ audioPath: `/${siblingRel}`, audioRoot: root ?? 'home', cached: false });
}
return ctx.json({ audioPath: cacheRel, audioRoot: 'user-data' });
});
@@ -495,7 +517,7 @@ router.post('/tts-text', async (ctx) => {
// OCR image via vision model with caching
router.post('/ocr', async (ctx) => {
const user = ctx.get('user');
const { path: filePath, root } = ctx.get('body') as { path: string; root?: string };
const { path: filePath, root, saveNextTo } = ctx.get('body') as { path: string; root?: string; saveNextTo?: boolean };
if (!filePath) throw errors.BAD_REQUEST('path is required');
const rootDir = getRootDir(user, root);
@@ -508,7 +530,23 @@ router.post('/ocr', async (ctx) => {
const cacheRel = dir ? `cache/ocr/${dir}/${name}.md` : `cache/ocr/${name}.md`;
const cacheAbs = resolve(userDataDir, cacheRel);
// When saving next to file, check if the sibling .md already exists
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.md` : `${name}.md`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
if (existsSync(siblingAbs)) {
return ctx.json({ ocrPath: `/${siblingRel}`, ocrRoot: root ?? 'home', cached: true });
}
}
if (existsSync(cacheAbs)) {
if (saveNextTo) {
const text = await readFile(cacheAbs, 'utf-8');
const siblingRel = dir ? `${dir}/${name}.md` : `${name}.md`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
await Bun.write(siblingAbs, text);
return ctx.json({ ocrPath: `/${siblingRel}`, ocrRoot: root ?? 'home', cached: true });
}
const text = await readFile(cacheAbs, 'utf-8');
return ctx.json({ text, ocrPath: cacheRel, ocrRoot: 'user-data', cached: true });
}
@@ -566,6 +604,13 @@ router.post('/ocr', async (ctx) => {
await mkdir(dirname(cacheAbs), { recursive: true });
await Bun.write(cacheAbs, text);
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.md` : `${name}.md`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
await Bun.write(siblingAbs, text);
return ctx.json({ ocrPath: `/${siblingRel}`, ocrRoot: root ?? 'home', cached: false });
}
return ctx.json({ text, ocrPath: cacheRel, ocrRoot: 'user-data', cached: false });
});
@@ -696,7 +741,7 @@ router.post('/extract', async (ctx) => {
// Workflow: detect language → check user's spoken languages → translate if needed → transcribe
router.post('/transcribe', async (ctx) => {
const user = ctx.get('user');
const { path: filePath, root } = ctx.get('body') as { path: string; root?: string };
const { path: filePath, root, saveNextTo } = ctx.get('body') as { path: string; root?: string; saveNextTo?: boolean };
if (!filePath) throw errors.BAD_REQUEST('path is required');
const rootDir = getRootDir(user, root);
@@ -709,7 +754,22 @@ router.post('/transcribe', async (ctx) => {
const cacheRel = dir ? `cache/transcriptions/${dir}/${name}.md` : `cache/transcriptions/${name}.md`;
const cacheAbs = resolve(userDataDir, cacheRel);
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.md` : `${name}.md`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
if (existsSync(siblingAbs)) {
return ctx.json({ transcriptionPath: `/${siblingRel}`, transcriptionRoot: root ?? 'home', cached: true });
}
}
if (existsSync(cacheAbs)) {
if (saveNextTo) {
const text = await readFile(cacheAbs, 'utf-8');
const siblingRel = dir ? `${dir}/${name}.md` : `${name}.md`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
await Bun.write(siblingAbs, text);
return ctx.json({ transcriptionPath: `/${siblingRel}`, transcriptionRoot: root ?? 'home', cached: true });
}
return ctx.json({ transcriptionPath: cacheRel, transcriptionRoot: 'user-data', cached: true });
}
@@ -762,6 +822,13 @@ router.post('/transcribe', async (ctx) => {
await mkdir(dirname(cacheAbs), { recursive: true });
await Bun.write(cacheAbs, text);
if (saveNextTo) {
const siblingRel = dir ? `${dir}/${name}.md` : `${name}.md`;
const siblingAbs = resolveUserPath(rootDir, siblingRel);
await Bun.write(siblingAbs, text);
return ctx.json({ transcriptionPath: `/${siblingRel}`, transcriptionRoot: root ?? 'home', cached: false });
}
return ctx.json({ transcriptionPath: cacheRel, transcriptionRoot: 'user-data', cached: false });
});