# LLM Translator — 4단계 고품질 번역 파이프라인 지인 공유용 웹 기반 번역기. OpenAI 호환 LLM 서버를 통해 원문을 도착 언어로 고품질 번역합니다. ## 특징 - **4단계 번역 파이프라인**: 초벌번역 → 고유명사/문체 분석 → 재번역 → 마무리 다듬기 - **LLM 단계별 모델 선택**: Phase 1, 3, 4에 서로 다른 모델 지정 가능 - **고유명사 관리**: 추출된 고유명사를 사용자가 검토·수정 가능 - **세로 3열 비교 UI**: Phase 1 / Phase 3 / Phase 4 결과를 한눈에 비교 - **다크모드 지원**: 시스템 설정 자동 감지 + 수동 토글 (localStorage 지속화) - **JWT 기반 인증**: config-file 기반 ID/PW 로그인 - **다중 사용자 및 영속 세션**: 사용자별 SQLite 세션 저장과 동일 세션 재개 - **직전 버전 복원**: 번역을 덮어쓰기 전 세션 snapshot 보존 - **관리자 사용자 관리**: 계정 생성, 비밀번호 초기화, 활성화 및 권한 관리 - **단일 서버 배포**: FastAPI가 API + 정적 프론트엔드 동시에 서빙 ## 빠른 시작 ```bash # 1. 의존성 설치 pip install -r requirements.txt cd frontend && npm install && cd .. # 2. 프론트엔드 빌드 (프로덕션 모드) cd frontend && npm run build && cd .. # 3. 서버 실행 uvicorn backend.main:app --host 0.0.0.0 --port 8000 # 4. 브라우저에서 http://localhost:8000 접속 ``` **기본 로그인**: `admin` / `changeme123` (첫 시작 시 비밀번호가 bcrypt 해시로 자동 변환됨) ## 설정 파일 ### LLM 서버 (`config/llms.json`) ```json [ { "alias": "Qwen3.6-35B", "base_url": "http://btuna.net:45455/v1", "api_key": "...", "model": "hx370/qwen3.6-35b-a3b", "context_size": 65536 } ] ``` ### 사용자 (`config/users.json`) ```json [{"id": "admin", "password_plain": "changeme123"}] ``` 첫 시작 시 `password_plain`이 자동으로 `password`(bcrypt hash)로 변환되어 파일에 저장됩니다. ## 환경 변수 (`.env.example` 참조) | 변수 | 설명 | 기본값 | |------|------|--------| | `JWT_SECRET_KEY` | JWT 서명 키 | 미설정 시 키 파일 자동 생성 | | `JWT_SECRET_FILE` | 서명 키 자동 생성 파일 | `config/.jwt-secret` | | `JWT_EXPIRE_HOURS` | 토큰 만료 시간(시간) | `24` | | `LLMS_CONFIG_PATH` | LLM 설정 파일 경로 | `config/llms.json` | | `USERS_CONFIG_PATH` | 사용자 설정 파일 경로 | `config/users.json` | | `PORT` | 서버 포트 | `8000` | | `DATABASE_PATH` | SQLite DB 경로 | `data/translator.db` | | `BOOTSTRAP_ADMIN_ID` | 최초 관리자 ID | 없음 | | `BOOTSTRAP_ADMIN_PASSWORD` | 최초 관리자 비밀번호 | 없음 | | `LLM_TIMEOUT_SECONDS` | 개별 LLM 호출 제한 시간 | `180` | | `TRANSLATION_CHUNK_CHARS` | 장문 분할 목표 크기 | `1500` | ## 프로젝트 구조 ``` ├── backend/ # FastAPI 백엔드 (auth, llm_client, routes) ├── frontend/ # Vue 3 + TypeScript 프론트엔드 (Vite 빌드) │ ├── dist/ # 빌드 출력물 (.gitignore 제외) │ └── src/ # Vue 컴포넌트, Pinia store, API 클라이언트 ├── config/ # LLM 서버 및 사용자 설정 파일 └── requirements.txt # Python 의존성 ``` ## 개발 모드 (프론트엔드 HMR) ```bash # 터미널 1: 백엔드 실행 uvicorn backend.main:app --reload # 터미널 2: 프론트엔드 dev 서버 cd frontend && npx vite # http://localhost:5173 # /api 요청은 자동으로 localhost:8000으로 프록시됨 ``` ## API 엔드포인트 | Method | Path | 설명 | 인증 | |--------|------|------|------| | `POST` | `/api/auth/login` | 로그인 → JWT 토큰 발급 | × | | `GET` | `/api/auth/me` | 현재 사용자 정보 | ○ (JWT) | | `GET` | `/api/models` | 사용 가능한 LLM 모델 목록 | ○ | | `POST` | `/api/translate/create` | 새 번역 세션 생성 | ○ | | `GET` | `/api/sessions/{id}` | 세션 상태 조회 | ○ | | `PATCH`| `/api/sessions/{id}` | 세션 필드 부분 업데이트 | ○ | | `POST` | `/api/translate/{id}/phase1` | 초벌번역 + 고유명사 추출 | ○ | | `POST` | `/api/translate/{id}/phase2` | 고유명사/문체 확인 | ○ | | `POST` | `/api/translate/{id}/phase3` | 재번역 (제약조건 적용) | ○ | | `POST` | `/api/translate/{id}/phase4` | 마무리 다듬기 | ○ | ## 빌드 & 배포 ```bash # 1. 프론트엔드 빌드 cd frontend && npm run build && cd .. # 2. 백엔드 서빙 (dist 정적 파일 포함) uvicorn backend.main:app --host 0.0.0.0 --port 8000 ``` `backend/main.py`는 `frontend/dist/` 폴더를 정적 파일로 마운트하므로, 빌드 후 백엔드 서버 하나만 실행하면 됩니다. 장문은 문장 또는 줄 경계를 우선하여 기본 1,500자 단위로 나누고 Phase 1, 3, 4에서 순차 처리합니다. 공개 LLM 서버가 혼잡하면 `.env`에서 `LLM_TIMEOUT_SECONDS`를 늘리거나 `TRANSLATION_CHUNK_CHARS`를 줄일 수 있습니다. LLM 처리 중에는 현재 chunk 번호와 스트리밍으로 수신 중인 최근 내용이 화면에 임시 표시됩니다. Phase 1 JSON이 불완전하면 코드 블록·주변 문구·후행 쉼표를 먼저 보정하고, 실패 시 LLM JSON 복구를 거쳐 일반 번역 결과로 폴백합니다. ## Docker 배포 ```bash docker compose up -d --build docker compose ps ``` 첫 실행 전에 `.env`에 `BOOTSTRAP_ADMIN_ID`와 충분히 강한 `BOOTSTRAP_ADMIN_PASSWORD`를 지정해야 합니다. Docker는 로컬 개발용 기본 계정을 가져오지 않습니다. SQLite DB와 자동 생성 JWT 키는 `translator-data` volume에 저장됩니다. 상세 설정, 백업 및 복원 절차는 `DEPLOYMENT.md`를 참고하세요. ## 자동 테스트 ```bash python -m unittest discover -v ``` ## LLM 서버 연결 확인 ```bash curl -s http://btuna.net:45455/v1/chat/completions \ -H "Authorization: Bearer btuna-public-key" \ -H "Content-Type: application/json" \ -d '{"model":"hx370/qwen3.6-35b-a3b","messages":[{"role":"user","content":"test"}],"max_tokens":10}' | jq ``` ## 라이선스 내부 사용 목적