# LinguaForge 網頁版環境設置指南 ## 🚀 快速開始(15分鐘) ### 完整指令(複製貼上即可) ```bash # 1. 建立專案 npx create-next-app@latest linguaforge-web --typescript --tailwind --app --src-dir=false --import-alias="@/*" cd linguaforge-web # 2. 安裝所有套件 npm install @supabase/supabase-js @supabase/ssr @supabase/auth-helpers-nextjs npm install zustand @tanstack/react-query @google/generative-ai npm install next-pwa next-themes npm install lucide-react date-fns zod npm install -D @types/node # 3. 安裝 shadcn/ui npx shadcn-ui@latest init -y # 4. 安裝常用元件 npx shadcn-ui@latest add button card dialog form input label textarea toast alert badge skeleton tabs # 5. 建立環境變數檔 touch .env.local # 6. 啟動開發伺服器 npm run dev ``` ## 📋 前置需求檢查 ### 必要工具 - [ ] Node.js 18+ (檢查:`node -v`) - [ ] npm 或 pnpm (檢查:`npm -v`) - [ ] Git (檢查:`git --version`) - [ ] VS Code 或其他編輯器 ### 必要帳號 - [ ] [Supabase](https://supabase.com) - 資料庫 - [ ] [Google AI Studio](https://makersuite.google.com) - Gemini API - [ ] [Vercel](https://vercel.com) - 部署平台 - [ ] [GitHub](https://github.com) - 版本控制 ## 🔧 詳細設置步驟 ### Step 1: Supabase 設置 #### 1.1 建立專案 1. 前往 [app.supabase.com](https://app.supabase.com) 2. 點擊「New project」 3. 設定: - Project name: `linguaforge` - Database Password: 設定強密碼 - Region: `Southeast Asia (Singapore)` (離台灣近) #### 1.2 建立資料表 在 SQL Editor 執行: ```sql -- 啟用 UUID 擴充 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- 用戶表(使用 Supabase Auth) CREATE TABLE profiles ( id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY, username TEXT UNIQUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()) ); -- 詞卡表 CREATE TABLE cards ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, word TEXT NOT NULL, pronunciation TEXT, definition TEXT NOT NULL, part_of_speech TEXT, examples JSONB DEFAULT '[]', source_sentence TEXT, difficulty TEXT CHECK (difficulty IN ('beginner', 'intermediate', 'advanced')), -- SM-2 演算法欄位 next_review_date TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), easiness_factor DECIMAL(3,2) DEFAULT 2.5, interval_days INTEGER DEFAULT 0, repetition_count INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), INDEX idx_user_cards (user_id), INDEX idx_next_review (user_id, next_review_date) ); -- 複習記錄表 CREATE TABLE review_logs ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, card_id UUID REFERENCES cards(id) ON DELETE CASCADE NOT NULL, user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, quality INTEGER CHECK (quality >= 0 AND quality <= 5) NOT NULL, reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), time_spent_seconds INTEGER, INDEX idx_user_reviews (user_id, reviewed_at) ); -- 啟用 Row Level Security ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; ALTER TABLE cards ENABLE ROW LEVEL SECURITY; ALTER TABLE review_logs ENABLE ROW LEVEL SECURITY; -- RLS 政策 CREATE POLICY "Users can view own profile" ON profiles FOR SELECT USING (auth.uid() = id); CREATE POLICY "Users can update own profile" ON profiles FOR UPDATE USING (auth.uid() = id); CREATE POLICY "Users can create own profile" ON profiles FOR INSERT WITH CHECK (auth.uid() = id); CREATE POLICY "Users can view own cards" ON cards FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "Users can create own cards" ON cards FOR INSERT WITH CHECK (auth.uid() = user_id); CREATE POLICY "Users can update own cards" ON cards FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "Users can delete own cards" ON cards FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "Users can view own reviews" ON review_logs FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "Users can create own reviews" ON review_logs FOR INSERT WITH CHECK (auth.uid() = user_id); -- 觸發器:自動更新 updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = TIMEZONE('utc', NOW()); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_profiles_updated_at BEFORE UPDATE ON profiles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_cards_updated_at BEFORE UPDATE ON cards FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); ``` #### 1.3 取得 API Keys 1. 進入 Project Settings > API 2. 複製: - `URL` (Project URL) - `anon public` key ### Step 2: Gemini API 設置 #### 2.1 取得 API Key 1. 前往 [Google AI Studio](https://makersuite.google.com/app/apikey) 2. 點擊「Get API key」 3. 選擇或建立 Google Cloud 專案 4. 複製 API Key #### 2.2 測試 API ```bash curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contents": [{ "parts": [{ "text": "Hello" }] }] }' ``` ### Step 3: 專案配置 #### 3.1 環境變數設置 編輯 `.env.local`: ```bash # Supabase NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key # Gemini API GEMINI_API_KEY=your-gemini-api-key # App NEXT_PUBLIC_APP_URL=http://localhost:3000 ``` #### 3.2 TypeScript 配置 更新 `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2017", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } ``` #### 3.3 Tailwind 配置 更新 `tailwind.config.ts`: ```typescript import type { Config } from 'tailwindcss' const config: Config = { darkMode: ["class"], content: [ './pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', ], theme: { extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, }, }, plugins: [require("tailwindcss-animate")], } export default config ``` ### Step 4: 建立專案結構 ```bash # 建立資料夾結構 mkdir -p app/{api,\(auth\),\(dashboard\)} mkdir -p app/api/{gemini,cards,review} mkdir -p app/\(auth\)/{login,register} mkdir -p app/\(dashboard\)/{cards,generate,review,stats} mkdir -p components/{ui,cards,layout,providers} mkdir -p lib/{supabase,gemini,algorithms} mkdir -p hooks mkdir -p types mkdir -p public/{icons,images} ``` ### Step 5: 初始化核心檔案 #### 5.1 Supabase Client 建立 `lib/supabase/client.ts`: ```typescript import { createBrowserClient } from '@supabase/ssr' export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ) } ``` #### 5.2 根 Layout 更新 `app/layout.tsx`: ```tsx import type { Metadata } from 'next' import { Inter } from 'next/font/google' import './globals.css' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'LinguaForge - AI 英語詞彙學習', description: 'AI 驅動的個人化英語詞彙學習平台', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return (
{children} ) } ``` #### 5.3 首頁 更新 `app/page.tsx`: ```tsx import Link from 'next/link' import { Button } from '@/components/ui/button' export default function HomePage() { return (AI 驅動的英語詞彙學習平台