Skip to content

Commit b5f2056

Browse files
cloudman6neosun100
andauthored
feat: optimize backend concurrency, add deploy workflow & new locales (#41)
* feat(ocr-queue): add intelligent dismissal with related action whitelisting - Add keep-queue-open class support in AppHeader for OCR-related buttons - Update OCRQueuePopover, OCRModeSelector, PageViewer to use whitelisted selectors - Add OCRRawTextPanel text formatting improvements - Add E2E tests for queue state persistence (ocr-queue-state.spec.ts) - Add unit tests for onClickOutside callback in AppHeader.test.ts * feat(frontend): add GitHub Star/Issue/Docs links to header - Add GitHub repository link with Star button - Add Issues link for bug reports and feature requests - Add Docs link to README documentation - Add i18n translations for zh-CN and en - Responsive design: hide text labels on mobile * feat(ui): add footer credits to home page - Add 'Made with ❤️ by neosun100 & cloudman6' footer - Add version info 'DeepSeek-OCR-WebUI v3.5 | © 2026' - Links to GitHub profiles * fix(ui): improve mobile responsive layout for header - Hide GitHub links on mobile (<768px) - Hide OCR status center section on mobile - Reduce header padding and font sizes - Hide page count badge on mobile - Compact import button on very small screens (<480px) * feat: add GitHub Pages deployment workflow * feat: add GitHub Pages deployment workflow * fix: resolve quality gate failures - Fix TypeScript errors in health tests - Fix complexity issues in OCR providers - Fix E2E test failures in Firefox/Webkit - Improve test coverage and fix Lint warnings * feat: add GitHub Pages deployment workflow * queue support * Fix CORS x-client-id Heaa * chore: pass commit quality gates and finalize pending features - Fix E2E test failures (rate-limiting, batch-export) - Improve AppHeader component coverage to 100% - Fix linting and complexity issues across codebase - Add missing i18n files (ja-JP, zh-TW) - Fix Crypto UUID generation for insecure contexts * refactor(ocr): improve queue full handling and retry logic - Separate UI-level blocking from service-level retry - UI components (PageItem, PageViewer) now check healthStore.isFull before submission - Display modal dialog for immediate user feedback when queue is full - OCRService.queueOCR() allows queuing during 'full' state, tasks wait internally - Add automatic retry with 5s interval for 429 errors - Extract helper methods to reduce cognitive complexity (19 → 13) - Fix E2E tests: health-check, pagelist-ui-interacting, base-test - Fix lint warnings: remove unused variables, improve type safety - Add new E2E test: ocr-retry.spec.ts - Add new type file: src/services/ocr/types.ts Tests: 668 unit tests, 278 E2E tests pass Coverage: 95% statements, 85% branches * test(e2e): fix e2e test failures and improve coverage - Ignore CORS and network errors in E2E tests console logs - Mock OCR health endpoint to ensure consistent test environment - Update ocr-mode-selector tests to use dynamic mocks and cover edge cases - Update ocr-input-modal tests to cover healthy/full/unavailable states - Fix lint errors in E2E spec files and page objects - Adjust Firefox drop-down timeout to prevent flake --------- Co-authored-by: neosun100 <neosun100@users.noreply.github.com>
1 parent bbbf598 commit b5f2056

72 files changed

Lines changed: 4734 additions & 478 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Deploy Frontend to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'frontend/**'
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
pages: write
14+
id-token: write
15+
16+
concurrency:
17+
group: "pages"
18+
cancel-in-progress: true
19+
20+
jobs:
21+
deploy:
22+
environment:
23+
name: github-pages
24+
url: ${{ steps.deployment.outputs.page_url }}
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Set up Node
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: 20
34+
cache: 'npm'
35+
cache-dependency-path: frontend/package-lock.json
36+
37+
- name: Install dependencies
38+
working-directory: frontend
39+
run: npm ci
40+
41+
- name: Build
42+
working-directory: frontend
43+
run: npm run build
44+
env:
45+
VITE_API_BASE_URL: https://deepseek-ocr.aws.xin
46+
47+
- name: Setup Pages
48+
uses: actions/configure-pages@v4
49+
50+
- name: Upload artifact
51+
uses: actions/upload-pages-artifact@v3
52+
with:
53+
path: './frontend/dist'
54+
55+
- name: Deploy to GitHub Pages
56+
id: deployment
57+
uses: actions/deploy-pages@v4

frontend/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ test-stability-logs/
1616

1717
# docs
1818
docs/
19+
E2E_DESIGN.md
1920

2021
# Dependency directories
2122
node_modules/
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Frontend Rate Limiting Integration Guide
2+
3+
## Background
4+
5+
The backend has implemented Client ID + IP composite rate limiting. This document provides a complete integration guide for frontend developers.
6+
7+
---
8+
9+
## Backend API Reference
10+
11+
### GET `/health` - Health Check
12+
13+
**Request Headers** (optional):
14+
15+
| Header | Description |
16+
|--------|-------------|
17+
| `X-Client-ID` | Client unique identifier (if provided, returns queue position) |
18+
19+
**Response Example 1**: Idle state
20+
21+
```json
22+
{
23+
"status": "healthy",
24+
"backend": "cuda",
25+
"platform": "Linux",
26+
"model_loaded": true,
27+
"ocr_queue": {
28+
"depth": 2,
29+
"max_size": 8,
30+
"is_full": false
31+
},
32+
"rate_limits": {
33+
"max_per_client": 1,
34+
"max_per_ip": 4,
35+
"active_clients": 2,
36+
"active_ips": 1
37+
}
38+
}
39+
```
40+
41+
**Response Example 2**: With `X-Client-ID` and in queue
42+
43+
```json
44+
{
45+
"status": "busy",
46+
"ocr_queue": { "depth": 5, "max_size": 8, "is_full": false },
47+
"your_queue_status": {
48+
"client_id": "abc-123-def",
49+
"position": 3,
50+
"total_queued": 5
51+
}
52+
}
53+
```
54+
55+
**`status` Field Values**:
56+
57+
| Value | Condition | Description |
58+
|-------|-----------|-------------|
59+
| `healthy` | depth < max/2 | Queue idle, new requests processed immediately |
60+
| `busy` | max/2 ≤ depth < max | Queue busy, new requests will be queued |
61+
| `full` | depth ≥ max | Queue full, new requests will be rejected |
62+
63+
---
64+
65+
### POST `/ocr` - OCR Recognition
66+
67+
**Request Headers**:
68+
69+
| Header | Description |
70+
|--------|-------------|
71+
| `X-Client-ID` | Client unique identifier (required for rate limiting) |
72+
73+
**Success Response** (200):
74+
75+
```json
76+
{
77+
"success": true,
78+
"text": "Recognized text content",
79+
"raw_text": "Raw text",
80+
"boxes": [],
81+
"image_dims": { "w": 800, "h": 600 }
82+
}
83+
```
84+
85+
**Rate Limited Response** (429):
86+
87+
```json
88+
{ "detail": "OCR queue full, please retry later" }
89+
```
90+
91+
```json
92+
{ "detail": "Client at max concurrency (1)" }
93+
```
94+
95+
```json
96+
{ "detail": "IP at max concurrency (4)" }
97+
```
98+
99+
---
100+
101+
## Frontend Changes
102+
103+
### Phase 1: Client ID Service
104+
105+
**New file**: `src/services/clientId.ts`
106+
107+
```typescript
108+
const SESSION_KEY = 'ocr-client-id'
109+
110+
export function getClientId(): string {
111+
let id = sessionStorage.getItem(SESSION_KEY)
112+
if (!id) {
113+
id = crypto.randomUUID()
114+
sessionStorage.setItem(SESSION_KEY, id)
115+
}
116+
return id
117+
}
118+
```
119+
120+
---
121+
122+
### Phase 2: OCR Request Header
123+
124+
**Modify**: `src/services/ocr/providers.ts`
125+
126+
```diff
127+
+ import { getClientId } from '@/services/clientId'
128+
129+
const response = await fetch(url, {
130+
method: 'POST',
131+
body: formData,
132+
- signal: options?.signal
133+
+ signal: options?.signal,
134+
+ headers: { 'X-Client-ID': getClientId() }
135+
})
136+
```
137+
138+
---
139+
140+
### Phase 3: Health Request Header
141+
142+
**Modify**: `src/services/health/index.ts`
143+
144+
```diff
145+
+ import { getClientId } from '@/services/clientId'
146+
147+
const response = await fetch(`${this.apiBaseUrl}/health`, {
148+
- signal: controller.signal
149+
+ signal: controller.signal,
150+
+ headers: { 'X-Client-ID': getClientId() }
151+
})
152+
```
153+
154+
**New types**: `src/services/health/types.ts`
155+
156+
```typescript
157+
export interface HealthResponse {
158+
status: 'healthy' | 'busy' | 'full'
159+
ocr_queue: {
160+
depth: number
161+
max_size: number
162+
is_full: boolean
163+
}
164+
rate_limits: {
165+
max_per_client: number
166+
max_per_ip: number
167+
active_clients: number
168+
active_ips: number
169+
}
170+
your_queue_status?: {
171+
client_id: string
172+
position: number | null
173+
total_queued: number
174+
}
175+
}
176+
```
177+
178+
---
179+
180+
### Phase 4: 429 Error Handling
181+
182+
**Rate limit reason parsing**:
183+
184+
```typescript
185+
type RateLimitReason = 'queue_full' | 'client_limit' | 'ip_limit' | 'unknown'
186+
187+
function parseRateLimitReason(detail: string): RateLimitReason {
188+
if (detail.includes('queue full')) return 'queue_full'
189+
if (detail.includes('Client at max')) return 'client_limit'
190+
if (detail.includes('IP at max')) return 'ip_limit'
191+
return 'unknown'
192+
}
193+
```
194+
195+
**User-friendly messages**:
196+
197+
| Reason | User Message |
198+
|--------|--------------|
199+
| `queue_full` | 🔴 Service is overloaded, please try again later |
200+
| `client_limit` | ⏳ You already have a request in progress |
201+
| `ip_limit` | 🚫 Too many requests from your network |
202+
203+
---
204+
205+
### Phase 5: Status Indicator UI
206+
207+
**Display based on `status`**:
208+
209+
| status | Icon | Color |
210+
|--------|------|-------|
211+
| `healthy` | 🟢 | Green |
212+
| `busy` | 🟡 | Yellow |
213+
| `full` | 🔴 | Red |
214+
215+
**Queue position hint** (when `position` has value):
216+
217+
```
218+
🔄 Your request is #3 in queue (5 total)
219+
```
220+
221+
---
222+
223+
## Verification
224+
225+
```bash
226+
cd frontend
227+
npm run test -- src/services/clientId.test.ts
228+
npm run test -- src/services/ocr/providers.test.ts
229+
```

frontend/index.html

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,41 @@
77
<title>DeepSeek-OCR-WebUI</title>
88
</head>
99
<body>
10-
<div id="app"></div>
10+
<div id="app">
11+
<style>
12+
.app-loading {
13+
display: flex;
14+
flex-direction: column;
15+
align-items: center;
16+
justify-content: center;
17+
height: 100vh;
18+
width: 100vw;
19+
background-color: #f6f7f8;
20+
font-family: system-ui, -apple-system, sans-serif;
21+
}
22+
.app-loading-spinner {
23+
width: 48px;
24+
height: 48px;
25+
border: 4px solid rgba(24, 160, 88, 0.2);
26+
border-left-color: #18a058;
27+
border-radius: 50%;
28+
animation: spin 1s linear infinite;
29+
margin-bottom: 16px;
30+
}
31+
.app-loading-text {
32+
color: #666;
33+
font-size: 14px;
34+
}
35+
@keyframes spin {
36+
0% { transform: rotate(0deg); }
37+
100% { transform: rotate(360deg); }
38+
}
39+
</style>
40+
<div class="app-loading">
41+
<div class="app-loading-spinner"></div>
42+
<div class="app-loading-text">Loading...</div>
43+
</div>
44+
</div>
1145
<script type="module" src="/src/main.ts"></script>
1246
</body>
1347
</html>

0 commit comments

Comments
 (0)