@@ -4,9 +4,12 @@ const API = ''; // same origin
44// Canonical external AI gateway for this project: Worker custom domain -> service `apis` / production.
55const AI_API = 'https://ai.bdfz.net/' ;
66const AI_CHAT_MODEL = 'gemini-flash-latest' ;
7- const AI_REQUEST_TIMEOUT_MS = 20000 ;
7+ const AI_CONTEXT_TIMEOUT_MS = 15000 ;
8+ const AI_SERVER_CHAT_TIMEOUT_MS = 45000 ;
9+ const AI_DIRECT_REQUEST_TIMEOUT_MS = 25000 ;
10+ const AI_SERVER_CHAT_RETRIES = 1 ;
811const IMG_CDN = 'https://img.rdfzer.com' ;
9- const DEFAULT_FRONTEND_VERSION = '2026.03.06-r11 ' ;
12+ const DEFAULT_FRONTEND_VERSION = '2026.03.06-r12 ' ;
1013const FRONTEND_VERSION_FILE = '/assets/version.json' ;
1114const AI_MEMORY_LIMIT = 12 ;
1215const DOWNLOADABLE_LIBRARY_BOOKS = 316 ;
@@ -59,7 +62,7 @@ function logClientAIChat({ query, userMessage, summary, provider, success, error
5962 } ) . catch ( ( ) => { } ) ;
6063}
6164
62- async function fetchWithTimeout ( url , options = { } , timeoutMs = AI_REQUEST_TIMEOUT_MS ) {
65+ async function fetchWithTimeout ( url , options = { } , timeoutMs = AI_SERVER_CHAT_TIMEOUT_MS ) {
6366 const controller = new AbortController ( ) ;
6467 const timer = setTimeout ( ( ) => controller . abort ( ) , timeoutMs ) ;
6568 try {
@@ -131,14 +134,37 @@ async function fetchAIContext(userMessage, history) {
131134 user_message : userMessage ,
132135 history,
133136 } ) ,
134- } ) ;
137+ } , AI_CONTEXT_TIMEOUT_MS ) ;
135138 const { data, raw } = await readJsonLike ( res ) ;
136139 if ( ! res . ok || ! data ) {
137140 throw new Error ( responseErrorMessage ( res , data , raw , '上下文构建失败' ) ) ;
138141 }
139142 return data ;
140143}
141144
145+ async function requestServerChat ( payload ) {
146+ let lastError = null ;
147+ for ( let attempt = 0 ; attempt <= AI_SERVER_CHAT_RETRIES ; attempt += 1 ) {
148+ try {
149+ const res = await fetchWithTimeout ( `${ API } /api/chat` , {
150+ method : 'POST' ,
151+ headers : { 'Content-Type' : 'application/json' } ,
152+ body : JSON . stringify ( payload ) ,
153+ } , AI_SERVER_CHAT_TIMEOUT_MS ) ;
154+ const { data, raw } = await readJsonLike ( res ) ;
155+ if ( ! res . ok || ! data ) {
156+ throw new Error ( responseErrorMessage ( res , data , raw , 'AI 对话失败' ) ) ;
157+ }
158+ return data ;
159+ } catch ( error ) {
160+ lastError = error ;
161+ if ( attempt >= AI_SERVER_CHAT_RETRIES ) break ;
162+ await new Promise ( resolve => setTimeout ( resolve , 350 ) ) ;
163+ }
164+ }
165+ throw lastError || new Error ( 'AI 对话失败' ) ;
166+ }
167+
142168function buildClientAIPrompt ( userMessage , contextPayload , history ) {
143169 const historyText = ( contextPayload . history_text || '' ) . trim ( )
144170 || history . map ( msg => `${ msg . role === 'user' ? '用户' : '助手' } : ${ msg . content } ` ) . join ( '\n' )
@@ -324,19 +350,11 @@ async function sendAIMessage(userMessage) {
324350 let usedBrowserFallback = false ;
325351
326352 try {
327- const res = await fetchWithTimeout ( `${ API } /api/chat` , {
328- method : 'POST' ,
329- headers : { 'Content-Type' : 'application/json' } ,
330- body : JSON . stringify ( {
331- query : currentQuery ,
332- user_message : cleanMessage ,
333- history,
334- } ) ,
353+ const data = await requestServerChat ( {
354+ query : currentQuery ,
355+ user_message : cleanMessage ,
356+ history,
335357 } ) ;
336- const { data, raw } = await readJsonLike ( res ) ;
337- if ( ! res . ok || ! data ) {
338- throw new Error ( responseErrorMessage ( res , data , raw , 'AI 对话失败' ) ) ;
339- }
340358 answer = data . answer || '' ;
341359 contextPayload = data . context || { } ;
342360 setSearchAIProviderLabel ( data . provider || aiProviderLabel ) ;
@@ -353,7 +371,7 @@ async function sendAIMessage(userMessage) {
353371 taskType : 'chat' ,
354372 thinkingLevel : 'low' ,
355373 } ) ,
356- } ) ;
374+ } , AI_DIRECT_REQUEST_TIMEOUT_MS ) ;
357375 const { data : aiData , raw : aiRaw } = await readJsonLike ( aiRes ) ;
358376 if ( ! aiRes . ok || ! aiData ?. answer ) {
359377 throw new Error ( responseErrorMessage ( aiRes , aiData , aiRaw , serverChatError . message || 'AI 服务错误' ) ) ;
@@ -438,19 +456,17 @@ async function loadFrontendVersion() {
438456 if ( ! footer ) return ;
439457
440458 let version = DEFAULT_FRONTEND_VERSION ;
441- let updatedAt = '' ;
442459 try {
443460 const res = await fetch ( `${ FRONTEND_VERSION_FILE } ?v=${ Date . now ( ) } ` , { cache : 'no-store' } ) ;
444461 if ( res . ok ) {
445462 const data = await res . json ( ) ;
446463 version = data . frontend_refactor_version || version ;
447- updatedAt = data . updated_at || '' ;
448464 }
449465 } catch ( _ ) {
450466 // fallback to default version
451467 }
452468
453- footer . textContent = `重构版本 ${ version } ${ updatedAt ? ` · ${ updatedAt } ` : '' } ` ;
469+ footer . textContent = `重构版本 ${ version } ` ;
454470}
455471
456472loadFrontendVersion ( ) ;
@@ -1850,7 +1866,7 @@ ${crossSubjectContext || '(未找到直接跨学科教材)'}
18501866 taskType : 'chat' ,
18511867 thinkingLevel : 'low' ,
18521868 } ) ,
1853- } ) ;
1869+ } , AI_DIRECT_REQUEST_TIMEOUT_MS ) ;
18541870 const { data : aiData , raw : aiRaw } = await readJsonLike ( aiRes ) ;
18551871 if ( ! aiRes . ok || ! aiData ) {
18561872 throw new Error ( responseErrorMessage ( aiRes , aiData , aiRaw , 'AI 服务错误' ) ) ;
@@ -1907,6 +1923,11 @@ function renderMath(el) {
19071923 { left : '\\[' , right : '\\]' , display : true }
19081924 ] ,
19091925 throwOnError : false ,
1926+ strict : ( errorCode ) => (
1927+ errorCode === 'unicodeTextInMathMode' || errorCode === 'mathVsTextAccents'
1928+ ? 'ignore'
1929+ : 'warn'
1930+ ) ,
19101931 errorColor : '#e74c3c'
19111932 } ) ;
19121933 }
0 commit comments