当前位置: 首页 > news >正文

用 AI 写代码做家庭调酒小程序:真正难的是把酒库到保存跑通

用 AI 写代码做家庭调酒小程序:真正难的是把“酒库 -> 可做 -> 保存”跑通

我一开始以为,做一个家庭调酒小程序,核心应该是配方库。

经典鸡尾酒整理进去,做列表、搜索、详情、收藏,再配几张好看的图,表面上就像一个完整产品。但真把 Naniya 做成微信小程序后,我发现家庭调酒里更高频的问题不是“某杯酒的标准配方是什么”,而是:

我家里就这些材料,今晚到底能做什么?

这篇不讲“AI 写代码有多神”,而是复盘一个更具体的产品工程问题:当一个小程序不是资料站,而是要帮用户从真实库存出发完成一次调酒,它的数据链路应该怎么设计。

入口为什么不是搜索框

Naniya 现在的主入口是四个 tab:今日、酒单、酒库、我的。

这看起来很常规,但关键是“酒库”不是附属功能,而是主路径的起点。用户先把家里已有的基酒、利口酒、葡萄酒、软饮果汁、糖浆、苦精香料、新鲜食材和装饰调味记进去,后面的推荐、筛选、自由调和创作才有依据。

源码里这部分不是一个简单的字符串列表。utils/inventory-config.js里维护了原料分类、原料 id、展示名、搜索别名和库存状态:

constSTOCK_STATUS={NONE:'none',IN_STOCK:'inStock',UNLIMITED:'unlimited',};

库存判断也尽量收口到统一函数里:

functionisStocked(status){returnstatus===STOCK_STATUS.IN_STOCK||status===STOCK_STATUS.UNLIMITED;}functionisIngredientAvailable(ingredient,inventoryItems,customIngredients){if(!ingredient)returntrue;if(ingredient.customIngredientId){returnisStocked(inventoryItems['custom_'+ingredient.customIngredientId]);}constid=resolveIngredientId(ingredient);if(!id){constname=String(ingredient.name||'').trim();return!!name&&(customIngredients||[]).some(ci=>{constkey='custom_'+(ci._id||ci.id||'');returnisStocked(inventoryItems[key])&&ci.name===name;});}returnisStocked(inventoryItems[id]);}

这个设计的价值在于:后面不管是“首页今日推荐”“酒单筛选”“酒库助手结果页”,还是“只用我的材料”的灵感调酒师,都可以复用同一套库存事实,而不是各写一份判断。

库存到经典配方:先把确定性链路做稳

“我能做哪些经典酒”不需要一上来就用大模型。它更适合做成确定性判断:一杯酒的所有必要原料都能在库存中命中,就算可做。

源码里的核心逻辑很直接:

functiongetAvailableDrinks(drinks,inventoryItems){returndrinks.filter(drink=>{returndrink.ingredients.every(ing=>{returnisIngredientAvailable(ing,inventoryItems);});});}

难点不在这几行,而在“原料是否命中”这件事不能太脆。

比如同一个东西可能有中文名、英文名、别名、品牌名,也可能来自用户自定义原料。Naniya 里会用resolveIngredientIdNAME_TO_IDSEARCH_NAME_ALIASES和自定义原料的 parent 归因去尽量把它归一到稳定 id。

酒单页也不是只做搜索。它有两个和酒库强绑定的过滤:

  • “我能做”:缺失原料数为 0。
  • “补 1 样可做”:缺失原料数为 1。

后端getDrinkList会从drink_search_index读取酒款索引,必要时 fallback 到drinks集合,再按搜索词、风味标签和库存模式过滤。这里的判断仍然是确定性的:

constmissingCount=Array.from(newSet(ids)).filter(id=>!available.has(id)).length;if(mode==='available')returnmissingCount===0;if(mode==='missing_one')returnmissingCount===1;

这样做有一个好处:用户看到“可做”时,背后不是模糊推荐,而是能追溯到自己的酒库输入。

结果分层:经典、自由调、补材料不要混在一起

如果把所有答案都塞进一个列表,用户还是要自己判断下一步该干什么。

所以 Naniya 的酒库结果页分成三类:

  • 经典:库存已经满足的标准酒谱,可以直接看详情和做法。
  • 自由调:不追求复刻经典,而是用已有材料组出长饮、酸甜、气泡、短饮等结构。
  • 补材料:如果只差一种原料,就告诉用户补它能多做哪些酒。

这里面最容易被误解的是“自由调”。它不是一句“AI 推荐你随便发挥”,而是一个结构生成器。utils/structure-mix-builder.js会先把库存里的原料转成带角色的 item,例如:

  • base:基酒骨架。
  • lengthener:茶、果汁、气泡等拉长材料。
  • acid:柠檬、青柠、西柚、蔓越莓等酸度来源。
  • sweetener:糖浆、蜂蜜、甜味利口酒。
  • bitter/modifier/aroma:苦味、修饰和香气材料。

再按结构组合:

// 长饮:基酒 + 拉长材料,可选酸、甜和香气buildHighball(base,lengthener,items);// 酸甜:基酒 + 酸 + 甜buildSour(base,acid,sweetener,items);// 气泡开胃:风味核心 + 气泡buildSpritz(modifier,sparkling,items);// 搅拌短饮:基酒 + 副调buildStirred(base,modifier,items);// 家用基底:基酒 + 茶/果汁/咖啡/奶感材料buildTeaOrJuice(base,body,items);

这类结果对家庭场景很重要。用户可能没有做一杯标准玛格丽特所需的完整材料,但有金酒、苏打水、柠檬、蜂蜜。系统就不应该只回答“经典配方不足”,而应该告诉他可以走一个更现实的长饮结构。

“补材料”也是同理。源码里不会推荐一堆泛泛的采购清单,而是只处理“恰好缺一种系统原料”的酒款,把缺失原料聚合起来,按能解锁的酒款数量排序:

if(hasOtherMissing||missingSysIds.size!==1)return;constmissingId=[...missingSysIds][0];recMap[missingId].delta++;recMap[missingId].drinks.push({id:drinkId,name:drink.name||'',});

这就是为什么“补材料”对新手有用。它不是在种草某个瓶子,而是在回答“买这一样东西,能让我的酒库多打开哪些可能”。

生成能力要被约束,而不是只接一个输入框

Naniya 里还有一个更偏创作的入口,叫“灵感调酒师”。用户可以输入一个场景、情绪或物件,比如“雨夜旧书店”“朋友来家里”“饭后清爽”,也可以点“只用我的材料”。

这类功能很容易写成“把用户输入丢给大模型,然后展示一段漂亮文案”。但在调酒场景里,这样不够。

因为用户真正需要的是一杯可以照着做、可以修改、可以保存的酒,而不是一个名字很好听但材料不闭合的创意。

所以前端在构造请求时,会把创作边界一起传给后端:

intent:{rawText:startContext.rawText,targetLabel:startContext.rawText,targetTechnique:'none',timeConstraint:mode.timeConstraint,materialPolicy:{mode:materialMode},}

其中materialPolicy.mode有几个关键取值:

  • open_special:今晚能喝,偏当天可执行。
  • creative_special:开脑洞特调,允许更开放的现实材料和技法。
  • inventory_strict:只用我的材料。

当用户选择“只用我的材料”,或者输入里出现“根据我的酒库/只用现有材料”这类意图时,后端aiBartenderCreate会读取用户库存,构造可用材料范围。之后配方校验会检查材料闭包:不该在inventory_strict模式里把清单外材料写进配方、步骤或说明。

如果生成结果没有通过结构校验,后端不会硬把它包装成可操作做法,而是返回 fallback:

{status:'fallback',recipe:null,requirementChecks:[{key:'structure_validation',label:'结构校验',status:'failed',text:'这版配方没有通过结构校验,我不会把它展示为可操作做法。'}]}

这里的工程判断是:AI 适合处理开放灵感,但不能替代业务约束。用户看到的最终结果,仍然要回到材料、结构、比例、步骤和保存状态。

保存闭环比生成本身更重要

如果灵感调酒师只是生成一次,那它更像玩具。Naniya 里生成结果后可以继续走到“存为作品”,前端会把生成结果整理成自定义酒款表单的预填数据:

constprefill={name:recipe.name||'',nameEn:recipe.nameEn||'',description:recipe.oneLineTaste||'',flavor:recipe.tasteProfile,ingredients:recipe.ingredients.map(item=>({name:item.name,amount:item.amountText,iconKey:item.ingredientId||'',})),steps:recipe.steps||[''],};wx.setStorageSync('prefillCustomDrink',prefill);wx.navigateTo({url:'/pages/custom-drink/custom-drink?prefill=1'});

自定义酒款页再让用户补齐或修改:

  • 基本信息:酒名、英文名、一句话描述、酒精度、杯型、标签、图片。
  • 风味配方:甜、酸、苦、烈、果香、草本,以及原料用量和制作步骤。
  • 预览确认:把酒款当成一个结构化作品检查。
  • 保存:私有保存或提交审核。

后端saveCustomDrink也会做基本校验:酒名不能为空,必须有步骤、风味、图片和原料;原料不能只有单位没有数值;公开能力关闭时会强制保存为私有。

这条链路才是产品闭环:

酒库输入 -> 可做经典 / 自由调 / 补材料 -> 查看做法或进入创作 -> 生成结构化配方 -> 保存为自己的特调 -> 之后继续修改、收藏或复做

很多功能单独看都不难:列表、详情、搜索、AI 输入框、收藏、分享、表单。真正难的是让它们围绕同一个用户问题工作。

这次项目里我学到的几件事

第一,AI 写代码会放大执行速度,也会放大产品发散。

页面可以很快搭出来,云函数也可以很快补上,但如果没有明确主路径,就会出现一堆“看起来都对”的功能。配方库、智能生成、收藏、分享、公开酒款、个人酒库都合理,但它们不天然构成用户路径。

第二,能确定的逻辑先确定。

“这杯酒能不能做”“差几种原料”“补哪个原料能多做几杯”,这些问题不应该交给模型猜。Naniya 里这部分尽量使用库存、原料 id、配方索引和缺失数量来计算。

第三,AI 只应该出现在模糊度高的地方,并且要有边界。

“雨夜旧书店”这种灵感确实需要生成能力,但生成结果必须接受材料、结构、安全和可执行性的约束。否则它只是文案,不是产品能力。

第四,闭环的最后一步往往不是“展示结果”,而是“沉淀结果”。

用户真正调出一杯喜欢的酒后,要能保存、修改、复做。否则一次生成再惊艳,也很难变成长期使用的工具。

所以我现在更愿意把 Naniya 看成一个“从家庭酒库出发的调酒工作流”,而不是一个鸡尾酒配方库。

配方库可以回答“某杯酒怎么做”。

酒库闭环要回答的是:“我现在有什么,今晚能喝什么,下一步还能怎么做。”

理性饮酒,未成年人请勿饮酒。

http://www.jsqmd.com/news/1123725/

相关文章:

  • ClaudeMax实战压测:什么场景下它才不可替代?
  • 质量门脚本:用Python给AI输出加上自动质检(附完整源码)
  • Azure Local离线模式身份规划(系列篇之三)
  • JVM是什么?
  • 良心盘点!2026AI论文写作工具榜单(覆盖 99% 学生论文写作需求)
  • YOLOv13超图视觉与NCNN部署实战指南
  • Wwise音频文件处理终极指南:3分钟掌握游戏音效解包与定制
  • 如何用大模型设计一个“国标级“智能体:从 prompt 到落地的完整指南
  • 【OpenHarmony/HarmonyOs 】实验室首页细节拆解:分类侧栏、搜索筛选与推荐探索交互
  • 小程序基础库3.16.0实战指南:NFC/离线运行/双端适配/接口迁移代码落地及公众号迁移公证书线上办理流程
  • IBM ODM JNDI注入漏洞CVE-2024-22319复现与深度解析
  • 91.吃透 PLC 底层!扫描周期 + 状态机物料分拣,全套 ST 源码
  • python___模块
  • 如何快速解决Windows热键冲突:终极热键检测工具使用指南
  • OpCore Simplify技术深度解析:揭秘黑苹果自动化配置的核心原理
  • 安卓手机 SIM 卡迁移至新款 iPhone 17/16?
  • 免费获取百度文库文档的终极方案:开源页面清理脚本完整指南
  • Elsevier投稿状态追踪插件:科研工作者的智能审稿监控工具
  • 如何用BilibiliDown三步搞定B站视频下载?小白也能掌握的完整指南
  • 影刀RPA新手教程:财务报表自动汇总完全指南——多Excel合并数据透视与自动发邮件
  • Crypto++实战指南:从CRC32到RSA的C++加密库集成与应用
  • 3分钟掌握抖音内容下载:免费工具助你高效保存视频、直播和合集
  • STM32F072RB与SLO2016构建工业隔离通信系统
  • AI专著撰写实用技巧:利用AI工具,快速生成20万字专著的方法!
  • 基于策略模式与智能降级的高性能抖音下载器架构设计
  • AI模型工作流横评:端到端业务链路实战测评
  • 资深后端工程师分享:技术栈选型背后的思考
  • Pyfa终极指南:5个技巧快速掌握EVE Online免费Python舰船配装工具
  • OpenHarmony Image 图片组件全场景开发与 API23 + 适配优化
  • 排序算法百科全书:从基础到精进的完整指南