feat(ui): add content editor components for skills and thoughts
Some checks failed
CI / build-and-test (push) Failing after -31m24s

* Implement ContentEditorField for inline editing of content
* Create ContentEditorModal for editing content in a modal
* Introduce FormerShell for managing forms related to skills and thoughts
* Enhance SkillsPage and ThoughtsPage with new components for better content management
This commit is contained in:
2026-05-02 19:35:27 +02:00
parent 442cc3ef53
commit 9e6d05e055
59 changed files with 4727 additions and 3430 deletions

View File

@@ -1,212 +1,212 @@
package tools
import (
"context"
"strings"
"time"
// import (
// "context"
// "strings"
// "time"
"github.com/google/uuid"
"github.com/modelcontextprotocol/go-sdk/mcp"
// "github.com/google/uuid"
// "github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/store"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
// "git.warky.dev/wdevs/amcs/internal/store"
// ext "git.warky.dev/wdevs/amcs/internal/types"
// )
type CalendarTool struct {
store *store.DB
}
// type CalendarTool struct {
// store *store.DB
// }
func NewCalendarTool(db *store.DB) *CalendarTool {
return &CalendarTool{store: db}
}
// func NewCalendarTool(db *store.DB) *CalendarTool {
// return &CalendarTool{store: db}
// }
// add_family_member
// // add_family_member
type AddFamilyMemberInput struct {
Name string `json:"name" jsonschema:"person's name"`
Relationship string `json:"relationship,omitempty" jsonschema:"e.g. self, spouse, child, parent"`
BirthDate *time.Time `json:"birth_date,omitempty"`
Notes string `json:"notes,omitempty"`
}
// type AddFamilyMemberInput struct {
// Name string `json:"name" jsonschema:"person's name"`
// Relationship string `json:"relationship,omitempty" jsonschema:"e.g. self, spouse, child, parent"`
// BirthDate *time.Time `json:"birth_date,omitempty"`
// Notes string `json:"notes,omitempty"`
// }
type AddFamilyMemberOutput struct {
Member ext.FamilyMember `json:"member"`
}
// type AddFamilyMemberOutput struct {
// Member ext.FamilyMember `json:"member"`
// }
func (t *CalendarTool) AddMember(ctx context.Context, _ *mcp.CallToolRequest, in AddFamilyMemberInput) (*mcp.CallToolResult, AddFamilyMemberOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, AddFamilyMemberOutput{}, errRequiredField("name")
}
member, err := t.store.AddFamilyMember(ctx, ext.FamilyMember{
Name: strings.TrimSpace(in.Name),
Relationship: strings.TrimSpace(in.Relationship),
BirthDate: in.BirthDate,
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddFamilyMemberOutput{}, err
}
return nil, AddFamilyMemberOutput{Member: member}, nil
}
// func (t *CalendarTool) AddMember(ctx context.Context, _ *mcp.CallToolRequest, in AddFamilyMemberInput) (*mcp.CallToolResult, AddFamilyMemberOutput, error) {
// if strings.TrimSpace(in.Name) == "" {
// return nil, AddFamilyMemberOutput{}, errRequiredField("name")
// }
// member, err := t.store.AddFamilyMember(ctx, ext.FamilyMember{
// Name: strings.TrimSpace(in.Name),
// Relationship: strings.TrimSpace(in.Relationship),
// BirthDate: in.BirthDate,
// Notes: strings.TrimSpace(in.Notes),
// })
// if err != nil {
// return nil, AddFamilyMemberOutput{}, err
// }
// return nil, AddFamilyMemberOutput{Member: member}, nil
// }
// list_family_members
// // list_family_members
type ListFamilyMembersInput struct{}
// type ListFamilyMembersInput struct{}
type ListFamilyMembersOutput struct {
Members []ext.FamilyMember `json:"members"`
}
// type ListFamilyMembersOutput struct {
// Members []ext.FamilyMember `json:"members"`
// }
func (t *CalendarTool) ListMembers(ctx context.Context, _ *mcp.CallToolRequest, _ ListFamilyMembersInput) (*mcp.CallToolResult, ListFamilyMembersOutput, error) {
members, err := t.store.ListFamilyMembers(ctx)
if err != nil {
return nil, ListFamilyMembersOutput{}, err
}
if members == nil {
members = []ext.FamilyMember{}
}
return nil, ListFamilyMembersOutput{Members: members}, nil
}
// func (t *CalendarTool) ListMembers(ctx context.Context, _ *mcp.CallToolRequest, _ ListFamilyMembersInput) (*mcp.CallToolResult, ListFamilyMembersOutput, error) {
// members, err := t.store.ListFamilyMembers(ctx)
// if err != nil {
// return nil, ListFamilyMembersOutput{}, err
// }
// if members == nil {
// members = []ext.FamilyMember{}
// }
// return nil, ListFamilyMembersOutput{Members: members}, nil
// }
// add_activity
// // add_activity
type AddActivityInput struct {
Title string `json:"title" jsonschema:"activity title"`
ActivityType string `json:"activity_type,omitempty" jsonschema:"e.g. sports, medical, school, social"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"leave empty for whole-family activities"`
DayOfWeek string `json:"day_of_week,omitempty" jsonschema:"for recurring: monday, tuesday, etc."`
StartTime string `json:"start_time,omitempty" jsonschema:"HH:MM format"`
EndTime string `json:"end_time,omitempty" jsonschema:"HH:MM format"`
StartDate *time.Time `json:"start_date,omitempty"`
EndDate *time.Time `json:"end_date,omitempty" jsonschema:"for recurring activities, when they end"`
Location string `json:"location,omitempty"`
Notes string `json:"notes,omitempty"`
}
// type AddActivityInput struct {
// Title string `json:"title" jsonschema:"activity title"`
// ActivityType string `json:"activity_type,omitempty" jsonschema:"e.g. sports, medical, school, social"`
// FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"leave empty for whole-family activities"`
// DayOfWeek string `json:"day_of_week,omitempty" jsonschema:"for recurring: monday, tuesday, etc."`
// StartTime string `json:"start_time,omitempty" jsonschema:"HH:MM format"`
// EndTime string `json:"end_time,omitempty" jsonschema:"HH:MM format"`
// StartDate *time.Time `json:"start_date,omitempty"`
// EndDate *time.Time `json:"end_date,omitempty" jsonschema:"for recurring activities, when they end"`
// Location string `json:"location,omitempty"`
// Notes string `json:"notes,omitempty"`
// }
type AddActivityOutput struct {
Activity ext.Activity `json:"activity"`
}
// type AddActivityOutput struct {
// Activity ext.Activity `json:"activity"`
// }
func (t *CalendarTool) AddActivity(ctx context.Context, _ *mcp.CallToolRequest, in AddActivityInput) (*mcp.CallToolResult, AddActivityOutput, error) {
if strings.TrimSpace(in.Title) == "" {
return nil, AddActivityOutput{}, errRequiredField("title")
}
activity, err := t.store.AddActivity(ctx, ext.Activity{
FamilyMemberID: in.FamilyMemberID,
Title: strings.TrimSpace(in.Title),
ActivityType: strings.TrimSpace(in.ActivityType),
DayOfWeek: strings.ToLower(strings.TrimSpace(in.DayOfWeek)),
StartTime: strings.TrimSpace(in.StartTime),
EndTime: strings.TrimSpace(in.EndTime),
StartDate: in.StartDate,
EndDate: in.EndDate,
Location: strings.TrimSpace(in.Location),
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddActivityOutput{}, err
}
return nil, AddActivityOutput{Activity: activity}, nil
}
// func (t *CalendarTool) AddActivity(ctx context.Context, _ *mcp.CallToolRequest, in AddActivityInput) (*mcp.CallToolResult, AddActivityOutput, error) {
// if strings.TrimSpace(in.Title) == "" {
// return nil, AddActivityOutput{}, errRequiredField("title")
// }
// activity, err := t.store.AddActivity(ctx, ext.Activity{
// FamilyMemberID: in.FamilyMemberID,
// Title: strings.TrimSpace(in.Title),
// ActivityType: strings.TrimSpace(in.ActivityType),
// DayOfWeek: strings.ToLower(strings.TrimSpace(in.DayOfWeek)),
// StartTime: strings.TrimSpace(in.StartTime),
// EndTime: strings.TrimSpace(in.EndTime),
// StartDate: in.StartDate,
// EndDate: in.EndDate,
// Location: strings.TrimSpace(in.Location),
// Notes: strings.TrimSpace(in.Notes),
// })
// if err != nil {
// return nil, AddActivityOutput{}, err
// }
// return nil, AddActivityOutput{Activity: activity}, nil
// }
// get_week_schedule
// // get_week_schedule
type GetWeekScheduleInput struct {
WeekStart time.Time `json:"week_start" jsonschema:"start of the week (Monday) to retrieve"`
}
// type GetWeekScheduleInput struct {
// WeekStart time.Time `json:"week_start" jsonschema:"start of the week (Monday) to retrieve"`
// }
type GetWeekScheduleOutput struct {
Activities []ext.Activity `json:"activities"`
}
// type GetWeekScheduleOutput struct {
// Activities []ext.Activity `json:"activities"`
// }
func (t *CalendarTool) GetWeekSchedule(ctx context.Context, _ *mcp.CallToolRequest, in GetWeekScheduleInput) (*mcp.CallToolResult, GetWeekScheduleOutput, error) {
activities, err := t.store.GetWeekSchedule(ctx, in.WeekStart)
if err != nil {
return nil, GetWeekScheduleOutput{}, err
}
if activities == nil {
activities = []ext.Activity{}
}
return nil, GetWeekScheduleOutput{Activities: activities}, nil
}
// func (t *CalendarTool) GetWeekSchedule(ctx context.Context, _ *mcp.CallToolRequest, in GetWeekScheduleInput) (*mcp.CallToolResult, GetWeekScheduleOutput, error) {
// activities, err := t.store.GetWeekSchedule(ctx, in.WeekStart)
// if err != nil {
// return nil, GetWeekScheduleOutput{}, err
// }
// if activities == nil {
// activities = []ext.Activity{}
// }
// return nil, GetWeekScheduleOutput{Activities: activities}, nil
// }
// search_activities
// // search_activities
type SearchActivitiesInput struct {
Query string `json:"query,omitempty" jsonschema:"search text matching title or notes"`
ActivityType string `json:"activity_type,omitempty" jsonschema:"filter by type"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"filter by family member"`
}
// type SearchActivitiesInput struct {
// Query string `json:"query,omitempty" jsonschema:"search text matching title or notes"`
// ActivityType string `json:"activity_type,omitempty" jsonschema:"filter by type"`
// FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"filter by family member"`
// }
type SearchActivitiesOutput struct {
Activities []ext.Activity `json:"activities"`
}
// type SearchActivitiesOutput struct {
// Activities []ext.Activity `json:"activities"`
// }
func (t *CalendarTool) SearchActivities(ctx context.Context, _ *mcp.CallToolRequest, in SearchActivitiesInput) (*mcp.CallToolResult, SearchActivitiesOutput, error) {
activities, err := t.store.SearchActivities(ctx, in.Query, in.ActivityType, in.FamilyMemberID)
if err != nil {
return nil, SearchActivitiesOutput{}, err
}
if activities == nil {
activities = []ext.Activity{}
}
return nil, SearchActivitiesOutput{Activities: activities}, nil
}
// func (t *CalendarTool) SearchActivities(ctx context.Context, _ *mcp.CallToolRequest, in SearchActivitiesInput) (*mcp.CallToolResult, SearchActivitiesOutput, error) {
// activities, err := t.store.SearchActivities(ctx, in.Query, in.ActivityType, in.FamilyMemberID)
// if err != nil {
// return nil, SearchActivitiesOutput{}, err
// }
// if activities == nil {
// activities = []ext.Activity{}
// }
// return nil, SearchActivitiesOutput{Activities: activities}, nil
// }
// add_important_date
// // add_important_date
type AddImportantDateInput struct {
Title string `json:"title" jsonschema:"description of the date"`
DateValue time.Time `json:"date_value" jsonschema:"the date"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty"`
RecurringYearly bool `json:"recurring_yearly,omitempty" jsonschema:"if true, reminds every year"`
ReminderDaysBefore int `json:"reminder_days_before,omitempty" jsonschema:"how many days before to remind (default: 7)"`
Notes string `json:"notes,omitempty"`
}
// type AddImportantDateInput struct {
// Title string `json:"title" jsonschema:"description of the date"`
// DateValue time.Time `json:"date_value" jsonschema:"the date"`
// FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty"`
// RecurringYearly bool `json:"recurring_yearly,omitempty" jsonschema:"if true, reminds every year"`
// ReminderDaysBefore int `json:"reminder_days_before,omitempty" jsonschema:"how many days before to remind (default: 7)"`
// Notes string `json:"notes,omitempty"`
// }
type AddImportantDateOutput struct {
Date ext.ImportantDate `json:"date"`
}
// type AddImportantDateOutput struct {
// Date ext.ImportantDate `json:"date"`
// }
func (t *CalendarTool) AddImportantDate(ctx context.Context, _ *mcp.CallToolRequest, in AddImportantDateInput) (*mcp.CallToolResult, AddImportantDateOutput, error) {
if strings.TrimSpace(in.Title) == "" {
return nil, AddImportantDateOutput{}, errRequiredField("title")
}
reminder := in.ReminderDaysBefore
if reminder <= 0 {
reminder = 7
}
d, err := t.store.AddImportantDate(ctx, ext.ImportantDate{
FamilyMemberID: in.FamilyMemberID,
Title: strings.TrimSpace(in.Title),
DateValue: in.DateValue,
RecurringYearly: in.RecurringYearly,
ReminderDaysBefore: reminder,
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddImportantDateOutput{}, err
}
return nil, AddImportantDateOutput{Date: d}, nil
}
// func (t *CalendarTool) AddImportantDate(ctx context.Context, _ *mcp.CallToolRequest, in AddImportantDateInput) (*mcp.CallToolResult, AddImportantDateOutput, error) {
// if strings.TrimSpace(in.Title) == "" {
// return nil, AddImportantDateOutput{}, errRequiredField("title")
// }
// reminder := in.ReminderDaysBefore
// if reminder <= 0 {
// reminder = 7
// }
// d, err := t.store.AddImportantDate(ctx, ext.ImportantDate{
// FamilyMemberID: in.FamilyMemberID,
// Title: strings.TrimSpace(in.Title),
// DateValue: in.DateValue,
// RecurringYearly: in.RecurringYearly,
// ReminderDaysBefore: reminder,
// Notes: strings.TrimSpace(in.Notes),
// })
// if err != nil {
// return nil, AddImportantDateOutput{}, err
// }
// return nil, AddImportantDateOutput{Date: d}, nil
// }
// get_upcoming_dates
// // get_upcoming_dates
type GetUpcomingDatesInput struct {
DaysAhead int `json:"days_ahead,omitempty" jsonschema:"how many days to look ahead (default: 30)"`
}
// type GetUpcomingDatesInput struct {
// DaysAhead int `json:"days_ahead,omitempty" jsonschema:"how many days to look ahead (default: 30)"`
// }
type GetUpcomingDatesOutput struct {
Dates []ext.ImportantDate `json:"dates"`
}
// type GetUpcomingDatesOutput struct {
// Dates []ext.ImportantDate `json:"dates"`
// }
func (t *CalendarTool) GetUpcomingDates(ctx context.Context, _ *mcp.CallToolRequest, in GetUpcomingDatesInput) (*mcp.CallToolResult, GetUpcomingDatesOutput, error) {
dates, err := t.store.GetUpcomingDates(ctx, in.DaysAhead)
if err != nil {
return nil, GetUpcomingDatesOutput{}, err
}
if dates == nil {
dates = []ext.ImportantDate{}
}
return nil, GetUpcomingDatesOutput{Dates: dates}, nil
}
// func (t *CalendarTool) GetUpcomingDates(ctx context.Context, _ *mcp.CallToolRequest, in GetUpcomingDatesInput) (*mcp.CallToolResult, GetUpcomingDatesOutput, error) {
// dates, err := t.store.GetUpcomingDates(ctx, in.DaysAhead)
// if err != nil {
// return nil, GetUpcomingDatesOutput{}, err
// }
// if dates == nil {
// dates = []ext.ImportantDate{}
// }
// return nil, GetUpcomingDatesOutput{Dates: dates}, nil
// }