cook-mcp-windy
Version:
HowToCook MCP Server - Intelligent Chinese recipe management and meal planning for AI assistants
188 lines • 6.45 kB
JavaScript
import { Recipe, RecipeCategory, DifficultyLevel, SearchParams } from '../types/index.js';
/**
* Filter recipes by category
*/
export function filterRecipesByCategory(recipes, category) {
return recipes.filter(recipe => recipe.category === category);
}
/**
* Search recipes by query string
*/
export function searchRecipes(recipes, searchParams) {
let filteredRecipes = [...recipes];
// Filter by search query (name, description, ingredients)
if (searchParams.query) {
const query = searchParams.query.toLowerCase();
filteredRecipes = filteredRecipes.filter(recipe => recipe.name.toLowerCase().includes(query) ||
recipe.description?.toLowerCase().includes(query) ||
recipe.ingredients.some(ingredient => ingredient.name.toLowerCase().includes(query)) ||
recipe.tags?.some(tag => tag.toLowerCase().includes(query)));
}
// Filter by categories
if (searchParams.categories && searchParams.categories.length > 0) {
filteredRecipes = filteredRecipes.filter(recipe => searchParams.categories.includes(recipe.category));
}
// Filter by tags
if (searchParams.tags && searchParams.tags.length > 0) {
filteredRecipes = filteredRecipes.filter(recipe => recipe.tags?.some(tag => searchParams.tags.includes(tag)));
}
// Filter by difficulty
if (searchParams.difficulty) {
filteredRecipes = filteredRecipes.filter(recipe => recipe.difficulty === searchParams.difficulty);
}
// Filter by max prep time
if (searchParams.maxPrepTime) {
filteredRecipes = filteredRecipes.filter(recipe => parseTimeInMinutes(recipe.prepTime) <= parseTimeInMinutes(searchParams.maxPrepTime));
}
// Filter by max cook time
if (searchParams.maxCookTime) {
filteredRecipes = filteredRecipes.filter(recipe => parseTimeInMinutes(recipe.cookTime) <= parseTimeInMinutes(searchParams.maxCookTime));
}
return filteredRecipes;
}
/**
* Parse time string to minutes (e.g., "30分钟" -> 30, "1小时" -> 60)
*/
export function parseTimeInMinutes(timeStr) {
const hourMatch = timeStr.match(/(\d+)小时/);
const minuteMatch = timeStr.match(/(\d+)分钟/);
let totalMinutes = 0;
if (hourMatch && hourMatch[1]) {
totalMinutes += parseInt(hourMatch[1]) * 60;
}
if (minuteMatch && minuteMatch[1]) {
totalMinutes += parseInt(minuteMatch[1]);
}
return totalMinutes;
}
/**
* Format minutes to Chinese time string
*/
export function formatTimeFromMinutes(minutes) {
if (minutes < 60) {
return `${minutes}分钟`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (remainingMinutes === 0) {
return `${hours}小时`;
}
return `${hours}小时${remainingMinutes}分钟`;
}
/**
* Calculate total cooking time for a recipe
*/
export function calculateTotalTime(prepTime, cookTime) {
const prepMinutes = parseTimeInMinutes(prepTime);
const cookMinutes = parseTimeInMinutes(cookTime);
const totalMinutes = prepMinutes + cookMinutes;
return formatTimeFromMinutes(totalMinutes);
}
/**
* Get recipes suitable for a specific number of people
*/
export function getRecipesForServings(recipes, targetServings, tolerance = 2) {
return recipes.filter(recipe => Math.abs(recipe.servings - targetServings) <= tolerance);
}
/**
* Scale recipe ingredients for different serving sizes
*/
export function scaleRecipeIngredients(recipe, targetServings) {
const scaleFactor = targetServings / recipe.servings;
const scaledIngredients = recipe.ingredients.map(ingredient => ({
...ingredient,
amount: scaleIngredientAmount(ingredient.amount, scaleFactor)
}));
return {
...recipe,
servings: targetServings,
ingredients: scaledIngredients
};
}
/**
* Scale ingredient amount (handles various Chinese units)
*/
function scaleIngredientAmount(amount, scaleFactor) {
// Handle numeric amounts with units
const numericMatch = amount.match(/^(\d+(?:\.\d+)?)\s*(.*)$/);
if (numericMatch && numericMatch[1]) {
const [, numStr, unit] = numericMatch;
const originalNum = parseFloat(numStr);
const scaledNum = originalNum * scaleFactor;
// Round to reasonable precision
const roundedNum = Math.round(scaledNum * 100) / 100;
return `${roundedNum}${unit}`;
}
// Handle special cases like "适量", "少许", etc.
const specialCases = ['适量', '少许', '一点', '若干'];
if (specialCases.some(special => amount.includes(special))) {
return amount; // Don't scale these
}
return amount; // Return original if can't parse
}
/**
* Get recipe difficulty distribution
*/
export function getRecipeDifficultyDistribution(recipes) {
const distribution = {
'简单': 0,
'中等': 0,
'困难': 0
};
recipes.forEach(recipe => {
distribution[recipe.difficulty]++;
});
return distribution;
}
/**
* Get recipe category distribution
*/
export function getRecipeCategoryDistribution(recipes) {
const distribution = {
'水产': 0,
'早餐': 0,
'调味料': 0,
'甜品': 0,
'饮品': 0,
'荤菜': 0,
'半成品': 0,
'汤羹': 0,
'主食': 0,
'素菜': 0
};
recipes.forEach(recipe => {
distribution[recipe.category]++;
});
return distribution;
}
/**
* Validate recipe data
*/
export function validateRecipe(recipe) {
const errors = [];
if (!recipe.name.trim()) {
errors.push('Recipe name is required');
}
if (recipe.ingredients.length === 0) {
errors.push('Recipe must have at least one ingredient');
}
if (recipe.steps.length === 0) {
errors.push('Recipe must have at least one cooking step');
}
if (recipe.servings <= 0) {
errors.push('Recipe servings must be greater than 0');
}
// Validate step numbers are sequential
const stepNumbers = recipe.steps.map(step => step.stepNumber).sort((a, b) => a - b);
for (let i = 0; i < stepNumbers.length; i++) {
if (stepNumbers[i] !== i + 1) {
errors.push('Recipe steps must be numbered sequentially starting from 1');
break;
}
}
return {
isValid: errors.length === 0,
errors
};
}
//# sourceMappingURL=recipeUtils.js.map