api base url
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m0s

This commit is contained in:
Song367 2026-03-18 15:34:17 +08:00
parent 4031d21dcc
commit 9ddcdc9ec6
8 changed files with 42 additions and 5 deletions

1
.env
View File

@ -2,3 +2,4 @@ GEMINI_API_KEY="AIzaSyAex0MkGj_X-h3L38334xVdZsFzOcU9cC0"
ARK_API_KEY="e96194a9-8eda-4a90-a211-6db288045bdc" ARK_API_KEY="e96194a9-8eda-4a90-a211-6db288045bdc"
MINIMAX_API_KEY="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLkuIrmtbfpopzpgJTnp5HmioDmnInpmZDlhazlj7giLCJVc2VyTmFtZSI6IuadqOmqpSIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxNzI4NzEyMzI0OTc5NjI2ODM5IiwiUGhvbmUiOiIxMzM4MTU1OTYxOCIsIkdyb3VwSUQiOiIxNzI4NzEyMzI0OTcxMjM4MjMxIiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDYtMDYgMTU6MDU6NTUiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aw1AUJnBYxXerJ4qNUaXM3DqPTd94WSVHWRiIpnjImhuCia3Ta1AyANTQTx__2CF5eByHOaHJFHhBCg6KgHUEaR6TiWFn0fWwXaU7XgnHwbvD4pNAmF_uYxMKbi-a6IyIGNyFdEMy22V5JEqfY4okAco5U96cnSOQZH7lyIBpvOsesjZU6L9q6Tf2jvlcnO9QG8GPg2DVpeL8Q3zLuYWezN4Wk6N-ISwQmZUwBYL3BhYamsFqCdSEyMd_uYQ_aQJa5tmlQqpimtALiutFshPUXB6VsvXEO6q-lCZ6Tg8QWwlFHkmEtUMQw4pWoX25d7Us06VFUhvV6pOzvM7yqCaWw" MINIMAX_API_KEY="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLkuIrmtbfpopzpgJTnp5HmioDmnInpmZDlhazlj7giLCJVc2VyTmFtZSI6IuadqOmqpSIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxNzI4NzEyMzI0OTc5NjI2ODM5IiwiUGhvbmUiOiIxMzM4MTU1OTYxOCIsIkdyb3VwSUQiOiIxNzI4NzEyMzI0OTcxMjM4MjMxIiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDYtMDYgMTU6MDU6NTUiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aw1AUJnBYxXerJ4qNUaXM3DqPTd94WSVHWRiIpnjImhuCia3Ta1AyANTQTx__2CF5eByHOaHJFHhBCg6KgHUEaR6TiWFn0fWwXaU7XgnHwbvD4pNAmF_uYxMKbi-a6IyIGNyFdEMy22V5JEqfY4okAco5U96cnSOQZH7lyIBpvOsesjZU6L9q6Tf2jvlcnO9QG8GPg2DVpeL8Q3zLuYWezN4Wk6N-ISwQmZUwBYL3BhYamsFqCdSEyMd_uYQ_aQJa5tmlQqpimtALiutFshPUXB6VsvXEO6q-lCZ6Tg8QWwlFHkmEtUMQw4pWoX25d7Us06VFUhvV6pOzvM7yqCaWw"
VITE_BASE_URL=/video_translate/ VITE_BASE_URL=/video_translate/
VITE_API_BASE_PATH=/video_translate/api

View File

@ -12,6 +12,11 @@ DEFAULT_LLM_PROVIDER="doubao"
# Defaults to doubao-seed-2-0-pro-260215. # Defaults to doubao-seed-2-0-pro-260215.
DOUBAO_MODEL="doubao-seed-2-0-pro-260215" DOUBAO_MODEL="doubao-seed-2-0-pro-260215"
# VITE_API_BASE_PATH: Optional frontend API base path.
# Defaults to /api.
# Set to /video_translate/api when the app is served under /video_translate.
# VITE_API_BASE_PATH="/video_translate/api"
# MINIMAX_API_KEY: Required for MiniMax TTS API calls. # MINIMAX_API_KEY: Required for MiniMax TTS API calls.
# Use a MiniMax API secret key that has TTS access enabled. # Use a MiniMax API secret key that has TTS access enabled.
MINIMAX_API_KEY="YOUR_MINIMAX_API_KEY" MINIMAX_API_KEY="YOUR_MINIMAX_API_KEY"

View File

@ -22,6 +22,7 @@ View your app in AI Studio: https://ai.studio/apps/a38a3cd5-7f82-49f0-a26e-99be4
3. Optional defaults: 3. Optional defaults:
`DEFAULT_LLM_PROVIDER=doubao` `DEFAULT_LLM_PROVIDER=doubao`
`DOUBAO_MODEL=doubao-seed-2-0-pro-260215` `DOUBAO_MODEL=doubao-seed-2-0-pro-260215`
`VITE_API_BASE_PATH=/api` (optional)
`FFMPEG_PATH=/path/to/ffmpeg` (optional) `FFMPEG_PATH=/path/to/ffmpeg` (optional)
`FFPROBE_PATH=/path/to/ffprobe` (optional) `FFPROBE_PATH=/path/to/ffprobe` (optional)
4. Run the app: 4. Run the app:
@ -34,6 +35,12 @@ View your app in AI Studio: https://ai.studio/apps/a38a3cd5-7f82-49f0-a26e-99be4
3. `TTS` stays fixed on `MiniMax` regardless of the selected LLM. 3. `TTS` stays fixed on `MiniMax` regardless of the selected LLM.
4. All provider keys are read from `.env`; the browser no longer calls LLM providers directly. 4. All provider keys are read from `.env`; the browser no longer calls LLM providers directly.
## Deploying Under a Subpath
1. If your site is hosted under a subpath (for example `https://ai.yantootech.com/video_translate/`), set:
`VITE_API_BASE_PATH=/video_translate/api`
2. Keep backend routes unchanged (`/api/*` on the server); the frontend will prepend the configured base path.
## Subtitle Generation ## Subtitle Generation
1. Subtitle generation is now driven by server-side multimodal LLM calls on the uploaded video file. 1. Subtitle generation is now driven by server-side multimodal LLM calls on the uploaded video file.

View File

@ -7,6 +7,7 @@ import { LlmProvider, PipelineQuality, Subtitle, TextStyles } from '../types';
import { generateSubtitlePipeline } from '../services/subtitleService'; import { generateSubtitlePipeline } from '../services/subtitleService';
import { generateTTS } from '../services/ttsService'; import { generateTTS } from '../services/ttsService';
import { MINIMAX_VOICES } from '../voices'; import { MINIMAX_VOICES } from '../voices';
import { apiUrl } from '../lib/apiBasePath';
export default function EditorScreen({ videoFile, targetLanguage, trimRange, onBack }: { videoFile: File | null; targetLanguage: string; trimRange?: {start: number, end: number} | null; onBack: () => void }) { export default function EditorScreen({ videoFile, targetLanguage, trimRange, onBack }: { videoFile: File | null; targetLanguage: string; trimRange?: {start: number, end: number} | null; onBack: () => void }) {
const [subtitles, setSubtitles] = useState<Subtitle[]>([]); const [subtitles, setSubtitles] = useState<Subtitle[]>([]);
@ -182,7 +183,7 @@ export default function EditorScreen({ videoFile, targetLanguage, trimRange, onB
const formData = new FormData(); const formData = new FormData();
formData.append('video', videoFile); formData.append('video', videoFile);
const response = await axios.post('/api/separate-vocal', formData, { const response = await axios.post(apiUrl('/separate-vocal'), formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }

View File

@ -3,6 +3,7 @@ import { X, Download, Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
import axios from 'axios'; import axios from 'axios';
import { Subtitle, TextStyles } from '../types'; import { Subtitle, TextStyles } from '../types';
import { buildExportPayload } from '../lib/exportPayload'; import { buildExportPayload } from '../lib/exportPayload';
import { apiUrl } from '../lib/apiBasePath';
interface ExportModalProps { interface ExportModalProps {
onClose: () => void; onClose: () => void;
@ -61,7 +62,7 @@ export default function ExportModal({ onClose, videoFile, subtitles, bgmUrl, bgm
setProgress(p => (p < 90 ? p + 2 : p)); setProgress(p => (p < 90 ? p + 2 : p));
}, 500); }, 500);
const response = await axios.post('/api/export-video', formData, { const response = await axios.post(apiUrl('/export-video'), formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }

19
src/lib/apiBasePath.ts Normal file
View File

@ -0,0 +1,19 @@
const normalizeBasePath = (value: string | undefined) => {
const trimmed = value?.trim();
if (!trimmed) {
return '/api';
}
if (/^https?:\/\//i.test(trimmed)) {
return trimmed.replace(/\/+$/, '');
}
const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
return withLeadingSlash.replace(/\/+$/, '');
};
export const apiUrl = (path: string) => {
const basePath = normalizeBasePath(import.meta.env.VITE_API_BASE_PATH);
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
return `${basePath}${normalizedPath}`;
};

View File

@ -1,4 +1,5 @@
import { LlmProvider, PipelineQuality, SubtitlePipelineResult } from '../types'; import { LlmProvider, PipelineQuality, SubtitlePipelineResult } from '../types';
import { apiUrl } from '../lib/apiBasePath';
type JsonResponseResult<T> = type JsonResponseResult<T> =
| { ok: true; status: number; data: T } | { ok: true; status: number; data: T }
@ -56,7 +57,7 @@ export const generateSubtitlePipeline = async (
formData.append('trimRange', JSON.stringify(trimRange)); formData.append('trimRange', JSON.stringify(trimRange));
} }
const resp = await fetchImpl('/api/generate-subtitles', { const resp = await fetchImpl(apiUrl('/generate-subtitles'), {
method: 'POST', method: 'POST',
body: formData, body: formData,
}); });

View File

@ -1,3 +1,5 @@
import { apiUrl } from '../lib/apiBasePath';
type JsonResponseResult<T> = type JsonResponseResult<T> =
| { ok: true; status: number; data: T } | { ok: true; status: number; data: T }
| { ok: false; status: number; error: string }; | { ok: false; status: number; error: string };
@ -64,7 +66,7 @@ export const generateTTS = async (
} }
const audio = await withRetry(async () => { const audio = await withRetry(async () => {
const resp = await fetch('/api/tts', { const resp = await fetch(apiUrl('/tts'), {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',