apps/ai/services/remix.py (Line 233:13 - Line 248:21), apps/ai/services/remix.py (Line 107:17 - Line 122:15)
,
modification=modification,
)
# Call AI service
service = OpenRouterService()
response = service.complete(
system_prompt=prompt.system_prompt,
user_prompt=user_prompt,
model=prompt.model,
json_response=True,
)
# Validate response
validator = AIResponseValidator()
validated = validator.validate('nutrition_estimate'
apps/ai/services/remix.py (Line 262:5 - Line 277:16), apps/ai/services/scaling.py (Line 28:5 - Line 43:13)
numbers = re.findall(r'\d+', time_str)
if not numbers:
return None
minutes = int(numbers[0])
# Convert hours to minutes if needed
if 'hour' in time_str:
minutes *= 60
if len(numbers) > 1:
minutes += int(numbers[1])
return minutes
def _parse_servings
apps/ai/services/openrouter.py (Line 152:11 - Line 172:6), apps/ai/services/openrouter.py (Line 81:5 - Line 101:31)
(
messages=messages,
model=model,
stream=False,
)
# Extract the response content
if not response or not hasattr(response, 'choices'):
raise AIResponseError('Invalid response structure from OpenRouter')
if not response.choices:
raise AIResponseError('No choices in OpenRouter response')
content = response.choices[0].message.content
if json_response:
# Parse JSON from the response
try:
# Handle potential markdown code blocks
if content.startswith('```'):
lines
apps/ai/services/openrouter.py (Line 172:25 - Line 185:7), apps/ai/services/openrouter.py (Line 102:25 - Line 115:82)
lines = content.split('\n')
json_lines = []
in_block = False
for line in lines:
if line.startswith('```'):
in_block = not in_block
continue
if in_block:
json_lines.append(line)
content = '\n'.join(json_lines)
return json.loads(content)
except json.JSONDecodeError as e:
logger
apps/legacy/views.py (Line 85:5 - Line 95:6), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
query
apps/legacy/views.py (Line 112:5 - Line 122:47), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get the recipe (must belong to this profile)
apps/legacy/views.py (Line 169:5 - Line 182:37), apps/legacy/views.py (Line 38:5 - Line 125:22)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get the recipe (must belong to this profile)
recipe = get_object_or_404(Recipe, id=recipe_id, profile=profile)
# Check if AI features are available
apps/legacy/views.py (Line 205:5 - Line 215:46), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get all history for this profile (no limit)
apps/legacy/views.py (Line 238:5 - Line 248:37), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get all favorites for this profile
apps/legacy/views.py (Line 265:5 - Line 275:39), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get all collections for this profile
apps/legacy/views.py (Line 292:5 - Line 302:51), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get the collection (must belong to this profile)
apps/legacy/views.py (Line 329:5 - Line 339:19), apps/legacy/views.py (Line 38:5 - Line 48:33)
profile_id = request.session.get('profile_id')
if not profile_id:
return redirect('legacy:profile_selector')
try:
profile = Profile.objects.get(id=profile_id)
except Profile.DoesNotExist:
del request.session['profile_id']
return redirect('legacy:profile_selector')
# Get app settings
apps/ai/api.py (Line 359:9 - Line 378:18), apps/ai/api.py (Line 289:12 - Line 308:8)
}
except Recipe.DoesNotExist:
return 404, {
'error': 'not_found',
'message': f'Recipe {data.recipe_id} not found',
}
except AIUnavailableError as e:
return 503, {
'error': 'ai_unavailable',
'message': str(e) or 'AI features are not available. Please configure your API key in Settings.',
'action': 'configure_key',
}
except (AIResponseError, ValidationError) as e:
return 400, {
'error': 'ai_error',
'message': str(e),
}
# Scaling Schemas
apps/ai/api.py (Line 413:5 - Line 430:7), apps/ai/api.py (Line 315:5 - Line 332:26)
from apps.profiles.utils import get_current_profile_or_none
profile = get_current_profile_or_none(request)
if not profile:
return 404, {
'error': 'not_found',
'message': 'Profile not found',
}
# Verify the profile_id in the request matches the session profile
if data.profile_id != profile.id:
return 404, {
'error': 'not_found',
'message': f'Profile {data.profile_id} not found',
}
try:
recipe
apps/ai/api.py (Line 475:2 - Line 490:15), apps/ai/api.py (Line 293:36 - Line 308:8)
,
}
except AIUnavailableError as e:
return 503, {
'error': 'ai_unavailable',
'message': str(e) or 'AI features are not available. Please configure your API key in Settings.',
'action': 'configure_key',
}
except (AIResponseError, ValidationError) as e:
return 400, {
'error': 'ai_error',
'message': str(e),
}
# Tips Schemas
apps/ai/api.py (Line 511:5 - Line 523:46), apps/ai/api.py (Line 276:5 - Line 288:12)
from apps.profiles.utils import get_current_profile_or_none
profile = get_current_profile_or_none(request)
try:
# Verify recipe ownership
recipe = Recipe.objects.get(id=data.recipe_id)
if not profile or recipe.profile_id != profile.id:
return 404, {
'error': 'not_found',
'message': f'Recipe {data.recipe_id} not found',
}
# Clear existing tips if regenerate requested
apps/ai/api.py (Line 529:5 - Line 547:23), apps/ai/api.py (Line 290:5 - Line 308:8)
except Recipe.DoesNotExist:
return 404, {
'error': 'not_found',
'message': f'Recipe {data.recipe_id} not found',
}
except AIUnavailableError as e:
return 503, {
'error': 'ai_unavailable',
'message': str(e) or 'AI features are not available. Please configure your API key in Settings.',
'action': 'configure_key',
}
except (AIResponseError, ValidationError) as e:
return 400, {
'error': 'ai_error',
'message': str(e),
}
# Timer Naming Schemas
apps/ai/api.py (Line 584:5 - Line 597:19), apps/ai/api.py (Line 295:5 - Line 308:8)
except AIUnavailableError as e:
return 503, {
'error': 'ai_unavailable',
'message': str(e) or 'AI features are not available. Please configure your API key in Settings.',
'action': 'configure_key',
}
except (AIResponseError, ValidationError) as e:
return 400, {
'error': 'ai_error',
'message': str(e),
}
# Discover Schemas
apps/ai/api.py (Line 704:15 - Line 719:4), apps/ai/api.py (Line 475:2 - Line 308:5)
),
}
except AIUnavailableError as e:
return 503, {
'error': 'ai_unavailable',
'message': str(e) or 'AI features are not available. Please configure your API key in Settings.',
'action': 'configure_key',
}
except (AIResponseError, ValidationError) as e:
return 400, {
'error': 'ai_error',
'message': str(e),
}
@router.get