diff --git a/ai/ai_config.json b/ai/ai_config.json index 0245f10..fb60a25 100644 --- a/ai/ai_config.json +++ b/ai/ai_config.json @@ -2,9 +2,13 @@ "model": "qwen/qwen3-next-80b-a3b-thinking:nitro", "max_tokens": 8192, "prompts": { + "step_generator": { + "system": "You are an ADHD-friendly routine planning assistant. Given a goal or task description, generate 4 to 7 concrete, actionable steps. Return ONLY valid JSON.\n\nRules:\n- Step names: 2-6 words, verb + object (e.g. 'Wash face', 'Pack gym bag')\n- Durations: realistic short times (1-10 min each) to avoid overwhelm\n- Exactly 4-7 steps, ordered logically\n- Physical actions only, no vague reminders", + "user_template": "Generate ADHD-friendly routine steps for this goal:\n\n\"{user_input}\"\n\nReturn JSON: {{\"steps\": [{{\"name\": \"string\", \"duration_minutes\": number}}]}}\n\n{history_context}" + }, "command_parser": { "system": "You are a helpful AI assistant that parses user commands into structured JSON. Extract the user's intent and relevant parameters from natural language. Return ONLY valid JSON, no explanations.\n\nBe flexible with language - handle typos, slang, and casual phrasing. Consider conversation context when available.\n\n=== TIME CONVERSION RULES ===\nConvert all times to 24-hour format HH:MM in a JSON array:\n- \"4:20pm\", \"4:20 PM\" → [\"16:20\"]\n- \"9am\", \"9 AM\" → [\"09:00\"]\n- \"morning\" → [\"09:00\"]\n- \"evening\", \"night\" → [\"20:00\"]\n- \"noon\" → [\"12:00\"]\n- \"midnight\" → [\"00:00\"]\n- \"4:20\" (ambiguous) → set needs_clarification: \"Is that 4:20 AM or PM?\"\n- Multiple times: \"9am and 9pm\" → [\"09:00\", \"21:00\"]\n\n=== FREQUENCY MAPPING ===\nMap natural language to exact enum values:\n- \"every day\", \"daily\" → frequency: \"daily\"\n- \"twice a day\", \"twice daily\", \"2x daily\" → frequency: \"twice_daily\", times: [\"08:00\", \"20:00\"] (unless specified otherwise)\n- \"every tuesday\", \"tuesdays\" → frequency: \"specific_days\", days_of_week: [\"tue\"]\n- \"monday wednesday friday\", \"m/w/f\" → frequency: \"specific_days\", days_of_week: [\"mon\", \"wed\", \"fri\"]\n- \"every 3 days\", \"every three days\" → frequency: \"every_n_days\", interval_days: 3\n- \"as needed\", \"prn\" → frequency: \"as_needed\", times: []\n\nDay abbreviations: mon, tue, wed, thu, fri, sat, sun\n\n=== DOSAGE EXTRACTION ===\n- \"50 mcg\" → dosage: 50, unit: \"mcg\"\n- \"1 pill\", \"one pill\" → dosage: 1, unit: \"pill\"\n- \"5mg\" → dosage: 5, unit: \"mg\"\n- \"100 micrograms\" → dosage: 100, unit: \"mcg\"\n- No dosage mentioned → set needs_clarification\n\n=== VALIDATION RULES ===\nSet needs_clarification if:\n1. Dosage is missing for 'add' action\n2. Time is ambiguous (e.g., just \"4:20\" without AM/PM)\n3. Frequency is unclear (e.g., \"sometimes\", \"often\")\n4. Name cannot be determined\n\n=== INTERACTION TYPES ===\n- \"routine\": habits, morning routines, task sequences\n- \"medication\": pills, prescriptions, supplements, dosages\n- \"knowledge\": ANY question or request for advice/help that is not about managing medications or routines. This includes: questions about books, DBT skills, mental health, motivation, behavior, emotions, ADHD, productivity, self-improvement, and any general how-to or advice question.\n\nIMPORTANT: If the user asks ANY question (how do I..., what should I..., why do I..., how can I..., what is..., explain...) and it is not clearly about adding/taking a medication or managing a routine, classify it as knowledge with action \"query\". Do NOT return needs_clarification for questions — route them to knowledge.\n\nAvailable books: (1) DBT Skills — covers distress tolerance, emotion regulation, mindfulness, interpersonal effectiveness, opposite action, coping skills. (2) Taking Charge of Adult ADHD — covers focus, executive function, organization, motivation, procrastination, ADHD at work/home. If the question is clearly about ADHD symptoms/strategies, set book to \"adhd\". If it's clearly about emotional coping/DBT skills, set book to \"dbt\". If unsure, omit book and let the system search both.\n\n=== KNOWLEDGE BASE EXAMPLES ===\n- \"what does the book say about time management?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"time management\"}\n- \"ask atomic habits about habit formation\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"atomic habits\", \"query\": \"habit formation\"}\n- \"list available books\" → {\"interaction_type\": \"knowledge\", \"action\": \"list\"}\n- \"select book 2\" → {\"interaction_type\": \"knowledge\", \"action\": \"select\", \"book\": \"2\"}\n- \"how does the ADHD book suggest handling procrastination?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"adhd\", \"query\": \"handling procrastination\"}\n- \"what does taking charge of adult adhd say about sleep?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"taking charge of adult adhd\", \"query\": \"sleep\"}\n- \"how do I handle ADHD at work according to the book?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"handling ADHD at work\"}\n- \"how do I do things I don't want to do?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"how to do things you don't want to do\"}\n- \"why do I keep avoiding tasks?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"avoiding tasks procrastination\"}\n- \"how do I cope with feeling overwhelmed?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"coping with feeling overwhelmed\"}\n- \"what is radical acceptance?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"radical acceptance\"}\n- \"give me a skill for managing anger\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"managing anger\"}\n- \"how do I focus better with ADHD?\" → {\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"focus strategies ADHD\"}", - "user_template": "Parse this command into structured JSON.\n\nCurrent conversation context:\n{history_context}\n\nUser message: \"{user_input}\"\n\nReturn JSON with these exact fields:\n{{\n \"interaction_type\": \"routine\" | \"medication\" | \"knowledge\",\n \"action\": \"string\",\n \"name\": \"string\" (med/routine name),\n \"routine_name\": \"string\" (for step-related actions),\n \"description\": \"string\" (optional),\n \"steps\": [\"step1\", \"step2\"] (for routine creation),\n \"dosage\": number (for meds),\n \"unit\": \"string\" (mg, mcg, pill, etc),\n \"frequency\": \"daily\" | \"twice_daily\" | \"specific_days\" | \"every_n_days\" | \"as_needed\",\n \"times\": [\"HH:MM\"],\n \"days_of_week\": [\"mon\", \"tue\", ...],\n \"interval_days\": number (for every_n_days),\n \"query\": \"string\" (for knowledge questions),\n \"book\": \"string\" (book name/number for knowledge queries),\n \"needs_confirmation\": boolean (true for destructive/create actions),\n \"confirmation_prompt\": \"string\" (what to ask user),\n \"confidence\": number (0-1),\n \"needs_clarification\": \"string\" (if confidence < 0.8 or missing required fields)\n}}\n\n=== EXAMPLES ===\n\nMedication examples:\n1. User: \"take a giant dab of THC\"\n {{\"interaction_type\": \"medication\", \"action\": \"take\", \"name\": \"THC\", \"confidence\": 0.9}}\n\n2. User: \"add lsd 50 mcg daily at 9am\"\n {{\"interaction_type\": \"medication\", \"action\": \"add\", \"name\": \"lsd\", \"dosage\": 50, \"unit\": \"mcg\", \"frequency\": \"daily\", \"times\": [\"09:00\"], \"confidence\": 0.95}}\n\n3. User: \"add wellbutrin 150 mg twice daily\"\n {{\"interaction_type\": \"medication\", \"action\": \"add\", \"name\": \"wellbutrin\", \"dosage\": 150, \"unit\": \"mg\", \"frequency\": \"twice_daily\", \"times\": [\"08:00\", \"20:00\"], \"confidence\": 0.95}}\n\n4. User: \"i took my spironolactone\"\n {{\"interaction_type\": \"medication\", \"action\": \"take\", \"name\": \"spironolactone\", \"confidence\": 0.95}}\n\n5. User: \"took my meds\" or \"i took all my meds\" or \"all the due ones\" (batch - mark all pending doses as taken)\n {{\"interaction_type\": \"medication\", \"action\": \"take_all\", \"confidence\": 0.9}}\n\n6. User: \"i took my 50mg spiro\"\n {{\"interaction_type\": \"medication\", \"action\": \"take\", \"name\": \"spiro\", \"dosage\": 50, \"unit\": \"mg\", \"confidence\": 0.9}}\n\n7. User: \"skip my wellbutrin today\"\n {{\"interaction_type\": \"medication\", \"action\": \"skip\", \"name\": \"wellbutrin\", \"confidence\": 0.9}}\n\n8. User: \"snooze my spironolactone reminder for 30 minutes\"\n {{\"interaction_type\": \"medication\", \"action\": \"snooze\", \"name\": \"spironolactone\", \"minutes\": 30, \"confidence\": 0.95}}\n\nRoutine examples:\n1. User: \"create morning routine with brush teeth, shower, eat\"\n {{\"interaction_type\": \"routine\", \"action\": \"create_with_steps\", \"name\": \"morning\", \"steps\": [\"brush teeth\", \"shower\", \"eat\"], \"confidence\": 0.95}}\n\n2. User: \"start my morning routine\"\n {{\"interaction_type\": \"routine\", \"action\": \"start\", \"name\": \"morning\", \"confidence\": 0.9}}\n\n3. User: \"done with step 3\"\n {{\"interaction_type\": \"routine\", \"action\": \"advance_step\", \"confidence\": 0.9}}\n\n4. User: \"skip this step\"\n {{\"interaction_type\": \"routine\", \"action\": \"skip_step\", \"confidence\": 0.9}}\n\n5. User: \"pause my routine\"\n {{\"interaction_type\": \"routine\", \"action\": \"pause\", \"confidence\": 0.9}}\n\nKnowledge examples:\n1. User: \"what does the book say about time management?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"time management\", \"confidence\": 0.9}}\n\n2. User: \"ask atomic habits about habit formation\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"atomic habits\", \"query\": \"habit formation\", \"confidence\": 0.95}}\n\n3. User: \"list available books\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"list\", \"confidence\": 0.95}}\n\n4. User: \"select book 2\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"select\", \"book\": \"2\", \"confidence\": 0.95}}\n\n5. User: \"what does taking charge of adult adhd say about sleep?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"taking charge of adult adhd\", \"query\": \"sleep\", \"confidence\": 0.95}}\n\n6. User: \"how do I handle ADHD at work according to the book?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"handling ADHD at work\", \"confidence\": 0.9}}\n\n7. User: \"how do I do things I don't want to do?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"how to do things you don't want to do\", \"confidence\": 0.9}}\n\n8. User: \"why do I keep avoiding tasks?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"avoiding tasks procrastination\", \"confidence\": 0.9}}\n\n9. User: \"how do I cope with feeling overwhelmed?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"coping with feeling overwhelmed\", \"confidence\": 0.9}}\n\n10. User: \"give me a skill for managing anger\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"managing anger\", \"confidence\": 0.9}}\n\n11. User: \"what is radical acceptance?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"radical acceptance\", \"confidence\": 0.95}}" + "user_template": "Parse this command into structured JSON.\n\nCurrent conversation context:\n{history_context}\n\nUser message: \"{user_input}\"\n\nReturn JSON with these exact fields:\n{{\n \"interaction_type\": \"routine\" | \"medication\" | \"knowledge\",\n \"action\": \"string\",\n \"goal\": \"string\" (user's original goal, used with ai_compose action),\n \"name\": \"string\" (med/routine name),\n \"routine_name\": \"string\" (for step-related actions),\n \"description\": \"string\" (optional),\n \"steps\": [\"step1\", \"step2\"] (for routine creation),\n \"dosage\": number (for meds),\n \"unit\": \"string\" (mg, mcg, pill, etc),\n \"frequency\": \"daily\" | \"twice_daily\" | \"specific_days\" | \"every_n_days\" | \"as_needed\",\n \"times\": [\"HH:MM\"],\n \"days_of_week\": [\"mon\", \"tue\", ...],\n \"interval_days\": number (for every_n_days),\n \"query\": \"string\" (for knowledge questions),\n \"book\": \"string\" (book name/number for knowledge queries),\n \"needs_confirmation\": boolean (true for destructive/create actions),\n \"confirmation_prompt\": \"string\" (what to ask user),\n \"confidence\": number (0-1),\n \"needs_clarification\": \"string\" (if confidence < 0.8 or missing required fields)\n}}\n\n=== EXAMPLES ===\n\nMedication examples:\n1. User: \"take a giant dab of THC\"\n {{\"interaction_type\": \"medication\", \"action\": \"take\", \"name\": \"THC\", \"confidence\": 0.9}}\n\n2. User: \"add lsd 50 mcg daily at 9am\"\n {{\"interaction_type\": \"medication\", \"action\": \"add\", \"name\": \"lsd\", \"dosage\": 50, \"unit\": \"mcg\", \"frequency\": \"daily\", \"times\": [\"09:00\"], \"confidence\": 0.95}}\n\n3. User: \"add wellbutrin 150 mg twice daily\"\n {{\"interaction_type\": \"medication\", \"action\": \"add\", \"name\": \"wellbutrin\", \"dosage\": 150, \"unit\": \"mg\", \"frequency\": \"twice_daily\", \"times\": [\"08:00\", \"20:00\"], \"confidence\": 0.95}}\n\n4. User: \"i took my spironolactone\"\n {{\"interaction_type\": \"medication\", \"action\": \"take\", \"name\": \"spironolactone\", \"confidence\": 0.95}}\n\n5. User: \"took my meds\" or \"i took all my meds\" or \"all the due ones\" (batch - mark all pending doses as taken)\n {{\"interaction_type\": \"medication\", \"action\": \"take_all\", \"confidence\": 0.9}}\n\n6. User: \"i took my 50mg spiro\"\n {{\"interaction_type\": \"medication\", \"action\": \"take\", \"name\": \"spiro\", \"dosage\": 50, \"unit\": \"mg\", \"confidence\": 0.9}}\n\n7. User: \"skip my wellbutrin today\"\n {{\"interaction_type\": \"medication\", \"action\": \"skip\", \"name\": \"wellbutrin\", \"confidence\": 0.9}}\n\n8. User: \"snooze my spironolactone reminder for 30 minutes\"\n {{\"interaction_type\": \"medication\", \"action\": \"snooze\", \"name\": \"spironolactone\", \"minutes\": 30, \"confidence\": 0.95}}\n\nRoutine examples:\n1. User: \"create morning routine with brush teeth, shower, eat\"\n {{\"interaction_type\": \"routine\", \"action\": \"create_with_steps\", \"name\": \"morning\", \"steps\": [\"brush teeth\", \"shower\", \"eat\"], \"confidence\": 0.95}}\n\n2. User: \"start my morning routine\"\n {{\"interaction_type\": \"routine\", \"action\": \"start\", \"name\": \"morning\", \"confidence\": 0.9}}\n\n3. User: \"done with step 3\"\n {{\"interaction_type\": \"routine\", \"action\": \"advance_step\", \"confidence\": 0.9}}\n\n4. User: \"skip this step\"\n {{\"interaction_type\": \"routine\", \"action\": \"skip_step\", \"confidence\": 0.9}}\n\n5. User: \"pause my routine\"\n {{\"interaction_type\": \"routine\", \"action\": \"pause\", \"confidence\": 0.9}}\n\n6. User: \"I need to clean my room\"\n {{\"interaction_type\": \"routine\", \"action\": \"ai_compose\", \"goal\": \"clean my room\", \"name\": \"room cleaning\", \"confidence\": 0.9}}\n\n7. User: \"help me build a morning routine\"\n {{\"interaction_type\": \"routine\", \"action\": \"ai_compose\", \"goal\": \"build a morning routine\", \"name\": \"morning\", \"confidence\": 0.9}}\n\n8. User: \"create an evening wind-down routine for me\"\n {{\"interaction_type\": \"routine\", \"action\": \"ai_compose\", \"goal\": \"evening wind-down\", \"name\": \"wind-down\", \"confidence\": 0.9}}\n\nKnowledge examples:\n1. User: \"what does the book say about time management?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"time management\", \"confidence\": 0.9}}\n\n2. User: \"ask atomic habits about habit formation\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"atomic habits\", \"query\": \"habit formation\", \"confidence\": 0.95}}\n\n3. User: \"list available books\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"list\", \"confidence\": 0.95}}\n\n4. User: \"select book 2\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"select\", \"book\": \"2\", \"confidence\": 0.95}}\n\n5. User: \"what does taking charge of adult adhd say about sleep?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"book\": \"taking charge of adult adhd\", \"query\": \"sleep\", \"confidence\": 0.95}}\n\n6. User: \"how do I handle ADHD at work according to the book?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"handling ADHD at work\", \"confidence\": 0.9}}\n\n7. User: \"how do I do things I don't want to do?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"how to do things you don't want to do\", \"confidence\": 0.9}}\n\n8. User: \"why do I keep avoiding tasks?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"avoiding tasks procrastination\", \"confidence\": 0.9}}\n\n9. User: \"how do I cope with feeling overwhelmed?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"coping with feeling overwhelmed\", \"confidence\": 0.9}}\n\n10. User: \"give me a skill for managing anger\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"managing anger\", \"confidence\": 0.9}}\n\n11. User: \"what is radical acceptance?\"\n {{\"interaction_type\": \"knowledge\", \"action\": \"query\", \"query\": \"radical acceptance\", \"confidence\": 0.95}}" } }, "validation": { diff --git a/api/main.py b/api/main.py index 878a78b..4cef2fd 100644 --- a/api/main.py +++ b/api/main.py @@ -23,6 +23,7 @@ import api.routes.rewards as rewards_routes import api.routes.victories as victories_routes import api.routes.adaptive_meds as adaptive_meds_routes import api.routes.snitch as snitch_routes +import api.routes.ai as ai_routes app = flask.Flask(__name__) CORS(app) @@ -41,6 +42,7 @@ ROUTE_MODULES = [ victories_routes, adaptive_meds_routes, snitch_routes, + ai_routes, ] diff --git a/api/routes/ai.py b/api/routes/ai.py new file mode 100644 index 0000000..ecb4ce1 --- /dev/null +++ b/api/routes/ai.py @@ -0,0 +1,76 @@ +""" +api/routes/ai.py - AI-powered generation endpoints +""" + +import asyncio +import flask +import jwt +import os + +import ai.parser as ai_parser + +JWT_SECRET = os.getenv("JWT_SECRET") + + +def _get_user_uuid(request): + """Extract and validate user UUID from JWT token.""" + auth_header = request.headers.get("Authorization", "") + if not auth_header.startswith("Bearer "): + return None + token = auth_header[7:] + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) + return payload.get("sub") + except (jwt.ExpiredSignatureError, jwt.InvalidTokenError): + return None + + +def register(app): + + @app.route("/api/ai/generate-steps", methods=["POST"]) + def api_generate_steps(): + """ + Generate ADHD-friendly routine steps from a goal description. + Body: {"goal": string} + Returns: {"steps": [{"name": string, "duration_minutes": int}]} + """ + user_uuid = _get_user_uuid(flask.request) + if not user_uuid: + return flask.jsonify({"error": "unauthorized"}), 401 + + data = flask.request.get_json() + if not data: + return flask.jsonify({"error": "missing body"}), 400 + + goal = data.get("goal", "").strip() + if not goal: + return flask.jsonify({"error": "missing required field: goal"}), 400 + if len(goal) > 500: + return flask.jsonify({"error": "goal too long (max 500 characters)"}), 400 + + try: + result = asyncio.run(ai_parser.parse(goal, "step_generator")) + except Exception as e: + return flask.jsonify({"error": f"AI service error: {str(e)}"}), 500 + + if "error" in result: + return flask.jsonify({"error": result["error"]}), 500 + + steps = result.get("steps", []) + validated = [] + for s in steps: + if not isinstance(s, dict): + continue + name = str(s.get("name", "")).strip() + if not name: + continue + try: + dur = max(1, min(60, int(s.get("duration_minutes", 5)))) + except (ValueError, TypeError): + dur = 5 + validated.append({"name": name, "duration_minutes": dur}) + + if len(validated) < 2: + return flask.jsonify({"error": "AI failed to generate valid steps"}), 500 + + return flask.jsonify({"steps": validated}), 200 diff --git a/bot/commands/routines.py b/bot/commands/routines.py index d6a956a..f86408a 100644 --- a/bot/commands/routines.py +++ b/bot/commands/routines.py @@ -93,6 +93,56 @@ async def handle_routine(message, session, parsed): await _create_routine_with_steps(message, token, name, description, steps) + elif action == "ai_compose": + goal = parsed.get("goal") + name = parsed.get("name", "my routine") + + if not goal: + await message.channel.send( + "What's the goal for this routine? Tell me what you want to accomplish." + ) + return + + async with message.channel.typing(): + resp, status = api_request( + "post", "/api/ai/generate-steps", token, {"goal": goal} + ) + + if status != 200: + await message.channel.send( + f"Couldn't generate steps: {resp.get('error', 'unknown error')}\n" + f"Try: \"create {name} routine with step1, step2, step3\"" + ) + return + + steps = resp.get("steps", []) + if not steps: + await message.channel.send("The AI didn't return any steps. Try describing your goal differently.") + return + + if "pending_confirmations" not in session: + session["pending_confirmations"] = {} + + confirmation_id = f"routine_create_{name}" + session["pending_confirmations"][confirmation_id] = { + "action": "create_with_steps", + "interaction_type": "routine", + "name": name, + "description": f"AI-generated routine for: {goal}", + "steps": [s["name"] for s in steps], + "needs_confirmation": False, + } + + total_min = sum(s.get("duration_minutes", 5) for s in steps) + steps_list = "\n".join( + [f"{i+1}. {s['name']} ({s.get('duration_minutes', 5)} min)" for i, s in enumerate(steps)] + ) + await message.channel.send( + f"Here's what I suggest for **{name}** (~{total_min} min total):\n\n" + f"{steps_list}\n\n" + f"Reply **yes** to create this routine, or **no** to cancel." + ) + elif action == "add_steps": routine_name = parsed.get("routine_name") steps = parsed.get("steps", []) diff --git a/synculous-client/src/app/dashboard/routines/new/page.tsx b/synculous-client/src/app/dashboard/routines/new/page.tsx index 1ab0e34..24366e6 100644 --- a/synculous-client/src/app/dashboard/routines/new/page.tsx +++ b/synculous-client/src/app/dashboard/routines/new/page.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import api from '@/lib/api'; -import { ArrowLeftIcon, PlusIcon, TrashIcon, GripVerticalIcon, CopyIcon } from '@/components/ui/Icons'; +import { ArrowLeftIcon, PlusIcon, TrashIcon, GripVerticalIcon, CopyIcon, SparklesIcon } from '@/components/ui/Icons'; interface Step { id: string; @@ -41,6 +41,10 @@ export default function NewRoutinePage() { const [steps, setSteps] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); + const [isGenerating, setIsGenerating] = useState(false); + const [aiGoal, setAiGoal] = useState(''); + const [showAiInput, setShowAiInput] = useState(false); + const [aiError, setAiError] = useState(''); // Schedule const [scheduleDays, setScheduleDays] = useState(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']); @@ -74,6 +78,31 @@ export default function NewRoutinePage() { setSteps(newSteps.map((s, i) => ({ ...s, position: i + 1 }))); }; + const handleGenerateSteps = async () => { + const goal = aiGoal.trim() || name.trim(); + if (!goal) { + setAiError('Enter a goal or fill in the routine name first.'); + return; + } + setIsGenerating(true); + setAiError(''); + try { + const result = await api.ai.generateSteps(goal); + const generated = result.steps.map((s, i) => ({ + id: `temp-${Date.now()}-${i}`, + name: s.name, + duration_minutes: s.duration_minutes, + position: steps.length + i + 1, + })); + setSteps(prev => [...prev, ...generated]); + setShowAiInput(false); + } catch (err) { + setAiError((err as Error).message || 'Failed to generate steps. Try again.'); + } finally { + setIsGenerating(false); + } + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) { @@ -282,27 +311,101 @@ export default function NewRoutinePage() {

Steps

- -
- - {steps.length === 0 ? ( -
-

Add steps to your routine

+
+ + |
+
+ + {/* AI Generation Panel */} + {showAiInput && ( +
+

+ Describe your goal and AI will suggest steps +

+