feat: add document translation via DIFY AI API

Implement document translation feature using DIFY AI API with batch processing:

Backend:
- Add DIFY client with batch translation support (5000 chars, 20 items per batch)
- Add translation service with element extraction and result building
- Add translation router with start/status/result/list/delete endpoints
- Add translation schemas (TranslationRequest, TranslationStatus, etc.)

Frontend:
- Enable translation UI in TaskDetailPage
- Add translation API methods to apiV2.ts
- Add translation types

Features:
- Batch translation with numbered markers [1], [2], [3]...
- Support for text, title, header, footer, paragraph, footnote, table cells
- Translation result JSON with statistics (tokens, latency, batch_count)
- Background task processing with progress tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-02 11:57:02 +08:00
parent 87dc97d951
commit 8d9b69ba93
18 changed files with 2970 additions and 26 deletions

View File

@@ -35,6 +35,11 @@ import type {
DocumentAnalysisResponse,
PreprocessingPreviewRequest,
PreprocessingPreviewResponse,
TranslationRequest,
TranslationStartResponse,
TranslationStatusResponse,
TranslationListResponse,
TranslationResult,
} from '@/types/apiV2'
/**
@@ -613,6 +618,74 @@ class ApiClientV2 {
)
return response.data
}
// ==================== Translation APIs ====================
/**
* Start a translation job
*/
async startTranslation(taskId: string, request: TranslationRequest): Promise<TranslationStartResponse> {
const response = await this.client.post<TranslationStartResponse>(
`/translate/${taskId}`,
request
)
return response.data
}
/**
* Get translation status
*/
async getTranslationStatus(taskId: string): Promise<TranslationStatusResponse> {
const response = await this.client.get<TranslationStatusResponse>(
`/translate/${taskId}/status`
)
return response.data
}
/**
* Get translation result
*/
async getTranslationResult(taskId: string, lang: string): Promise<TranslationResult> {
const response = await this.client.get<TranslationResult>(
`/translate/${taskId}/result`,
{ params: { lang } }
)
return response.data
}
/**
* Download translation result as JSON file
*/
async downloadTranslation(taskId: string, lang: string): Promise<void> {
const response = await this.client.get(`/translate/${taskId}/result`, {
params: { lang },
responseType: 'blob',
})
const blob = new Blob([response.data], { type: 'application/json' })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = `${taskId}_translated_${lang}.json`
link.click()
window.URL.revokeObjectURL(link.href)
}
/**
* List available translations for a task
*/
async listTranslations(taskId: string): Promise<TranslationListResponse> {
const response = await this.client.get<TranslationListResponse>(
`/translate/${taskId}/translations`
)
return response.data
}
/**
* Delete a translation
*/
async deleteTranslation(taskId: string, lang: string): Promise<void> {
await this.client.delete(`/translate/${taskId}/translations/${lang}`)
}
}
// Export singleton instance