mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 13:56:15 -08:00
refactor(docs): [SITE-AI-DOCS-01] make generation offline and translation draft based
This commit is contained in:
1 parent
647f3e7f12
commit
078cd9ef72
27 files changed
+8642
-4263
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { setTimeout as delay } from 'node:timers/promises'
|
||||
|
||||
interface RemoteOptions {
|
||||
key: string
|
||||
request?: typeof fetch
|
||||
signal?: AbortSignal
|
||||
timeoutMs?: number
|
||||
retries?: number
|
||||
wait?: (ms: number) => Promise<unknown>
|
||||
}
|
||||
|
||||
function object(value: unknown): Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object'
|
||||
? (value as Record<string, unknown>)
|
||||
: {}
|
||||
}
|
||||
|
||||
export async function translateChunk(
|
||||
content: string,
|
||||
options: RemoteOptions,
|
||||
): Promise<string> {
|
||||
assert(options.key, 'Missing DEEPSEEK_API_KEY')
|
||||
const retries = options.retries ?? 3
|
||||
assert(
|
||||
Number.isInteger(retries) && retries > 0 && retries <= 5,
|
||||
'Invalid retry limit',
|
||||
)
|
||||
const timeoutMs = options.timeoutMs ?? 60000
|
||||
assert(
|
||||
Number.isFinite(timeoutMs) && timeoutMs > 0,
|
||||
'Invalid request timeout',
|
||||
)
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
options.signal?.throwIfAborted()
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(
|
||||
() => controller.abort(new Error('Translation request timed out')),
|
||||
timeoutMs,
|
||||
)
|
||||
const signal = options.signal
|
||||
? AbortSignal.any([controller.signal, options.signal])
|
||||
: controller.signal
|
||||
let retry = false
|
||||
try {
|
||||
const response = await (options.request ?? fetch)(
|
||||
'https://api.deepseek.com/v1/chat/completions',
|
||||
{
|
||||
method: 'POST',
|
||||
signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${options.key}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.2,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `Translate this Chinese technical Markdown to English. Preserve structure, inline code, URLs and every ARTPLAYER_KEEP marker exactly once. Return only Markdown, without an outer code fence or explanations.\n\n${content}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
)
|
||||
retry = response.status === 429 || response.status >= 500
|
||||
if (!response.ok)
|
||||
await response.body?.cancel()
|
||||
assert(response.ok, `Translation HTTP ${response.status}`)
|
||||
const data: unknown = await response.json().catch((error: unknown) => {
|
||||
if (signal.aborted)
|
||||
throw error
|
||||
assert.fail('Invalid translation JSON response')
|
||||
})
|
||||
const choices = object(data).choices
|
||||
const text = object(
|
||||
object(Array.isArray(choices) ? choices[0] : undefined).message,
|
||||
).content
|
||||
assert(
|
||||
typeof text === 'string' && text.trim(),
|
||||
'Invalid or empty translation response',
|
||||
)
|
||||
return text.trim()
|
||||
}
|
||||
catch (error) {
|
||||
if (options.signal?.aborted)
|
||||
throw error
|
||||
if (!(error instanceof assert.AssertionError))
|
||||
retry = true
|
||||
if (!retry || attempt === retries)
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
await (options.wait ?? delay)(500 * attempt)
|
||||
}
|
||||
throw new Error('Translation retries exhausted')
|
||||
}
|
||||
Reference in new issue
Block a user