feat: journal dashboard widgets, agent polish, and edit-mode live previews
CI / checks (push) Failing after 2m7s
CI / build (push) Successful in 4m36s

Journal dashboard widgets and quick-add; rich-text quick-add dialogs.

Dashboard draft sync for live edit previews; assistant bubble + API tools.

Journal UX: stress slider, mood grid, query cap fix.
This commit is contained in:
ginnoir
2026-07-04 22:03:45 -05:00
parent 4a924a4107
commit a09747c314
76 changed files with 4594 additions and 611 deletions
+640 -2
View File
@@ -1,4 +1,7 @@
import type { AgentToolDefinition } from "@/lib/llm";
import { MOOD_CATALOG } from "@/modules/journal/mood-catalog";
const JOURNAL_MOOD_IDS = MOOD_CATALOG.map((mood) => mood.id).join(", ");
export const AGENT_TOOLS: AgentToolDefinition[] = [
{
@@ -46,6 +49,85 @@ export const AGENT_TOOLS: AgentToolDefinition[] = [
},
},
},
{
type: "function",
function: {
name: "update_list_item",
description:
"Update a list item — mark done/undone, rename, or change quantity. Use list_list_items first to resolve itemId.",
parameters: {
type: "object",
properties: {
listId: { type: "string", description: "UUID of the list" },
itemId: { type: "string", description: "UUID of the item" },
done: { type: "boolean", description: "Mark completed (true) or open (false)" },
text: { type: "string", description: "New item text" },
qty: { type: "string", description: "New quantity" },
},
required: ["listId", "itemId"],
},
},
},
{
type: "function",
function: {
name: "delete_list_item",
description: "Remove an item from a list permanently.",
parameters: {
type: "object",
properties: {
listId: { type: "string", description: "UUID of the list" },
itemId: { type: "string", description: "UUID of the item" },
},
required: ["listId", "itemId"],
},
},
},
{
type: "function",
function: {
name: "create_list",
description: "Create a new list (e.g. type shopping or tasks).",
parameters: {
type: "object",
properties: {
type: { type: "string", description: "List type, e.g. shopping or tasks" },
name: { type: "string", description: "Display name" },
},
required: ["type", "name"],
},
},
},
{
type: "function",
function: {
name: "update_list",
description: "Rename a list or archive/unarchive it.",
parameters: {
type: "object",
properties: {
listId: { type: "string" },
name: { type: "string" },
archived: { type: "boolean" },
},
required: ["listId"],
},
},
},
{
type: "function",
function: {
name: "delete_list",
description: "Permanently delete a list and all its items.",
parameters: {
type: "object",
properties: {
listId: { type: "string" },
},
required: ["listId"],
},
},
},
{
type: "function",
function: {
@@ -88,11 +170,66 @@ export const AGENT_TOOLS: AgentToolDefinition[] = [
allDay: { type: "boolean" },
location: { type: "string" },
notes: { type: "string" },
remindMinutesBefore: {
type: "number",
description: "Optional reminder N minutes before start",
},
},
required: ["calendarId", "title", "startAt", "endAt"],
},
},
},
{
type: "function",
function: {
name: "update_event",
description: "Update or reschedule a calendar event.",
parameters: {
type: "object",
properties: {
eventId: { type: "string" },
title: { type: "string" },
startAt: { type: "string" },
endAt: { type: "string" },
allDay: { type: "boolean" },
location: { type: "string" },
notes: { type: "string" },
remindMinutesBefore: { type: "number" },
},
required: ["eventId"],
},
},
},
{
type: "function",
function: {
name: "delete_event",
description: "Delete a calendar event.",
parameters: {
type: "object",
properties: {
eventId: { type: "string" },
},
required: ["eventId"],
},
},
},
{
type: "function",
function: {
name: "create_calendar",
description: "Create a new calendar.",
parameters: {
type: "object",
properties: {
name: { type: "string" },
color: { type: "string" },
visibility: { type: "string", description: "private or household" },
},
required: ["name"],
},
},
},
{
type: "function",
function: {
@@ -112,11 +249,44 @@ export const AGENT_TOOLS: AgentToolDefinition[] = [
title: { type: "string" },
body: { type: "string" },
pinned: { type: "boolean" },
remindAt: { type: "string", description: "ISO 8601 reminder datetime, or null to clear" },
},
required: ["title"],
},
},
},
{
type: "function",
function: {
name: "update_note",
description: "Update a note's title, body, pin state, or reminder.",
parameters: {
type: "object",
properties: {
noteId: { type: "string" },
title: { type: "string" },
body: { type: "string" },
pinned: { type: "boolean" },
remindAt: { type: "string", description: "ISO 8601 datetime or null" },
},
required: ["noteId"],
},
},
},
{
type: "function",
function: {
name: "delete_note",
description: "Delete a note.",
parameters: {
type: "object",
properties: {
noteId: { type: "string" },
},
required: ["noteId"],
},
},
},
{
type: "function",
function: {
@@ -130,10 +300,478 @@ export const AGENT_TOOLS: AgentToolDefinition[] = [
},
},
},
{
type: "function",
function: {
name: "create_journal_entry",
description:
"Create a journal entry for the current user. Supports moods, stress (1-10), and pillsTaken.",
parameters: {
type: "object",
properties: {
recordedAt: { type: "string", description: "ISO 8601 date/time for the entry" },
title: { type: "string" },
body: { type: "string" },
moods: {
type: "array",
items: { type: "string" },
description: `Mood ids: ${JOURNAL_MOOD_IDS}`,
},
stress: { type: "number", description: "Stress level 1-10" },
pillsTaken: { type: "boolean", description: "Whether pills were taken" },
},
required: ["recordedAt"],
},
},
},
{
type: "function",
function: {
name: "update_journal_entry",
description: "Update a journal entry including moods, stress, and pillsTaken.",
parameters: {
type: "object",
properties: {
entryId: { type: "string" },
recordedAt: { type: "string" },
title: { type: "string" },
body: { type: "string" },
moods: {
type: "array",
items: { type: "string" },
description: `Mood ids: ${JOURNAL_MOOD_IDS}`,
},
stress: { type: "number" },
pillsTaken: { type: "boolean" },
},
required: ["entryId"],
},
},
},
{
type: "function",
function: {
name: "delete_journal_entry",
description: "Delete a journal entry.",
parameters: {
type: "object",
properties: {
entryId: { type: "string" },
},
required: ["entryId"],
},
},
},
{
type: "function",
function: {
name: "list_bangs",
description: "Get bang counter stats and recent entries for the household.",
parameters: {
type: "object",
properties: {
limit: { type: "number", description: "Recent entries to include (default 10)" },
},
},
},
},
{
type: "function",
function: {
name: "add_bang",
description: "Record a new bang. Date defaults to today if omitted.",
parameters: {
type: "object",
properties: {
occurredOn: { type: "string", description: "YYYY-MM-DD" },
},
},
},
},
{
type: "function",
function: {
name: "update_bang",
description: "Change the date of an existing bang entry.",
parameters: {
type: "object",
properties: {
bangId: { type: "string" },
occurredOn: { type: "string", description: "YYYY-MM-DD" },
},
required: ["bangId", "occurredOn"],
},
},
},
{
type: "function",
function: {
name: "delete_bang",
description: "Delete a bang entry.",
parameters: {
type: "object",
properties: {
bangId: { type: "string" },
},
required: ["bangId"],
},
},
},
{
type: "function",
function: {
name: "list_garden_containers",
description: "List garden containers (pots, beds, etc.).",
parameters: { type: "object", properties: {} },
},
},
{
type: "function",
function: {
name: "list_garden_plants",
description: "List plants. Optionally filter by containerId.",
parameters: {
type: "object",
properties: {
containerId: { type: "string", description: "UUID of container" },
},
},
},
},
{
type: "function",
function: {
name: "get_garden_plant",
description: "Get full detail for a plant by id.",
parameters: {
type: "object",
properties: { plantId: { type: "string" } },
required: ["plantId"],
},
},
},
{
type: "function",
function: {
name: "create_garden_container",
description: "Create a garden container.",
parameters: {
type: "object",
properties: {
name: { type: "string" },
type: { type: "string", description: "e.g. pot, bed, greenhouse" },
locationNotes: { type: "string" },
},
required: ["name"],
},
},
},
{
type: "function",
function: {
name: "update_garden_container",
description: "Update a garden container.",
parameters: {
type: "object",
properties: {
containerId: { type: "string" },
name: { type: "string" },
type: { type: "string" },
locationNotes: { type: "string" },
},
required: ["containerId"],
},
},
},
{
type: "function",
function: {
name: "delete_garden_container",
description: "Delete a garden container.",
parameters: {
type: "object",
properties: { containerId: { type: "string" } },
required: ["containerId"],
},
},
},
{
type: "function",
function: {
name: "create_garden_plant",
description: "Add a new plant to the garden.",
parameters: {
type: "object",
properties: {
name: { type: "string" },
category: { type: "string" },
containerId: { type: "string" },
healthStatus: { type: "string" },
notes: { type: "string" },
},
required: ["name"],
},
},
},
{
type: "function",
function: {
name: "update_garden_plant",
description: "Update plant details (name, health, container, notes, etc.).",
parameters: {
type: "object",
properties: {
plantId: { type: "string" },
name: { type: "string" },
category: { type: "string" },
containerId: { type: "string" },
healthStatus: { type: "string" },
notes: { type: "string" },
},
required: ["plantId"],
},
},
},
{
type: "function",
function: {
name: "delete_garden_plant",
description: "Delete a plant.",
parameters: {
type: "object",
properties: { plantId: { type: "string" } },
required: ["plantId"],
},
},
},
{
type: "function",
function: {
name: "log_garden_care",
description: "Record care performed on a plant (water, fertilize, prune, etc.).",
parameters: {
type: "object",
properties: {
plantId: { type: "string" },
careType: { type: "string", description: "e.g. water, fertilize, prune" },
notes: { type: "string" },
performedAt: { type: "string", description: "ISO 8601; defaults to now" },
},
required: ["plantId", "careType"],
},
},
},
{
type: "function",
function: {
name: "list_garden_care_logs",
description: "List recent care logs for a plant.",
parameters: {
type: "object",
properties: {
plantId: { type: "string" },
limit: { type: "number" },
},
required: ["plantId"],
},
},
},
{
type: "function",
function: {
name: "list_garden_care_schedules",
description: "List care schedules (recurring reminders) for a plant.",
parameters: {
type: "object",
properties: { plantId: { type: "string" } },
required: ["plantId"],
},
},
},
{
type: "function",
function: {
name: "upsert_garden_care_schedule",
description: "Create or update a recurring care schedule for a plant.",
parameters: {
type: "object",
properties: {
plantId: { type: "string" },
careType: { type: "string" },
intervalDays: { type: "number", description: "Days between care" },
enabled: { type: "boolean" },
},
required: ["plantId", "careType", "intervalDays"],
},
},
},
{
type: "function",
function: {
name: "delete_garden_care_schedule",
description: "Delete a care schedule by schedule id.",
parameters: {
type: "object",
properties: { scheduleId: { type: "string" } },
required: ["scheduleId"],
},
},
},
{
type: "function",
function: {
name: "toggle_garden_care_schedule",
description: "Enable or disable a care schedule.",
parameters: {
type: "object",
properties: {
scheduleId: { type: "string" },
enabled: { type: "boolean" },
},
required: ["scheduleId", "enabled"],
},
},
},
{
type: "function",
function: {
name: "push_overdue_garden_care",
description: "Push overdue garden care items to the household task list.",
parameters: { type: "object", properties: {} },
},
},
{
type: "function",
function: {
name: "schedule_garden_care_on_calendar",
description: "Add a care schedule's next due date as a calendar event.",
parameters: {
type: "object",
properties: {
scheduleId: { type: "string" },
calendarId: { type: "string" },
reminderMinutesBefore: { type: "number" },
},
required: ["scheduleId", "calendarId"],
},
},
},
{
type: "function",
function: {
name: "list_shareable_entity_types",
description: "List entity types that can be shared via public links.",
parameters: { type: "object", properties: {} },
},
},
{
type: "function",
function: {
name: "list_share_links",
description: "List active share links. Optionally filter by entityType and entityId.",
parameters: {
type: "object",
properties: {
entityType: { type: "string" },
entityId: { type: "string" },
},
},
},
},
{
type: "function",
function: {
name: "create_share_link",
description:
"Create a temporary public share link for an entity. Entity types: calendar, calendar.event, list, note, garden.plant, garden.container.",
parameters: {
type: "object",
properties: {
entityType: { type: "string" },
entityId: { type: "string" },
expiresAt: { type: "string", description: "ISO 8601 expiry, or omit for no expiry" },
write: { type: "boolean", description: "Allow write via link (default false)" },
},
required: ["entityType", "entityId"],
},
},
},
{
type: "function",
function: {
name: "revoke_share_link",
description: "Revoke an active share link by its id.",
parameters: {
type: "object",
properties: { linkId: { type: "string" } },
required: ["linkId"],
},
},
},
{
type: "function",
function: {
name: "get_api_docs",
description:
"Read famapp REST API documentation (OpenAPI). Use when unsure which endpoint to call or no dedicated tool exists. Pass search to filter relevant paths.",
parameters: {
type: "object",
properties: {
search: {
type: "string",
description: "Optional keyword to filter paths (e.g. garden, share, journal)",
},
},
},
},
},
{
type: "function",
function: {
name: "call_api",
description:
"Fallback: call any /api/v1/* endpoint directly. Use get_api_docs first when unsure. Only household-scoped v1 routes.",
parameters: {
type: "object",
properties: {
method: {
type: "string",
enum: ["GET", "POST", "PATCH", "DELETE"],
description: "HTTP method",
},
path: {
type: "string",
description: "API path starting with /api/v1/ (e.g. /api/v1/notes)",
},
query: {
type: "object",
description: "Optional query string parameters as key-value pairs",
},
body: {
type: "object",
description: "Optional JSON body for POST/PATCH",
},
},
required: ["method", "path"],
},
},
},
];
export const AGENT_SYSTEM_PROMPT = `You are the famapp household assistant. Help Matt and his wife manage calendar events, shopping and task lists, notes, and personal journal entries.
export const AGENT_SYSTEM_PROMPT = `You are the famapp household assistant. Help Matt and his wife manage calendar events, shopping and task lists, notes, journal entries, garden plants, share links, and the bang counter.
Use the provided tools to read and update data. Prefer calling tools instead of guessing. Be concise and friendly.
When adding shopping items, resolve the shopping list id via list_lists if needed. Use ISO 8601 datetimes for calendar tools.`;
When no dedicated tool fits, or you are unsure how to do something:
1. Call get_api_docs with a relevant search term to find the right /api/v1/* endpoint.
2. Call call_api with the documented method, path, query, and body.
Lists: resolve list ids via list_lists. To complete items, list_list_items then update_list_item with done: true.
Journal: per-user private entries. Valid mood ids: ${JOURNAL_MOOD_IDS}. stress is 1-10. pillsTaken is boolean.
Garden: care types are free text (water, fertilize, prune, etc.). Use list_garden_plants to find plant ids.
Sharing: journal entries are not shareable. Shareable types: calendar, calendar.event, list, note, garden.plant, garden.container.
Calendar: use ISO 8601 datetimes. Bang dates use YYYY-MM-DD.`;