dramaling-vocab-learning/AI生成網頁前端實際技術規格.md

21 KiB
Raw Blame History

AI生成網頁前端實際技術規格

📋 文件資訊

  • 文件名稱: AI生成網頁前端實際技術規格
  • 版本: v1.0 (基於實際實現)
  • 建立日期: 2025-09-22
  • 最後更新: 2025-09-22
  • 對應代碼: /app/generate/page.tsx + /components/ClickableTextV2.tsx

🏗️ 實際技術架構

A1. 技術棧組成

A1.1 實際使用的技術

{
  "framework": "Next.js 15.5.3",
  "language": "TypeScript",
  "styling": "Tailwind CSS",
  "stateManagement": "React Hooks (useState, useMemo, useCallback)",
  "api": "Fetch API (目前使用假資料)",
  "routing": "Next.js App Router",
  "authentication": "ProtectedRoute組件"
}

A1.2 實際組件結構

GeneratePage (路由保護)
└── GenerateContent (主邏輯組件)
    ├── 輸入模式 (!showAnalysisView)
    │   ├── 頁面標題
    │   ├── 文本輸入區域 (textarea + 字符計數)
    │   ├── 分析按鈕 (載入狀態處理)
    │   └── 個人化程度指示器
    └── 分析結果模式 (showAnalysisView)
        ├── 語法修正面板 (條件顯示)
        ├── 詞彙統計卡片區 (4張卡片)
        ├── ClickableTextV2 (例句展示)
        ├── 翻譯區域 (灰色背景)
        ├── 慣用語展示區域
        ├── 慣用語彈窗 (Portal渲染)
        └── 返回按鈕

💾 實際數據架構

D1. 類型定義實現

D1.1 語法修正類型

interface GrammarCorrection {
  hasErrors: boolean;
  originalText: string;
  correctedText: string;
  corrections: Array<{
    error: string;
    correction: string;
    type: string;
    explanation: string;
  }>;
}

D1.2 慣用語彈窗類型

interface PhrasePopup {
  phrase: string;
  analysis: any;
  position: {
    x: number;
    y: number;
  };
}

D1.3 實際WordAnalysis結構

// 實際測試數據中的結構
interface MockWordAnalysis {
  word: string;
  translation: string;
  definition: string;
  partOfSpeech: string;
  pronunciation: string;
  difficultyLevel: string;
  isPhrase: boolean;
  synonyms: string[];
  example: string;
  exampleTranslation: string;
}

實際性能優化實現

P1. React Hooks優化

P1.1 記憶化函數

// 統計計算優化
const vocabularyStats = useMemo(() => {
  if (!sentenceAnalysis) return null

  const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
  // 計算邏輯...

  return { simpleCount, moderateCount, difficultCount, phraseCount }
}, [sentenceAnalysis])

// 事件處理優化
const handleAnalyzeSentence = useCallback(async () => {
  setIsAnalyzing(true)

  try {
    await new Promise(resolve => setTimeout(resolve, 1000)) // 模擬延遲
    // 設置假資料...
    setShowAnalysisView(true)
  } finally {
    setIsAnalyzing(false)
  }
}, [])

const handleSaveWord = useCallback(async (word: string, analysis: any) => {
  try {
    const cardData = {
      word: word,
      translation: analysis.translation || '',
      definition: analysis.definition || '',
      pronunciation: analysis.pronunciation || `/${word}/`,
      partOfSpeech: analysis.partOfSpeech || 'unknown',
      example: `Example sentence with ${word}.`
    }

    const response = await flashcardsService.createFlashcard(cardData)
    if (response.success) {
      alert(`✅ 已將「${word}」保存到詞卡!`)
    }
  } catch (error) {
    console.error('Save word error:', error)
    throw error
  }
}, [])

P1.2 ClickableTextV2性能優化

// 工具函數記憶化
const getCEFRColor = useCallback((level: string) => {
  switch (level) {
    case 'A1': return 'bg-green-100 text-green-700 border-green-200'
    case 'A2': return 'bg-blue-100 text-blue-700 border-blue-200'
    // ...其他等級
    default: return 'bg-gray-100 text-gray-700 border-gray-200'
  }
}, [])

const findWordAnalysis = useCallback((word: string) => {
  const cleanWord = word.toLowerCase().replace(/[.,!?;:]/g, '')
  return analysis?.[cleanWord] || analysis?.[word] || analysis?.[word.toLowerCase()] || null
}, [analysis])

// 文字分割優化
const words = useMemo(() => text.split(/(\s+|[.,!?;:])/g), [text])

🎨 實際樣式系統

S1. 實現的設計Token

S1.1 實際使用的顏色

/* 詞彙分類顏色 - 實際實現 */
.simple-word {
  background: #f9fafb;    /* bg-gray-50 */
  border: #d1d5db;       /* border-gray-300 */
  color: #6b7280;        /* text-gray-600 */
  border-style: dashed;
  opacity: 0.8;
}

.moderate-word {
  background: #f0fdf4;    /* bg-green-50 */
  border: #bbf7d0;       /* border-green-200 */
  color: #15803d;        /* text-green-700 */
  font-weight: 500;      /* font-medium */
}

.difficult-word {
  background: #fff7ed;    /* bg-orange-50 */
  border: #fed7aa;       /* border-orange-200 */
  color: #c2410c;        /* text-orange-700 */
  font-weight: 500;      /* font-medium */
}

.phrase-word {
  background: #eff6ff;    /* bg-blue-50 */
  border: #bfdbfe;       /* border-blue-200 */
  color: #1d4ed8;        /* text-blue-700 */
  font-weight: 500;      /* font-medium */
}

S1.2 基礎樣式類別

/* 實際基礎樣式 */
.word-base {
  cursor: pointer;
  transition: all 0.2s ease;
  border-radius: 0.25rem;    /* rounded */
  position: relative;
  margin: 0 0.125rem;        /* mx-0.5 */
  padding: 0.125rem 0.25rem; /* px-1 py-0.5 */
}

/* 文字容器 */
.text-container {
  font-size: 1.25rem;        /* text-lg */
  line-height: 2.5;          /* 自訂行高 */
}

🔌 實際API整合

API1. 當前實現狀態

API1.1 假資料模式

// 當前使用的測試數據生成
const handleAnalyzeSentence = async () => {
  console.log('🚀 handleAnalyzeSentence 被調用 (假資料模式)')

  setIsAnalyzing(true)

  try {
    // 模擬API延遲
    await new Promise(resolve => setTimeout(resolve, 1000))

    const testSentence = "She just join the team, so let's cut her some slack until she get used to the workflow."

    // 完整的假資料設置...
    setSentenceAnalysis(mockAnalysis)
    setSentenceMeaning("她剛加入團隊,所以讓我們對她寬容一點,直到她習慣工作流程。")
    setGrammarCorrection({
      hasErrors: true,
      originalText: testSentence,
      correctedText: correctedSentence,
      corrections: [/* 修正詳情 */]
    })
    setShowAnalysisView(true)
  } finally {
    setIsAnalyzing(false)
  }
}

API1.2 真實API準備

預留的API結構:

// 註解中的真實API調用代碼
const response = await fetch('http://localhost:5000/api/ai/analyze-sentence', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
  },
  body: JSON.stringify({
    inputText: textInput,
    userLevel: userLevel,
    analysisMode: 'full'
  })
})

🎯 實際算法實現

A1. 詞彙分類算法

A1.1 CEFR等級比較實現

const CEFR_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] as const

const getLevelIndex = (level: string): number => {
  return CEFR_LEVELS.indexOf(level as typeof CEFR_LEVELS[number])
}

// ClickableTextV2中的實際分類邏輯
const getWordClass = (word: string) => {
  const wordAnalysis = findWordAnalysis(word)
  const baseClass = "cursor-pointer transition-all duration-200 rounded relative mx-0.5 px-1 py-0.5"

  if (wordAnalysis) {
    const isPhrase = getWordProperty(wordAnalysis, 'isPhrase')
    const difficultyLevel = getWordProperty(wordAnalysis, 'difficultyLevel') || 'A1'
    const userLevel = typeof window !== 'undefined' ? localStorage.getItem('userEnglishLevel') || 'A2' : 'A2'

    if (isPhrase) {
      return "" // 慣用語:純黑字
    }

    const userIndex = getLevelIndex(userLevel)
    const wordIndex = getLevelIndex(difficultyLevel)

    if (userIndex > wordIndex) {
      return `${baseClass} bg-gray-50 border border-dashed border-gray-300 hover:bg-gray-100 hover:border-gray-400 text-gray-600 opacity-80`
    } else if (userIndex === wordIndex) {
      return `${baseClass} bg-green-50 border border-green-200 hover:bg-green-100 hover:shadow-lg transform hover:-translate-y-0.5 text-green-700 font-medium`
    } else {
      return `${baseClass} bg-orange-50 border border-orange-200 hover:bg-orange-100 hover:shadow-lg transform hover:-translate-y-0.5 text-orange-700 font-medium`
    }
  } else {
    return "" // 無資料:純黑字
  }
}

A1.2 統計計算算法實現

// 實際的統計計算邏輯
Object.entries(sentenceAnalysis).forEach(([, wordData]: [string, any]) => {
  const isPhrase = wordData?.isPhrase || wordData?.IsPhrase
  const difficultyLevel = wordData?.difficultyLevel || 'A1'

  if (isPhrase) {
    phraseCount++
  } else {
    const userIndex = getLevelIndex(userLevel)
    const wordIndex = getLevelIndex(difficultyLevel)

    if (userIndex > wordIndex) {
      simpleCount++
    } else if (userIndex === wordIndex) {
      moderateCount++
    } else {
      difficultCount++
    }
  }
})

🎪 實際互動系統實現

I1. Portal彈窗系統

I1.1 React Portal實現

// ClickableTextV2中的實際Portal實現
const VocabPopup = () => {
  if (!selectedWord || !analysis?.[selectedWord] || !mounted) return null

  return createPortal(
    <>
      {/* 背景遮罩 */}
      <div
        className="fixed inset-0 bg-black bg-opacity-50 z-40"
        onClick={closePopup}
      />

      {/* 彈窗內容 */}
      <div
        className="fixed z-50 bg-white rounded-xl shadow-lg w-80 sm:w-96 max-w-[90vw] overflow-hidden"
        style={{
          left: `${popupPosition.x}px`,
          top: `${popupPosition.y}px`,
          transform: 'translate(-50%, 8px)',
          maxHeight: '85vh',
          overflowY: 'auto'
        }}
      >
        {/* 彈窗結構 */}
      </div>
    </>,
    document.body
  )
}

I1.2 位置計算實現

const handleWordClick = async (word: string, event: React.MouseEvent) => {
  const rect = event.currentTarget.getBoundingClientRect()
  const position = {
    x: rect.left + rect.width / 2,
    y: rect.bottom + 10,
    showBelow: true
  }

  setPopupPosition(position)
  setSelectedWord(cleanWord)
  onWordClick?.(cleanWord, wordAnalysis)
}

📊 實際狀態管理

ST1. 狀態架構實現

ST1.1 主要狀態

// 實際的狀態定義
const [textInput, setTextInput] = useState('')
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [showAnalysisView, setShowAnalysisView] = useState(false)
const [sentenceAnalysis, setSentenceAnalysis] = useState<Record<string, any> | null>(null)
const [sentenceMeaning, setSentenceMeaning] = useState('')
const [grammarCorrection, setGrammarCorrection] = useState<GrammarCorrection | null>(null)
const [finalText, setFinalText] = useState('')
const [phrasePopup, setPhrasePopup] = useState<PhrasePopup | null>(null)

// 未使用但存在的狀態
const [mode, setMode] = useState<'manual' | 'screenshot'>('manual') // 未使用
const [usageCount] = useState(0) // 固定值
const [isPremium] = useState(true) // 固定值

ST1.2 狀態更新流程

// 實際的分析完成處理
const setAnalysisResults = () => {
  setFinalText(correctedSentence)
  setSentenceAnalysis(mockAnalysis)
  setSentenceMeaning(translatedText)
  setGrammarCorrection(correctionData)
  setShowAnalysisView(true)
}

🔍 實際測試數據架構

TD1. 完整測試數據

TD1.1 語法錯誤測試

// 實際的測試句子
const originalSentence = "She just join the team, so let's cut her some slack until she get used to the workflow."
const correctedSentence = "She just joined the team, so let's cut her some slack until she gets used to the workflow."

// 實際的語法修正數據
const grammarCorrection = {
  hasErrors: true,
  originalText: originalSentence,
  correctedText: correctedSentence,
  corrections: [
    {
      error: "join",
      correction: "joined",
      type: "時態錯誤",
      explanation: "第三人稱單數過去式應使用 'joined'"
    },
    {
      error: "get",
      correction: "gets",
      type: "時態錯誤",
      explanation: "第三人稱單數現在式應使用 'gets'"
    }
  ]
}

TD1.2 詞彙分析測試數據

// 實際包含的詞彙16個詞彙 + 1個慣用語
const mockAnalysis = {
  "she": { word: "she", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "just": { word: "just", difficultyLevel: "A2", isPhrase: false, /* ... */ },
  "joined": { word: "joined", difficultyLevel: "B1", isPhrase: false, /* ... */ },
  "the": { word: "the", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "team": { word: "team", difficultyLevel: "A2", isPhrase: false, /* ... */ },
  "so": { word: "so", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "let's": { word: "let's", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "cut": { word: "cut", difficultyLevel: "A2", isPhrase: false, /* ... */ },
  "her": { word: "her", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "some": { word: "some", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "slack": { word: "slack", difficultyLevel: "B1", isPhrase: false, /* ... */ },
  "until": { word: "until", difficultyLevel: "A2", isPhrase: false, /* ... */ },
  "gets": { word: "gets", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "used": { word: "used", difficultyLevel: "A2", isPhrase: false, /* ... */ },
  "to": { word: "to", difficultyLevel: "A1", isPhrase: false, /* ... */ },
  "workflow": { word: "workflow", difficultyLevel: "B2", isPhrase: false, /* ... */ },
  "cut someone some slack": {
    word: "cut someone some slack",
    difficultyLevel: "B2",
    isPhrase: true,
    translation: "對某人寬容一點",
    /* ... */
  }
}

TD1.3 預期統計結果A2用戶

// 實際計算結果
const expectedStats = {
  simpleCount: 8,    // A1等級詞彙she, the, so, let's, her, some, gets, to
  moderateCount: 4,  // A2等級詞彙just, team, cut, until, used
  difficultCount: 3, // >A2等級詞彙joined(B1), slack(B1), workflow(B2)
  phraseCount: 1     // 慣用語cut someone some slack
}

🎨 實際UI組件實現

UI1. 統計卡片組件

UI1.1 實際卡片實現

// 實際的統計卡片結構
const StatisticsCards = ({ vocabularyStats }: { vocabularyStats: VocabularyStats }) => (
  <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6">
    {/* 簡單詞彙卡片 */}
    <div className="bg-gray-50 border border-dashed border-gray-300 rounded-lg p-3 sm:p-4 text-center">
      <div className="text-xl sm:text-2xl font-bold text-gray-600 mb-1">
        {vocabularyStats.simpleCount}
      </div>
      <div className="text-gray-600 text-xs sm:text-sm font-medium">太簡單啦</div>
    </div>

    {/* 適中詞彙卡片 */}
    <div className="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4 text-center">
      <div className="text-xl sm:text-2xl font-bold text-green-700 mb-1">
        {vocabularyStats.moderateCount}
      </div>
      <div className="text-green-700 text-xs sm:text-sm font-medium">重點學習</div>
    </div>

    {/* 艱難詞彙卡片 */}
    <div className="bg-orange-50 border border-orange-200 rounded-lg p-3 sm:p-4 text-center">
      <div className="text-xl sm:text-2xl font-bold text-orange-700 mb-1">
        {vocabularyStats.difficultCount}
      </div>
      <div className="text-orange-700 text-xs sm:text-sm font-medium">有點挑戰</div>
    </div>

    {/* 慣用語卡片 */}
    <div className="bg-blue-50 border border-blue-200 rounded-lg p-3 sm:p-4 text-center">
      <div className="text-xl sm:text-2xl font-bold text-blue-700 mb-1">
        {vocabularyStats.phraseCount}
      </div>
      <div className="text-blue-700 text-xs sm:text-sm font-medium">慣用語</div>
    </div>
  </div>
)

UI2. 慣用語展示組件

UI2.1 實際展示區域

// 實際的慣用語展示實現
<div className="bg-gray-50 rounded-lg p-4 mt-4">
  <h3 className="font-semibold text-gray-900 mb-2 text-left">慣用語</h3>
  <div className="flex flex-wrap gap-2">
    {phrases.map((phrase, index) => (
      <span
        key={index}
        className="cursor-pointer transition-all duration-200 rounded relative mx-0.5 px-1 py-0.5 bg-blue-50 border border-blue-200 hover:bg-blue-100 hover:shadow-lg transform hover:-translate-y-0.5 text-blue-700 font-medium"
        onClick={handlePhraseClick}
        title={`${phrase.phrase}: ${phrase.meaning}`}
      >
        {phrase.phrase}
      </span>
    ))}
  </div>
</div>

📱 實際響應式實現

R1. 斷點實現

R1.1 實際使用的斷點

/* 實際實現的響應式類別 */
.responsive-grid {
  grid-template-columns: repeat(2, 1fr);     /* 預設:移動 */
}

@media (min-width: 640px) {
  .responsive-grid {
    grid-template-columns: repeat(4, 1fr);   /* sm:grid-cols-4 */
  }
}

.responsive-text {
  font-size: 1.25rem;                        /* text-xl */
}

@media (min-width: 640px) {
  .responsive-text {
    font-size: 1.5rem;                       /* sm:text-2xl */
  }
}

@media (min-width: 1024px) {
  .responsive-text {
    font-size: 1.875rem;                     /* lg:text-3xl */
  }
}

R1.2 彈窗響應式實現

// 實際的彈窗響應式處理
<div className="fixed z-50 bg-white rounded-xl shadow-lg w-80 sm:w-96 max-w-[90vw] overflow-hidden">
  {/* 彈窗內容 */}
</div>

🧪 實際開發配置

Dev1. 開發環境

Dev1.1 實際依賴

{
  "next": "15.5.3",
  "react": "^18",
  "typescript": "^5",
  "tailwindcss": "^3",
  "@types/react": "^18"
}

Dev1.2 實際腳本

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

🔧 技術債務與改進建議

Debt1. 當前技術債務

Debt1.1 未使用的代碼

// 需要清理的未使用變數
const [mode, setMode] = useState<'manual' | 'screenshot'>('manual') // setMode未使用
const [isPremium] = useState(true) // isPremium未使用

Debt1.2 假資料依賴

// 需要替換為真實API的部分
const handleAnalyzeSentence = async () => {
  // 目前:假資料模式
  // 未來真實API調用
}

Improve1. 建議改進

Improve1.1 短期改進

  1. 移除未使用變數: 清理setMode, isPremium等
  2. API切換準備: 為真實API調用做準備
  3. 錯誤處理: 完善API失敗的錯誤處理

Improve1.2 中期改進

  1. 使用限制: 實現真實的使用次數限制
  2. 本地緩存: 添加分析結果的本地緩存
  3. 性能監控: 添加性能指標收集

🚀 部署就緒檢查表

Deploy1. 生產準備度

Deploy1.1 功能完整性

  • 核心詞彙標記功能完整
  • 統計展示準確
  • 慣用語功能完整
  • 彈窗互動正常
  • 響應式設計完整

Deploy1.2 代碼品質

  • TypeScript零錯誤
  • React性能優化
  • 無console錯誤
  • 記憶體穩定

Deploy1.3 用戶體驗

  • 載入時間 < 2秒
  • 互動響應 < 100ms
  • 視覺設計一致
  • 移動設備兼容

📋 實際維護指南

Maintain1. 日常維護

Maintain1.1 監控指標

  • 性能: 頁面載入時間、互動響應時間
  • 錯誤: JavaScript錯誤率、API失敗率
  • 使用: 詞彙點擊率、保存成功率

Maintain1.2 更新流程

  1. 測試: 完整功能測試
  2. 性能: 性能回歸測試
  3. 兼容: 瀏覽器兼容性檢查
  4. 用戶: 用戶體驗驗證

文件版本: v1.0 (實際實現版) 對應代碼: 當前main分支 最後驗證: 2025-09-22 下次檢查: 功能更新時