Improve translation reliability and progress feedback
This commit is contained in:
parent
9439858b11
commit
51a6e845d1
20 changed files with 1194 additions and 216 deletions
28
AGENTS.md
28
AGENTS.md
|
|
@ -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`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue