feat(routines): add minoxidil beard/mustache option to routine suggestions
- Add include_minoxidil_beard flag to SuggestRoutineRequest and SuggestBatchRequest - Detect minoxidil products by scanning name, brand, INCI and actives; pass them to the LLM even though they are medications - Inject CELE UŻYTKOWNIKA context block into prompts when flag is enabled - Add _build_objectives_context() returning empty string when flag is off - Add call_gemini() helper that centralises Gemini API calls and logs every request/response to a new ai_call_logs table (AICallLog model + /ai-logs router) - Nginx: raise client_max_body_size to 16 MB for photo uploads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3aa03b412b
commit
75ef1bca56
15 changed files with 337 additions and 62 deletions
|
|
@ -132,6 +132,8 @@
|
|||
"suggest_contextLabel": "Additional context for AI",
|
||||
"suggest_contextOptional": "(optional)",
|
||||
"suggest_contextPlaceholder": "e.g. party night, focusing on hydration...",
|
||||
"suggest_minoxidilToggleLabel": "Prioritize beard/mustache density (minoxidil)",
|
||||
"suggest_minoxidilToggleHint": "When enabled, AI will explicitly consider minoxidil for beard/mustache areas if available.",
|
||||
"suggest_generateBtn": "Generate suggestion",
|
||||
"suggest_generating": "Generating…",
|
||||
"suggest_proposalTitle": "Suggestion",
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@
|
|||
"suggest_contextLabel": "Dodatkowy kontekst dla AI",
|
||||
"suggest_contextOptional": "(opcjonalny)",
|
||||
"suggest_contextPlaceholder": "np. wieczór imprezowy, skupiam się na nawilżeniu...",
|
||||
"suggest_minoxidilToggleLabel": "Priorytet: gęstość brody/wąsów (minoksydyl)",
|
||||
"suggest_minoxidilToggleHint": "Po włączeniu AI jawnie uwzględni minoksydyl dla obszaru brody/wąsów, jeśli jest dostępny.",
|
||||
"suggest_generateBtn": "Generuj propozycję",
|
||||
"suggest_generating": "Generuję…",
|
||||
"suggest_proposalTitle": "Propozycja",
|
||||
|
|
|
|||
|
|
@ -141,12 +141,14 @@ export const suggestRoutine = (body: {
|
|||
routine_date: string;
|
||||
part_of_day: PartOfDay;
|
||||
notes?: string;
|
||||
include_minoxidil_beard?: boolean;
|
||||
}): Promise<RoutineSuggestion> => api.post('/routines/suggest', body);
|
||||
|
||||
export const suggestBatch = (body: {
|
||||
from_date: string;
|
||||
to_date: string;
|
||||
notes?: string;
|
||||
include_minoxidil_beard?: boolean;
|
||||
}): Promise<BatchSuggestion> => api.post('/routines/suggest-batch', body);
|
||||
|
||||
export const getGroomingSchedule = (): Promise<GroomingSchedule[]> =>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,19 @@ export const actions: Actions = {
|
|||
const routine_date = form.get('routine_date') as string;
|
||||
const part_of_day = form.get('part_of_day') as 'am' | 'pm';
|
||||
const notes = (form.get('notes') as string) || undefined;
|
||||
const include_minoxidil_beard = form.get('include_minoxidil_beard') === 'on';
|
||||
|
||||
if (!routine_date || !part_of_day) {
|
||||
return fail(400, { error: 'Data i pora dnia są wymagane.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const suggestion = await suggestRoutine({ routine_date, part_of_day, notes });
|
||||
const suggestion = await suggestRoutine({
|
||||
routine_date,
|
||||
part_of_day,
|
||||
notes,
|
||||
include_minoxidil_beard
|
||||
});
|
||||
return { suggestion, routine_date, part_of_day };
|
||||
} catch (e) {
|
||||
return fail(502, { error: (e as Error).message });
|
||||
|
|
@ -32,6 +38,7 @@ export const actions: Actions = {
|
|||
const from_date = form.get('from_date') as string;
|
||||
const to_date = form.get('to_date') as string;
|
||||
const notes = (form.get('notes') as string) || undefined;
|
||||
const include_minoxidil_beard = form.get('include_minoxidil_beard') === 'on';
|
||||
|
||||
if (!from_date || !to_date) {
|
||||
return fail(400, { error: 'Daty początkowa i końcowa są wymagane.' });
|
||||
|
|
@ -44,7 +51,7 @@ export const actions: Actions = {
|
|||
}
|
||||
|
||||
try {
|
||||
const batch = await suggestBatch({ from_date, to_date, notes });
|
||||
const batch = await suggestBatch({ from_date, to_date, notes, include_minoxidil_beard });
|
||||
return { batch, from_date, to_date };
|
||||
} catch (e) {
|
||||
return fail(502, { error: (e as Error).message });
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { resolve } from '$app/paths';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import type { ActionData, PageData } from './$types';
|
||||
import type { BatchSuggestion, RoutineSuggestion, SuggestedStep } from '$lib/types';
|
||||
|
|
@ -104,7 +105,7 @@
|
|||
|
||||
<div class="max-w-2xl space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/routines" class="text-sm text-muted-foreground hover:underline">{m["suggest_backToRoutines"]()}</a>
|
||||
<a href={resolve('/routines')} class="text-sm text-muted-foreground hover:underline">{m["suggest_backToRoutines"]()}</a>
|
||||
<h2 class="text-2xl font-bold tracking-tight">{m.suggest_title()}</h2>
|
||||
</div>
|
||||
|
||||
|
|
@ -152,6 +153,18 @@
|
|||
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 rounded-md border border-border px-3 py-2">
|
||||
<input
|
||||
id="single_include_minoxidil_beard"
|
||||
name="include_minoxidil_beard"
|
||||
type="checkbox"
|
||||
class="mt-0.5 h-4 w-4 rounded border-input"
|
||||
/>
|
||||
<div class="space-y-0.5">
|
||||
<Label for="single_include_minoxidil_beard" class="font-medium">{m["suggest_minoxidilToggleLabel"]()}</Label>
|
||||
<p class="text-xs text-muted-foreground">{m["suggest_minoxidilToggleHint"]()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={loadingSingle} class="w-full">
|
||||
{#if loadingSingle}
|
||||
|
|
@ -247,6 +260,18 @@
|
|||
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 rounded-md border border-border px-3 py-2">
|
||||
<input
|
||||
id="batch_include_minoxidil_beard"
|
||||
name="include_minoxidil_beard"
|
||||
type="checkbox"
|
||||
class="mt-0.5 h-4 w-4 rounded border-input"
|
||||
/>
|
||||
<div class="space-y-0.5">
|
||||
<Label for="batch_include_minoxidil_beard" class="font-medium">{m["suggest_minoxidilToggleLabel"]()}</Label>
|
||||
<p class="text-xs text-muted-foreground">{m["suggest_minoxidilToggleHint"]()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={loadingBatch} class="w-full">
|
||||
{#if loadingBatch}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue