| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908 |
- package howtocook
- import (
- "context"
- "errors"
- "fmt"
- "math"
- "math/rand"
- "strings"
- "github.com/bytedance/sonic"
- mcpservers "github.com/labring/aiproxy/mcp-servers"
- "github.com/mark3labs/mcp-go/mcp"
- "github.com/mark3labs/mcp-go/server"
- )
- const (
- RecipesURL = "https://weilei.site/all_recipes.json"
- Version = "0.0.6"
- )
- // Server represents the HowToCook MCP server
- type Server struct {
- *server.MCPServer
- recipes []Recipe
- categories []string
- }
- // NewServer creates a new HowToCook MCP server
- func NewServer(_, _ map[string]string) (mcpservers.Server, error) {
- // Create MCP server
- mcpServer := server.NewMCPServer("howtocook-mcp", Version)
- cookServer := &Server{
- MCPServer: mcpServer,
- }
- // Initialize recipes and categories
- if err := cookServer.initialize(context.Background()); err != nil {
- return nil, fmt.Errorf("failed to initialize server: %w", err)
- }
- // Add tools
- cookServer.addTools()
- return cookServer, nil
- }
- func ListTools(ctx context.Context) ([]mcp.Tool, error) {
- cookServer := &Server{
- MCPServer: server.NewMCPServer("howtocook-mcp", Version),
- }
- cookServer.addTools()
- return mcpservers.ListServerTools(ctx, cookServer)
- }
- // initialize loads recipe data and categories
- func (s *Server) initialize(ctx context.Context) error {
- recipes, err := s.fetchRecipes(ctx)
- if err != nil {
- return fmt.Errorf("failed to fetch recipes: %w", err)
- }
- if len(recipes) == 0 {
- return errors.New("no recipes found")
- }
- s.recipes = recipes
- s.categories = s.getAllCategories()
- return nil
- }
- // addTools adds all tools to the server
- func (s *Server) addTools() {
- s.addGetAllRecipesTool()
- s.addGetRecipesByCategoryTool()
- s.addRecommendMealsTool()
- s.addWhatToEatTool()
- }
- // addGetAllRecipesTool adds the get all recipes tool
- func (s *Server) addGetAllRecipesTool() {
- s.AddTool(
- mcp.Tool{
- Name: "mcp_howtocook_getAllRecipes",
- Description: "获取所有菜谱",
- InputSchema: mcp.ToolInputSchema{
- Type: "object",
- Properties: map[string]any{},
- Required: []string{},
- },
- },
- func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- simplifiedRecipes := make([]NameOnlyRecipe, 0, len(s.recipes))
- for _, recipe := range s.recipes {
- simplifiedRecipes = append(simplifiedRecipes, NameOnlyRecipe{
- Name: recipe.Name,
- Description: recipe.Description,
- })
- }
- result, err := sonic.Marshal(simplifiedRecipes)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal recipes: %w", err)
- }
- return mcp.NewToolResultText(string(result)), nil
- },
- )
- }
- // addGetRecipesByCategoryTool adds the get recipes by category tool
- func (s *Server) addGetRecipesByCategoryTool() {
- s.AddTool(
- mcp.Tool{
- Name: "mcp_howtocook_getRecipesByCategory",
- Description: "根据分类查询菜谱,可选分类有: " + strings.Join(s.categories, ", "),
- InputSchema: mcp.ToolInputSchema{
- Type: "object",
- Properties: map[string]any{
- "category": map[string]any{
- "type": "string",
- "description": "菜谱分类名称,如水产、早餐、荤菜、主食等",
- "enum": s.categories,
- },
- },
- Required: []string{"category"},
- },
- },
- s.handleGetRecipesByCategory,
- )
- }
- // addRecommendMealsTool adds the recommend meals tool
- func (s *Server) addRecommendMealsTool() {
- s.AddTool(
- mcp.Tool{
- Name: "mcp_howtocook_recommendMeals",
- Description: "根据用户的忌口、过敏原、人数智能推荐菜谱,创建一周的膳食计划以及大致的购物清单",
- InputSchema: mcp.ToolInputSchema{
- Type: "object",
- Properties: map[string]any{
- "allergies": map[string]any{
- "type": "array",
- "items": map[string]any{"type": "string"},
- "description": "过敏原列表,如[\"大蒜\", \"虾\"]",
- },
- "avoidItems": map[string]any{
- "type": "array",
- "items": map[string]any{"type": "string"},
- "description": "忌口食材列表,如[\"葱\", \"姜\"]",
- },
- "peopleCount": map[string]any{
- "type": "integer",
- "minimum": 1,
- "maximum": 10,
- "description": "用餐人数,1-10之间的整数",
- },
- },
- Required: []string{"peopleCount"},
- },
- },
- s.handleRecommendMeals,
- )
- }
- // addWhatToEatTool adds the what to eat tool
- func (s *Server) addWhatToEatTool() {
- s.AddTool(
- mcp.Tool{
- Name: "mcp_howtocook_whatToEat",
- Description: "不知道吃什么?根据人数直接推荐适合的菜品组合",
- InputSchema: mcp.ToolInputSchema{
- Type: "object",
- Properties: map[string]any{
- "peopleCount": map[string]any{
- "type": "integer",
- "minimum": 1,
- "maximum": 10,
- "description": "用餐人数,1-10之间的整数,会根据人数推荐合适数量的菜品",
- },
- },
- Required: []string{"peopleCount"},
- },
- },
- s.handleWhatToEat,
- )
- }
- // handleGetRecipesByCategory handles the get recipes by category request
- func (s *Server) handleGetRecipesByCategory(
- _ context.Context,
- request mcp.CallToolRequest,
- ) (*mcp.CallToolResult, error) {
- args := request.GetArguments()
- category, ok := args["category"].(string)
- if !ok || category == "" {
- return nil, errors.New("category is required")
- }
- var filteredRecipes []SimpleRecipe
- for _, recipe := range s.recipes {
- if recipe.Category == category {
- filteredRecipes = append(filteredRecipes, s.simplifyRecipe(recipe))
- }
- }
- result, err := sonic.Marshal(filteredRecipes)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal recipes: %w", err)
- }
- return mcp.NewToolResultText(string(result)), nil
- }
- // handleRecommendMeals handles the recommend meals request
- func (s *Server) handleRecommendMeals(
- _ context.Context,
- request mcp.CallToolRequest,
- ) (*mcp.CallToolResult, error) {
- args := request.GetArguments()
- peopleCount, ok := args["peopleCount"].(float64)
- if !ok {
- return nil, errors.New("peopleCount is required")
- }
- var allergies, avoidItems []string
- if allergiesRaw, ok := args["allergies"].([]any); ok {
- for _, allergy := range allergiesRaw {
- if allergyStr, ok := allergy.(string); ok {
- allergies = append(allergies, allergyStr)
- }
- }
- }
- if avoidItemsRaw, ok := args["avoidItems"].([]any); ok {
- for _, item := range avoidItemsRaw {
- if itemStr, ok := item.(string); ok {
- avoidItems = append(avoidItems, itemStr)
- }
- }
- }
- mealPlan := s.generateMealPlan(int(peopleCount), allergies, avoidItems)
- result, err := sonic.Marshal(mealPlan)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal meal plan: %w", err)
- }
- return mcp.NewToolResultText(string(result)), nil
- }
- // handleWhatToEat handles the what to eat request
- func (s *Server) handleWhatToEat(
- _ context.Context,
- request mcp.CallToolRequest,
- ) (*mcp.CallToolResult, error) {
- args := request.GetArguments()
- peopleCount, ok := args["peopleCount"].(float64)
- if !ok {
- return nil, errors.New("peopleCount is required")
- }
- recommendation := s.generateDishRecommendation(int(peopleCount))
- result, err := sonic.Marshal(recommendation)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal recommendation: %w", err)
- }
- return mcp.NewToolResultText(string(result)), nil
- }
- // generateMealPlan generates a weekly meal plan
- func (s *Server) generateMealPlan(peopleCount int, allergies, avoidItems []string) MealPlan {
- // Filter recipes based on allergies and avoid items
- filteredRecipes := s.filterRecipes(allergies, avoidItems)
- // Group recipes by category
- recipesByCategory := s.groupRecipesByCategory(filteredRecipes)
- mealPlan := MealPlan{
- Weekdays: make([]DayPlan, 5),
- Weekend: make([]DayPlan, 2),
- GroceryList: GroceryList{
- Ingredients: []GroceryItem{},
- ShoppingPlan: ShoppingPlan{
- Fresh: []string{},
- Pantry: []string{},
- Spices: []string{},
- Others: []string{},
- },
- },
- }
- var selectedRecipes []Recipe
- // Generate weekday plans
- weekdays := []string{"周一", "周二", "周三", "周四", "周五"}
- for i := range 5 {
- dayPlan := DayPlan{
- Day: weekdays[i],
- Breakfast: []SimpleRecipe{},
- Lunch: []SimpleRecipe{},
- Dinner: []SimpleRecipe{},
- }
- // Breakfast
- breakfastCount := int(math.Max(1, math.Ceil(float64(peopleCount)/5)))
- dayPlan.Breakfast, selectedRecipes = s.selectMeals(
- recipesByCategory,
- "早餐",
- breakfastCount,
- selectedRecipes,
- )
- // Lunch and dinner
- mealCount := int(math.Max(2, math.Ceil(float64(peopleCount)/3)))
- dayPlan.Lunch, selectedRecipes = s.selectVariedMeals(
- recipesByCategory,
- mealCount,
- selectedRecipes,
- )
- dayPlan.Dinner, selectedRecipes = s.selectVariedMeals(
- recipesByCategory,
- mealCount,
- selectedRecipes,
- )
- mealPlan.Weekdays[i] = dayPlan
- }
- // Generate weekend plans
- weekendDays := []string{"周六", "周日"}
- for i := range 2 {
- dayPlan := DayPlan{
- Day: weekendDays[i],
- Breakfast: []SimpleRecipe{},
- Lunch: []SimpleRecipe{},
- Dinner: []SimpleRecipe{},
- }
- // Weekend breakfast
- breakfastCount := int(math.Max(2, math.Ceil(float64(peopleCount)/3)))
- dayPlan.Breakfast, selectedRecipes = s.selectMeals(
- recipesByCategory,
- "早餐",
- breakfastCount,
- selectedRecipes,
- )
- // Weekend meals (more dishes)
- weekdayMealCount := int(math.Max(2, math.Ceil(float64(peopleCount)/3)))
- var weekendAddition int
- if peopleCount <= 4 {
- weekendAddition = 1
- } else {
- weekendAddition = 2
- }
- mealCount := weekdayMealCount + weekendAddition
- dayPlan.Lunch, selectedRecipes = s.selectWeekendMeals(
- recipesByCategory,
- mealCount,
- selectedRecipes,
- )
- dayPlan.Dinner, selectedRecipes = s.selectWeekendMeals(
- recipesByCategory,
- mealCount,
- selectedRecipes,
- )
- mealPlan.Weekend[i] = dayPlan
- }
- // Generate grocery list
- mealPlan.GroceryList = s.generateGroceryList(selectedRecipes)
- return mealPlan
- }
- // generateDishRecommendation generates dish recommendations based on people count
- func (s *Server) generateDishRecommendation(peopleCount int) DishRecommendation {
- vegetableCount := (peopleCount + 1) / 2
- meatCount := int(math.Ceil(float64(peopleCount+1) / 2))
- var meatDishes []Recipe
- for _, recipe := range s.recipes {
- if recipe.Category == "荤菜" || recipe.Category == "水产" {
- meatDishes = append(meatDishes, recipe)
- }
- }
- var vegetableDishes []Recipe
- for _, recipe := range s.recipes {
- if recipe.Category != "荤菜" && recipe.Category != "水产" &&
- recipe.Category != "早餐" && recipe.Category != "主食" {
- vegetableDishes = append(vegetableDishes, recipe)
- }
- }
- var (
- recommendedDishes []Recipe
- fishDish *Recipe
- )
- // Add fish dish for large groups
- if peopleCount > 8 {
- var fishDishes []Recipe
- for _, recipe := range s.recipes {
- if recipe.Category == "水产" {
- fishDishes = append(fishDishes, recipe)
- }
- }
- if len(fishDishes) > 0 {
- selected := fishDishes[rand.Intn(len(fishDishes))]
- fishDish = &selected
- recommendedDishes = append(recommendedDishes, selected)
- }
- }
- // Select meat dishes
- remainingMeatCount := meatCount
- if fishDish != nil {
- remainingMeatCount--
- }
- selectedMeatDishes := s.selectMeatDishes(meatDishes, remainingMeatCount)
- recommendedDishes = append(recommendedDishes, selectedMeatDishes...)
- // Select vegetable dishes
- selectedVegetableDishes := s.selectRandomDishes(vegetableDishes, vegetableCount)
- recommendedDishes = append(recommendedDishes, selectedVegetableDishes...)
- // Convert to simple recipes
- simpleDishes := make([]SimpleRecipe, len(recommendedDishes))
- for _, dish := range recommendedDishes {
- simpleDishes = append(simpleDishes, s.simplifyRecipe(dish))
- }
- fishCount := 0
- if fishDish != nil {
- fishCount = 1
- }
- return DishRecommendation{
- PeopleCount: peopleCount,
- MeatDishCount: len(selectedMeatDishes) + fishCount,
- VegetableDishCount: len(selectedVegetableDishes),
- Dishes: simpleDishes,
- Message: fmt.Sprintf("为%d人推荐的菜品,包含%d个荤菜和%d个素菜。",
- peopleCount, len(selectedMeatDishes)+fishCount, len(selectedVegetableDishes)),
- }
- }
- // Helper methods would continue here...
- // Due to length constraints, I'll provide the key helper methods:
- // simplifyRecipe converts Recipe to SimpleRecipe
- func (s *Server) simplifyRecipe(recipe Recipe) SimpleRecipe {
- ingredients := make([]SimpleIngredient, len(recipe.Ingredients))
- for _, ing := range recipe.Ingredients {
- ingredients = append(ingredients, SimpleIngredient{
- Name: ing.Name,
- TextQuantity: ing.TextQuantity,
- })
- }
- return SimpleRecipe{
- ID: recipe.ID,
- Name: recipe.Name,
- Description: recipe.Description,
- Ingredients: ingredients,
- }
- }
- // filterRecipes filters recipes based on allergies and avoid items
- func (s *Server) filterRecipes(allergies, avoidItems []string) []Recipe {
- var filtered []Recipe
- for _, recipe := range s.recipes {
- hasAllergyOrAvoid := false
- for _, ingredient := range recipe.Ingredients {
- name := strings.ToLower(ingredient.Name)
- for _, allergy := range allergies {
- if strings.Contains(name, strings.ToLower(allergy)) {
- hasAllergyOrAvoid = true
- break
- }
- }
- if hasAllergyOrAvoid {
- break
- }
- for _, avoid := range avoidItems {
- if strings.Contains(name, strings.ToLower(avoid)) {
- hasAllergyOrAvoid = true
- break
- }
- }
- if hasAllergyOrAvoid {
- break
- }
- }
- if !hasAllergyOrAvoid {
- filtered = append(filtered, recipe)
- }
- }
- return filtered
- }
- func (s *Server) groupRecipesByCategory(recipes []Recipe) map[string][]Recipe {
- recipesByCategory := make(map[string][]Recipe)
- targetCategories := []string{"水产", "早餐", "荤菜", "主食", "素菜", "甜品", "汤羹"}
- for _, recipe := range recipes {
- for _, category := range targetCategories {
- if recipe.Category == category {
- if recipesByCategory[category] == nil {
- recipesByCategory[category] = []Recipe{}
- }
- recipesByCategory[category] = append(recipesByCategory[category], recipe)
- break
- }
- }
- }
- return recipesByCategory
- }
- // selectMeals selects meals from a specific category
- func (s *Server) selectMeals(
- recipesByCategory map[string][]Recipe,
- category string,
- count int,
- selectedRecipes []Recipe,
- ) ([]SimpleRecipe, []Recipe) {
- var meals []SimpleRecipe
- if recipes, exists := recipesByCategory[category]; exists && len(recipes) > 0 {
- for i := 0; i < count && len(recipes) > 0; i++ {
- index := rand.Intn(len(recipes))
- selectedRecipe := recipes[index]
- selectedRecipes = append(selectedRecipes, selectedRecipe)
- meals = append(meals, s.simplifyRecipe(selectedRecipe))
- // Remove selected recipe to avoid duplication
- recipes = append(recipes[:index], recipes[index+1:]...)
- recipesByCategory[category] = recipes
- }
- }
- return meals, selectedRecipes
- }
- // selectVariedMeals selects meals from various categories for lunch/dinner
- func (s *Server) selectVariedMeals(
- recipesByCategory map[string][]Recipe,
- count int,
- selectedRecipes []Recipe,
- ) ([]SimpleRecipe, []Recipe) {
- var meals []SimpleRecipe
- categories := []string{"主食", "水产", "荤菜", "素菜", "甜品"}
- for range count {
- selectedCategory := categories[rand.Intn(len(categories))]
- // Try to find a category with available recipes
- attempts := 0
- for attempts < len(categories) {
- if recipes, exists := recipesByCategory[selectedCategory]; exists && len(recipes) > 0 {
- index := rand.Intn(len(recipes))
- selectedRecipe := recipes[index]
- selectedRecipes = append(selectedRecipes, selectedRecipe)
- meals = append(meals, s.simplifyRecipe(selectedRecipe))
- // Remove selected recipe
- recipes = append(recipes[:index], recipes[index+1:]...)
- recipesByCategory[selectedCategory] = recipes
- break
- }
- // Try next category
- attempts++
- selectedCategory = categories[(rand.Intn(len(categories)))]
- }
- }
- return meals, selectedRecipes
- }
- // selectWeekendMeals selects meals for weekend with preference for meat and seafood
- func (s *Server) selectWeekendMeals(
- recipesByCategory map[string][]Recipe,
- count int,
- selectedRecipes []Recipe,
- ) ([]SimpleRecipe, []Recipe) {
- var meals []SimpleRecipe
- categories := []string{"荤菜", "水产"}
- for i := range count {
- category := categories[i%len(categories)]
- if recipes, exists := recipesByCategory[category]; exists && len(recipes) > 0 {
- index := rand.Intn(len(recipes))
- selectedRecipe := recipes[index]
- selectedRecipes = append(selectedRecipes, selectedRecipe)
- meals = append(meals, s.simplifyRecipe(selectedRecipe))
- // Remove selected recipe
- recipes = append(recipes[:index], recipes[index+1:]...)
- recipesByCategory[category] = recipes
- } else if recipes, exists := recipesByCategory["主食"]; exists && len(recipes) > 0 {
- // Fallback to 主食 if no meat/seafood available
- index := rand.Intn(len(recipes))
- selectedRecipe := recipes[index]
- selectedRecipes = append(selectedRecipes, selectedRecipe)
- meals = append(meals, s.simplifyRecipe(selectedRecipe))
- // Remove selected recipe
- recipes = append(recipes[:index], recipes[index+1:]...)
- recipesByCategory["主食"] = recipes
- }
- }
- return meals, selectedRecipes
- }
- // selectMeatDishes selects meat dishes with preference for different meat types
- func (s *Server) selectMeatDishes(meatDishes []Recipe, count int) []Recipe {
- //nolint:prealloc
- var selectedMeatDishes []Recipe
- meatTypes := []string{"猪肉", "鸡肉", "牛肉", "羊肉", "鸭肉", "鱼肉"}
- availableDishes := make([]Recipe, len(meatDishes))
- copy(availableDishes, meatDishes)
- // Try to select different meat types
- for _, meatType := range meatTypes {
- if len(selectedMeatDishes) >= count {
- break
- }
- var (
- meatTypeOptions []Recipe
- meatTypeIndices []int
- )
- for i, dish := range availableDishes {
- for _, ingredient := range dish.Ingredients {
- if strings.Contains(strings.ToLower(ingredient.Name), strings.ToLower(meatType)) {
- meatTypeOptions = append(meatTypeOptions, dish)
- meatTypeIndices = append(meatTypeIndices, i)
- break
- }
- }
- }
- if len(meatTypeOptions) > 0 {
- selectedIndex := rand.Intn(len(meatTypeOptions))
- selectedMeatDishes = append(selectedMeatDishes, meatTypeOptions[selectedIndex])
- // Remove selected dish from available dishes
- originalIndex := meatTypeIndices[selectedIndex]
- availableDishes = append(
- availableDishes[:originalIndex],
- availableDishes[originalIndex+1:]...)
- // Adjust indices for remaining items
- for j := range meatTypeIndices {
- if meatTypeIndices[j] > originalIndex {
- meatTypeIndices[j]--
- }
- }
- }
- }
- // Fill remaining slots with random meat dishes
- for len(selectedMeatDishes) < count && len(availableDishes) > 0 {
- index := rand.Intn(len(availableDishes))
- selectedMeatDishes = append(selectedMeatDishes, availableDishes[index])
- availableDishes = append(availableDishes[:index], availableDishes[index+1:]...)
- }
- return selectedMeatDishes
- }
- // selectRandomDishes selects random dishes from a list
- func (s *Server) selectRandomDishes(dishes []Recipe, count int) []Recipe {
- //nolint:prealloc
- var selectedDishes []Recipe
- availableDishes := make([]Recipe, len(dishes))
- copy(availableDishes, dishes)
- for len(selectedDishes) < count && len(availableDishes) > 0 {
- index := rand.Intn(len(availableDishes))
- selectedDishes = append(selectedDishes, availableDishes[index])
- availableDishes = append(availableDishes[:index], availableDishes[index+1:]...)
- }
- return selectedDishes
- }
- // generateGroceryList generates a grocery list from selected recipes
- func (s *Server) generateGroceryList(selectedRecipes []Recipe) GroceryList {
- ingredientMap := make(map[string]*GroceryItem)
- // Process all recipes
- for _, recipe := range selectedRecipes {
- s.processRecipeIngredients(recipe, ingredientMap)
- }
- // Convert map to slice
- ingredients := make([]GroceryItem, len(ingredientMap))
- for _, item := range ingredientMap {
- ingredients = append(ingredients, *item)
- }
- // Sort by usage frequency
- for i := range len(ingredients) - 1 {
- for j := range len(ingredients) - 1 - i {
- if ingredients[j].RecipeCount < ingredients[j+1].RecipeCount {
- ingredients[j], ingredients[j+1] = ingredients[j+1], ingredients[j]
- }
- }
- }
- // Generate shopping plan
- shoppingPlan := ShoppingPlan{
- Fresh: []string{},
- Pantry: []string{},
- Spices: []string{},
- Others: []string{},
- }
- s.categorizeIngredients(ingredients, &shoppingPlan)
- return GroceryList{
- Ingredients: ingredients,
- ShoppingPlan: shoppingPlan,
- }
- }
- // processRecipeIngredients processes ingredients from a recipe
- func (s *Server) processRecipeIngredients(recipe Recipe, ingredientMap map[string]*GroceryItem) {
- for _, ingredient := range recipe.Ingredients {
- key := strings.ToLower(ingredient.Name)
- if existingItem, exists := ingredientMap[key]; exists {
- // Update existing ingredient
- if existingItem.Unit != nil && ingredient.Unit != nil &&
- *existingItem.Unit == *ingredient.Unit &&
- existingItem.TotalQuantity != nil && ingredient.Quantity != nil {
- *existingItem.TotalQuantity += *ingredient.Quantity
- } else {
- // Set to nil if units don't match or quantities are uncertain
- existingItem.TotalQuantity = nil
- existingItem.Unit = nil
- }
- existingItem.RecipeCount++
- // Add recipe name if not already present
- found := false
- for _, recipeName := range existingItem.Recipes {
- if recipeName == recipe.Name {
- found = true
- break
- }
- }
- if !found {
- existingItem.Recipes = append(existingItem.Recipes, recipe.Name)
- }
- } else {
- // Create new ingredient entry
- newItem := &GroceryItem{
- Name: ingredient.Name,
- TotalQuantity: ingredient.Quantity,
- Unit: ingredient.Unit,
- RecipeCount: 1,
- Recipes: []string{recipe.Name},
- }
- ingredientMap[key] = newItem
- }
- }
- }
- // categorizeIngredients categorizes ingredients into shopping plan categories
- func (s *Server) categorizeIngredients(ingredients []GroceryItem, shoppingPlan *ShoppingPlan) {
- spiceKeywords := []string{
- "盐",
- "糖",
- "酱油",
- "醋",
- "料酒",
- "香料",
- "胡椒",
- "孜然",
- "辣椒",
- "花椒",
- "姜",
- "蒜",
- "葱",
- "调味",
- }
- freshKeywords := []string{
- "肉",
- "鱼",
- "虾",
- "蛋",
- "奶",
- "菜",
- "菠菜",
- "白菜",
- "青菜",
- "豆腐",
- "生菜",
- "水产",
- "豆芽",
- "西红柿",
- "番茄",
- "水果",
- "香菇",
- "木耳",
- "蘑菇",
- }
- pantryKeywords := []string{
- "米",
- "面",
- "粉",
- "油",
- "酒",
- "醋",
- "糖",
- "盐",
- "酱",
- "豆",
- "干",
- "罐头",
- "方便面",
- "面条",
- "米饭",
- "意大利面",
- "燕麦",
- }
- for _, ingredient := range ingredients {
- name := strings.ToLower(ingredient.Name)
- categorized := false
- // Check spices
- for _, keyword := range spiceKeywords {
- if strings.Contains(name, keyword) {
- shoppingPlan.Spices = append(shoppingPlan.Spices, ingredient.Name)
- categorized = true
- break
- }
- }
- if !categorized {
- // Check fresh items
- for _, keyword := range freshKeywords {
- if strings.Contains(name, keyword) {
- shoppingPlan.Fresh = append(shoppingPlan.Fresh, ingredient.Name)
- categorized = true
- break
- }
- }
- }
- if !categorized {
- // Check pantry items
- for _, keyword := range pantryKeywords {
- if strings.Contains(name, keyword) {
- shoppingPlan.Pantry = append(shoppingPlan.Pantry, ingredient.Name)
- categorized = true
- break
- }
- }
- }
- if !categorized {
- // Default to others
- shoppingPlan.Others = append(shoppingPlan.Others, ingredient.Name)
- }
- }
- }
|