ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

rip some stuff out

+52 -244
+1 -2
cmd/careme/main.go
··· 46 46 formatter := recipes.NewFormatter() 47 47 48 48 fmt.Printf("🍽️ Generating 4 weekly recipes for location: %s\n", location) 49 - fmt.Println("📍 Checking available ingredients at local QFC/Fred Meyer...") 50 - fmt.Println("🌱 Using seasonal ingredient recommendations...") 49 + fmt.Println("🏷️ Checking current sales at local QFC/Fred Meyer...") 51 50 fmt.Println("📚 Avoiding recipes from the past 2 weeks...") 52 51 fmt.Println() 53 52
+7 -16
internal/ai/client.go
··· 55 55 } 56 56 } 57 57 58 - func (c *Client) GenerateRecipes(location string, availableIngredients []string, seasonalIngredients []string, previousRecipes []string) (string, error) { 59 - prompt := c.buildRecipePrompt(location, availableIngredients, seasonalIngredients, previousRecipes) 58 + func (c *Client) GenerateRecipes(location string, saleIngredients []string, previousRecipes []string) (string, error) { 59 + prompt := c.buildRecipePrompt(location, saleIngredients, previousRecipes) 60 60 61 61 messages := []Message{ 62 62 { ··· 166 166 return anthropicResp.Content[0].Text, nil 167 167 } 168 168 169 - func (c *Client) buildRecipePrompt(location string, availableIngredients, seasonalIngredients, previousRecipes []string) string { 169 + func (c *Client) buildRecipePrompt(location string, saleIngredients, previousRecipes []string) string { 170 170 prompt := fmt.Sprintf("Generate 4 unique weekly recipes for location: %s\n\n", location) 171 171 172 - if len(availableIngredients) > 0 { 173 - prompt += "Available fresh ingredients at local QFC/Fred Meyer:\n" 174 - for _, ingredient := range availableIngredients { 175 - prompt += fmt.Sprintf("- %s\n", ingredient) 176 - } 177 - prompt += "\n" 178 - } 179 - 180 - if len(seasonalIngredients) > 0 { 181 - prompt += "Seasonal ingredients currently available:\n" 182 - for _, ingredient := range seasonalIngredients { 172 + if len(saleIngredients) > 0 { 173 + prompt += "Ingredients currently on sale at local QFC/Fred Meyer:\n" 174 + for _, ingredient := range saleIngredients { 183 175 prompt += fmt.Sprintf("- %s\n", ingredient) 184 176 } 185 177 prompt += "\n" ··· 195 187 196 188 prompt += "Requirements:\n" 197 189 prompt += "- Generate exactly 4 recipes\n" 198 - prompt += "- Prioritize available fresh ingredients\n" 199 - prompt += "- Use seasonal ingredients when possible\n" 190 + prompt += "- Prioritize ingredients currently on sale\n" 200 191 prompt += "- Avoid repeating previous recipes\n" 201 192 prompt += "- Include variety in cooking methods and cuisines\n" 202 193 prompt += "- Each recipe should serve 2 people\n"
+5 -17
internal/config/config.go
··· 5 5 ) 6 6 7 7 type Config struct { 8 - AI AIConfig `json:"ai"` 9 - Kroger KrogerConfig `json:"kroger"` 10 - Epicurious EpicuriousConfig `json:"epicurious"` 11 - History HistoryConfig `json:"history"` 8 + AI AIConfig `json:"ai"` 9 + Kroger KrogerConfig `json:"kroger"` 10 + History HistoryConfig `json:"history"` 12 11 } 13 12 14 13 type AIConfig struct { ··· 18 17 } 19 18 20 19 type KrogerConfig struct { 21 - MCPServerURL string `json:"mcp_server_url"` 22 - APIKey string `json:"api_key"` 23 - } 24 - 25 - type EpicuriousConfig struct { 26 - APIEndpoint string `json:"api_endpoint"` 27 - APIKey string `json:"api_key"` 20 + APIKey string `json:"api_key"` 28 21 } 29 22 30 23 type HistoryConfig struct { ··· 40 33 Model: getEnvOrDefault("AI_MODEL", "gpt-4"), 41 34 }, 42 35 Kroger: KrogerConfig{ 43 - MCPServerURL: getEnvOrDefault("KROGER_MCP_URL", "http://localhost:8080"), 44 - APIKey: os.Getenv("KROGER_API_KEY"), 45 - }, 46 - Epicurious: EpicuriousConfig{ 47 - APIEndpoint: getEnvOrDefault("EPICURIOUS_ENDPOINT", "https://api.epicurious.com"), 48 - APIKey: os.Getenv("EPICURIOUS_API_KEY"), 36 + APIKey: os.Getenv("KROGER_API_KEY"), 49 37 }, 50 38 History: HistoryConfig{ 51 39 StoragePath: getEnvOrDefault("HISTORY_PATH", "./data/history.json"),
-133
internal/ingredients/seasonal.go
··· 1 - package ingredients 2 - 3 - import ( 4 - "encoding/json" 5 - "fmt" 6 - "net/http" 7 - "strings" 8 - "time" 9 - ) 10 - 11 - type SeasonalClient struct { 12 - apiEndpoint string 13 - apiKey string 14 - httpClient *http.Client 15 - } 16 - 17 - type SeasonalIngredient struct { 18 - Name string `json:"name"` 19 - Season []string `json:"season"` 20 - Peak []string `json:"peak"` 21 - Category string `json:"category"` 22 - Description string `json:"description"` 23 - Substitutes []string `json:"substitutes"` 24 - } 25 - 26 - type SeasonalResponse struct { 27 - Ingredients []SeasonalIngredient `json:"ingredients"` 28 - Season string `json:"current_season"` 29 - Region string `json:"region"` 30 - } 31 - 32 - func NewSeasonalClient(apiEndpoint, apiKey string) *SeasonalClient { 33 - return &SeasonalClient{ 34 - apiEndpoint: apiEndpoint, 35 - apiKey: apiKey, 36 - httpClient: &http.Client{Timeout: 30 * time.Second}, 37 - } 38 - } 39 - 40 - func (c *SeasonalClient) GetSeasonalIngredients(location string) ([]SeasonalIngredient, error) { 41 - season := getCurrentSeason() 42 - url := fmt.Sprintf("%s/seasonal?location=%s&season=%s", c.apiEndpoint, location, season) 43 - 44 - req, err := http.NewRequest("GET", url, nil) 45 - if err != nil { 46 - return nil, fmt.Errorf("failed to create request: %w", err) 47 - } 48 - 49 - req.Header.Set("Authorization", "Bearer "+c.apiKey) 50 - req.Header.Set("Accept", "application/json") 51 - 52 - resp, err := c.httpClient.Do(req) 53 - if err != nil { 54 - return nil, fmt.Errorf("failed to make request: %w", err) 55 - } 56 - defer resp.Body.Close() 57 - 58 - if resp.StatusCode != http.StatusOK { 59 - return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode) 60 - } 61 - 62 - var seasonalResp SeasonalResponse 63 - if err := json.NewDecoder(resp.Body).Decode(&seasonalResp); err != nil { 64 - return nil, fmt.Errorf("failed to decode response: %w", err) 65 - } 66 - 67 - return seasonalResp.Ingredients, nil 68 - } 69 - 70 - func (c *SeasonalClient) GetIngredientsForSeason(season, location string) ([]SeasonalIngredient, error) { 71 - url := fmt.Sprintf("%s/seasonal?location=%s&season=%s", c.apiEndpoint, location, season) 72 - 73 - req, err := http.NewRequest("GET", url, nil) 74 - if err != nil { 75 - return nil, fmt.Errorf("failed to create request: %w", err) 76 - } 77 - 78 - req.Header.Set("Authorization", "Bearer "+c.apiKey) 79 - req.Header.Set("Accept", "application/json") 80 - 81 - resp, err := c.httpClient.Do(req) 82 - if err != nil { 83 - return nil, fmt.Errorf("failed to make request: %w", err) 84 - } 85 - defer resp.Body.Close() 86 - 87 - if resp.StatusCode != http.StatusOK { 88 - return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode) 89 - } 90 - 91 - var seasonalResp SeasonalResponse 92 - if err := json.NewDecoder(resp.Body).Decode(&seasonalResp); err != nil { 93 - return nil, fmt.Errorf("failed to decode response: %w", err) 94 - } 95 - 96 - return seasonalResp.Ingredients, nil 97 - } 98 - 99 - func getCurrentSeason() string { 100 - now := time.Now() 101 - month := now.Month() 102 - 103 - switch { 104 - case month >= time.March && month <= time.May: 105 - return "spring" 106 - case month >= time.June && month <= time.August: 107 - return "summer" 108 - case month >= time.September && month <= time.November: 109 - return "fall" 110 - default: 111 - return "winter" 112 - } 113 - } 114 - 115 - func (s *SeasonalIngredient) IsInSeason(season string) bool { 116 - season = strings.ToLower(season) 117 - for _, s := range s.Season { 118 - if strings.ToLower(s) == season { 119 - return true 120 - } 121 - } 122 - return false 123 - } 124 - 125 - func (s *SeasonalIngredient) IsAtPeak(season string) bool { 126 - season = strings.ToLower(season) 127 - for _, p := range s.Peak { 128 - if strings.ToLower(p) == season { 129 - return true 130 - } 131 - } 132 - return false 133 - }
+19 -26
internal/kroger/client.go
··· 8 8 ) 9 9 10 10 type Client struct { 11 - mcpServerURL string 12 - apiKey string 13 - httpClient *http.Client 11 + apiKey string 12 + httpClient *http.Client 13 + baseURL string 14 14 } 15 15 16 16 type Product struct { ··· 18 18 Name string `json:"name"` 19 19 Brand string `json:"brand"` 20 20 Price float64 `json:"price"` 21 + SalePrice float64 `json:"sale_price"` 22 + OnSale bool `json:"on_sale"` 21 23 Available bool `json:"available"` 22 24 Fresh bool `json:"fresh"` 23 25 Category string `json:"category"` 24 26 Description string `json:"description"` 25 27 } 26 28 27 - type SearchRequest struct { 28 - Location string `json:"location"` 29 - Keywords []string `json:"keywords"` 30 - Categories []string `json:"categories"` 31 - FreshOnly bool `json:"fresh_only"` 29 + type SalesRequest struct { 30 + Location string `json:"location"` 32 31 } 33 32 34 - type SearchResponse struct { 33 + type SalesResponse struct { 35 34 Products []Product `json:"products"` 36 35 Total int `json:"total"` 37 36 } 38 37 39 - func NewClient(mcpServerURL, apiKey string) *Client { 38 + func NewClient(apiKey string) *Client { 40 39 return &Client{ 41 - mcpServerURL: mcpServerURL, 42 - apiKey: apiKey, 43 - httpClient: &http.Client{}, 40 + apiKey: apiKey, 41 + httpClient: &http.Client{}, 42 + baseURL: "https://api.kroger.com/v1", 44 43 } 45 44 } 46 45 47 - func (c *Client) SearchProducts(location string, keywords []string, freshOnly bool) ([]Product, error) { 48 - request := SearchRequest{ 49 - Location: location, 50 - Keywords: keywords, 51 - FreshOnly: freshOnly, 46 + func (c *Client) GetSaleProducts(location string) ([]Product, error) { 47 + request := SalesRequest{ 48 + Location: location, 52 49 } 53 50 54 51 jsonData, err := json.Marshal(request) ··· 56 53 return nil, fmt.Errorf("failed to marshal request: %w", err) 57 54 } 58 55 59 - req, err := http.NewRequest("POST", c.mcpServerURL+"/search", bytes.NewBuffer(jsonData)) 56 + req, err := http.NewRequest("POST", c.baseURL+"/products/sales", bytes.NewBuffer(jsonData)) 60 57 if err != nil { 61 58 return nil, fmt.Errorf("failed to create request: %w", err) 62 59 } ··· 74 71 return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode) 75 72 } 76 73 77 - var searchResp SearchResponse 78 - if err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil { 74 + var salesResp SalesResponse 75 + if err := json.NewDecoder(resp.Body).Decode(&salesResp); err != nil { 79 76 return nil, fmt.Errorf("failed to decode response: %w", err) 80 77 } 81 78 82 - return searchResp.Products, nil 83 - } 84 - 85 - func (c *Client) GetFreshIngredients(location string, ingredients []string) ([]Product, error) { 86 - return c.SearchProducts(location, ingredients, true) 79 + return salesResp.Products, nil 87 80 }
+20 -50
internal/recipes/generator.go
··· 8 8 "careme/internal/ai" 9 9 "careme/internal/config" 10 10 "careme/internal/history" 11 - "careme/internal/ingredients" 12 11 "careme/internal/kroger" 13 12 ) 14 13 15 14 type Generator struct { 16 - config *config.Config 17 - aiClient *ai.Client 18 - krogerClient *kroger.Client 19 - seasonalClient *ingredients.SeasonalClient 20 - historyStorage *history.HistoryStorage 15 + config *config.Config 16 + aiClient *ai.Client 17 + krogerClient *kroger.Client 18 + historyStorage *history.HistoryStorage 21 19 } 22 20 23 21 type GeneratedRecipes struct { ··· 26 24 27 25 func NewGenerator(cfg *config.Config) *Generator { 28 26 return &Generator{ 29 - config: cfg, 30 - aiClient: ai.NewClient(cfg.AI.Provider, cfg.AI.APIKey, cfg.AI.Model), 31 - krogerClient: kroger.NewClient(cfg.Kroger.MCPServerURL, cfg.Kroger.APIKey), 32 - seasonalClient: ingredients.NewSeasonalClient(cfg.Epicurious.APIEndpoint, cfg.Epicurious.APIKey), 33 - historyStorage: history.NewHistoryStorage(cfg.History.StoragePath, cfg.History.RetentionDays), 27 + config: cfg, 28 + aiClient: ai.NewClient(cfg.AI.Provider, cfg.AI.APIKey, cfg.AI.Model), 29 + krogerClient: kroger.NewClient(cfg.Kroger.APIKey), 30 + historyStorage: history.NewHistoryStorage(cfg.History.StoragePath, cfg.History.RetentionDays), 34 31 } 35 32 } 36 33 37 34 func (g *Generator) GenerateWeeklyRecipes(location string) ([]history.Recipe, error) { 38 35 log.Printf("Generating recipes for location: %s", location) 39 36 40 - availableIngredients, err := g.getAvailableIngredients(location) 37 + saleIngredients, err := g.getSaleIngredients(location) 41 38 if err != nil { 42 - log.Printf("Warning: Could not fetch available ingredients: %v", err) 43 - availableIngredients = []string{} 44 - } 45 - 46 - seasonalIngredients, err := g.getSeasonalIngredients(location) 47 - if err != nil { 48 - log.Printf("Warning: Could not fetch seasonal ingredients: %v", err) 49 - seasonalIngredients = []string{} 39 + log.Printf("Warning: Could not fetch sale ingredients: %v", err) 40 + saleIngredients = []string{} 50 41 } 51 42 52 43 previousRecipes, err := g.getPreviousRecipes() ··· 55 46 previousRecipes = []string{} 56 47 } 57 48 58 - log.Printf("Found %d available ingredients, %d seasonal ingredients, %d previous recipes", 59 - len(availableIngredients), len(seasonalIngredients), len(previousRecipes)) 49 + log.Printf("Found %d sale ingredients, %d previous recipes", 50 + len(saleIngredients), len(previousRecipes)) 60 51 61 - response, err := g.aiClient.GenerateRecipes(location, availableIngredients, seasonalIngredients, previousRecipes) 52 + response, err := g.aiClient.GenerateRecipes(location, saleIngredients, previousRecipes) 62 53 if err != nil { 63 54 return nil, fmt.Errorf("failed to generate recipes with AI: %w", err) 64 55 } ··· 75 66 return recipes, nil 76 67 } 77 68 78 - func (g *Generator) getAvailableIngredients(location string) ([]string, error) { 79 - commonIngredients := []string{ 80 - "chicken", "beef", "pork", "salmon", "eggs", 81 - "onions", "garlic", "potatoes", "carrots", "celery", 82 - "tomatoes", "bell peppers", "broccoli", "spinach", "lettuce", 83 - "rice", "pasta", "bread", "milk", "cheese", "butter", 84 - } 85 - 86 - products, err := g.krogerClient.GetFreshIngredients(location, commonIngredients) 69 + func (g *Generator) getSaleIngredients(location string) ([]string, error) { 70 + products, err := g.krogerClient.GetSaleProducts(location) 87 71 if err != nil { 88 72 return nil, err 89 73 } 90 74 91 - var available []string 75 + var saleIngredients []string 92 76 for _, product := range products { 93 - if product.Available && product.Fresh { 94 - available = append(available, product.Name) 77 + if product.OnSale && product.Available { 78 + saleIngredients = append(saleIngredients, product.Name) 95 79 } 96 80 } 97 81 98 - return available, nil 99 - } 100 - 101 - func (g *Generator) getSeasonalIngredients(location string) ([]string, error) { 102 - seasonalItems, err := g.seasonalClient.GetSeasonalIngredients(location) 103 - if err != nil { 104 - return nil, err 105 - } 106 - 107 - var ingredients []string 108 - for _, item := range seasonalItems { 109 - ingredients = append(ingredients, item.Name) 110 - } 111 - 112 - return ingredients, nil 82 + return saleIngredients, nil 113 83 } 114 84 115 85 func (g *Generator) getPreviousRecipes() ([]string, error) {