refactor: improve codebase structure and Univer integration

- Add AGENTS.md knowledge base with project documentation
- Move UserPreferences model to separate models.py file
- Extract API_KEY to environment variable for security
- Enhance Univer Editor with PPTX support and improved UI
- Improve file system handling with binary file detection
- Add HF_ENDPOINT mirror for better China connectivity
- Clean up unused imports and code structure
This commit is contained in:
2026-04-11 09:24:14 +08:00
parent 2fdc996af9
commit d8b7832b14
18 changed files with 901 additions and 544 deletions
+51 -48
View File
@@ -35,15 +35,20 @@ async function withStore(mode, handler) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, mode)
const store = transaction.objectStore(STORE_NAME)
let result
let request
try {
result = handler(store)
request = handler(store)
} catch (error) {
reject(error)
return
}
transaction.oncomplete = () => resolve(result)
transaction.onerror = () => reject(transaction.error || new Error('本地数据库写入失败'))
if (request && typeof request.onsuccess === 'function') {
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error || new Error('本地数据库操作失败'))
} else {
transaction.oncomplete = () => resolve(request)
transaction.onerror = () => reject(transaction.error || new Error('本地数据库操作失败'))
}
transaction.onabort = () => reject(transaction.error || new Error('本地数据库操作已取消'))
})
}
@@ -62,43 +67,32 @@ function getExtension(name = '') {
}
function isTextExtension(ext) {
return [
'md',
'markdown',
'txt',
'json',
'js',
'jsx',
'ts',
'tsx',
'css',
'scss',
'less',
'html',
'htm',
'py',
'vue',
'xml',
'yaml',
'yml',
'csv',
'log',
'sql',
'toml',
'ini',
'cfg',
'conf',
'sh',
'bat',
'ps1',
'java',
'c',
'cpp',
'h',
'hpp',
'go',
'rs'
].includes(ext)
const textExtensions = [
'md', 'markdown', 'txt', 'json', 'js', 'jsx', 'ts', 'tsx',
'css', 'scss', 'less', 'html', 'htm', 'py', 'vue', 'xml',
'yaml', 'yml', 'csv', 'log', 'sql', 'toml', 'ini', 'cfg',
'conf', 'sh', 'bat', 'ps1', 'java', 'c', 'cpp', 'h', 'hpp',
'go', 'rs', 'swift', 'kt', 'rb', 'php', 'pl', 'r', 'scala',
'gradle', 'properties', 'env', 'gitignore', 'dockerfile'
]
return textExtensions.includes(ext)
}
function isBinaryExtension(ext) {
const binaryExtensions = [
'exe', 'dll', 'so', 'dylib', 'bin', 'dat', 'obj', 'o', 'a',
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp',
'pdf', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz',
'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', 'svg',
'mp3', 'mp4', 'wav', 'avi', 'mov', 'mkv', 'flv', 'wmv',
'ttf', 'otf', 'woff', 'woff2', 'eot',
'class', 'pyc', 'pyo', 'jar', 'war', 'ear',
'db', 'sqlite', 'mdb', 'accdb',
'pem', 'key', 'crt', 'cer', 'p12', 'pfx', 'jks',
'msg', 'eml', 'pst', 'ost',
'dwg', 'dxf', 'step', 'stl', 'obj', 'fbx', '3ds', 'blend'
]
return binaryExtensions.includes(ext.toLowerCase())
}
function inferMimeType(name, fallback = '') {
@@ -143,6 +137,8 @@ function inferMimeType(name, fallback = '') {
function isTextFile(record) {
const ext = getExtension(record?.name)
// 二进制文件不提供预览
if (isBinaryExtension(ext)) return false
const mime = String(record?.mimeType || '')
return isTextExtension(ext) || mime.startsWith('text/') || mime.includes('json') || mime.includes('xml')
}
@@ -218,6 +214,7 @@ async function readFilePayload(file) {
const mimeType = inferMimeType(file.name, file.type)
const ext = getExtension(file.name)
const textFile = isTextExtension(ext) || mimeType.startsWith('text/') || mimeType.includes('json') || mimeType.includes('xml')
if (!textFile) {
return {
mimeType,
@@ -226,6 +223,17 @@ async function readFilePayload(file) {
blob: file
}
}
// 二进制扩展名文件不尝试读取内容,避免长时间等待
if (isBinaryExtension(ext)) {
return {
mimeType,
size: file.size,
storageKind: 'blob',
blob: file
}
}
if (file.size <= MAX_TEXT_SIZE) {
const content = await file.text()
return {
@@ -314,13 +322,8 @@ export function useFileSystem() {
async function load() {
loading.value = true
try {
const stored = await withStore('readonly', (store) => store.getAll())
const request = stored
const nextRecords = await new Promise((resolve, reject) => {
request.onsuccess = () => resolve(Array.isArray(request.result) ? request.result : [])
request.onerror = () => reject(request.error || new Error('读取本地文件失败'))
})
if (nextRecords.length === 0) {
const nextRecords = await withStore('readonly', (store) => store.getAll())
if (!Array.isArray(nextRecords) || nextRecords.length === 0) {
const seed = createWelcomeRecords()
await Promise.all(seed.map((record) => persistRecord(record)))
records.value = seed