自動化生成現代全端 AI 應用專案:Laravel, Vue.js, FastAPI 與 Docker Compose 的關鍵技術與實現
前言
在快速變遷的軟體開發領域,高效的專案初始化與配置是成功的關鍵。當面對由多個技術棧組成的複雜全端應用程式,特別是涉及人工智慧微服務時,手動設置可能會耗時且容易出錯。本文將深入探討如何透過自動化流程,快速且一致地生成一個現代化的全端 AI 應用專案骨架,該專案整合了 Laravel 後端、Vue.js 前端、FastAPI AI 微服務,並利用 Docker Compose 進行容器化部署。
您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/workflow-ai-platform.git
在快速變遷的軟體開發領域,高效的專案初始化與配置是成功的關鍵。當面對由多個技術棧組成的複雜全端應用程式,特別是涉及人工智慧微服務時,手動設置可能會耗時且容易出錯。本文將深入探討如何透過自動化流程,快速且一致地生成一個現代化的全端 AI 應用專案骨架,該專案整合了 Laravel 後端、Vue.js 前端、FastAPI AI 微服務,並利用 Docker Compose 進行容器化部署。
您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/workflow-ai-platform.git
專案亮點
本專案集成了多項關鍵技術,提供以下核心亮點:
全端整合:無縫協作的 Laravel (PHP) 後端、Vue.js (JavaScript) 前端和 FastAPI (Python) AI 微服務。
智慧文件管理:支援文件上傳、AI 摘要(OpenAI LLM),以及基於 OpenAI Embedding 和 Qdrant 的語意搜尋功能。
AI 語音助理:整合 Faster-Whisper 模型實現高效語音轉錄,並結合 LangChain 構建 RAG (檢索增強生成) 流程,提供智能對話回應及完整的對話歷史記錄。
安全認證:採用 Laravel Sanctum 實現安全的 API Token 認證,並透過 Pinia 在前端管理認證狀態。
資料持久化:使用 MySQL 儲存應用數據,並利用 Qdrant 作為高效能向量資料庫。
自動化 API 文件:透過 Laravel Scribe 自動生成清晰互動的後端 API 文檔,FastAPI 則提供 Swagger UI。
全面測試覆蓋:包含前端 (Vitest 單元測試、Cypress E2E 測試) 和後端 (PHPUnit) 以及 AI 微服務 (Pytest) 的自動化測試,確保應用程式的穩定性和可靠性。
便捷部署:基於 Docker Compose 的容器化方案,實現開發環境的快速搭建和一致性部署。
本專案集成了多項關鍵技術,提供以下核心亮點:
全端整合:無縫協作的 Laravel (PHP) 後端、Vue.js (JavaScript) 前端和 FastAPI (Python) AI 微服務。
智慧文件管理:支援文件上傳、AI 摘要(OpenAI LLM),以及基於 OpenAI Embedding 和 Qdrant 的語意搜尋功能。
AI 語音助理:整合 Faster-Whisper 模型實現高效語音轉錄,並結合 LangChain 構建 RAG (檢索增強生成) 流程,提供智能對話回應及完整的對話歷史記錄。
安全認證:採用 Laravel Sanctum 實現安全的 API Token 認證,並透過 Pinia 在前端管理認證狀態。
資料持久化:使用 MySQL 儲存應用數據,並利用 Qdrant 作為高效能向量資料庫。
自動化 API 文件:透過 Laravel Scribe 自動生成清晰互動的後端 API 文檔,FastAPI 則提供 Swagger UI。
全面測試覆蓋:包含前端 (Vitest 單元測試、Cypress E2E 測試) 和後端 (PHPUnit) 以及 AI 微服務 (Pytest) 的自動化測試,確保應用程式的穩定性和可靠性。
便捷部署:基於 Docker Compose 的容器化方案,實現開發環境的快速搭建和一致性部署。
專案架構概覽
我們將生成的專案是一個典型的微服務架構,其核心組件包括:
Laravel (PHP):作為強大的後端 API,負責用戶認證、文件管理以及協調與 AI 微服務的通訊。
Vue.js (JavaScript):作為響應式的前端介面,提供用戶上傳文件、語音互動及展示結果的功能。
FastAPI (Python):專門的 AI 微服務,處理諸如語音轉錄(Faster-Whisper)、文件向量化(OpenAI Embedding)、語意搜尋(Qdrant)和檢索增強生成(RAG)等 AI 核心功能。
MySQL & Qdrant:分別作為關係型資料庫和向量資料庫,提供數據持久化。
Docker Compose:用於定義和運行多容器 Docker 應用程式,簡化部署。
Caddy:可選的反向代理服務,用於統一各服務的訪問入口。
這種架構確保了各服務的解耦、獨立部署和可擴展性。
我們將生成的專案是一個典型的微服務架構,其核心組件包括:
Laravel (PHP):作為強大的後端 API,負責用戶認證、文件管理以及協調與 AI 微服務的通訊。
Vue.js (JavaScript):作為響應式的前端介面,提供用戶上傳文件、語音互動及展示結果的功能。
FastAPI (Python):專門的 AI 微服務,處理諸如語音轉錄(Faster-Whisper)、文件向量化(OpenAI Embedding)、語意搜尋(Qdrant)和檢索增強生成(RAG)等 AI 核心功能。
MySQL & Qdrant:分別作為關係型資料庫和向量資料庫,提供數據持久化。
Docker Compose:用於定義和運行多容器 Docker 應用程式,簡化部署。
Caddy:可選的反向代理服務,用於統一各服務的訪問入口。
這種架構確保了各服務的解耦、獨立部署和可擴展性。
自動化專案啟動流程解析
為了實現專案的快速啟動和環境一致性,我們採用了一套自動化流程來生成專案骨架和初始代碼。這套流程旨在消除手動配置的繁瑣與潛在錯誤,讓開發者能迅速進入開發狀態。
為了實現專案的快速啟動和環境一致性,我們採用了一套自動化流程來生成專案骨架和初始代碼。這套流程旨在消除手動配置的繁瑣與潛在錯誤,讓開發者能迅速進入開發狀態。
自動化流程的核心功能
這套自動化流程將負責完成以下關鍵任務:
專案目錄結構建立:
流程會自動創建 workflow-ai-platform 作為專案主目錄,並在其內部建立 backend、frontend、ai-orchestrator 等主要服務目錄,以及 data-volumes 用於數據持久化。
根目錄配置檔案生成:
流程會生成專案級別的配置文件,包括:
.env.example
:提供所有服務共享的環境變數範本,例如 OpenAI API Key、資料庫連接配置、AI Orchestrator 的內部 URL 等。
Caddyfile
:配置 Caddy 作為可選的反向代理,用於統一各服務的外部訪問入口,例如將 localhost:5173
的前端服務映射到 localhost:8081
。
README.md
:提供專案的概覽、快速啟動指南、API 文檔地址和測試方法等關鍵資訊。
Docker Compose 配置檔案創建:
流程會生成核心的 docker-compose.yml 檔案,詳細定義了構成整個應用程式的多個 Docker 服務,包括它們的構建上下文、端口映射、卷掛載和服務間的依賴關係。這包括了 backend (Laravel)、frontend (Vue.js)、ai-orchestrator (FastAPI)、qdrant (向量資料庫) 和 database (MySQL)。
AI 微服務 (ai-orchestrator) 檔案與代碼生成:
流程將生成 ai-orchestrator 目錄下的所有必要文件,包括:
app/main.py
:FastAPI 應用的主入口點,定義了語音轉錄、文件上傳通知、語意搜尋和 AI 回應的 API 端點。它負責全局加載 Faster-Whisper 模型。
app/services/document_service.py
:實現文件的向量化、Qdrant 資料庫操作和文件摘要生成邏輯。
app/services/rag_pipeline.py
:負責構建檢索增強生成(RAG)流程,利用 LangChain 整合檢索結果和對話歷史來生成智能回應。
requirements.txt
:列出所有 Python 依賴(如 fastapi
、uvicorn
、qdrant-client
、langchain
、openai
、faster-whisper
等)。
Dockerfile
:定義 ai-orchestrator
服務的 Docker 映像構建步驟。
tests/
:包含針對核心服務的 Pytest 測試案例,這些測試會模擬外部依賴(如 OpenAI 和 Qdrant)以確保測試的獨立性。
後端 (backend) 檔案與代碼生成:
流程會生成 backend 目錄下的 Laravel 專案結構和核心代碼:
composer.json
:定義 PHP 依賴,包括 laravel/sanctum
(用於 API 認證)和 knuckleswtf/scribe
(用於自動化 API 文檔生成)。
app/Http/Controllers/
:生成 AuthController.php
(處理認證)、DocumentController.php
(處理文件上傳和搜尋) 和 VoiceController.php
(處理語音輸入和回應)。
app/Models/
:定義 User
、Document
、Voice
等 Eloquent 模型。
database/migrations/
和 database/seeders/
:用於資料庫表結構定義和測試數據填充。
routes/api.php
:定義所有後端 API 路由。
Dockerfile
、nginx/default.conf
和 etc/supervisor/conf.d/supervisord.conf
:用於後端服務的 Docker 容器化和進程管理。
tests/Feature/
:包含 Laravel 的功能測試,覆蓋認證、文件和語音模組,並使用 Laravel 的 Http::fake()
模擬外部服務調用。
storage/responses/
:為 Scribe API 文檔提供的範例響應文件。
前端 (frontend) 檔案與代碼生成:
流程將生成 frontend 目錄下的 Vue.js 專案結構和核心代碼:
package.json
:定義前端的 Node.js 依賴,包括 Vue.js、Pinia(狀態管理)、Vue Router、TailwindCSS(樣式框架)以及 Vitest(單元測試)和 Cypress(E2E 測試)。
src/App.vue
和 src/main.js
:Vue 應用程式的入口點。
src/router/index.js
:Vue Router 配置,管理前端頁面導航和路由守衛。
src/stores/auth.js
:使用 Pinia 管理用戶認證狀態和 API Token。
src/views/
:包含 DocumentsView.vue
(文件管理介面) 和 VoiceAssistantView.vue
(語音助理介面) 等主要頁面組件。
cypress.config.js
和 cypress/
:Cypress 端到端測試的配置和測試腳本,用於模擬用戶在瀏覽器中的真實互動。
vite.config.js
和 Dockerfile
:Vite 開發伺服器配置和前端服務的 Docker 映像構建過程。
這套自動化流程將負責完成以下關鍵任務:
專案目錄結構建立:
流程會自動創建 workflow-ai-platform 作為專案主目錄,並在其內部建立 backend、frontend、ai-orchestrator 等主要服務目錄,以及 data-volumes 用於數據持久化。
根目錄配置檔案生成:
流程會生成專案級別的配置文件,包括:
.env.example
:提供所有服務共享的環境變數範本,例如 OpenAI API Key、資料庫連接配置、AI Orchestrator 的內部 URL 等。Caddyfile
:配置 Caddy 作為可選的反向代理,用於統一各服務的外部訪問入口,例如將localhost:5173
的前端服務映射到localhost:8081
。README.md
:提供專案的概覽、快速啟動指南、API 文檔地址和測試方法等關鍵資訊。
Docker Compose 配置檔案創建:
流程會生成核心的 docker-compose.yml 檔案,詳細定義了構成整個應用程式的多個 Docker 服務,包括它們的構建上下文、端口映射、卷掛載和服務間的依賴關係。這包括了 backend (Laravel)、frontend (Vue.js)、ai-orchestrator (FastAPI)、qdrant (向量資料庫) 和 database (MySQL)。
AI 微服務 (ai-orchestrator) 檔案與代碼生成:
流程將生成 ai-orchestrator 目錄下的所有必要文件,包括:
app/main.py
:FastAPI 應用的主入口點,定義了語音轉錄、文件上傳通知、語意搜尋和 AI 回應的 API 端點。它負責全局加載 Faster-Whisper 模型。app/services/document_service.py
:實現文件的向量化、Qdrant 資料庫操作和文件摘要生成邏輯。app/services/rag_pipeline.py
:負責構建檢索增強生成(RAG)流程,利用 LangChain 整合檢索結果和對話歷史來生成智能回應。requirements.txt
:列出所有 Python 依賴(如fastapi
、uvicorn
、qdrant-client
、langchain
、openai
、faster-whisper
等)。Dockerfile
:定義ai-orchestrator
服務的 Docker 映像構建步驟。tests/
:包含針對核心服務的 Pytest 測試案例,這些測試會模擬外部依賴(如 OpenAI 和 Qdrant)以確保測試的獨立性。
後端 (backend) 檔案與代碼生成:
流程會生成 backend 目錄下的 Laravel 專案結構和核心代碼:
composer.json
:定義 PHP 依賴,包括laravel/sanctum
(用於 API 認證)和knuckleswtf/scribe
(用於自動化 API 文檔生成)。app/Http/Controllers/
:生成AuthController.php
(處理認證)、DocumentController.php
(處理文件上傳和搜尋) 和VoiceController.php
(處理語音輸入和回應)。app/Models/
:定義User
、Document
、Voice
等 Eloquent 模型。database/migrations/
和database/seeders/
:用於資料庫表結構定義和測試數據填充。routes/api.php
:定義所有後端 API 路由。Dockerfile
、nginx/default.conf
和etc/supervisor/conf.d/supervisord.conf
:用於後端服務的 Docker 容器化和進程管理。tests/Feature/
:包含 Laravel 的功能測試,覆蓋認證、文件和語音模組,並使用 Laravel 的Http::fake()
模擬外部服務調用。storage/responses/
:為 Scribe API 文檔提供的範例響應文件。
前端 (frontend) 檔案與代碼生成:
流程將生成 frontend 目錄下的 Vue.js 專案結構和核心代碼:
package.json
:定義前端的 Node.js 依賴,包括 Vue.js、Pinia(狀態管理)、Vue Router、TailwindCSS(樣式框架)以及 Vitest(單元測試)和 Cypress(E2E 測試)。src/App.vue
和src/main.js
:Vue 應用程式的入口點。src/router/index.js
:Vue Router 配置,管理前端頁面導航和路由守衛。src/stores/auth.js
:使用 Pinia 管理用戶認證狀態和 API Token。src/views/
:包含DocumentsView.vue
(文件管理介面) 和VoiceAssistantView.vue
(語音助理介面) 等主要頁面組件。cypress.config.js
和cypress/
:Cypress 端到端測試的配置和測試腳本,用於模擬用戶在瀏覽器中的真實互動。vite.config.js
和Dockerfile
:Vite 開發伺服器配置和前端服務的 Docker 映像構建過程。
關鍵程式碼片段
本節將展示專案中各模組的關鍵程式碼片段,幫助讀者快速理解其核心邏輯。
本節將展示專案中各模組的關鍵程式碼片段,幫助讀者快速理解其核心邏輯。
1. 後端:Laravel 用戶認證控制器 (backend/app/Http/Controllers/AuthController.php
)
此程式碼展示了用戶註冊的核心邏輯,包括數據驗證、密碼加密和 Sanctum API Token 的生成。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Log;
class AuthController extends Controller
{
public function register(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'message' => 'User registered successfully',
'token' => $token,
'user' => $user,
], 201);
}
}
此程式碼展示了用戶註冊的核心邏輯,包括數據驗證、密碼加密和 Sanctum API Token 的生成。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Log;
class AuthController extends Controller
{
public function register(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'message' => 'User registered successfully',
'token' => $token,
'user' => $user,
], 201);
}
}
2. AI 微服務:FastAPI 語音轉錄 (ai-orchestrator/app/main.py
)
此程式碼片段展示了 FastAPI 服務如何接收音頻文件,並使用 faster_whisper
進行語音轉錄的邏輯。
from fastapi import FastAPI, UploadFile, File, HTTPException
import logging
import io
import os
from faster_whisper import WhisperModel
logger = logging.getLogger(__name__)
app = FastAPI(title="AI Orchestrator Microservice")
WHISPER_MODEL_NAME = os.getenv("WHISPER_MODEL", "tiny")
WHISPER_DEVICE = os.getenv("WHISPER_DEVICE", "cpu")
WHISPER_COMPUTE_TYPE = os.getenv("WHISPER_COMPUTE_TYPE", "int8")
WHISPER_DOWNLOAD_ROOT = "./data/whisper_models"
whisper_model = None
try:
whisper_model = WhisperModel(
WHISPER_MODEL_NAME,
device=WHISPER_DEVICE,
compute_type=WHISPER_COMPUTE_TYPE,
download_root=WHISPER_DOWNLOAD_ROOT
)
logger.info(f"Faster-Whisper model '{WHISPER_MODEL_NAME}' loaded successfully.")
except Exception as e:
logger.error(f"載入 Faster-Whisper 模型失敗: {e}", exc_info=True)
@app.post("/voice/transcribe")
async def transcribe_voice(audio_file: UploadFile = File(...)):
if whisper_model is None:
raise HTTPException(status_code=503, detail="語音轉錄服務暫時不可用,模型載入失敗。")
if audio_file.content_type not in ["audio/webm", "audio/mp3", "audio/wav", "audio/ogg"]:
raise HTTPException(status_code=400, detail=f"不支援的音頻格式: {audio_file.content_type}")
try:
audio_bytes = await audio_file.read()
audio_stream = io.BytesIO(audio_bytes)
segments, info = whisper_model.transcribe(audio_stream, beam_size=5)
transcribed_text = ""
for segment in segments:
transcribed_text += segment.text + " "
return {"transcribed_text": transcribed_text.strip()}
except Exception as e:
logger.error(f"語音轉錄失敗: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"語音轉錄失敗: {e}")
此程式碼片段展示了 FastAPI 服務如何接收音頻文件,並使用 faster_whisper
進行語音轉錄的邏輯。
from fastapi import FastAPI, UploadFile, File, HTTPException
import logging
import io
import os
from faster_whisper import WhisperModel
logger = logging.getLogger(__name__)
app = FastAPI(title="AI Orchestrator Microservice")
WHISPER_MODEL_NAME = os.getenv("WHISPER_MODEL", "tiny")
WHISPER_DEVICE = os.getenv("WHISPER_DEVICE", "cpu")
WHISPER_COMPUTE_TYPE = os.getenv("WHISPER_COMPUTE_TYPE", "int8")
WHISPER_DOWNLOAD_ROOT = "./data/whisper_models"
whisper_model = None
try:
whisper_model = WhisperModel(
WHISPER_MODEL_NAME,
device=WHISPER_DEVICE,
compute_type=WHISPER_COMPUTE_TYPE,
download_root=WHISPER_DOWNLOAD_ROOT
)
logger.info(f"Faster-Whisper model '{WHISPER_MODEL_NAME}' loaded successfully.")
except Exception as e:
logger.error(f"載入 Faster-Whisper 模型失敗: {e}", exc_info=True)
@app.post("/voice/transcribe")
async def transcribe_voice(audio_file: UploadFile = File(...)):
if whisper_model is None:
raise HTTPException(status_code=503, detail="語音轉錄服務暫時不可用,模型載入失敗。")
if audio_file.content_type not in ["audio/webm", "audio/mp3", "audio/wav", "audio/ogg"]:
raise HTTPException(status_code=400, detail=f"不支援的音頻格式: {audio_file.content_type}")
try:
audio_bytes = await audio_file.read()
audio_stream = io.BytesIO(audio_bytes)
segments, info = whisper_model.transcribe(audio_stream, beam_size=5)
transcribed_text = ""
for segment in segments:
transcribed_text += segment.text + " "
return {"transcribed_text": transcribed_text.strip()}
except Exception as e:
logger.error(f"語音轉錄失敗: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"語音轉錄失敗: {e}")
3. 前端:Vue.js 語音助理組件 (frontend/src/views/VoiceAssistantView.vue
)
此程式碼片段展示了前端如何使用 MediaRecorder
錄製語音,並將其發送到後端進行處理。
<template>
<div class="flex flex-col items-center space-y-4">
<button @click="toggleRecording" :class="{ 'bg-red-600': isRecording, 'bg-green-600': !isRecording }"
:disabled="processing" class="w-32 h-32 rounded-full text-white flex items-center justify-center shadow-lg">
<svg v-if="!isRecording" class="w-16 h-16" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>
<svg v-else class="w-16 h-16 animate-pulse" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>
</button>
<p v-if="processing" class="text-indigo-600 font-semibold text-lg">AI 思考中...</p>
<p v-else-if="isRecording" class="text-red-500 font-semibold text-lg">錄音中...</p>
<p v-else class="text-gray-600 text-lg">點擊麥克風開始錄音</p>
<p v-if="error" class="text-red-500 text-center mt-2">{{ error }}</p>
</div>
<div class="mt-8 p-6 border rounded-lg shadow-sm max-h-96 overflow-y-auto">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">對話歷史:</h2>
<div v-for="(message, index) in conversationHistory" :key="index">
<div v-if="message.speaker === 'user'" class="text-right">
<span class="inline-block bg-blue-100 rounded-lg px-4 py-2">{{ message.text }}</span>
</div>
<div v-else class="text-left">
<span class="inline-block bg-green-100 rounded-lg px-4 py-2">{{ message.text }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import axios from 'axios';
import { useAuthStore } from '../stores/auth';
const authStore = useAuthStore();
const API_URL = import.meta.env.VITE_API_URL;
const isRecording = ref(false);
const processing = ref(false);
const mediaRecorder = ref(null);
const audioChunks = ref([]);
const error = ref(null);
const conversationHistory = ref([]);
const currentUserId = ref(localStorage.getItem('userId') || 'guest_user');
onMounted(async () => {
await fetchConversationHistory();
});
onUnmounted(() => {
if (isRecording.value && mediaRecorder.value) {
mediaRecorder.value.stop();
isRecording.value = false;
}
});
const fetchConversationHistory = async () => { /* ... fetch logic ... */ };
const toggleRecording = async () => {
if (processing.value) return;
if (!isRecording.value) {
error.value = null;
audioChunks.value = [];
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder.value = new MediaRecorder(stream, { mimeType: 'audio/webm' });
mediaRecorder.value.ondataavailable = (event) => { audioChunks.value.push(event.data); };
mediaRecorder.value.onstop = async () => {
processing.value = true;
isRecording.value = false;
const audioBlob = new Blob(audioChunks.value, { type: 'audio/webm' });
await sendAudioToBackend(audioBlob);
stream.getTracks().forEach(track => track.stop());
processing.value = false;
};
mediaRecorder.value.start();
isRecording.value = true;
} catch (err) {
error.value = '無法獲取麥克風權限或開始錄音。';
}
} else {
if (mediaRecorder.value && mediaRecorder.value.state === 'recording') {
mediaRecorder.value.stop();
}
}
};
const sendAudioToBackend = async (audioBlob) => { /* ... send logic ... */ };
</script>
<style scoped>
button { transition: all 0.2s ease-in-out; }
button:hover { transform: scale(1.05); }
button:disabled { opacity: 0.7; cursor: not-allowed; }
</style>
此程式碼片段展示了前端如何使用 MediaRecorder
錄製語音,並將其發送到後端進行處理。
<template>
<div class="flex flex-col items-center space-y-4">
<button @click="toggleRecording" :class="{ 'bg-red-600': isRecording, 'bg-green-600': !isRecording }"
:disabled="processing" class="w-32 h-32 rounded-full text-white flex items-center justify-center shadow-lg">
<svg v-if="!isRecording" class="w-16 h-16" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>
<svg v-else class="w-16 h-16 animate-pulse" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>
</button>
<p v-if="processing" class="text-indigo-600 font-semibold text-lg">AI 思考中...</p>
<p v-else-if="isRecording" class="text-red-500 font-semibold text-lg">錄音中...</p>
<p v-else class="text-gray-600 text-lg">點擊麥克風開始錄音</p>
<p v-if="error" class="text-red-500 text-center mt-2">{{ error }}</p>
</div>
<div class="mt-8 p-6 border rounded-lg shadow-sm max-h-96 overflow-y-auto">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">對話歷史:</h2>
<div v-for="(message, index) in conversationHistory" :key="index">
<div v-if="message.speaker === 'user'" class="text-right">
<span class="inline-block bg-blue-100 rounded-lg px-4 py-2">{{ message.text }}</span>
</div>
<div v-else class="text-left">
<span class="inline-block bg-green-100 rounded-lg px-4 py-2">{{ message.text }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import axios from 'axios';
import { useAuthStore } from '../stores/auth';
const authStore = useAuthStore();
const API_URL = import.meta.env.VITE_API_URL;
const isRecording = ref(false);
const processing = ref(false);
const mediaRecorder = ref(null);
const audioChunks = ref([]);
const error = ref(null);
const conversationHistory = ref([]);
const currentUserId = ref(localStorage.getItem('userId') || 'guest_user');
onMounted(async () => {
await fetchConversationHistory();
});
onUnmounted(() => {
if (isRecording.value && mediaRecorder.value) {
mediaRecorder.value.stop();
isRecording.value = false;
}
});
const fetchConversationHistory = async () => { /* ... fetch logic ... */ };
const toggleRecording = async () => {
if (processing.value) return;
if (!isRecording.value) {
error.value = null;
audioChunks.value = [];
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder.value = new MediaRecorder(stream, { mimeType: 'audio/webm' });
mediaRecorder.value.ondataavailable = (event) => { audioChunks.value.push(event.data); };
mediaRecorder.value.onstop = async () => {
processing.value = true;
isRecording.value = false;
const audioBlob = new Blob(audioChunks.value, { type: 'audio/webm' });
await sendAudioToBackend(audioBlob);
stream.getTracks().forEach(track => track.stop());
processing.value = false;
};
mediaRecorder.value.start();
isRecording.value = true;
} catch (err) {
error.value = '無法獲取麥克風權限或開始錄音。';
}
} else {
if (mediaRecorder.value && mediaRecorder.value.state === 'recording') {
mediaRecorder.value.stop();
}
}
};
const sendAudioToBackend = async (audioBlob) => { /* ... send logic ... */ };
</script>
<style scoped>
button { transition: all 0.2s ease-in-out; }
button:hover { transform: scale(1.05); }
button:disabled { opacity: 0.7; cursor: not-allowed; }
</style>
啟動專案的步驟
自動化流程執行完畢後,啟動整個專案變得非常簡單:
複製環境變數範本:將專案根目錄下的 .env.example
檔案複製為 .env
,然後開啟 .env
檔案,務必填寫您的 OPENAI_API_KEY
。
構建並啟動所有服務:在專案根目錄執行 docker compose build
和 docker compose up -d
。Docker Compose 將根據 docker-compose.yml
定義自動構建映像並啟動所有服務。
Laravel 後端初始化:透過 Docker 命令進入 backend
容器內部,執行 Laravel 的初始化命令,包括 php artisan key:generate
(生成應用程式金鑰)、php artisan migrate
(執行資料庫遷移)、php artisan db:seed
(可選,填充假數據) 和 php artisan scribe:generate
(生成 API 文檔)。
訪問應用程式:一旦所有服務啟動並初始化完成,您可以透過瀏覽器訪問前端應用程式(預設 http://localhost:5173
),以及後端 API 文檔(Scribe 於 http://localhost:8000/docs
)和 AI 微服務 API 文檔(Swagger UI 於 http://localhost:8001/docs
)。
Caddy 反向代理 (可選):如果您希望透過 Caddy 進行統一的服務訪問,請確保已安裝 Caddy,然後在專案根目錄運行 caddy run --config Caddyfile
。
自動化流程執行完畢後,啟動整個專案變得非常簡單:
複製環境變數範本:將專案根目錄下的
.env.example
檔案複製為.env
,然後開啟.env
檔案,務必填寫您的OPENAI_API_KEY
。構建並啟動所有服務:在專案根目錄執行
docker compose build
和docker compose up -d
。Docker Compose 將根據docker-compose.yml
定義自動構建映像並啟動所有服務。Laravel 後端初始化:透過 Docker 命令進入
backend
容器內部,執行 Laravel 的初始化命令,包括php artisan key:generate
(生成應用程式金鑰)、php artisan migrate
(執行資料庫遷移)、php artisan db:seed
(可選,填充假數據) 和php artisan scribe:generate
(生成 API 文檔)。訪問應用程式:一旦所有服務啟動並初始化完成,您可以透過瀏覽器訪問前端應用程式(預設
http://localhost:5173
),以及後端 API 文檔(Scribe 於http://localhost:8000/docs
)和 AI 微服務 API 文檔(Swagger UI 於http://localhost:8001/docs
)。Caddy 反向代理 (可選):如果您希望透過 Caddy 進行統一的服務訪問,請確保已安裝 Caddy,然後在專案根目錄運行
caddy run --config Caddyfile
。
測試策略
本專案實施了多層次的測試策略,以確保各個模組的功能正確性和系統的穩定性:
前端單元測試 (Vitest):針對 Vue 組件、Pinia Store 和其他前端邏輯單元進行快速、隔離的測試,驗證每個小型組件或功能的行為是否符合預期。
前端端到端測試 (Cypress E2E):模擬真實用戶在瀏覽器中的操作流程,從用戶界面層面驗證整個應用程式的完整功能,包括用戶認證、文件上傳、語音互動等關鍵流程。這些測試會與後端服務進行實際交互。
後端測試 (PHPUnit):針對 Laravel 控制器、API 端點和服務邏輯進行功能測試和單元測試,確保 API 的正確性、數據處理邏輯以及與外部服務(如 AI Orchestrator)的集成。測試中會利用 Laravel 的 Http::fake()
來模擬外部 API 的響應,以保證測試的獨立性。
AI 微服務測試 (Pytest):針對 FastAPI 服務中的 AI 邏輯、模型加載和第三方集成(如 Qdrant、OpenAI)進行單元測試和集成測試,確保 AI 功能的準確性和穩定性。
所有這些測試都被設計為可以集成到 CI/CD 流程中,實現自動化測試,從而確保每次代碼提交都能得到充分驗證。
本專案實施了多層次的測試策略,以確保各個模組的功能正確性和系統的穩定性:
前端單元測試 (Vitest):針對 Vue 組件、Pinia Store 和其他前端邏輯單元進行快速、隔離的測試,驗證每個小型組件或功能的行為是否符合預期。
前端端到端測試 (Cypress E2E):模擬真實用戶在瀏覽器中的操作流程,從用戶界面層面驗證整個應用程式的完整功能,包括用戶認證、文件上傳、語音互動等關鍵流程。這些測試會與後端服務進行實際交互。
後端測試 (PHPUnit):針對 Laravel 控制器、API 端點和服務邏輯進行功能測試和單元測試,確保 API 的正確性、數據處理邏輯以及與外部服務(如 AI Orchestrator)的集成。測試中會利用 Laravel 的
Http::fake()
來模擬外部 API 的響應,以保證測試的獨立性。AI 微服務測試 (Pytest):針對 FastAPI 服務中的 AI 邏輯、模型加載和第三方集成(如 Qdrant、OpenAI)進行單元測試和集成測試,確保 AI 功能的準確性和穩定性。
所有這些測試都被設計為可以集成到 CI/CD 流程中,實現自動化測試,從而確保每次代碼提交都能得到充分驗證。
開發筆記與注意事項
OpenAI API Key:這是整個 AI 功能的核心。請務必確保在 .env
檔案中正確設定了您的 OPENAI_API_KEY
,否則所有依賴 OpenAI 服務的 AI 功能將無法正常運作。
Faster-Whisper 模型:AI Orchestrator 會在首次運行 faster-whisper
時自動下載語音轉錄模型。您可以透過修改根目錄 .env
中的 WHISPER_MODEL
參數,來選擇不同大小的模型(如 tiny
, base
, small
, medium
, large-v2
, large-v3
),以平衡性能和準確度。同時,WHISPER_DEVICE
和 WHISPER_COMPUTE_TYPE
參數可用於優化 CPU 或 GPU 環境下的模型運行。
Laravel Sanctum 與 CORS 配置:為了確保前端應用程式與 Laravel 後端之間的認證和跨域請求正常工作,.env.example
中已預配置 SANCTUM_STATEFUL_DOMAINS
和 SESSION_DOMAIN
。請確保這些設定與您的前端應用程式的實際網址相符。
前端路由保護:Vue.js 前端應用中的敏感路由(例如 /documents
和 /voice
)已設置了路由守衛。這表示只有經過認證(即已登入)的用戶才能訪問這些頁面,未登入的用戶將被自動重定向到登入頁面,保障應用程式的安全性。
日誌記錄:專案中的各個微服務都配置了詳細的日誌記錄機制。這對於開發過程中的除錯、監控應用程式運行狀態以及在生產環境中排查問題至關重要。
OpenAI API Key:這是整個 AI 功能的核心。請務必確保在
.env
檔案中正確設定了您的OPENAI_API_KEY
,否則所有依賴 OpenAI 服務的 AI 功能將無法正常運作。Faster-Whisper 模型:AI Orchestrator 會在首次運行
faster-whisper
時自動下載語音轉錄模型。您可以透過修改根目錄.env
中的WHISPER_MODEL
參數,來選擇不同大小的模型(如tiny
,base
,small
,medium
,large-v2
,large-v3
),以平衡性能和準確度。同時,WHISPER_DEVICE
和WHISPER_COMPUTE_TYPE
參數可用於優化 CPU 或 GPU 環境下的模型運行。Laravel Sanctum 與 CORS 配置:為了確保前端應用程式與 Laravel 後端之間的認證和跨域請求正常工作,
.env.example
中已預配置SANCTUM_STATEFUL_DOMAINS
和SESSION_DOMAIN
。請確保這些設定與您的前端應用程式的實際網址相符。前端路由保護:Vue.js 前端應用中的敏感路由(例如
/documents
和/voice
)已設置了路由守衛。這表示只有經過認證(即已登入)的用戶才能訪問這些頁面,未登入的用戶將被自動重定向到登入頁面,保障應用程式的安全性。日誌記錄:專案中的各個微服務都配置了詳細的日誌記錄機制。這對於開發過程中的除錯、監控應用程式運行狀態以及在生產環境中排查問題至關重要。
結論
透過這種基於自動化流程的專案生成方法,我們能夠以前所未有的效率和一致性,快速建立一個由多個現代技術棧組成的複雜全端 AI 應用專案。這種方法不僅顯著減少了手動配置帶來的錯誤和時間成本,更重要的是,它為開發團隊提供了一個標準化、可重複的起點,使得開發者能夠將更多的精力投入到核心業務邏輯的創新和 AI 功能的優化上,而非重複性的環境搭建工作。這不僅極大地提升了開發效率,也為專案的長期可維護性、可擴展性和安全性奠定了堅實的基礎。
透過這種基於自動化流程的專案生成方法,我們能夠以前所未有的效率和一致性,快速建立一個由多個現代技術棧組成的複雜全端 AI 應用專案。這種方法不僅顯著減少了手動配置帶來的錯誤和時間成本,更重要的是,它為開發團隊提供了一個標準化、可重複的起點,使得開發者能夠將更多的精力投入到核心業務邏輯的創新和 AI 功能的優化上,而非重複性的環境搭建工作。這不僅極大地提升了開發效率,也為專案的長期可維護性、可擴展性和安全性奠定了堅實的基礎。
沒有留言:
張貼留言