Bladeren bron

接口分离

liuziting 7 maanden geleden
bovenliggende
commit
db22b6ebb8

+ 4 - 7
.env.example

@@ -1,8 +1,5 @@
-# AI接口配置 - 智谱AI
-VITE_AI_BASE_URL=https://open.bigmodel.cn/api/paas/v4/
-VITE_AI_API_KEY=a835b9f6866d48ec956d341418df8a50.NuhlKYn58EkCb5iP
-VITE_AI_MODEL=glm-4-flash-250414
+# Text Generation Service Configuration
+VITE_TEXT_GENERATION_API_KEY=******
 
-# 应用配置
-VITE_APP_TITLE=一饭封神
-VITE_APP_DESCRIPTION=AI厨艺大师指导平台
+# Image Generation Service Configuration
+VITE_IMAGE_GENERATION_API_KEY=******

+ 0 - 2
src/components/NutritionAnalysis.vue

@@ -1,7 +1,5 @@
 <template>
     <div v-if="nutritionAnalysis" class="nutrition-analysis">
-        <h4 class="text-sm font-bold text-dark-800 mb-3 flex items-center gap-1">🥗 营养分析</h4>
-
         <div class="bg-green-50 border border-green-200 rounded-lg p-4 space-y-4">
             <!-- 基础营养信息 -->
             <div class="grid grid-cols-2 gap-3">

+ 143 - 2
src/components/RecipeCard.vue

@@ -80,12 +80,76 @@
 
             <!-- 营养分析 -->
             <div v-if="isExpanded" class="mb-4">
-                <NutritionAnalysis :nutritionAnalysis="recipe.nutritionAnalysis" />
+                <div class="flex items-center justify-between mb-3">
+                    <h4 class="text-sm font-bold text-dark-800 flex items-center gap-1">📊 营养分析</h4>
+                    <button
+                        @click="fetchNutritionAnalysis"
+                        :disabled="isFetchingNutrition"
+                        class="bg-green-500 hover:bg-green-600 disabled:bg-gray-400 text-white px-3 py-1 rounded text-xs font-medium border border-black transition-all duration-200 disabled:cursor-not-allowed"
+                    >
+                        <span class="flex items-center gap-1">
+                            <template v-if="isFetchingNutrition">
+                                <div class="animate-spin w-3 h-3 border border-white border-t-transparent rounded-full"></div>
+                                获取中...
+                            </template>
+                            <template v-else-if="recipe.nutritionAnalysis"> 🔄 重新获取 </template>
+                            <template v-else> ✨ 获取营养分析 </template>
+                        </span>
+                    </button>
+                </div>
+
+                <div v-if="isFetchingNutrition" class="bg-gray-50 border-2 border-gray-300 rounded-lg p-6 text-center">
+                    <div class="w-12 h-12 border-4 border-gray-300 border-t-green-500 rounded-full animate-spin mx-auto mb-3"></div>
+                    <h5 class="text-sm font-bold text-dark-800 mb-1">营养师正在分析中...</h5>
+                    <p class="text-gray-600 text-xs">{{ nutritionLoadingText }}</p>
+                </div>
+
+                <div v-else-if="nutritionError" class="bg-red-100 border border-red-400 text-red-700 px-3 py-2 rounded text-xs mb-3">
+                    {{ nutritionError }}
+                </div>
+
+                <NutritionAnalysis v-if="recipe.nutritionAnalysis" :nutritionAnalysis="recipe.nutritionAnalysis" />
+                <div v-else-if="!isFetchingNutrition" class="bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
+                    <div class="text-gray-400 text-2xl mb-2">🥗</div>
+                    <p class="text-gray-500 text-xs">点击上方按钮获取营养分析</p>
+                </div>
             </div>
 
             <!-- 酒水搭配 -->
             <div v-if="isExpanded" class="mb-4">
-                <WinePairing :winePairing="recipe.winePairing" />
+                <div class="flex items-center justify-between mb-3">
+                    <h4 class="text-sm font-bold text-dark-800 flex items-center gap-1">🍷 酒水搭配</h4>
+                    <button
+                        @click="fetchWinePairing"
+                        :disabled="isFetchingWine"
+                        class="bg-purple-500 hover:bg-purple-600 disabled:bg-gray-400 text-white px-3 py-1 rounded text-xs font-medium border border-black transition-all duration-200 disabled:cursor-not-allowed"
+                    >
+                        <span class="flex items-center gap-1">
+                            <template v-if="isFetchingWine">
+                                <div class="animate-spin w-3 h-3 border border-white border-t-transparent rounded-full"></div>
+                                获取中...
+                            </template>
+                            <template v-else-if="recipe.winePairing"> 🔄 重新获取 </template>
+                            <template v-else> ✨ 获取酒水搭配 </template>
+                        </span>
+                    </button>
+                </div>
+
+                <div v-if="isFetchingWine" class="bg-gray-50 border-2 border-gray-300 rounded-lg p-6 text-center">
+                    <div class="w-12 h-12 border-4 border-gray-300 border-t-purple-500 rounded-full animate-spin mx-auto mb-3"></div>
+                    <h5 class="text-sm font-bold text-dark-800 mb-1">侍酒师正在推荐中...</h5>
+                    <p class="text-gray-600 text-xs">{{ wineLoadingText }}</p>
+                </div>
+
+                <div v-else-if="wineError" class="bg-red-100 border border-red-400 text-red-700 px-3 py-2 rounded text-xs mb-3">
+                    {{ wineError }}
+                </div>
+
+                <WinePairing v-if="recipe.winePairing" :winePairing="recipe.winePairing" />
+                <div v-else-if="!isFetchingWine" class="bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
+                    <div class="text-gray-400 text-2xl mb-2">🍾</div>
+                    <p class="text-gray-500 text-xs">点击上方按钮获取酒水搭配</p>
+                </div>
             </div>
 
             <!-- 效果图区域 -->
@@ -138,6 +202,7 @@
 import { computed, ref, onUnmounted } from 'vue'
 import type { Recipe } from '@/types'
 import { generateRecipeImage, type GeneratedImage } from '@/services/imageService'
+import { getNutritionAnalysis, getWinePairing } from '@/services/aiService'
 import NutritionAnalysis from './NutritionAnalysis.vue'
 import WinePairing from './WinePairing.vue'
 
@@ -151,6 +216,12 @@ const isGeneratingImage = ref(false)
 const generatedImage = ref<GeneratedImage | null>(null)
 const imageError = ref<string>('')
 const imageLoadingText = ref('正在构思画面布局...')
+const nutritionLoadingText = ref('营养师正在分析中...')
+const wineLoadingText = ref('侍酒师正在推荐中...')
+const isFetchingNutrition = ref(false)
+const nutritionError = ref('')
+const isFetchingWine = ref(false)
+const wineError = ref('')
 
 // 图片生成加载文字轮播
 const imageLoadingTexts = [
@@ -163,6 +234,28 @@ const imageLoadingTexts = [
     '精美效果图即将完成...'
 ]
 
+// 营养分析加载文字轮播
+const nutritionLoadingTexts = [
+    '营养师正在分析中...',
+    '正在计算卡路里...',
+    '正在分析蛋白质含量...',
+    '正在评估维生素含量...',
+    '正在生成健康建议...',
+    '正在准备饮食建议...',
+    '营养分析即将完成...'
+]
+
+// 酒水搭配加载文字轮播
+const wineLoadingTexts = [
+    '侍酒师正在推荐中...',
+    '正在匹配风味特征...',
+    '正在考虑酒体平衡...',
+    '正在评估搭配效果...',
+    '正在选择最佳温度...',
+    '正在准备搭配理由...',
+    '完美搭配即将呈现...'
+]
+
 let imageLoadingInterval: NodeJS.Timeout | null = null
 
 const difficultyText = computed(() => {
@@ -211,6 +304,54 @@ const handleImageError = () => {
     generatedImage.value = null
 }
 
+const fetchNutritionAnalysis = async () => {
+    if (isFetchingNutrition.value) return
+
+    isFetchingNutrition.value = true
+    nutritionError.value = ''
+
+    let textIndex = 0
+    const interval = setInterval(() => {
+        nutritionLoadingText.value = nutritionLoadingTexts[textIndex]
+        textIndex = (textIndex + 1) % nutritionLoadingTexts.length
+    }, 2000)
+
+    try {
+        const analysis = await getNutritionAnalysis(props.recipe)
+        props.recipe.nutritionAnalysis = analysis
+    } catch (error) {
+        console.error('获取营养分析失败:', error)
+        nutritionError.value = '获取营养分析失败,请稍后重试'
+    } finally {
+        isFetchingNutrition.value = false
+        clearInterval(interval)
+    }
+}
+
+const fetchWinePairing = async () => {
+    if (isFetchingWine.value) return
+
+    isFetchingWine.value = true
+    wineError.value = ''
+
+    let textIndex = 0
+    const interval = setInterval(() => {
+        wineLoadingText.value = wineLoadingTexts[textIndex]
+        textIndex = (textIndex + 1) % wineLoadingTexts.length
+    }, 2000)
+
+    try {
+        const pairing = await getWinePairing(props.recipe)
+        props.recipe.winePairing = pairing
+    } catch (error) {
+        console.error('获取酒水搭配失败:', error)
+        wineError.value = '获取酒水搭配失败,请稍后重试'
+    } finally {
+        isFetchingWine.value = false
+        clearInterval(interval)
+    }
+}
+
 onUnmounted(() => {
     if (imageLoadingInterval) {
         clearInterval(imageLoadingInterval)

+ 0 - 2
src/components/WinePairing.vue

@@ -1,7 +1,5 @@
 <template>
     <div v-if="winePairing" class="wine-pairing">
-        <h4 class="text-sm font-bold text-dark-800 mb-3 flex items-center gap-1">🍷 酒水搭配</h4>
-
         <div class="bg-purple-50 border border-purple-200 rounded-lg p-4">
             <!-- 酒水信息卡片 -->
             <div class="bg-white rounded-lg p-4 border border-purple-300 mb-3">

+ 135 - 136
src/services/aiService.ts

@@ -1,15 +1,10 @@
 import axios from 'axios'
 import type { Recipe, CuisineType, NutritionAnalysis, WinePairing } from '@/types'
 
-// AI服务配置 - 智谱AI
+// AI服务配置 - 从环境变量读取
 const AI_CONFIG = {
-    // baseURL: 'https://open.bigmodel.cn/api/paas/v4/',
-    // apiKey: 'a835b9f6866d48ec956d341418df8a50.NuhlKYn58EkCb5iP',
-    // model: 'glm-4-flash-250414',
-    // temperature: 0.7,
-    // timeout: 30000
     baseURL: 'https://api.deepseek.com/v1/',
-    apiKey: 'sk-78d4fed678fa4a5ebc5f7beac54b1a78',
+    apiKey: import.meta.env.VITE_TEXT_GENERATION_API_KEY,
     model: 'deepseek-chat',
     temperature: 0.7,
     timeout: 300000
@@ -48,7 +43,7 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
 
         prompt += `
 
-请按照以下JSON格式返回菜谱,包含营养分析和酒水搭配:
+请按照以下JSON格式返回菜谱,包含营养分析和酒水搭配:
 {
   "name": "菜品名称",
   "ingredients": ["食材1", "食材2"],
@@ -62,35 +57,7 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
   ],
   "cookingTime": 30,
   "difficulty": "medium",
-  "tips": ["技巧1", "技巧2"],
-  "nutritionAnalysis": {
-    "nutrition": {
-      "calories": 350,
-      "protein": 25,
-      "carbs": 45,
-      "fat": 12,
-      "fiber": 8,
-      "sodium": 800,
-      "sugar": 6,
-      "vitaminC": 30,
-      "calcium": 150,
-      "iron": 3
-    },
-    "healthScore": 8,
-    "balanceAdvice": ["建议搭配蔬菜沙拉增加维生素", "可适量减少盐分"],
-    "dietaryTags": ["高蛋白", "低脂"],
-    "servingSize": "1人份"
-  },
-  "winePairing": {
-    "name": "推荐酒水名称",
-    "type": "red_wine",
-    "reason": "搭配理由说明",
-    "servingTemperature": "16-18°C",
-    "glassType": "波尔多杯",
-    "alcoholContent": "13.5%",
-    "flavor": "风味描述",
-    "origin": "产地"
-  }
+  "tips": ["技巧1", "技巧2"]
 }`
 
         // 调用智谱AI接口
@@ -136,8 +103,8 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
             cookingTime: recipeData.cookingTime || 25,
             difficulty: recipeData.difficulty || 'medium',
             tips: recipeData.tips || ['注意火候控制', '调味要适中'],
-            nutritionAnalysis: recipeData.nutritionAnalysis || generateFallbackNutrition(ingredients),
-            winePairing: recipeData.winePairing || generateFallbackWinePairing(cuisine, ingredients)
+            nutritionAnalysis: undefined,
+            winePairing: undefined
         }
 
         return recipe
@@ -150,17 +117,131 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
 }
 
 /**
- * 批量生成多个菜系的菜谱
- * @param ingredients 食材列表
- * @param cuisines 菜系列表
- * @param customPrompt 自定义提示词(可选)
- * @returns Promise<Recipe[]>
+ * 获取菜谱的营养分析
+ * @param recipe 菜谱信息
+ * @returns Promise<NutritionAnalysis>
+ */
+export const getNutritionAnalysis = async (recipe: Recipe): Promise<NutritionAnalysis> => {
+    try {
+        const prompt = `请为以下菜谱生成详细的营养分析:
+菜名:${recipe.name}
+食材:${recipe.ingredients.join('、')}
+烹饪方法:${recipe.steps.map(step => step.description).join(',')}
+
+请按照以下JSON格式返回营养分析:
+{
+  "nutrition": {
+    "calories": 350,
+    "protein": 25,
+    "carbs": 45,
+    "fat": 12,
+    "fiber": 8,
+    "sodium": 800,
+    "sugar": 6,
+    "vitaminC": 30,
+    "calcium": 150,
+    "iron": 3
+  },
+  "healthScore": 8,
+  "balanceAdvice": ["建议搭配蔬菜沙拉增加维生素", "可适量减少盐分"],
+  "dietaryTags": ["高蛋白", "低脂"],
+  "servingSize": "1人份"
+}`
+
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位专业的营养师,请根据菜谱信息生成详细的营养分析。请严格按照JSON格式返回,不要包含任何其他文字。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: 0.5, // 使用更低的temperature以获得更准确的分析
+            stream: false
+        })
+
+        // 解析AI响应
+        const aiResponse = response.data.choices[0].message.content
+        let cleanResponse = aiResponse.trim()
+        if (cleanResponse.startsWith('```json')) {
+            cleanResponse = cleanResponse.replace(/```json\s*/, '').replace(/```\s*$/, '')
+        } else if (cleanResponse.startsWith('```')) {
+            cleanResponse = cleanResponse.replace(/```\s*/, '').replace(/```\s*$/, '')
+        }
+
+        const nutritionData = JSON.parse(cleanResponse)
+        return nutritionData
+    } catch (error) {
+        console.error('获取营养分析失败:', error)
+        return generateFallbackNutrition(recipe.ingredients)
+    }
+}
+
+/**
+ * 获取菜谱的酒水搭配建议
+ * @param recipe 菜谱信息
+ * @returns Promise<WinePairing>
  */
+export const getWinePairing = async (recipe: Recipe): Promise<WinePairing> => {
+    try {
+        const prompt = `请为以下菜谱推荐合适的酒水搭配:
+菜名:${recipe.name}
+菜系:${recipe.cuisine}
+食材:${recipe.ingredients.join('、')}
+
+请按照以下JSON格式返回酒水搭配建议:
+{
+  "name": "推荐酒水名称",
+  "type": "red_wine",
+  "reason": "搭配理由说明",
+  "servingTemperature": "16-18°C",
+  "glassType": "波尔多杯",
+  "alcoholContent": "13.5%",
+  "flavor": "风味描述",
+  "origin": "产地"
+}`
+
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位专业的侍酒师,请根据菜谱信息推荐合适的酒水搭配。请严格按照JSON格式返回,不要包含任何其他文字。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: 0.7,
+            stream: false
+        })
+
+        // 解析AI响应
+        const aiResponse = response.data.choices[0].message.content
+        let cleanResponse = aiResponse.trim()
+        if (cleanResponse.startsWith('```json')) {
+            cleanResponse = cleanResponse.replace(/```json\s*/, '').replace(/```\s*$/, '')
+        } else if (cleanResponse.startsWith('```')) {
+            cleanResponse = cleanResponse.replace(/```\s*/, '').replace(/```\s*$/, '')
+        }
+
+        const wineData = JSON.parse(cleanResponse)
+        return wineData
+    } catch (error) {
+        console.error('获取酒水搭配失败:', error)
+        return generateFallbackWinePairing({ id: 'custom', name: recipe.cuisine } as CuisineType, recipe.ingredients)
+    }
+}
+
+// 批量生成多个菜系的菜谱
 export const generateMultipleRecipes = async (ingredients: string[], cuisines: CuisineType[], customPrompt?: string): Promise<Recipe[]> => {
     try {
-        // 并发调用多个AI接口
         const promises = cuisines.map(cuisine => generateRecipe(ingredients, cuisine, customPrompt))
-
         const recipes = await Promise.all(promises)
         return recipes
     } catch (error) {
@@ -169,14 +250,7 @@ export const generateMultipleRecipes = async (ingredients: string[], cuisines: C
     }
 }
 
-/**
- * 流式生成多个菜系的菜谱,每完成一个就通过回调返回
- * @param ingredients 食材列表
- * @param cuisines 菜系列表
- * @param onRecipeGenerated 每生成一个菜谱时的回调函数
- * @param customPrompt 自定义提示词(可选)
- * @returns Promise<void>
- */
+// 流式生成多个菜系的菜谱
 export const generateMultipleRecipesStream = async (
     ingredients: string[],
     cuisines: CuisineType[],
@@ -186,12 +260,10 @@ export const generateMultipleRecipesStream = async (
     const total = cuisines.length
     let completedCount = 0
 
-    // 创建所有的Promise,但不等待全部完成
     const promises = cuisines.map(async (cuisine, index) => {
         try {
             const recipe = await generateRecipe(ingredients, cuisine, customPrompt)
             completedCount++
-            // 每完成一个就立即回调
             onRecipeGenerated(recipe, index, total)
             return { success: true, recipe, index }
         } catch (error) {
@@ -200,51 +272,28 @@ export const generateMultipleRecipesStream = async (
         }
     })
 
-    // 等待所有Promise完成(无论成功还是失败)
     const results = await Promise.allSettled(promises)
-
-    // 检查是否有失败的情况
     const failedResults = results.filter(result => result.status === 'rejected' || (result.status === 'fulfilled' && !result.value.success))
 
-    // 如果所有菜谱都失败了,抛出错误
     if (completedCount === 0 && failedResults.length > 0) {
         throw new Error('所有菜系生成都失败了,请稍后重试')
     }
 
-    // 如果部分失败,在控制台记录但不抛出错误
     if (failedResults.length > 0) {
         console.warn(`${failedResults.length}个菜系生成失败,但已成功生成${completedCount}个菜谱`)
     }
 }
 
-/**
- * 更新AI配置
- * @param config 新的配置
- */
-export const updateAIConfig = (config: Partial<typeof AI_CONFIG>) => {
-    Object.assign(AI_CONFIG, config)
-
-    // 更新axios实例配置
-    aiClient.defaults.baseURL = AI_CONFIG.baseURL
-    aiClient.defaults.headers['Authorization'] = `Bearer ${AI_CONFIG.apiKey}`
-}
-
-/**
- * 使用自定义提示词生成菜谱
- * @param ingredients 食材列表
- * @param customPrompt 自定义提示词
- * @returns Promise<Recipe>
- */
+// 使用自定义提示词生成菜谱
 export const generateCustomRecipe = async (ingredients: string[], customPrompt: string): Promise<Recipe> => {
     try {
-        // 构建自定义提示词
         const prompt = `你是一位专业的厨师,请根据用户提供的食材和特殊要求,生成详细的菜谱。请严格按照JSON格式返回,不要包含任何其他文字。
 
 用户提供的食材:${ingredients.join('、')}
 
 用户的特殊要求:${customPrompt}
 
-请按照以下JSON格式返回菜谱,包含营养分析和酒水搭配:
+请按照以下JSON格式返回菜谱,不包含营养分析和酒水搭配:
 {
   "name": "菜品名称",
   "ingredients": ["食材1", "食材2"],
@@ -258,38 +307,9 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
   ],
   "cookingTime": 30,
   "difficulty": "medium",
-  "tips": ["技巧1", "技巧2"],
-  "nutritionAnalysis": {
-    "nutrition": {
-      "calories": 350,
-      "protein": 25,
-      "carbs": 45,
-      "fat": 12,
-      "fiber": 8,
-      "sodium": 800,
-      "sugar": 6,
-      "vitaminC": 30,
-      "calcium": 150,
-      "iron": 3
-    },
-    "healthScore": 8,
-    "balanceAdvice": ["建议搭配蔬菜沙拉增加维生素", "可适量减少盐分"],
-    "dietaryTags": ["高蛋白", "低脂"],
-    "servingSize": "1人份"
-  },
-  "winePairing": {
-    "name": "推荐酒水名称",
-    "type": "red_wine",
-    "reason": "搭配理由说明",
-    "servingTemperature": "16-18°C",
-    "glassType": "波尔多杯",
-    "alcoholContent": "13.5%",
-    "flavor": "风味描述",
-    "origin": "产地"
-  }
+  "tips": ["技巧1", "技巧2"]
 }`
 
-        // 调用智谱AI接口
         const response = await aiClient.post('/chat/completions', {
             model: AI_CONFIG.model,
             messages: [
@@ -307,10 +327,7 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
             stream: false
         })
 
-        // 解析AI响应
         const aiResponse = response.data.choices[0].message.content
-
-        // 清理响应内容,提取JSON部分
         let cleanResponse = aiResponse.trim()
         if (cleanResponse.startsWith('```json')) {
             cleanResponse = cleanResponse.replace(/```json\s*/, '').replace(/```\s*$/, '')
@@ -320,7 +337,6 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
 
         const recipeData = JSON.parse(cleanResponse)
 
-        // 构建完整的Recipe对象
         const recipe: Recipe = {
             id: `recipe-custom-${Date.now()}`,
             name: recipeData.name || '自定义菜品',
@@ -333,32 +349,24 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
             cookingTime: recipeData.cookingTime || 25,
             difficulty: recipeData.difficulty || 'medium',
             tips: recipeData.tips || ['根据个人口味调整', '注意火候控制'],
-            nutritionAnalysis: recipeData.nutritionAnalysis || generateFallbackNutrition(ingredients),
-            winePairing: recipeData.winePairing || generateFallbackWinePairing({ id: 'custom', name: '自定义' } as any, ingredients)
+            nutritionAnalysis: undefined,
+            winePairing: undefined
         }
 
         return recipe
     } catch (error) {
         console.error('生成自定义菜谱失败:', error)
-
-        // 抛出错误,让上层处理
         throw new Error('AI生成自定义菜谱失败,请稍后重试')
     }
 }
 
-/**
- * 生成后备营养分析数据
- * @param ingredients 食材列表
- * @returns NutritionAnalysis
- */
+// 生成后备营养分析数据
 const generateFallbackNutrition = (ingredients: string[]): NutritionAnalysis => {
-    // 基于食材数量和类型估算营养成分
     const baseCalories = ingredients.length * 50 + Math.floor(Math.random() * 100) + 200
     const hasVegetables = ingredients.some(ing => ['菜', '瓜', '豆', '萝卜', '白菜', '菠菜', '西红柿', '黄瓜', '茄子', '土豆'].some(veg => ing.includes(veg)))
     const hasMeat = ingredients.some(ing => ['肉', '鸡', '鱼', '虾', '蛋', '牛', '猪', '羊'].some(meat => ing.includes(meat)))
     const hasGrains = ingredients.some(ing => ['米', '面', '粉', '饭', '面条', '馒头'].some(grain => ing.includes(grain)))
 
-    // 生成饮食标签
     const dietaryTags: string[] = []
     if (hasVegetables && !hasMeat) dietaryTags.push('素食')
     if (hasMeat) dietaryTags.push('高蛋白')
@@ -366,7 +374,6 @@ const generateFallbackNutrition = (ingredients: string[]): NutritionAnalysis =>
     if (!hasGrains) dietaryTags.push('低碳水')
     if (baseCalories < 300) dietaryTags.push('低卡路里')
 
-    // 生成营养建议
     const balanceAdvice: string[] = []
     if (!hasVegetables) balanceAdvice.push('建议搭配新鲜蔬菜增加维生素和膳食纤维')
     if (!hasMeat && !ingredients.some(ing => ['豆', '蛋', '奶'].some(protein => ing.includes(protein)))) {
@@ -395,19 +402,13 @@ const generateFallbackNutrition = (ingredients: string[]): NutritionAnalysis =>
     }
 }
 
-/**
- * 生成后备酒水搭配数据
- * @param cuisine 菜系信息
- * @param ingredients 食材列表
- * @returns WinePairing
- */
+// 生成后备酒水搭配数据
 const generateFallbackWinePairing = (cuisine: CuisineType, ingredients: string[]): WinePairing => {
     const hasSpicy = ingredients.some(ing => ['辣椒', '花椒', '胡椒', '姜', '蒜', '洋葱'].some(spice => ing.includes(spice)))
     const hasMeat = ingredients.some(ing => ['肉', '鸡', '鱼', '虾', '蛋', '牛', '猪', '羊'].some(meat => ing.includes(meat)))
     const hasSeafood = ingredients.some(ing => ['鱼', '虾', '蟹', '贝', '海带', '紫菜'].some(seafood => ing.includes(seafood)))
     const isLight = ingredients.some(ing => ['菜', '瓜', '豆腐', '蛋'].some(light => ing.includes(light)))
 
-    // 根据菜系推荐酒水
     const cuisineWineMap: Record<string, Partial<WinePairing>> = {
         川菜大师: {
             name: '德国雷司令',
@@ -471,10 +472,8 @@ const generateFallbackWinePairing = (cuisine: CuisineType, ingredients: string[]
         }
     }
 
-    // 获取菜系推荐,如果没有则根据食材特点推荐
     let winePairing = cuisineWineMap[cuisine.name] || {}
 
-    // 如果没有菜系特定推荐,根据食材特点推荐
     if (!winePairing.name) {
         if (hasSpicy) {
             winePairing = {

+ 1 - 1
src/services/imageService.ts

@@ -1,6 +1,6 @@
 import type { Recipe } from '@/types'
 
-const API_KEY = 'a835b9f6866d48ec956d341418df8a50.NuhlKYn58EkCb5iP'
+const API_KEY = import.meta.env.VITE_IMAGE_GENERATION_API_KEY
 const API_URL = 'https://open.bigmodel.cn/api/paas/v4/images/generations'
 
 export interface GeneratedImage {

+ 1 - 1
src/views/Home.vue

@@ -27,7 +27,7 @@
             </div>
         </div> -->
 
-        <div class="max-w-5xl mx-auto px-4 py-6">
+        <div class="max-w-6xl mx-auto px-4 py-6">
             <!-- 步骤1: 输入食材 -->
             <div class="mb-6">
                 <div class="bg-pink-400 text-white px-4 py-2 rounded-t-lg border-2 border-black border-b-0 inline-block">

+ 0 - 91
test-ai.js

@@ -1,91 +0,0 @@
-// 简单的AI接口测试脚本
-const axios = require('axios');
-
-const AI_CONFIG = {
-    baseURL: 'https://open.bigmodel.cn/api/paas/v4/',
-    apiKey: 'a835b9f6866d48ec956d341418df8a50.NuhlKYn58EkCb5iP',
-    model: 'glm-4-flash-250414',
-    temperature: 0.7,
-    timeout: 30000
-};
-
-async function testAI() {
-    try {
-        console.log('🧪 开始测试智谱AI接口...');
-
-        const response = await axios.post(`${AI_CONFIG.baseURL}/chat/completions`, {
-            model: AI_CONFIG.model,
-            messages: [
-                {
-                    role: 'system',
-                    content: '你是一位专业的厨师,请根据用户提供的食材和菜系要求,生成详细的菜谱。请严格按照JSON格式返回,不要包含任何其他文字。'
-                },
-                {
-                    role: 'user',
-                    content: `你是一位川菜大师,精通四川菜系。川菜以麻辣鲜香、口味多变著称,有"一菜一格,百菜百味"的美誉。请根据用户提供的食材,设计一道地道的川菜,突出麻辣特色和层次丰富的口感。回答要包含菜名、详细制作步骤、调料配比和川菜技法。
-
-用户提供的食材:土豆、肉丝
-
-请按照以下JSON格式返回菜谱:
-{
-  "name": "菜品名称",
-  "ingredients": ["食材1", "食材2"],
-  "steps": [
-    {
-      "step": 1,
-      "description": "步骤描述",
-      "time": 5,
-      "temperature": "中火"
-    }
-  ],
-  "cookingTime": 30,
-  "difficulty": "medium",
-  "tips": ["技巧1", "技巧2"]
-}`
-                }
-            ],
-            temperature: AI_CONFIG.temperature,
-            max_tokens: 2000,
-            stream: false
-        }, {
-            headers: {
-                'Content-Type': 'application/json',
-                'Authorization': `Bearer ${AI_CONFIG.apiKey}`
-            },
-            timeout: AI_CONFIG.timeout
-        });
-
-        console.log('✅ AI接口调用成功!');
-        console.log('📝 响应内容:');
-        console.log(response.data.choices[0].message.content);
-
-        // 尝试解析JSON
-        try {
-            const content = response.data.choices[0].message.content.trim();
-            let cleanContent = content;
-            if (cleanContent.startsWith('```json')) {
-                cleanContent = cleanContent.replace(/```json\s*/, '').replace(/```\s*$/, '');
-            } else if (cleanContent.startsWith('```')) {
-                cleanContent = cleanContent.replace(/```\s*/, '').replace(/```\s*$/, '');
-            }
-
-            const recipe = JSON.parse(cleanContent);
-            console.log('🍽️ 解析后的菜谱:');
-            console.log(JSON.stringify(recipe, null, 2));
-        } catch (parseError) {
-            console.log('⚠️ JSON解析失败,但接口调用成功');
-        }
-
-    } catch (error) {
-        console.error('❌ AI接口测试失败:');
-        if (error.response) {
-            console.error('状态码:', error.response.status);
-            console.error('错误信息:', error.response.data);
-        } else {
-            console.error('错误详情:', error.message);
-        }
-    }
-}
-
-// 运行测试
-testAI();

+ 2 - 1
tsconfig.json

@@ -15,6 +15,7 @@
     "noUnusedLocals": true,
     "noUnusedParameters": true,
     "noFallthroughCasesInSwitch": true,
+    "types": ["vite/client"],
     "baseUrl": ".",
     "paths": {
       "@/*": ["src/*"]
@@ -22,4 +23,4 @@
   },
   "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
   "references": [{ "path": "./tsconfig.node.json" }]
-}
+}