# Web OCR AI — 專案規劃

## 專案目標

以**多模態雲端 API** 做圖片辨識，支援：

- **PC**：瀏覽器上傳圖片
- **手機**：瀏覽器直接拍照辨識

前端與後端分離，API Key 僅存於後端，前端不直接呼叫雲端。

---

## 技術棧

| 層級 | 技術 |
|------|------|
| 前端 | Vue 3 + Vite + Axios |
| 後端 | PHP Laravel 12（API only） |
| AI | 雲端多模態 API（**可選**） |
| 部署 | Docker（沿用 `docker/www` 環境） |

---

## AI Provider（可選切換）

支援兩家雲端 Vision API，由設定或請求參數選擇：

| Provider | 模型 | 套件 / 方式 |
|----------|------|-------------|
| **OpenAI** | `gpt-4o` | `openai-php/client` |
| **Google** | `gemini-1.5-flash` / `gemini-1.5-pro` | `google-gemini-php/client` 或 Laravel `Http::` |

### 切換方式

1. **預設**：`.env` 的 `VISION_PROVIDER=openai|gemini`
2. **單次請求覆寫**（可選）：`POST /api/recognize` 帶 `provider` 欄位
3. **前端 UI**（Phase 2+）：下拉選單讓使用者選 OpenAI / Gemini

### 抽象層設計

```
app/Contracts/VisionProviderInterface.php   # 介面
app/Services/Vision/
├── OpenAiVisionService.php                 # GPT-4o 實作
├── GeminiVisionService.php                 # Gemini 實作
└── VisionServiceFactory.php                # 依 provider 建立實例
```

**介面方法**（概念）：

```php
interface VisionProviderInterface
{
  public function recognize(string $imageBase64, string $mimeType, ?string $prompt = null): array;
}
```

回傳統一格式，Controller 不需知道實際呼叫哪家 API。

---

## 專案結構

```
web_ocr_ai/
├── backend/                          # Laravel API
│   ├── app/
│   │   ├── Contracts/
│   │   │   └── VisionProviderInterface.php
│   │   ├── Http/
│   │   │   ├── Controllers/Api/
│   │   │   │   └── RecognizeController.php
│   │   │   └── Requests/
│   │   │       └── RecognizeRequest.php
│   │   └── Services/Vision/
│   │       ├── OpenAiVisionService.php
│   │       ├── GeminiVisionService.php
│   │       └── VisionServiceFactory.php
│   ├── routes/api.php
│   ├── config/services.php
│   └── composer.json
│
├── frontend/                         # Vue 3 + Vite SPA
│   ├── src/
│   │   ├── views/Recognize.vue
│   │   ├── components/
│   │   │   ├── ImageUploader.vue     # 上傳 + 拍照
│   │   │   ├── ProviderSelect.vue    # OpenAI / Gemini 選擇
│   │   │   └── ResultPanel.vue
│   │   └── api/recognize.js
│   ├── vite.config.js
│   └── package.json
│
├── docker-compose.yml
├── .env.example
└── md/plan.md
```

---

## 資料流

```
[PC 上傳 / 手機拍照]
        ↓
   Vue 3 前端（預覽、可選壓圖、可選 Provider）
        ↓
   POST /api/recognize (multipart/form-data)
        ↓
   Laravel RecognizeController
        ↓
   VisionServiceFactory → OpenAiVisionService | GeminiVisionService
        ↓
   雲端 API（GPT-4o 或 Gemini）
        ↓
   統一 JSON 回應 → 前端顯示
```

---

## API 設計

### `POST /api/recognize`

**Request**（`multipart/form-data`）

| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `image` | file | ✅ | 圖片檔（jpg/png/webp，建議 ≤ 10MB） |
| `prompt` | string | ❌ | 自訂問題，預設為通用 OCR prompt |
| `provider` | string | ❌ | `openai` \| `gemini`，省略則用 `.env` 預設 |
| `output_format` | string | ❌ | `text` \| `json`，預設 `json` |

**Response**（成功）

```json
{
  "success": true,
  "provider": "openai",
  "model": "gpt-4o",
  "data": {
    "raw_text": "圖片中的完整文字...",
    "fields": {},
    "summary": "簡要說明（可選）"
  },
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0
  }
}
```

**Response**（失敗）

```json
{
  "success": false,
  "message": "錯誤說明",
  "provider": "gemini"
}
```

### `GET /api/providers`（可選）

回傳目前可用 provider 列表與預設值，供前端下拉選單使用。

```json
{
  "default": "openai",
  "providers": [
    { "id": "openai", "name": "OpenAI GPT-4o", "available": true },
    { "id": "gemini", "name": "Google Gemini", "available": true }
  ]
}
```

---

## 環境變數（`.env`）

```env
# 預設 Provider：openai | gemini
VISION_PROVIDER=openai

# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o

# Google Gemini
GEMINI_API_KEY=...
GEMINI_MODEL=gemini-1.5-flash

# 上傳限制
VISION_MAX_FILE_SIZE=10240   # KB
VISION_ALLOWED_MIMES=image/jpeg,image/png,image/webp
```

---

## 前端功能

### ImageUploader.vue

- PC：`<input type="file" accept="image/*">`
- 手機：`<input type="file" accept="image/*" capture="environment">`
- 選檔後即時預覽（`URL.createObjectURL`）
- 上傳前可選：最長邊壓至 1920px（省流量與 token）

### ProviderSelect.vue

- 下拉選單：OpenAI GPT-4o / Google Gemini
- 預設值來自 `GET /api/providers` 或前端設定

### Recognize.vue

- 整合上傳、Provider 選擇、送出、結果顯示
- Loading 狀態、錯誤提示

### Vite 開發代理

```javascript
// vite.config.js
server: {
  proxy: {
    '/api': 'http://localhost:8000'
  }
}
```

### 手機注意事項

- 相機 API 需 **HTTPS**（本機可用 `localhost`）
- 響應式版面，單頁即可同時支援 PC 與手機

---

## Laravel 後端要點

### Composer 依賴

```json
{
  "openai-php/client": "^0.10",
  "google-gemini-php/client": "^1.0",
  "intervention/image-laravel": "^1.5"
}
```

（Gemini 亦可僅用 Laravel 內建 `Http::`，視套件維護狀況決定。）

### RecognizeRequest 驗證

- `image`：required, image, mimes:jpeg,png,webp, max:10240
- `provider`：nullable, in:openai,gemini
- `prompt`：nullable, string, max:2000

### VisionServiceFactory

```php
public function make(?string $provider = null): VisionProviderInterface
{
    $provider = $provider ?? config('services.vision.default');

    return match ($provider) {
        'openai' => app(OpenAiVisionService::class),
        'gemini' => app(GeminiVisionService::class),
        default  => throw new InvalidArgumentException("Unknown provider: {$provider}"),
    };
}
```

### 預設 Prompt（通用 OCR）

```
請辨識這張圖片中的所有文字，包含繁體中文與英文。
以 JSON 格式回傳：{ "raw_text": "完整文字", "fields": {}, "summary": "簡要說明" }
```

---

## 開發階段

| 階段 | 內容 | 產出 |
|------|------|------|
| **Phase 1** | Laravel 骨架 + Vue 骨架 + 單一 Provider（OpenAI）+ 上傳辨識 | 端到端可跑 |
| **Phase 2** | 加入 Gemini Provider + Factory 切換 + 前端 Provider 選單 | 雙 Provider 可選 |
| **Phase 3** | 結構化 JSON、Prompt 模板、前端壓圖、錯誤處理 | 體驗完善 |
| **Phase 4** | 歷史紀錄、登入（Sanctum）、Docker 正式部署 | 可上線 |

---

## 待確認（實作前）

- [ ] 主要辨識場景：通用 OCR / 特定文件（發票、名片）/ 自由問答
- [ ] Phase 1 先接 OpenAI 還是 Gemini
- [ ] 是否需要使用者登入與辨識歷史
- [ ] Docker 是否對接現有 `docker/www` 的 Nginx / PHP-FPM 設定

---

## 參考既有專案

- `web_vue3_manager`：Laravel + Vue 3 前後端分離
- `line_cut_tools`：Laravel 12 + `intervention/image`
- `web_radar`：backend / frontend 目錄分離
