mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
- Consolidated loading styles in loading.less for better structure. - Updated mask styles in mask.less to enhance clarity. - Refined mini popup styles in mini.less, adding transition effects. - Improved mixins in mixins.less for consistent styling across components. - Adjusted mobile styles in mobile.less for better responsiveness. - Enhanced notice styles in notice.less for improved visibility. - Streamlined plugin styles in plugins.less for better organization. - Simplified poster styles in poster.less for clarity. - Refined progress styles in progress.less, enhancing hover effects. - Updated selector styles in selector.less for better interaction. - Improved settings styles in setting.less for better usability. - Enhanced subtitle styles in subtitle.less for better visibility. - Updated variable definitions in variables.less for consistency. - Refined video styles in video.less for improved layout. - Enhanced volume control styles in volume.less for better user experience. - Optimized rate limiting logic in build-llm.js and trans-docs.js for better performance.
192 lines
6.3 KiB
JavaScript
192 lines
6.3 KiB
JavaScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import process from 'node:process'
|
|
import { fileURLToPath } from 'node:url'
|
|
import dotenv from 'dotenv'
|
|
import { glob } from 'glob'
|
|
|
|
dotenv.config()
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const rootDir = path.resolve(__dirname, '../packages/artplayer-vitepress')
|
|
const dostDir = path.resolve(__dirname, '../docs')
|
|
const outputFile = path.resolve(__dirname, '../docs/llms.txt')
|
|
const API_URL = 'https://api.deepseek.com/v1/chat/completions'
|
|
const API_KEY = process.env.DEEPSEEK_API_KEY
|
|
const MAX_CONCURRENT_REQUESTS = 3 // 每类并发请求数
|
|
const REQUEST_TIMEOUT = 60000 // 请求超时时间 60秒
|
|
const MAX_RETRIES = 3 // 最大重试次数
|
|
|
|
if (!API_KEY) {
|
|
console.error('❌ Missing DEEPSEEK_API_KEY in .env file')
|
|
process.exit(1)
|
|
}
|
|
|
|
async function getFiles(pattern) {
|
|
const files = await glob(pattern.replace(/\\/g, '/'), { nodir: true })
|
|
return files.sort()
|
|
}
|
|
|
|
function readFiles(files) {
|
|
let content = ''
|
|
for (const f of files) {
|
|
console.log(`📄 Reading: ${f}`)
|
|
content += `\n\n===== ${path.basename(f)} =====\n\n`
|
|
content += fs.readFileSync(f, 'utf8')
|
|
}
|
|
return content
|
|
}
|
|
|
|
function splitText(text, maxLen = 8000) {
|
|
const paragraphs = text.split(/\n{2,}/)
|
|
const chunks = []
|
|
let buffer = ''
|
|
for (const p of paragraphs) {
|
|
if ((`${buffer}\n\n${p}`).length > maxLen) {
|
|
chunks.push(buffer)
|
|
buffer = ''
|
|
}
|
|
buffer += `\n\n${p}`
|
|
}
|
|
if (buffer.trim())
|
|
chunks.push(buffer)
|
|
return chunks
|
|
}
|
|
|
|
async function askDeepSeek(prompt, text, tag, id) {
|
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
try {
|
|
console.log(`🧠 [${tag}] Sending chunk ${id} (attempt ${attempt}/${MAX_RETRIES})`)
|
|
const controller = new AbortController()
|
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT)
|
|
|
|
const res = await fetch(API_URL, {
|
|
method: 'POST',
|
|
signal: controller.signal,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${API_KEY}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'deepseek-chat',
|
|
messages: [
|
|
{ role: 'system', content: 'You are a professional technical writer.' },
|
|
{ role: 'user', content: `${prompt}\n\n${text}` },
|
|
],
|
|
temperature: 0.3,
|
|
}),
|
|
})
|
|
clearTimeout(timeout)
|
|
|
|
// 处理限流情况
|
|
if (res.status === 429) {
|
|
const delay = 2 ** attempt * 1000
|
|
console.log(`⏳ [${tag}] Rate limited, waiting ${delay}ms...`)
|
|
await new Promise(r => setTimeout(r, delay))
|
|
continue
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const err = await res.text()
|
|
console.error(`[${tag}] API Error:`, err)
|
|
throw new Error(`DeepSeek API error: ${res.status}`)
|
|
}
|
|
|
|
const data = await res.json()
|
|
if (!data.choices || !data.choices[0]) {
|
|
throw new Error('Invalid API response: missing choices')
|
|
}
|
|
return data.choices[0].message?.content?.trim() || ''
|
|
}
|
|
catch (err) {
|
|
if (err.name === 'AbortError') {
|
|
console.error(`[${tag}] Request timeout for chunk ${id}`)
|
|
}
|
|
if (attempt === MAX_RETRIES) {
|
|
throw err
|
|
}
|
|
const delay = 1000 * attempt
|
|
console.log(`⚠️ [${tag}] Retry ${attempt}/${MAX_RETRIES} after ${delay}ms...`)
|
|
await new Promise(r => setTimeout(r, delay))
|
|
}
|
|
}
|
|
}
|
|
|
|
async function runConcurrent(tasks, limit = MAX_CONCURRENT_REQUESTS) {
|
|
const results = []
|
|
const executing = new Set()
|
|
for (const task of tasks) {
|
|
const p = task().finally(() => executing.delete(p))
|
|
results.push(p)
|
|
executing.add(p)
|
|
if (executing.size >= limit) {
|
|
await Promise.race(executing)
|
|
}
|
|
}
|
|
return Promise.all(results)
|
|
}
|
|
|
|
async function summarizeSection(title, files, type) {
|
|
if (files.length === 0)
|
|
return ''
|
|
const text = readFiles(files)
|
|
const chunks = splitText(text, 8000)
|
|
|
|
let prompt = ''
|
|
if (type === 'docs') {
|
|
prompt = `
|
|
You are preparing documentation content for an AI model to learn ArtPlayer.
|
|
Reorganize the following documentation into a clean, readable plain text format.
|
|
Keep all code blocks, examples, and configuration options fully intact.
|
|
Add minimal explanations or clarifications in natural English before or after each block if helpful.
|
|
Do not use Markdown syntax (no #, **, or lists). Preserve indentation for code.`
|
|
}
|
|
else if (type === 'ts') {
|
|
prompt = `
|
|
You are analyzing TypeScript declaration files that define the APIs of ArtPlayer.
|
|
Your goal is to produce a developer-oriented plain text output that preserves the actual type definitions,
|
|
interfaces, classes, and comments exactly as they are, while adding concise explanations
|
|
above or below them when necessary to clarify their purpose or usage.
|
|
Keep all code in the output, formatted as plain text (no Markdown).`
|
|
}
|
|
else if (type === 'js') {
|
|
prompt = `
|
|
You are analyzing example JavaScript files demonstrating how to use ArtPlayer and its plugins.
|
|
Produce a plain text output that includes the full example code and short inline explanations
|
|
describing what each example shows, which APIs it uses, and what feature it demonstrates.
|
|
Keep all code exactly as-is, formatted as plain text, without Markdown syntax.`
|
|
}
|
|
|
|
const tasks = chunks.map((chunk, i) => () => askDeepSeek(prompt, chunk, type, i + 1))
|
|
const results = await runConcurrent(tasks, MAX_CONCURRENT_REQUESTS)
|
|
const merged = results.join('\n\n')
|
|
return `\n\n===== ${title} =====\n\n${merged}`
|
|
}
|
|
|
|
async function main() {
|
|
console.log('🚀 Building llms.txt using DeepSeek...')
|
|
|
|
const [mdFiles, tsFiles, jsFiles] = await Promise.all([
|
|
getFiles(`${rootDir}/docs/en/**/*.md`),
|
|
getFiles(`${dostDir}/assets/ts/*.d.ts`),
|
|
getFiles(`${dostDir}/assets/example/*.js`),
|
|
])
|
|
|
|
// 并行执行三个大类任务
|
|
const [docs, ts, js] = await Promise.all([
|
|
summarizeSection('Documentation Summary', mdFiles, 'docs'),
|
|
summarizeSection('Type Definitions Overview', tsFiles, 'ts'),
|
|
summarizeSection('Examples Summary', jsFiles, 'js'),
|
|
])
|
|
|
|
const finalText = [docs, ts, js].filter(Boolean).join('\n\n')
|
|
fs.mkdirSync(path.dirname(outputFile), { recursive: true })
|
|
fs.writeFileSync(outputFile, finalText, 'utf8')
|
|
console.log(`✅ LLMs text built successfully at ${outputFile}`)
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('❌ Build failed:', err)
|
|
process.exit(1)
|
|
})
|