workspaces to dashboards, imap email sync, ffmpeg tool, tts fix, file browser refresh, automation sidebar reorder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 20:00:45 +00:00
co-authored by Claude Opus 4.6
parent bd362dc586
commit 927267e041
98 changed files with 1176 additions and 802 deletions
+15 -15
View File
@@ -8,14 +8,12 @@ import * as errors from '@@/custom-errors';
import { readTtsConfig } from '@@/api/server-settings/tts';
import { readSttConfig } from '@@/api/server-settings/stt';
import { readOcrConfig } from '@@/api/server-settings/ocr';
import { getUserSettings } from 'officerdb';
async function getUserTtsVoice(email: string): Promise<string | null> {
async function getUserTtsVoice(userId: number): Promise<string | null> {
try {
const settingsFile = Bun.file(getUserSettingsFile(email));
if (await settingsFile.exists()) {
const settings = (await settingsFile.json()) as { tts?: { voice?: string | null } };
return settings.tts?.voice ?? null;
}
const settings = await getUserSettings(userId) as { tts?: { voice?: string | null } };
return settings.tts?.voice ?? null;
} catch {}
return null;
}
@@ -387,7 +385,7 @@ router.post('/tts', async (ctx) => {
const ttsConfig = await readTtsConfig();
if (!ttsConfig) throw errors.BAD_REQUEST('TTS not configured — set it up in Settings → Text to Speech');
const userVoice = await getUserTtsVoice(user.email);
const userVoice = await getUserTtsVoice(user.id);
const voice = userVoice ?? ttsConfig.voice;
const userDataDir = getUserDataDir(user.email);
@@ -439,7 +437,7 @@ router.post('/tts-text', async (ctx) => {
const ttsConfig = await readTtsConfig();
if (!ttsConfig) throw errors.BAD_REQUEST('TTS not configured — set it up in Settings → Text to Speech');
const userVoice = await getUserTtsVoice(user.email);
const userVoice = await getUserTtsVoice(user.id);
const voice = userVoice ?? ttsConfig.voice;
const userDataDir = getUserDataDir(user.email);
@@ -906,16 +904,18 @@ router.post('/download-video', async (ctx) => {
if (!path) throw errors.BAD_REQUEST('path is required');
const absPath = resolveUserPath(rootDir, path);
const args = ['yt-dlp', '-o', '%(title)s.%(ext)s'];
await mkdir(absPath, { recursive: true });
const cookiesPath = join(DATA_PATH, '..', 'yt-dlp-cookies.txt');
const args = ['yt-dlp', '--js-runtimes', 'bun', '--cookies', cookiesPath, '-o', '%(title)s.%(ext)s'];
if (audioOnly) args.push('-x', '--audio-format', 'mp3');
args.push(url);
const proc = Bun.spawn(args, { cwd: absPath, stdout: 'pipe', stderr: 'pipe' });
const proc = Bun.spawn(args, { cwd: absPath, stdout: 'ignore', stderr: 'pipe' });
const stderrText = await new Response(proc.stderr).text();
const exitCode = await proc.exited;
if (exitCode !== 0) {
const stderr = await new Response(proc.stderr).text();
throw errors.BAD_REQUEST(stderr.trim() || 'yt-dlp download failed');
throw errors.BAD_REQUEST(stderrText.trim() || 'yt-dlp download failed');
}
return ctx.json({ ok: true });
@@ -932,12 +932,12 @@ router.post('/git-clone', async (ctx) => {
const absPath = resolveUserPath(rootDir, path);
await mkdir(absPath, { recursive: true });
const proc = Bun.spawn(['git', 'clone', url], { cwd: absPath, stdout: 'pipe', stderr: 'pipe' });
const proc = Bun.spawn(['git', 'clone', url], { cwd: absPath, stdout: 'ignore', stderr: 'pipe' });
const stderrText = await new Response(proc.stderr).text();
const exitCode = await proc.exited;
if (exitCode !== 0) {
const stderr = await new Response(proc.stderr).text();
throw errors.BAD_REQUEST(stderr.trim() || 'git clone failed');
throw errors.BAD_REQUEST(stderrText.trim() || 'git clone failed');
}
return ctx.json({ ok: true });