1+ import fs from 'fs'
2+ import path from 'path'
13import dotenv from 'dotenv'
4+
25dotenv . config ( )
36
7+ const toNumber = ( value , fallback ) => {
8+ const parsed = parseInt ( value , 10 )
9+ return Number . isNaN ( parsed ) ? fallback : parsed
10+ }
11+
12+ const parseArgs = value => {
13+ if ( ! value ) return null
14+ return value
15+ . split ( / \s + / )
16+ . map ( item => item . trim ( ) )
17+ . filter ( Boolean )
18+ }
19+
20+ const parseOptional = value => ( value && value . trim ( ) ) || null
21+
22+ const resolvePath = ( value , fallback ) => {
23+ if ( ! value ) return fallback
24+ return path . isAbsolute ( value ) ? value : path . join ( process . cwd ( ) , value )
25+ }
26+
27+ const ensureDir = dir => {
28+ try {
29+ fs . mkdirSync ( dir , { recursive : true } )
30+ } catch ( error ) {
31+ console . warn ( `[config] Could not ensure directory ${ dir } : ${ error . message } ` )
32+ }
33+ }
34+
35+ const workspaceRoot = resolvePath (
36+ process . env . YOUTUBE2POST_WORKSPACE_ROOT ,
37+ path . join ( process . cwd ( ) , 'terminal-backend/data/users/default/workspace/card' )
38+ )
39+ const tempRoot = resolvePath (
40+ process . env . YOUTUBE2POST_TEMP_ROOT ,
41+ path . join ( process . cwd ( ) , 'terminal-backend/data/tmp/youtube2post' )
42+ )
43+ const dataRoot = resolvePath (
44+ process . env . YOUTUBE2POST_DATA_ROOT ,
45+ path . join ( process . cwd ( ) , 'terminal-backend/data/youtube2post' )
46+ )
47+ const templateRoot = resolvePath (
48+ process . env . YOUTUBE2POST_TEMPLATE_ROOT ,
49+ path . join ( process . cwd ( ) , 'terminal-backend/data/public_template/youtube2post' )
50+ )
51+
52+ ensureDir ( workspaceRoot )
53+ ensureDir ( tempRoot )
54+ ensureDir ( dataRoot )
55+
56+ const videoDecomposeWorkspaceRoot = resolvePath (
57+ process . env . VIDEO_DECOMPOSE_WORKSPACE_ROOT ,
58+ path . join ( process . cwd ( ) , 'terminal-backend/data/users/default/workspace/video-decompose' )
59+ )
60+ const videoDecomposeTempRoot = resolvePath (
61+ process . env . VIDEO_DECOMPOSE_TEMP_ROOT ,
62+ path . join ( process . cwd ( ) , 'terminal-backend/data/tmp/video-decompose' )
63+ )
64+ const videoDecomposeOutputRoot = resolvePath (
65+ process . env . VIDEO_DECOMPOSE_OUTPUT_ROOT ,
66+ path . join ( process . cwd ( ) , 'terminal-backend/data/video-decompose' )
67+ )
68+
69+ ensureDir ( videoDecomposeWorkspaceRoot )
70+ ensureDir ( videoDecomposeTempRoot )
71+ ensureDir ( videoDecomposeOutputRoot )
72+
73+ const youtube2postConfig = {
74+ workerEnabled : process . env . YOUTUBE2POST_WORKER_ENABLED !== 'false' ,
75+ maxConcurrentTasks : toNumber ( process . env . YOUTUBE2POST_MAX_CONCURRENT , 1 ) ,
76+ maxPendingTasks : toNumber ( process . env . YOUTUBE2POST_MAX_PENDING , 20 ) ,
77+ pollIntervalMs : toNumber ( process . env . YOUTUBE2POST_POLL_INTERVAL_MS , 5000 ) ,
78+ lockTtlMs : toNumber ( process . env . YOUTUBE2POST_LOCK_TTL_MS , 10 * 60 * 1000 ) ,
79+ youtubeDlPath : process . env . YOUTUBE2POST_YOUTUBE_DL_PATH || path . join ( process . cwd ( ) , 'youtube-dl' , 'bin' , 'youtube-dl' ) ,
80+ downloadTimeoutMs : toNumber ( process . env . YOUTUBE2POST_DOWNLOAD_TIMEOUT_MS , 10 * 60 * 1000 ) ,
81+ youtubeDlArgs : parseArgs ( process . env . YOUTUBE2POST_YOUTUBE_DL_ARGS ) || [ '-f' , 'best' ] ,
82+ ffmpegPath : process . env . YOUTUBE2POST_FFMPEG_PATH || 'ffmpeg' ,
83+ maxQuotes : toNumber ( process . env . YOUTUBE2POST_MAX_QUOTES , 5 ) ,
84+ frameExtractionTimeoutMs : toNumber ( process . env . YOUTUBE2POST_FRAME_TIMEOUT_MS , 60 * 1000 ) ,
85+ frameQuality : toNumber ( process . env . YOUTUBE2POST_FRAME_QUALITY , 2 ) ,
86+ frameScaleFilter : parseOptional ( process . env . YOUTUBE2POST_FRAME_SCALE_FILTER ) ,
87+ transcriptionPollIntervalMs : toNumber ( process . env . YOUTUBE2POST_TRANSCRIPTION_POLL_MS , 5000 ) ,
88+ transcriptionMaxAttempts : toNumber ( process . env . YOUTUBE2POST_TRANSCRIPTION_MAX_ATTEMPTS , 120 ) ,
89+ ossEnabled : process . env . YOUTUBE2POST_OSS_ENABLED === 'true' ,
90+ ossProject : process . env . YOUTUBE2POST_OSS_PROJECT || 'youtube2post' ,
91+ ossBaseDir : process . env . YOUTUBE2POST_OSS_BASE_DIR || 'youtube2post' ,
92+ publicBaseUrl : process . env . YOUTUBE2POST_PUBLIC_BASE_URL || null ,
93+ workspaceRoot,
94+ tempRoot,
95+ dataRoot,
96+ templateRoot
97+ }
98+
99+ const videoDecomposeConfig = {
100+ ffmpegPath : process . env . VIDEO_DECOMPOSE_FFMPEG_PATH
101+ || process . env . YOUTUBE2POST_FFMPEG_PATH
102+ || 'ffmpeg' ,
103+ screenshotQuality : toNumber ( process . env . VIDEO_DECOMPOSE_SCREENSHOT_QUALITY , 2 ) ,
104+ frameScaleFilter : parseOptional ( process . env . VIDEO_DECOMPOSE_FRAME_SCALE_FILTER ) ,
105+ clipPreset : process . env . VIDEO_DECOMPOSE_FFMPEG_PRESET || 'fast' ,
106+ clipCrf : toNumber ( process . env . VIDEO_DECOMPOSE_FFMPEG_CRF , 23 ) ,
107+ maxHighlights : toNumber ( process . env . VIDEO_DECOMPOSE_MAX_HIGHLIGHTS , 5 ) ,
108+ minQuoteLength : toNumber ( process . env . VIDEO_DECOMPOSE_MIN_QUOTE_LENGTH , 12 ) ,
109+ clipPaddingSeconds : Number . isNaN ( Number ( process . env . VIDEO_DECOMPOSE_CLIP_PADDING ) )
110+ ? 0.5
111+ : Number ( process . env . VIDEO_DECOMPOSE_CLIP_PADDING ) ,
112+ ossBaseDir : process . env . VIDEO_DECOMPOSE_OSS_BASE_DIR || 'video-decompose' ,
113+ keepTemp : process . env . VIDEO_DECOMPOSE_KEEP_TEMP === 'true' ,
114+ workspaceRoot : videoDecomposeWorkspaceRoot ,
115+ tempRoot : videoDecomposeTempRoot ,
116+ outputRoot : videoDecomposeOutputRoot
117+ }
118+
4119export default {
5120 port : process . env . PORT || 6000 ,
6121 nodeEnv : process . env . NODE_ENV || 'development' ,
7-
122+
8123 jwt : {
9124 secret : process . env . JWT_SECRET || 'default-secret' ,
10125 expireTime : process . env . JWT_EXPIRE_TIME || '24h'
11126 } ,
12-
127+
128+ aliyun : {
129+ apiKey : process . env . DASHSCOPE_API_KEY
130+ || process . env . ALIYUN_API_KEY
131+ || 'sk-4c89a24b73d24731b86bf26337398cef'
132+ } ,
133+
13134 terminal : {
14- maxSessions : parseInt ( process . env . MAX_TERMINAL_SESSIONS ) || 10 ,
15- timeout : parseInt ( process . env . TERMINAL_TIMEOUT ) || 600000
135+ maxSessions : toNumber ( process . env . MAX_TERMINAL_SESSIONS , 10 ) ,
136+ timeout : toNumber ( process . env . TERMINAL_TIMEOUT , 600000 )
16137 } ,
17-
138+
18139 cors : {
19140 origins : process . env . ALLOWED_ORIGINS ?. split ( ',' ) || [
20- 'http://localhost:5173' ,
21- 'http://localhost:5174' ,
141+ 'http://localhost:5173' ,
142+ 'http://localhost:5174' ,
22143 'http://localhost:6000' ,
23144 'http://127.0.0.1:5173' ,
24145 'http://127.0.0.1:5174' ,
@@ -34,16 +155,45 @@ export default {
34155 'http://card.paitongai.com' ,
35156 'https://card.paitongai.com' ,
36157 'http://card.paitongai.com:80' ,
37- 'http://cardapi.paitongai.com' , // 新增API域名
38- 'https://cardapi.paitongai.com' , // 新增API域名HTTPS
39- 'http://aicard.paitongai.com' , // AI卡片域名
40- 'https://aicard.paitongai.com' , // AI卡片域名HTTPS
158+ 'http://cardapi.paitongai.com' ,
159+ 'https://cardapi.paitongai.com' ,
160+ 'http://aicard.paitongai.com' ,
161+ 'https://aicard.paitongai.com' ,
41162 'http://ai-terminal-xnbmzvtedv.ap-northeast-1.fcapp.run' ,
42163 'https://ai-terminal-xnbmzvtedv.ap-northeast-1.fcapp.run'
43164 ]
44165 } ,
45-
166+
46167 logging : {
47168 level : process . env . LOG_LEVEL || 'info'
48- }
49- }
169+ } ,
170+
171+ llm : {
172+ qwen : {
173+ enabled : true ,
174+ apiKey : process . env . QWEN_API_KEY || 'sk-4c89a24b73d24731b86bf26337398cef' ,
175+ baseUrl : 'https://dashscope.aliyuncs.com/api/v1' ,
176+ model : 'qwen-max' ,
177+ temperature : 0.3 ,
178+ maxTokens : 8000 ,
179+ timeout : 90 ,
180+ maxRetries : 3 ,
181+ retryDelay : 3.0
182+ } ,
183+ 'qwen-plus' : {
184+ enabled : true ,
185+ apiKey : process . env . QWEN_PLUS_API_KEY || 'sk-4c89a24b73d24731b86bf26337398cef' ,
186+ baseUrl : 'https://dashscope.aliyuncs.com/api/v1' ,
187+ model : 'qwen-plus' ,
188+ temperature : 0.3 ,
189+ maxTokens : 8000 ,
190+ timeout : 90 ,
191+ maxRetries : 3 ,
192+ retryDelay : 3.0 ,
193+ description : '阿里云通义千问Plus - 平衡性能和成本'
194+ }
195+ } ,
196+
197+ youtube2post : youtube2postConfig ,
198+ videoDecompose : videoDecomposeConfig
199+ }
0 commit comments