refactor: remove unused code and migrate legacy API
Backend cleanup: - Remove ocr_service_original.py (legacy OCR service, replaced by ocr_service.py) - Remove preprocessor.py (unused, functionality absorbed by layout_preprocessing_service.py) - Remove pdf_font_manager.py (unused, never referenced by any service) Frontend cleanup: - Remove MarkdownPreview.tsx (unused component) - Remove ResultsTable.tsx (unused, replaced by TaskHistoryPage) - Remove services/api.ts (legacy API client, migrated to apiV2) - Remove types/api.ts (legacy types, migrated to apiV2.ts) API migration: - Add export rules CRUD methods to apiClientV2 - Update SettingsPage.tsx to use apiClientV2 - Update Layout.tsx to use only apiClientV2 for logout This reduces ~1,500 lines of redundant code and unifies the API client. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Outlet, NavLink, useNavigate } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { apiClient } from '@/services/api'
|
||||
import { apiClientV2 } from '@/services/apiV2'
|
||||
import {
|
||||
Upload,
|
||||
@@ -29,12 +28,7 @@ export default function Layout() {
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
// Use V2 API if authenticated with V2
|
||||
if (apiClientV2.isAuthenticated()) {
|
||||
await apiClientV2.logout()
|
||||
} else {
|
||||
apiClient.logout()
|
||||
}
|
||||
await apiClientV2.logout()
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error)
|
||||
} finally {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
interface MarkdownPreviewProps {
|
||||
title?: string
|
||||
content: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function MarkdownPreview({ title, content, className }: MarkdownPreviewProps) {
|
||||
return (
|
||||
<Card className={className}>
|
||||
{title && (
|
||||
<CardHeader>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardContent>
|
||||
<div className="prose prose-sm max-w-none dark:prose-invert">
|
||||
<pre className="whitespace-pre-wrap break-words bg-muted p-4 rounded-md overflow-auto max-h-[600px]">
|
||||
{content}
|
||||
</pre>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { FileResult } from '@/types/apiV2'
|
||||
|
||||
interface ResultsTableProps {
|
||||
files: FileResult[]
|
||||
onViewResult?: (fileId: number) => void
|
||||
onDownloadPDF?: (fileId: number) => void
|
||||
}
|
||||
|
||||
export default function ResultsTable({ files, onViewResult, onDownloadPDF }: ResultsTableProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getStatusBadge = (status: FileResult['status']) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return <Badge variant="success">{t('processing.completed')}</Badge>
|
||||
case 'processing':
|
||||
return <Badge variant="default">{t('processing.processing')}</Badge>
|
||||
case 'failed':
|
||||
return <Badge variant="destructive">{t('processing.failed')}</Badge>
|
||||
default:
|
||||
return <Badge variant="secondary">{t('processing.pending')}</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
const formatTime = (seconds?: number) => {
|
||||
if (!seconds) return 'N/A'
|
||||
return `${seconds.toFixed(2)}s`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t('results.filename')}</TableHead>
|
||||
<TableHead>{t('results.status')}</TableHead>
|
||||
<TableHead>{t('results.processingTime')}</TableHead>
|
||||
<TableHead className="text-right">{t('results.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{files.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center text-muted-foreground">
|
||||
{t('results.noResults')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
files.map((file) => (
|
||||
<TableRow key={file.id}>
|
||||
<TableCell className="font-medium">{file.filename}</TableCell>
|
||||
<TableCell>{getStatusBadge(file.status)}</TableCell>
|
||||
<TableCell>{formatTime(file.processing_time)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
{file.status === 'completed' && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onViewResult?.(file.id)}
|
||||
>
|
||||
{t('results.viewMarkdown')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onDownloadPDF?.(file.id)}
|
||||
>
|
||||
{t('results.downloadPDF')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{file.status === 'failed' && file.error && (
|
||||
<span className="text-sm text-destructive">{file.error}</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useToast } from '@/components/ui/toast'
|
||||
import { apiClient } from '@/services/api'
|
||||
import { apiClientV2 } from '@/services/apiV2'
|
||||
import type { ExportRule } from '@/types/apiV2'
|
||||
|
||||
export default function SettingsPage() {
|
||||
@@ -25,12 +25,12 @@ export default function SettingsPage() {
|
||||
// Fetch export rules
|
||||
const { data: exportRules, isLoading } = useQuery({
|
||||
queryKey: ['exportRules'],
|
||||
queryFn: () => apiClient.getExportRules(),
|
||||
queryFn: () => apiClientV2.getExportRules(),
|
||||
})
|
||||
|
||||
// Create rule mutation
|
||||
const createRuleMutation = useMutation({
|
||||
mutationFn: (rule: any) => apiClient.createExportRule(rule),
|
||||
mutationFn: (rule: any) => apiClientV2.createExportRule(rule),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['exportRules'] })
|
||||
setIsCreating(false)
|
||||
@@ -53,7 +53,7 @@ export default function SettingsPage() {
|
||||
// Update rule mutation
|
||||
const updateRuleMutation = useMutation({
|
||||
mutationFn: ({ ruleId, rule }: { ruleId: number; rule: any }) =>
|
||||
apiClient.updateExportRule(ruleId, rule),
|
||||
apiClientV2.updateExportRule(ruleId, rule),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['exportRules'] })
|
||||
setEditingRule(null)
|
||||
@@ -75,7 +75,7 @@ export default function SettingsPage() {
|
||||
|
||||
// Delete rule mutation
|
||||
const deleteRuleMutation = useMutation({
|
||||
mutationFn: (ruleId: number) => apiClient.deleteExportRule(ruleId),
|
||||
mutationFn: (ruleId: number) => apiClientV2.deleteExportRule(ruleId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['exportRules'] })
|
||||
toast({
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import axios, { AxiosError } from 'axios'
|
||||
import type { AxiosInstance } from 'axios'
|
||||
import type {
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
UploadResponse,
|
||||
ProcessRequest,
|
||||
ProcessResponse,
|
||||
BatchStatus,
|
||||
OCRResult,
|
||||
ExportRequest,
|
||||
ExportRule,
|
||||
CSSTemplate,
|
||||
TranslateRequest,
|
||||
TranslateResponse,
|
||||
ApiError,
|
||||
} from '@/types/api'
|
||||
|
||||
/**
|
||||
* API Client Configuration
|
||||
* - In Docker: VITE_API_BASE_URL is empty string, use relative path
|
||||
* - In development: Use VITE_API_BASE_URL from .env or default to localhost:8000
|
||||
*/
|
||||
const envApiBaseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
const API_BASE_URL = envApiBaseUrl !== undefined ? envApiBaseUrl : 'http://localhost:8000'
|
||||
const API_VERSION = 'v2'
|
||||
|
||||
class ApiClient {
|
||||
private client: AxiosInstance
|
||||
private token: string | null = null
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
baseURL: `${API_BASE_URL}/api/${API_VERSION}`,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// Request interceptor to add auth token
|
||||
this.client.interceptors.request.use(
|
||||
(config) => {
|
||||
if (this.token) {
|
||||
config.headers.Authorization = `Bearer ${this.token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
|
||||
// Response interceptor for error handling
|
||||
this.client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error: AxiosError<ApiError>) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token expired or invalid
|
||||
this.clearToken()
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// Load token from localStorage
|
||||
this.loadToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set authentication token
|
||||
*/
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
localStorage.setItem('auth_token', token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication token
|
||||
*/
|
||||
clearToken() {
|
||||
this.token = null
|
||||
localStorage.removeItem('auth_token')
|
||||
}
|
||||
|
||||
/**
|
||||
* Load token from localStorage
|
||||
*/
|
||||
private loadToken() {
|
||||
const token = localStorage.getItem('auth_token')
|
||||
if (token) {
|
||||
this.token = token
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
return this.token !== null
|
||||
}
|
||||
|
||||
// ==================== Authentication ====================
|
||||
|
||||
/**
|
||||
* Login
|
||||
*/
|
||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await this.client.post<LoginResponse>('/auth/login', {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
})
|
||||
|
||||
this.setToken(response.data.access_token)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
logout() {
|
||||
this.clearToken()
|
||||
}
|
||||
|
||||
// ==================== File Upload ====================
|
||||
|
||||
/**
|
||||
* Upload files
|
||||
*/
|
||||
async uploadFiles(files: File[]): Promise<UploadResponse> {
|
||||
const formData = new FormData()
|
||||
files.forEach((file) => {
|
||||
formData.append('files', file)
|
||||
})
|
||||
|
||||
const response = await this.client.post<UploadResponse>('/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
// ==================== OCR Processing ====================
|
||||
|
||||
/**
|
||||
* Process OCR
|
||||
*/
|
||||
async processOCR(data: ProcessRequest): Promise<ProcessResponse> {
|
||||
const response = await this.client.post<ProcessResponse>('/ocr/process', data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OCR result by file ID
|
||||
* Note: Backend uses file-level tracking, not task-level
|
||||
*/
|
||||
async getOCRResult(fileId: number): Promise<OCRResult> {
|
||||
const response = await this.client.get<OCRResult>(`/ocr/result/${fileId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch status
|
||||
*/
|
||||
async getBatchStatus(batchId: number): Promise<BatchStatus> {
|
||||
const response = await this.client.get<BatchStatus>(`/batch/${batchId}/status`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// ==================== Export ====================
|
||||
|
||||
/**
|
||||
* Export results
|
||||
*/
|
||||
async exportResults(data: ExportRequest): Promise<Blob> {
|
||||
const response = await this.client.post('/export', data, {
|
||||
responseType: 'blob',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and download PDF
|
||||
*/
|
||||
async exportPDF(fileId: number, cssTemplate?: string): Promise<Blob> {
|
||||
const params = cssTemplate ? { css_template: cssTemplate } : {}
|
||||
const response = await this.client.get(`/export/pdf/${fileId}`, {
|
||||
params,
|
||||
responseType: 'blob',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get export rules
|
||||
*/
|
||||
async getExportRules(): Promise<ExportRule[]> {
|
||||
const response = await this.client.get<ExportRule[]>('/export/rules')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create export rule
|
||||
*/
|
||||
async createExportRule(rule: Omit<ExportRule, 'id' | 'created_at'>): Promise<ExportRule> {
|
||||
const response = await this.client.post<ExportRule>('/export/rules', rule)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update export rule
|
||||
*/
|
||||
async updateExportRule(ruleId: number, rule: Partial<ExportRule>): Promise<ExportRule> {
|
||||
const response = await this.client.put<ExportRule>(`/export/rules/${ruleId}`, rule)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete export rule
|
||||
*/
|
||||
async deleteExportRule(ruleId: number): Promise<void> {
|
||||
await this.client.delete(`/export/rules/${ruleId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS templates
|
||||
*/
|
||||
async getCSSTemplates(): Promise<CSSTemplate[]> {
|
||||
const response = await this.client.get<CSSTemplate[]>('/export/css-templates')
|
||||
return response.data
|
||||
}
|
||||
|
||||
// ==================== Translation (FUTURE FEATURE - STUB) ====================
|
||||
|
||||
/**
|
||||
* Translate document (STUB - Not yet implemented)
|
||||
* This is a placeholder for future translation functionality
|
||||
* @throws Will throw error with status 501 (Not Implemented)
|
||||
*/
|
||||
async translateDocument(data: TranslateRequest): Promise<TranslateResponse> {
|
||||
// This endpoint is expected to return 501 Not Implemented until Phase 5
|
||||
const response = await this.client.post<TranslateResponse>('/translate/document', data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation configs (NOT IMPLEMENTED)
|
||||
* This endpoint does not exist on backend - configs will be part of Phase 5
|
||||
* @deprecated Backend endpoint does not exist - will return 404
|
||||
*/
|
||||
// async getTranslationConfigs(): Promise<TranslationConfig[]> {
|
||||
// const response = await this.client.get<TranslationConfig[]>('/translate/configs')
|
||||
// return response.data
|
||||
// }
|
||||
|
||||
/**
|
||||
* Create translation config (NOT IMPLEMENTED)
|
||||
* This endpoint does not exist on backend - configs will be part of Phase 5
|
||||
* @deprecated Backend endpoint does not exist - will return 404
|
||||
*/
|
||||
// async createTranslationConfig(
|
||||
// config: Omit<TranslationConfig, 'id' | 'created_at'>
|
||||
// ): Promise<TranslationConfig> {
|
||||
// const response = await this.client.post<TranslationConfig>('/translate/configs', config)
|
||||
// return response.data
|
||||
// }
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const apiClient = new ApiClient()
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
TranslationStatusResponse,
|
||||
TranslationListResponse,
|
||||
TranslationResult,
|
||||
ExportRule,
|
||||
} from '@/types/apiV2'
|
||||
|
||||
/**
|
||||
@@ -713,6 +714,39 @@ class ApiClientV2 {
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
// ==================== Export Rules APIs ====================
|
||||
|
||||
/**
|
||||
* Get export rules
|
||||
*/
|
||||
async getExportRules(): Promise<ExportRule[]> {
|
||||
const response = await this.client.get<ExportRule[]>('/export/rules')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create export rule
|
||||
*/
|
||||
async createExportRule(rule: Omit<ExportRule, 'id' | 'created_at'>): Promise<ExportRule> {
|
||||
const response = await this.client.post<ExportRule>('/export/rules', rule)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update export rule
|
||||
*/
|
||||
async updateExportRule(ruleId: number, rule: Partial<ExportRule>): Promise<ExportRule> {
|
||||
const response = await this.client.put<ExportRule>(`/export/rules/${ruleId}`, rule)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete export rule
|
||||
*/
|
||||
async deleteExportRule(ruleId: number): Promise<void> {
|
||||
await this.client.delete(`/export/rules/${ruleId}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
/**
|
||||
* API Type Definitions
|
||||
* Based on backend OpenAPI specification
|
||||
*/
|
||||
|
||||
// Authentication
|
||||
export interface LoginRequest {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
access_token: string
|
||||
token_type: string
|
||||
expires_in: number // Token expiration time in seconds
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
username: string
|
||||
email?: string
|
||||
displayName?: string | null
|
||||
}
|
||||
|
||||
// File Upload (V2 API)
|
||||
export interface UploadResponse {
|
||||
task_id: string
|
||||
filename: string
|
||||
file_size: number
|
||||
file_type: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
id: number
|
||||
filename: string
|
||||
file_size: number
|
||||
file_format: string // Changed from 'format' to match backend
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
}
|
||||
|
||||
// OCR Processing
|
||||
export interface ProcessRequest {
|
||||
batch_id: number
|
||||
lang?: string
|
||||
detect_layout?: boolean // Changed from confidence_threshold to match backend
|
||||
}
|
||||
|
||||
export interface ProcessResponse {
|
||||
message: string // Added to match backend
|
||||
batch_id: number
|
||||
total_files: number // Added to match backend
|
||||
status: string
|
||||
// Removed task_id - backend uses batch-level tracking instead
|
||||
}
|
||||
|
||||
export interface TaskStatus {
|
||||
task_id: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
progress_percentage: number
|
||||
current_file?: string
|
||||
files_processed: number
|
||||
total_files: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface BatchStatus {
|
||||
batch: {
|
||||
id: number
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
progress_percentage: number
|
||||
created_at: string
|
||||
completed_at?: string
|
||||
}
|
||||
files: FileResult[]
|
||||
}
|
||||
|
||||
export interface FileResult {
|
||||
id: number
|
||||
filename: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
processing_time?: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
// OCR Results
|
||||
export interface OCRResult {
|
||||
file_id: number
|
||||
filename: string
|
||||
status: string
|
||||
markdown_content: string
|
||||
json_data: OCRJsonData
|
||||
confidence: number
|
||||
processing_time: number
|
||||
}
|
||||
|
||||
export interface OCRJsonData {
|
||||
total_text_regions: number
|
||||
average_confidence: number
|
||||
text_blocks: TextBlock[]
|
||||
layout_info?: LayoutInfo
|
||||
}
|
||||
|
||||
export interface TextBlock {
|
||||
text: string
|
||||
confidence: number
|
||||
bbox: [number, number, number, number]
|
||||
position: number
|
||||
}
|
||||
|
||||
export interface LayoutInfo {
|
||||
tables_detected: number
|
||||
images_detected: number
|
||||
structure: string
|
||||
}
|
||||
|
||||
// Export
|
||||
export interface ExportRequest {
|
||||
batch_id: number
|
||||
format: 'txt' | 'json' | 'excel' | 'markdown' | 'pdf'
|
||||
rule_id?: number
|
||||
options?: ExportOptions
|
||||
}
|
||||
|
||||
export interface ExportOptions {
|
||||
confidence_threshold?: number
|
||||
include_metadata?: boolean
|
||||
filename_pattern?: string
|
||||
css_template?: string
|
||||
}
|
||||
|
||||
export interface ExportRule {
|
||||
id: number
|
||||
rule_name: string
|
||||
config_json: Record<string, any>
|
||||
css_template?: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface CSSTemplate {
|
||||
name: string
|
||||
description: string
|
||||
// filename is not returned by backend - use name as identifier
|
||||
}
|
||||
|
||||
// Translation (FUTURE FEATURE)
|
||||
export interface TranslateRequest {
|
||||
file_id: number
|
||||
source_lang: string
|
||||
target_lang: string
|
||||
engine_type?: 'argos' | 'ernie' | 'google'
|
||||
}
|
||||
|
||||
export interface TranslateResponse {
|
||||
task_id: string
|
||||
file_id: number
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
translated_content?: string
|
||||
}
|
||||
|
||||
export interface TranslationConfig {
|
||||
id: number
|
||||
source_lang: string
|
||||
target_lang: string
|
||||
engine_type: 'argos' | 'ernie' | 'google'
|
||||
engine_config: Record<string, any>
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// API Response
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
// Error Response
|
||||
export interface ApiError {
|
||||
detail: string
|
||||
status_code: number
|
||||
}
|
||||
Reference in New Issue
Block a user