Improve translation reliability and progress feedback

This commit is contained in:
burnintuna 2026-07-30 17:07:02 +09:00
parent 9439858b11
commit 51a6e845d1
20 changed files with 1194 additions and 216 deletions

View file

@ -19,7 +19,7 @@ Single-server deployment: `uvicorn backend.main:app` serves both API and fronten
| Auth | JWT (PyJWT), bcrypt (direct via `bcrypt` module) |
| LLM Client | OpenAI SDK (`openai`) — OpenAI-compatible API format |
| Frontend | Vue 3 + Composition API `<script setup>`, TypeScript, Pinia |
| Styling | Tailwind CSS v4 with `darkMode: 'class'` strategy |
| Styling | Tailwind CSS v3 with `darkMode: 'class'` strategy |
| Build | Vite (frontend), no bundler for backend |
## Directory Structure
@ -55,10 +55,11 @@ LLM-translator/
│ └── package.json
├── config/
│ ├── llms.json # LLM server configs [{alias, base_url, api_key, model, context_size}]
│ └── users.json # Users [{id, password_hash}] — hashed on first startup from plain-text
│ └── users.json # Users [{id, password}] — password_plain is migrated on startup
├── requirements.txt # Python deps: fastapi, uvicorn[standard], pyjwt, bcrypt, openai, python-dotenv
├── .env.example # Environment variables template
└── README.md # (TBD)
├── tests/ # Backend API and session regression tests
└── README.md # Setup and operation guide
```
## Key Patterns & Conventions
@ -66,9 +67,12 @@ LLM-translator/
### Backend
- **No `passlib`** — use `bcrypt` module directly (`bcrypt.hashpw`, `bcrypt.checkpw`). The project uses Python 3.14 which has compatibility issues with passlib's bcrypt wrapper.
- **LLM client caching**`llm_client.py` maintains a per-alias `{AsyncOpenAI, LLMConfig}` cache dict keyed by alias string. Clients are lazily instantiated on first use.
- **Session store**simple in-memory dict (`sessions.py`). Key is UUID hex (16 chars). TTL 24h from last access. No persistence beyond process lifetime.
- **LLM client caching**`llm_client.py` caches an `AsyncOpenAI` client by alias. Clients are lazily instantiated on first use.
- **Session store**process-local in-memory dict (`sessions.py`) scoped by authenticated owner. Key is UUID hex (16 chars). TTL is 24h from last access; there is no persistence beyond process lifetime.
- **JSON extraction** — LLM responses may be wrapped in markdown code blocks. `_extract_json()` strips ```` ```json` ... ```` wrappers before `json.loads()`.
- **Long-text chunking** — phases 1, 3, and 4 process text sequentially near sentence boundaries. Defaults are 1,500 characters per chunk and a 180-second timeout per LLM call; both are configurable by environment variables.
- **Progress feedback** — LLM calls stream response deltas into transient session progress. The frontend polls `/api/sessions/{session_id}/progress` and displays the current chunk and a rolling preview.
- **Malformed Phase 1 JSON** — parsing tolerates fences, surrounding prose, alternate translation keys, missing noun fields, and trailing commas. Remaining failures trigger one JSON-repair call and then a plain-translation fallback.
### Frontend
@ -82,7 +86,7 @@ LLM-translator/
Models are defined in `config/llms.json`. Each entry:
```json
{
"alias": "Qwen3.6-35B", // Display name, used as modelPhase1..4 values
"alias": "Qwen3.6-35B", // Display name, used by LLM phases 1, 3, and 4
"base_url": "http://...", // OpenAI-compatible base URL (must end with /v1)
"api_key": "...", // API key for this server
"model": "hx370/qwen3.6-35b-a3b", // Exact model name as used by the LLM provider
@ -105,7 +109,7 @@ source_text ──► Phase1(LLM) ──► {translated, proper_nouns[], summary
phase4_result (final)
```
Phase buttons are always freely executable — user can jump to any phase independently. Dependencies are only logical: P2 needs P1 data, P3 needs P1 result, P4 needs P3 result.
Phase dependencies are enforced: Phase 2 and 3 require Phase 1, and Phase 4 requires Phase 3. Running Phase 3 automatically submits the current Phase 2 edits.
### Theme / Dark Mode
@ -121,7 +125,7 @@ pip install -r requirements.txt
cd frontend && npm install && cd ..
# 2. Build frontend (required before serving in production mode)
cd frontend && npx vite build && cd ..
cd frontend && npm run build && cd ..
# 3. Configure LLM servers and users
# Edit config/llms.json and config/users.json directly
@ -142,10 +146,12 @@ JWT_SECRET_KEY=your-secret uvicorn backend.main:app --reload
2. Add API endpoint in `backend/main.py` following the existing pattern:
```python
@app.post("/api/translate/{session_id}/phaseN")
async def run_phaseN(session_id: str):
session = session_store.get(session_id)
async def run_phaseN(session_id: str, user_id: str = Depends(get_current_user)):
session = session_store.get(session_id, user_id)
# ... call chat_complete(alias, system_prompt, user_content)
updated = session_store.update(session_id, {"phaseN_result": result_text})
updated = session_store.update(
session_id, {"phaseN_result": result_text}, user_id
)
return SessionResponse(session_id=session_id, data=updated).model_dump()
```
3. Add frontend component and button in `TranslatorView.vue`