hooch / app.py
Wayfindersix's picture
ship: remove emoji, add fallback matching
82005c5
Raw
History Blame Contribute Delete
12.9 kB
"""
Hooch — Make Some Booze With What You Already Have.
Your pantry is a brewery you haven't opened yet.
"""
import gradio as gr
import os
import random
from huggingface_hub import InferenceClient
HF_TOKEN = os.environ.get("HF_TOKEN", "")
MODEL = "mistralai/Mistral-7B-Instruct-v0.3"
client = InferenceClient(model=MODEL, token=HF_TOKEN) if HF_TOKEN else None
SYSTEM_PROMPT = """Someone tells you what food or ingredients they have lying around — leftovers, pantry staples, fruit going bad, whatever. Your job is to tell them what booze they can make with it. Real fermentation, real recipes, real results.
Rules:
- 5 to 7 sentences.
- Give them a specific thing they can make — mead, cider, wine, tepache, kvass, ginger beer, fruit wine, whatever fits their ingredients.
- Keep the instructions dead simple. No homebrew jargon. No specialized equipment they don't have.
- Be honest about timelines — "this takes 3 days" or "you're drinking in a week."
- Include the one thing that could go wrong and how to avoid it.
- Talk like a friend who's made questionable beverages in their kitchen and survived.
- This is fun, not precious. Nobody's entering a competition. They're making hooch.
- If the ingredients can't make anything alcoholic, tell them what non-alcoholic fermented drink they can make instead (kombucha, water kefir, fermented lemonade)."""
PANTRY = [
"apples going soft",
"a bag of sugar and some bread",
"leftover grape juice",
"honey and tap water",
"pineapple scraps",
"rice and raisins",
"ginger root and lemons",
"old bananas",
"a can of fruit cocktail",
"apple juice from the store",
"berries about to go bad",
"orange peels and sugar",
"leftover oatmeal",
"sweet potatoes",
"watermelon that's been in the fridge too long",
]
FALLBACKS = [
"**apples going soft**\n\nYou're sitting on cider. Chop those apples up, mash them in a pot, add enough water to cover them, and stir in a couple tablespoons of sugar. Cover it with a cloth and let it sit on the counter for 3-5 days, stirring once a day. When it starts fizzing and smells like a farm stand with ambition, strain it. You've got hard cider. The one thing that'll kill it is a dirty jar — rinse everything with hot water first. Your grandparents did this. It's not complicated, it's just patience.",
"**honey and tap water**\n\nCongratulations, you have the oldest alcoholic drink in human history. Mead. Mix one part honey with four parts warm water in a clean jar. That's it. Cover it with a cloth so bugs stay out but air gets in. Wait a week, stir it every day. Wild yeast from the honey and the air will start fermenting it. It'll bubble. It'll smell weird. That's correct. In two weeks you'll have something drinkable. In a month it'll be good. The only mistake is sealing it too tight too early — it needs to breathe or the jar becomes a grenade.",
"**pineapple scraps**\n\nYou're about to make tepache, which is basically Mexican pineapple beer and it's incredible. Take those peels and the core, throw them in a jar with water, half a cup of brown sugar, and a cinnamon stick if you've got one. Cover with a cloth. In 2-3 days it'll be fizzy, tangy, and slightly boozy. Strain it, drink it cold. The risk is letting it go too long — after 5 days it starts turning to vinegar. But even that's useful. You can't really lose here.",
"**ginger root and lemons**\n\nYou're making ginger beer, and it's going to be better than anything in a can. Grate about two inches of ginger into a jar, squeeze in the lemons, add a quarter cup of sugar and two cups of warm water. Stir it until the sugar dissolves. If you have a pinch of yeast, toss it in. If not, the wild yeast on the ginger skin will do the job — it just takes an extra day. Seal it loosely and wait 48 hours. The only danger is bottling it too tight. Ginger beer carbonates aggressively. Like, explodes-in-your-closet aggressively. Open it over the sink the first time.",
"**old bananas**\n\nThose brown bananas are sugar bombs and they're perfect for banana wine. Mash four or five of them, add them to a pot with a quart of water, boil it for 15 minutes, then let it cool and strain out the mush. Add a cup of sugar and a pinch of yeast to the liquid, pour it into a jar with a cloth cover. In a week you'll have a cloudy, slightly funky wine that tastes like bananas decided to grow up. The risk is not straining well enough — chunky wine is an acquired taste nobody has acquired.",
"**leftover grape juice**\n\nYou are literally holding unfinished wine. Grape juice is what wine was before someone let it sit. Pour it in a clean jar, add a quarter teaspoon of yeast if you have it, cover with a cloth, and leave it alone for a week. Stir once a day. That's it. In ten days you'll have something that's recognizably wine — not great wine, but wine your ancestors would have been proud of. The one thing that'll ruin it is too much heat. Keep it somewhere cool and dark. Your closet is a winery now.",
"**rice and raisins**\n\nYou're about to make a version of kilju, which is a Finnish prison wine that sounds terrible and tastes surprisingly okay. Cook the rice, let it cool, put it in a jar with a handful of raisins, a cup of sugar, and warm water. The raisins bring wild yeast and nutrients that keep the fermentation healthy. Cover with a cloth, stir daily, and in about a week you've got a mildly alcoholic rice drink. Strain it well. The main risk is contamination — if it smells like nail polish instead of bread, start over. But if it smells yeasty and slightly sweet, you're golden.",
"**berries about to go bad**\n\nThose sad berries are about to become the best thing in your fridge. Mash them up, throw them in a jar with enough water to cover them, add a few tablespoons of sugar, and stir. Cover with a cloth. In three to four days the natural yeast on the berry skins will kick in and you'll have a fizzy, tart, low-alcohol fruit wine. Strain it through a t-shirt if you don't have cheesecloth. The only mistake is using berries that are actually moldy — soft and sad is fine, fuzzy and green is not. There's a line. You know it when you see it.",
"**apple juice from the store**\n\nThis is the easiest alcohol you will ever make. Buy apple juice that has no preservatives — check the label, because potassium sorbate will kill your yeast dead. Pour out a couple inches from the top so there's room for bubbles. Add a pinch of bread yeast, put a balloon over the mouth of the bottle with a tiny pinhole in it. Wait a week. That balloon will inflate, which means carbon dioxide is escaping, which means alcohol is being made. When the balloon goes flat, it's done. You just made cider with a trip to the grocery store and zero skills.",
]
def find_fallback(thing):
if thing:
thing_lower = thing.lower().strip()
for fb in FALLBACKS:
try:
fb_topic = fb.split("**")[1].lower()
if thing_lower == fb_topic or (thing_lower in fb_topic and len(thing_lower) > 5):
return fb
except (IndexError, ValueError):
continue
return random.choice(FALLBACKS)
def brew(user_input):
stuff = user_input.strip() if user_input and user_input.strip() else ""
if client and stuff:
try:
response = client.chat_completion(
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"What they have: {stuff}"},
],
max_tokens=500,
temperature=0.8,
)
result = response.choices[0].message.content
if result and result.strip():
return f"**{stuff}**\n\n{result.strip()}"
except Exception:
pass
return find_fallback(stuff)
def brew_random():
return brew("")
CUSTOM_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
body, .gradio-container {
background: #f5f5f8 !important;
font-family: 'Inter', sans-serif !important;
color: #1a1a2e !important;
}
footer { display: none !important; }
.app-header { text-align: center; padding: 28px 20px 8px; }
.app-header h1 {
font-family: 'Space Mono', monospace; font-size: 2.6rem; font-weight: 700;
background: linear-gradient(135deg, #d4a030, #c06020);
-webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0;
}
.app-header .sub { color: #777777; font-size: 0.88rem; margin-top: 6px; font-weight: 300; letter-spacing: 0.04em; }
.app-visual { text-align: center; font-size: 5rem; padding: 12px 0 4px; filter: drop-shadow(0 0 20px rgba(212, 160, 48, 0.3)); }
.input-box textarea { background: #ffffff !important; border: 1px solid #d0d0d8 !important; color: #1a1a2e !important; font-family: 'Inter', sans-serif !important; border-radius: 12px !important; font-size: 0.95rem !important; }
.input-box textarea::placeholder { color: #888888 !important; }
.input-box textarea:focus { border-color: #d4a030 !important; box-shadow: 0 0 20px rgba(212, 160, 48, 0.15) !important; }
.input-box label, .output-box label { color: #666666 !important; font-family: 'Space Mono', monospace !important; font-size: 0.75rem !important; letter-spacing: 0.05em !important; }
button.primary { background: linear-gradient(135deg, #d4a030, #a07020) !important; border: none !important; color: #fff !important; font-family: 'Space Mono', monospace !important; font-size: 1.1rem !important; font-weight: 700 !important; border-radius: 24px !important; padding: 12px 40px !important; box-shadow: 0 4px 20px rgba(212, 160, 48, 0.3) !important; }
button.primary:hover { box-shadow: 0 4px 30px rgba(212, 160, 48, 0.5) !important; }
button.secondary { background: transparent !important; border: 1px solid rgba(212, 160, 48, 0.3) !important; color: #d4a030 !important; font-family: 'Space Mono', monospace !important; font-size: 0.8rem !important; border-radius: 20px !important; }
button.secondary:hover { background: rgba(212, 160, 48, 0.1) !important; }
.output-box .prose { background: #ffffff !important; border: 1px solid #e0e0e8 !important; border-radius: 12px !important; padding: 24px !important; color: #1a1a2e !important; font-size: 0.95rem !important; line-height: 1.7 !important; }
.output-box .prose, .output-box .prose *, .output-box .md, .output-box .md *,
.output-box p, .output-box span, .output-box div {
color: #1a1a2e !important;
background: #ffffff !important;
}
.output-box .prose strong { color: #e8c060 !important; font-family: 'Space Mono', monospace !important; }
.footer-text { text-align: center; padding: 20px; color: #777777; font-size: 0.65rem; font-weight: 300; letter-spacing: 0.05em; }
.footer-text a { color: #d4a030; text-decoration: none; }
"""
with gr.Blocks(css=CUSTOM_CSS, title="Hooch", theme=gr.themes.Base()) as demo:
gr.HTML("""
<div class="app-header">
<h1>Hooch</h1>
<div class="sub">Your pantry is a brewery you haven't opened yet</div>
</div>
""")
with gr.Column(elem_classes="input-box"):
user_input = gr.Textbox(
label="What's lying around?",
placeholder="apples going soft, honey and water, pineapple scraps, leftover juice...",
lines=2, max_lines=3,
)
with gr.Row():
go_btn = gr.Button("Brew it", variant="primary", size="lg")
random_btn = gr.Button("Surprise me", variant="secondary", size="sm")
with gr.Column(elem_classes="output-box"):
output = gr.Markdown(label="")
gr.Markdown("**Or try one of these:**")
with gr.Row():
ex1 = gr.Button("apples going soft", variant="secondary", size="sm")
ex2 = gr.Button("honey and tap water", variant="secondary", size="sm")
ex3 = gr.Button("pineapple scraps", variant="secondary", size="sm")
with gr.Row():
ex4 = gr.Button("ginger root and lemons", variant="secondary", size="sm")
ex5 = gr.Button("old bananas", variant="secondary", size="sm")
ex6 = gr.Button("surprise me", variant="secondary", size="sm")
go_btn.click(brew, [user_input], [output])
random_btn.click(brew_random, [], [output])
ex1.click(lambda: brew("apples going soft"), [], [output])
ex2.click(lambda: brew("honey and tap water"), [], [output])
ex3.click(lambda: brew("pineapple scraps"), [], [output])
ex4.click(lambda: brew("ginger root and lemons"), [], [output])
ex5.click(lambda: brew("old bananas"), [], [output])
ex6.click(brew_random, [], [output])
gr.HTML('<div class="footer-text">Heuremen — Let\'s stay connected and keep information free.<br><a href="https://heuremenforprofit.online">heuremenforprofit.online</a></div>')
if __name__ == "__main__":
demo.launch()