Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
pull_request:
push:
branches:
- main

jobs:
syntax-smoke:
name: syntax-smoke
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Check Python syntax
run: python -m compileall app

- name: Check shell syntax
run: bash -n start.sh
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Screenshot Translator (Qwen3.5-35B-A3B)
# Screenshot Translator (Gemma-4-E4B-It)

<img width="640" height="521" alt="Image" src="https://github.com/user-attachments/assets/f24ef322-08b5-48e6-aa54-71b9e06d7401" />

Expand All @@ -17,9 +17,9 @@
- CUDA 対応 GPU (例: CUDA 13 / nvcc 13.0.88)
- `uv` (Python パッケージマネージャ) がホストにインストール済み
- 下の2つのモデルファイルをローカル `models/` に配置
- `models/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf`(優先
- `models/mmproj-F32.gguf`
- 配布元: [unsloth/Qwen3.5-35B-A3B-GGUF](https://huggingface.co/unsloth/Qwen3.5-35B-A3B-GGUF/tree/main)
- `models/gemma-4-E4B-it-UD-Q4_K_XL.gguf`(既定
- `models/mmproj-F16.gguf`(既定)
- 配布元: [unsloth/gemma-4-E4B-it-GGUF](https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/tree/main)
- **音声読み上げ (TTS)**:
- バックエンド起動時に `Kokoro-82M` (約300MB) が自動でダウンロードされます。
- 音声再生のために、ホスト側に `libportaudio2` や `aplay` (ALSA) が必要です(Ubuntu Desktopなら通常は入っています)。
Expand All @@ -37,27 +37,37 @@
```bash
./start.sh
```
- デフォルト: llama-server 8009, Web UI 8012, ctx=8192。
- デフォルト: Gemma 4 E4B (`UD-Q4_K_XL`) + `mmproj-F16`, llama-server 8009, Web UI 8012, ctx=8192, parallel=1
- VRAMが少ない場合は起動時に `LLAMA_CTX` を下げて起動できます(例: `LLAMA_CTX=4096 ./start.sh`)。
- 既存の llama-server を使う場合: `SKIP_LLAMACPP=1 LLAMA_SERVER_URL=http://127.0.0.1:8009 ./start.sh`
- Qwen3.5 既定時は `app/chat_templates/qwen3.5-35b-a3b.chat_template.jinja` と `LLAMA_THINK_BUDGET=0` が自動適用されます。
- Gemma 4 既定時は `LLAMA_THINK_BUDGET=0` が自動適用されます。
- Qwen3.5 を使う場合は `LLAMA_MODEL=models/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf LLAMA_MMPROJ=models/mmproj-F32_Qwen3.5.gguf ./start.sh` のように明示指定してください。

## 主な環境変数
- `WEB_PORT` (既定: 8012)
- `LLAMA_PORT` (既定: 8009)
- `LLAMA_MODEL` (既定: `models/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf`)
- `LLAMA_MMPROJ` (既定: `models/mmproj-F32.gguf`)
- `LLAMA_MODEL` (既定: `models/gemma-4-E4B-it-UD-Q4_K_XL.gguf`)
- `LLAMA_MMPROJ` (既定: `models/mmproj-F16.gguf`)
- `LLAMA_MODEL_NAME` (既定: `Gemma-4-E4B-It`)
- `LLAMA_CTX` (既定: 8192)
- `LLAMA_PARALLEL` (既定: 1)
- `LLAMA_BIN` (既定: ./llama.cpp/build/bin/llama-server)
- `LLAMA_CHAT_TEMPLATE_FILE` (`--chat-template-file` に渡すテンプレートパス)
- `LLAMA_THINK_BUDGET` (`--reasoning-budget` に渡す値。Qwen3.5既定時は自動で `0`)
- `LLAMA_THINK_BUDGET` (`--reasoning-budget` に渡す値。Gemma 4 / Qwen3.5 既定時は自動で `0`)
- `LLAMA_ARG_CHAT_TEMPLATE_FILE` / `LLAMA_ARG_THINK_BUDGET` も互換入力として受け付け
- `SKIP_LLAMACPP`=1 で llama-server 起動をスキップ

### Qwen3.5 テンプレート運用
### Gemma 4 既定構成
- 既定構成は `gemma-4-E4B-it-UD-Q4_K_XL.gguf` と `mmproj-F16.gguf` です。
- chat template は Gemma 4 のモデル内蔵 template をそのまま使います。
- 単一ユーザー前提で `--parallel 1` を既定にしています。
- thinking を抑制するため、`LLAMA_THINK_BUDGET` 未指定時は `0` を自動適用します。

### Qwen3.5 テンプレート運用(互換)
- 追跡対象テンプレートは `app/chat_templates/qwen3.5-35b-a3b.chat_template.jinja` です。
- 元テンプレートは Qwen 公式 `chat_template.jinja`(Apache-2.0)で、このリポジトリでは `enable_thinking=false` を加えています。
- `models/` は `.gitignore` 対象のため、テンプレートは `models/` ではなく `app/chat_templates/` に置いて管理します。
- Qwen3.5 に切り替えたときだけ、このテンプレートが自動適用されます。
- 実行時に `LAMA_ARG_THINK_BUDGET`(typo)が与えられた場合も互換で受け付けますが、`LLAMA_THINK_BUDGET` の利用を推奨します。

### `LLAMA_CTX` について(VRAM調整)
Expand Down
2 changes: 1 addition & 1 deletion app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Settings:
def __init__(self) -> None:
self.api_base = os.getenv("LLAMA_SERVER_URL", "http://127.0.0.1:8009")
self.ctx_size = int(os.getenv("LLAMA_CTX", "8192"))
self.model_name = os.getenv("LLAMA_MODEL_NAME", "Qwen3.5-35B-A3B")
self.model_name = os.getenv("LLAMA_MODEL_NAME", "Gemma-4-E4B-It")
self.system_prompt = (
"You are a precise OCR + translation engine."
" Output the FULL text exactly as seen."
Expand Down
2 changes: 1 addition & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class SessionState:

session_state = SessionState()

app = FastAPI(title="Screenshot Translator", version="5.0.0")
app = FastAPI(title="Screenshot Translator", version="6.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
Expand Down
2 changes: 1 addition & 1 deletion app/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ <h1>Screenshot → Markdown 翻訳</h1>
</div>
<div id="markdown" class="markdown">ここに結果が表示されます</div>
</div>
<footer>バックエンド: llama.cpp (Qwen3.5-35B-A3B) / ポート 8012</footer>
<footer>バックエンド: llama.cpp (Gemma-4-E4B-It) / ポート 8012</footer>
</div>
<script src="/static/main.js?v=20251215-2"></script>
</body>
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "screenshot-translator"
version = "5.0.0"
description = "Clipboard screenshot OCR + JP translation using llama.cpp Qwen3.5"
version = "6.0.0"
description = "Clipboard screenshot OCR + JP translation using llama.cpp Gemma 4"
authors = [{ name = "" }]
readme = "README.md"
requires-python = ">=3.10"
Expand Down
22 changes: 17 additions & 5 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ WEB_PORT=${WEB_PORT:-8012}
LLAMA_PORT=${LLAMA_PORT:-8009}
LLAMA_CTX=${LLAMA_CTX:-8192}
LLAMA_BIN=${LLAMA_BIN:-./llama.cpp/build/bin/llama-server}
LLAMA_PARALLEL=${LLAMA_PARALLEL:-1}
SKIP_LLAMACPP=${SKIP_LLAMACPP:-0}

DEFAULT_MODEL_GEMMA4="models/gemma-4-E4B-it-UD-Q4_K_XL.gguf"
DEFAULT_MMPROJ_GEMMA4="models/mmproj-F16.gguf"
DEFAULT_MODEL_NAME_GEMMA4="Gemma-4-E4B-It"
DEFAULT_MODEL_QWEN35="models/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf"
DEFAULT_MMPROJ_QWEN35="models/mmproj-F32.gguf"
DEFAULT_CHAT_TEMPLATE_QWEN35="app/chat_templates/qwen3.5-35b-a3b.chat_template.jinja"

LLAMA_MODEL="${LLAMA_MODEL:-$DEFAULT_MODEL_QWEN35}"
LLAMA_MMPROJ="${LLAMA_MMPROJ:-$DEFAULT_MMPROJ_QWEN35}"
LLAMA_MODEL="${LLAMA_MODEL:-$DEFAULT_MODEL_GEMMA4}"
LLAMA_MMPROJ="${LLAMA_MMPROJ:-$DEFAULT_MMPROJ_GEMMA4}"
LLAMA_MODEL_NAME="${LLAMA_MODEL_NAME:-$DEFAULT_MODEL_NAME_GEMMA4}"

LLAMA_CHAT_TEMPLATE_FILE="${LLAMA_CHAT_TEMPLATE_FILE:-}"
LLAMA_THINK_BUDGET="${LLAMA_THINK_BUDGET:-}"
Expand All @@ -31,6 +35,12 @@ if [ -z "$LLAMA_THINK_BUDGET" ] && [ -n "${LAMA_ARG_THINK_BUDGET:-}" ]; then
echo "[WARN] LAMA_ARG_THINK_BUDGET is a typo; use LLAMA_THINK_BUDGET instead."
fi

if [ "$LLAMA_MODEL" = "$DEFAULT_MODEL_GEMMA4" ]; then
if [ -z "$LLAMA_THINK_BUDGET" ]; then
LLAMA_THINK_BUDGET=0
fi
fi

if [ "$LLAMA_MODEL" = "$DEFAULT_MODEL_QWEN35" ]; then
if [ -z "$LLAMA_CHAT_TEMPLATE_FILE" ] && [ -f "$DEFAULT_CHAT_TEMPLATE_QWEN35" ]; then
LLAMA_CHAT_TEMPLATE_FILE="$DEFAULT_CHAT_TEMPLATE_QWEN35"
Expand Down Expand Up @@ -68,12 +78,11 @@ if [ "$SKIP_LLAMACPP" != "1" ]; then
LLAMA_ARGS=(
--host 127.0.0.1
--port "$LLAMA_PORT"
--parallel "$LLAMA_PARALLEL"
-m "$LLAMA_MODEL"
-c "$LLAMA_CTX"
-ngl 999
--jinja
-ub 4096
-b 4096
--flash-attn on
--mmproj "$LLAMA_MMPROJ"
)
Expand All @@ -86,7 +95,9 @@ if [ "$SKIP_LLAMACPP" != "1" ]; then

echo "[INFO] starting llama.cpp server on port $LLAMA_PORT"
echo "[INFO] model: $LLAMA_MODEL"
echo "[INFO] model name: $LLAMA_MODEL_NAME"
echo "[INFO] mmproj: $LLAMA_MMPROJ"
echo "[INFO] parallel: $LLAMA_PARALLEL"
if [ -n "$LLAMA_CHAT_TEMPLATE_FILE" ]; then
echo "[INFO] chat template: $LLAMA_CHAT_TEMPLATE_FILE"
fi
Expand All @@ -104,6 +115,7 @@ fi

export LLAMA_SERVER_URL=${LLAMA_SERVER_URL:-http://127.0.0.1:${LLAMA_PORT}}
export LLAMA_CTX
export LLAMA_MODEL_NAME

echo "[INFO] starting FastAPI on port $WEB_PORT"
uv run uvicorn app.main:app --host 127.0.0.1 --port "$WEB_PORT"
Loading