From a576355e7c6ff7b9c9f2b7533a2e72dc4543d233 Mon Sep 17 00:00:00 2001 From: John Costa Date: Wed, 26 Mar 2025 16:16:48 +0000 Subject: [PATCH] feat: creating events and attaching locations --- .../haystack/haystack/model/user_events.go | 18 +++++ .../haystack/table/table_use_schema.go | 1 + .../haystack/haystack/table/user_events.go | 81 +++++++++++++++++++ backend/agents/event_location_agent.go | 58 ++++++++----- backend/main.go | 12 +-- backend/models/events.go | 68 ++++++++-------- backend/models/locations.go | 3 +- backend/schema.sql | 6 ++ 8 files changed, 185 insertions(+), 62 deletions(-) create mode 100644 backend/.gen/haystack/haystack/model/user_events.go create mode 100644 backend/.gen/haystack/haystack/table/user_events.go diff --git a/backend/.gen/haystack/haystack/model/user_events.go b/backend/.gen/haystack/haystack/model/user_events.go new file mode 100644 index 0000000..ecd993c --- /dev/null +++ b/backend/.gen/haystack/haystack/model/user_events.go @@ -0,0 +1,18 @@ +// +// Code generated by go-jet DO NOT EDIT. +// +// WARNING: Changes to this file may cause incorrect behavior +// and will be lost if the code is regenerated +// + +package model + +import ( + "github.com/google/uuid" +) + +type UserEvents struct { + ID uuid.UUID `sql:"primary_key"` + EventID uuid.UUID + UserID uuid.UUID +} diff --git a/backend/.gen/haystack/haystack/table/table_use_schema.go b/backend/.gen/haystack/haystack/table/table_use_schema.go index 6d0922f..1acb87d 100644 --- a/backend/.gen/haystack/haystack/table/table_use_schema.go +++ b/backend/.gen/haystack/haystack/table/table_use_schema.go @@ -18,6 +18,7 @@ func UseSchema(schema string) { ImageTags = ImageTags.FromSchema(schema) ImageText = ImageText.FromSchema(schema) Locations = Locations.FromSchema(schema) + UserEvents = UserEvents.FromSchema(schema) UserImages = UserImages.FromSchema(schema) UserImagesToProcess = UserImagesToProcess.FromSchema(schema) UserLocations = UserLocations.FromSchema(schema) diff --git a/backend/.gen/haystack/haystack/table/user_events.go b/backend/.gen/haystack/haystack/table/user_events.go new file mode 100644 index 0000000..5090222 --- /dev/null +++ b/backend/.gen/haystack/haystack/table/user_events.go @@ -0,0 +1,81 @@ +// +// Code generated by go-jet DO NOT EDIT. +// +// WARNING: Changes to this file may cause incorrect behavior +// and will be lost if the code is regenerated +// + +package table + +import ( + "github.com/go-jet/jet/v2/postgres" +) + +var UserEvents = newUserEventsTable("haystack", "user_events", "") + +type userEventsTable struct { + postgres.Table + + // Columns + ID postgres.ColumnString + EventID postgres.ColumnString + UserID postgres.ColumnString + + AllColumns postgres.ColumnList + MutableColumns postgres.ColumnList +} + +type UserEventsTable struct { + userEventsTable + + EXCLUDED userEventsTable +} + +// AS creates new UserEventsTable with assigned alias +func (a UserEventsTable) AS(alias string) *UserEventsTable { + return newUserEventsTable(a.SchemaName(), a.TableName(), alias) +} + +// Schema creates new UserEventsTable with assigned schema name +func (a UserEventsTable) FromSchema(schemaName string) *UserEventsTable { + return newUserEventsTable(schemaName, a.TableName(), a.Alias()) +} + +// WithPrefix creates new UserEventsTable with assigned table prefix +func (a UserEventsTable) WithPrefix(prefix string) *UserEventsTable { + return newUserEventsTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) +} + +// WithSuffix creates new UserEventsTable with assigned table suffix +func (a UserEventsTable) WithSuffix(suffix string) *UserEventsTable { + return newUserEventsTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) +} + +func newUserEventsTable(schemaName, tableName, alias string) *UserEventsTable { + return &UserEventsTable{ + userEventsTable: newUserEventsTableImpl(schemaName, tableName, alias), + EXCLUDED: newUserEventsTableImpl("", "excluded", ""), + } +} + +func newUserEventsTableImpl(schemaName, tableName, alias string) userEventsTable { + var ( + IDColumn = postgres.StringColumn("id") + EventIDColumn = postgres.StringColumn("event_id") + UserIDColumn = postgres.StringColumn("user_id") + allColumns = postgres.ColumnList{IDColumn, EventIDColumn, UserIDColumn} + mutableColumns = postgres.ColumnList{EventIDColumn, UserIDColumn} + ) + + return userEventsTable{ + Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), + + //Columns + ID: IDColumn, + EventID: EventIDColumn, + UserID: UserIDColumn, + + AllColumns: allColumns, + MutableColumns: mutableColumns, + } +} diff --git a/backend/agents/event_location_agent.go b/backend/agents/event_location_agent.go index 3212f67..d79b35a 100644 --- a/backend/agents/event_location_agent.go +++ b/backend/agents/event_location_agent.go @@ -23,9 +23,12 @@ It is possible that there is no location or event on an image. You should ask for a list of locations, as the user is likely to have this location saved. Reuse existing locations where possible. +Always reuse existing locations from listLocations. Do not create duplicates. + Do not create an event if you don't see any dates, or a name indicating an event. -Always reuse existing locations from listLocations . Do not create duplicates. +Events can have an associated location, if you think there is a location, then you must either use a location from listLocations or you must create it first. +Wherever possible, find the location in the image. ` // TODO: this should be read directly from a file on load. @@ -64,23 +67,6 @@ const TOOLS = ` } } }, - { - "type": "function", - "function": { - "name": "attachImageLocation", - "description": "Add a location to an image. You must use UUID.", - "parameters": { - "type": "object", - "properties": { - "locationId": { - "type": "string", - "description": "UUID of an existing location, you can use listLocations to get values, or use the return value of createLocation" - } - }, - "required": ["locationId"] - } - } - }, { "type": "function", "function": { @@ -139,6 +125,12 @@ type AttachImageLocationArguments struct { LocationId string `json:"locationId"` } +type CreateEventArguments struct { + Name string `json:"name"` + Datetime string `json:"datetime"` + LocationId string `json:"locationId"` +} + func (agent EventLocationAgent) GetLocations(userId uuid.UUID, imageId uuid.UUID, imageName string, imageData []byte) error { var tools any err := json.Unmarshal([]byte(TOOLS), &tools) @@ -299,6 +291,8 @@ func NewLocationEventAgent(locationModel models.LocationModel, eventModel models }, } + // I'm not sure this one actually makes sense either. + // I think the earlier tool can do more. toolHandler.Handlers["attachImageLocation"] = ToolHandler[AttachImageLocationArguments, model.ImageLocations]{ FunctionName: "attachImageLocation", Parse: func(stringArgs string) (AttachImageLocationArguments, error) { @@ -312,6 +306,34 @@ func NewLocationEventAgent(locationModel models.LocationModel, eventModel models }, } + toolHandler.Handlers["createEvent"] = ToolHandler[CreateEventArguments, model.Events]{ + FunctionName: "createEvent", + Parse: func(stringArgs string) (CreateEventArguments, error) { + args := CreateEventArguments{} + err := json.Unmarshal([]byte(stringArgs), &args) + + return args, err + }, + Fn: func(info ToolHandlerInfo, args CreateEventArguments, call ToolCall) (model.Events, error) { + ctx := context.Background() + + event, err := agent.eventModel.Save(ctx, info.userId, model.Events{ + Name: args.Name, + }) + + if err != nil { + return event, err + } + + locationId, err := uuid.Parse(args.LocationId) + if err != nil { + return event, err + } + + return agent.eventModel.UpdateLocation(ctx, event.ID, locationId) + }, + } + agent.toolHandler = toolHandler return agent, nil diff --git a/backend/main.go b/backend/main.go index 9b803d8..99f1263 100644 --- a/backend/main.go +++ b/backend/main.go @@ -167,12 +167,12 @@ func main() { return } - err = eventModel.SaveToImage(ctx, userImage.ImageID, imageInfo.Events) - if err != nil { - log.Println("Failed to save events") - log.Println(err) - return - } + // err = eventModel.SaveToImage(ctx, userImage.ImageID, imageInfo.Events) + // if err != nil { + // log.Println("Failed to save events") + // log.Println(err) + // return + // } }() } } diff --git a/backend/models/events.go b/backend/models/events.go index 1287194..d707e5c 100644 --- a/backend/models/events.go +++ b/backend/models/events.go @@ -3,7 +3,7 @@ package models import ( "context" "database/sql" - "log" + . "github.com/go-jet/jet/v2/postgres" "screenmark/screenmark/.gen/haystack/haystack/model" . "screenmark/screenmark/.gen/haystack/haystack/table" @@ -14,49 +14,32 @@ type EventModel struct { dbPool *sql.DB } -// This looks stupid -func getEventValues(event model.Events) []any { - arr := make([]any, 0) - - if event.Description != nil { - arr = append(arr, *event.Description) - } else { - arr = append(arr, nil) - } - - if event.LocationID != nil { - arr = append(arr, *event.LocationID) - } else { - arr = append(arr, nil) - } - - return arr -} - -func (m EventModel) Save(ctx context.Context, events []model.Events) (model.Events, error) { +func (m EventModel) Save(ctx context.Context, userId uuid.UUID, event model.Events) (model.Events, error) { + // TODO tx here insertEventStmt := Events. - INSERT(Events.Name, Events.Description) - - for _, event := range events { - insertEventStmt = insertEventStmt.VALUES(event.Name, getEventValues(event)...) - } - - insertEventStmt = insertEventStmt.RETURNING(Events.AllColumns) - - log.Println(insertEventStmt.DebugSql()) + INSERT(Events.Name, Events.Description). + VALUES(event.Name, event.Description). + RETURNING(Events.AllColumns) insertedEvent := model.Events{} err := insertEventStmt.QueryContext(ctx, m.dbPool, &insertedEvent) + if err != nil { + return insertedEvent, err + } + + insertUserEventStmt := UserEvents. + INSERT(UserEvents.UserID, UserEvents.EventID). + VALUES(userId, insertedEvent.ID). + RETURNING(UserEvents.AllColumns) + + _, err = insertUserEventStmt.ExecContext(ctx, m.dbPool) + return insertedEvent, err } -func (m EventModel) SaveToImage(ctx context.Context, imageId uuid.UUID, events []model.Events) error { - if len(events) == 0 { - return nil - } - - event, err := m.Save(ctx, events) +func (m EventModel) SaveToImage(ctx context.Context, userId uuid.UUID, imageId uuid.UUID, event model.Events) error { + event, err := m.Save(ctx, userId, event) if err != nil { return err @@ -71,6 +54,19 @@ func (m EventModel) SaveToImage(ctx context.Context, imageId uuid.UUID, events [ return err } +func (m EventModel) UpdateLocation(ctx context.Context, eventId uuid.UUID, locationId uuid.UUID) (model.Events, error) { + updateEventLocationStmt := Events. + UPDATE(Events.LocationID). + SET(locationId). + WHERE(Events.ID.EQ(UUID(eventId))). + RETURNING(Events.AllColumns) + + updatedEvent := model.Events{} + err := updateEventLocationStmt.QueryContext(ctx, m.dbPool, &updatedEvent) + + return updatedEvent, err +} + func NewEventModel(db *sql.DB) EventModel { return EventModel{dbPool: db} } diff --git a/backend/models/locations.go b/backend/models/locations.go index 49509bc..c26148d 100644 --- a/backend/models/locations.go +++ b/backend/models/locations.go @@ -3,7 +3,6 @@ package models import ( "context" "database/sql" - "log" "screenmark/screenmark/.gen/haystack/haystack/model" . "screenmark/screenmark/.gen/haystack/haystack/table" @@ -57,7 +56,7 @@ func (m LocationModel) SaveToImage(ctx context.Context, imageId uuid.UUID, locat VALUES(imageId, locationId) imageLocation := model.ImageLocations{} - _, err := insertImageLocationStmt.ExecContext(ctx, m.dbPool) + err := insertImageLocationStmt.QueryContext(ctx, m.dbPool, &imageLocation) return imageLocation, err } diff --git a/backend/schema.sql b/backend/schema.sql index e2e9ee1..a71f268 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -86,6 +86,12 @@ CREATE TABLE haystack.image_events ( image_id UUID NOT NULL REFERENCES haystack.image (id) ); +CREATE TABLE haystack.user_events ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + event_id UUID NOT NULL REFERENCES haystack.events (id), + user_id UUID NOT NULL REFERENCES haystack.users (id) +); + /* -----| Indexes |----- */ CREATE INDEX user_tags_index ON haystack.user_tags(tag);