Haystack V2: Removing entities completely
This commit is contained in:
@ -1,22 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Contacts struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
Name string
|
||||
Description *string
|
||||
PhoneNumber *string
|
||||
Email *string
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Events struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
Name string
|
||||
Description *string
|
||||
StartDateTime *time.Time
|
||||
EndDateTime *time.Time
|
||||
LocationID *uuid.UUID
|
||||
OrganizerID *uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImageContacts struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
ImageID uuid.UUID
|
||||
ContactID uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImageEvents struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
EventID uuid.UUID
|
||||
ImageID uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImageLocations struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
LocationID uuid.UUID
|
||||
ImageID uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Locations struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
Name string
|
||||
Address *string
|
||||
Description *string
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserContacts struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
UserID uuid.UUID
|
||||
ContactID uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserEvents struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
EventID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserLocations struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
LocationID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
CreatedAt *time.Time
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
//
|
||||
// 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 Contacts = newContactsTable("haystack", "contacts", "")
|
||||
|
||||
type contactsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
Name postgres.ColumnString
|
||||
Description postgres.ColumnString
|
||||
PhoneNumber postgres.ColumnString
|
||||
Email postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type ContactsTable struct {
|
||||
contactsTable
|
||||
|
||||
EXCLUDED contactsTable
|
||||
}
|
||||
|
||||
// AS creates new ContactsTable with assigned alias
|
||||
func (a ContactsTable) AS(alias string) *ContactsTable {
|
||||
return newContactsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new ContactsTable with assigned schema name
|
||||
func (a ContactsTable) FromSchema(schemaName string) *ContactsTable {
|
||||
return newContactsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new ContactsTable with assigned table prefix
|
||||
func (a ContactsTable) WithPrefix(prefix string) *ContactsTable {
|
||||
return newContactsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new ContactsTable with assigned table suffix
|
||||
func (a ContactsTable) WithSuffix(suffix string) *ContactsTable {
|
||||
return newContactsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newContactsTable(schemaName, tableName, alias string) *ContactsTable {
|
||||
return &ContactsTable{
|
||||
contactsTable: newContactsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newContactsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newContactsTableImpl(schemaName, tableName, alias string) contactsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
NameColumn = postgres.StringColumn("name")
|
||||
DescriptionColumn = postgres.StringColumn("description")
|
||||
PhoneNumberColumn = postgres.StringColumn("phone_number")
|
||||
EmailColumn = postgres.StringColumn("email")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, NameColumn, DescriptionColumn, PhoneNumberColumn, EmailColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{NameColumn, DescriptionColumn, PhoneNumberColumn, EmailColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return contactsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
Name: NameColumn,
|
||||
Description: DescriptionColumn,
|
||||
PhoneNumber: PhoneNumberColumn,
|
||||
Email: EmailColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
//
|
||||
// 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 Events = newEventsTable("haystack", "events", "")
|
||||
|
||||
type eventsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
Name postgres.ColumnString
|
||||
Description postgres.ColumnString
|
||||
StartDateTime postgres.ColumnTimestamp
|
||||
EndDateTime postgres.ColumnTimestamp
|
||||
LocationID postgres.ColumnString
|
||||
OrganizerID postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type EventsTable struct {
|
||||
eventsTable
|
||||
|
||||
EXCLUDED eventsTable
|
||||
}
|
||||
|
||||
// AS creates new EventsTable with assigned alias
|
||||
func (a EventsTable) AS(alias string) *EventsTable {
|
||||
return newEventsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new EventsTable with assigned schema name
|
||||
func (a EventsTable) FromSchema(schemaName string) *EventsTable {
|
||||
return newEventsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new EventsTable with assigned table prefix
|
||||
func (a EventsTable) WithPrefix(prefix string) *EventsTable {
|
||||
return newEventsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new EventsTable with assigned table suffix
|
||||
func (a EventsTable) WithSuffix(suffix string) *EventsTable {
|
||||
return newEventsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newEventsTable(schemaName, tableName, alias string) *EventsTable {
|
||||
return &EventsTable{
|
||||
eventsTable: newEventsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newEventsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newEventsTableImpl(schemaName, tableName, alias string) eventsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
NameColumn = postgres.StringColumn("name")
|
||||
DescriptionColumn = postgres.StringColumn("description")
|
||||
StartDateTimeColumn = postgres.TimestampColumn("start_date_time")
|
||||
EndDateTimeColumn = postgres.TimestampColumn("end_date_time")
|
||||
LocationIDColumn = postgres.StringColumn("location_id")
|
||||
OrganizerIDColumn = postgres.StringColumn("organizer_id")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, NameColumn, DescriptionColumn, StartDateTimeColumn, EndDateTimeColumn, LocationIDColumn, OrganizerIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{NameColumn, DescriptionColumn, StartDateTimeColumn, EndDateTimeColumn, LocationIDColumn, OrganizerIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return eventsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
Name: NameColumn,
|
||||
Description: DescriptionColumn,
|
||||
StartDateTime: StartDateTimeColumn,
|
||||
EndDateTime: EndDateTimeColumn,
|
||||
LocationID: LocationIDColumn,
|
||||
OrganizerID: OrganizerIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// 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 ImageContacts = newImageContactsTable("haystack", "image_contacts", "")
|
||||
|
||||
type imageContactsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
ImageID postgres.ColumnString
|
||||
ContactID postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type ImageContactsTable struct {
|
||||
imageContactsTable
|
||||
|
||||
EXCLUDED imageContactsTable
|
||||
}
|
||||
|
||||
// AS creates new ImageContactsTable with assigned alias
|
||||
func (a ImageContactsTable) AS(alias string) *ImageContactsTable {
|
||||
return newImageContactsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new ImageContactsTable with assigned schema name
|
||||
func (a ImageContactsTable) FromSchema(schemaName string) *ImageContactsTable {
|
||||
return newImageContactsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new ImageContactsTable with assigned table prefix
|
||||
func (a ImageContactsTable) WithPrefix(prefix string) *ImageContactsTable {
|
||||
return newImageContactsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new ImageContactsTable with assigned table suffix
|
||||
func (a ImageContactsTable) WithSuffix(suffix string) *ImageContactsTable {
|
||||
return newImageContactsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newImageContactsTable(schemaName, tableName, alias string) *ImageContactsTable {
|
||||
return &ImageContactsTable{
|
||||
imageContactsTable: newImageContactsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newImageContactsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newImageContactsTableImpl(schemaName, tableName, alias string) imageContactsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
ImageIDColumn = postgres.StringColumn("image_id")
|
||||
ContactIDColumn = postgres.StringColumn("contact_id")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, ImageIDColumn, ContactIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{ImageIDColumn, ContactIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return imageContactsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
ImageID: ImageIDColumn,
|
||||
ContactID: ContactIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// 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 ImageEvents = newImageEventsTable("haystack", "image_events", "")
|
||||
|
||||
type imageEventsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
EventID postgres.ColumnString
|
||||
ImageID postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type ImageEventsTable struct {
|
||||
imageEventsTable
|
||||
|
||||
EXCLUDED imageEventsTable
|
||||
}
|
||||
|
||||
// AS creates new ImageEventsTable with assigned alias
|
||||
func (a ImageEventsTable) AS(alias string) *ImageEventsTable {
|
||||
return newImageEventsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new ImageEventsTable with assigned schema name
|
||||
func (a ImageEventsTable) FromSchema(schemaName string) *ImageEventsTable {
|
||||
return newImageEventsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new ImageEventsTable with assigned table prefix
|
||||
func (a ImageEventsTable) WithPrefix(prefix string) *ImageEventsTable {
|
||||
return newImageEventsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new ImageEventsTable with assigned table suffix
|
||||
func (a ImageEventsTable) WithSuffix(suffix string) *ImageEventsTable {
|
||||
return newImageEventsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newImageEventsTable(schemaName, tableName, alias string) *ImageEventsTable {
|
||||
return &ImageEventsTable{
|
||||
imageEventsTable: newImageEventsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newImageEventsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newImageEventsTableImpl(schemaName, tableName, alias string) imageEventsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
EventIDColumn = postgres.StringColumn("event_id")
|
||||
ImageIDColumn = postgres.StringColumn("image_id")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, EventIDColumn, ImageIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{EventIDColumn, ImageIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return imageEventsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
EventID: EventIDColumn,
|
||||
ImageID: ImageIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// 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 ImageLocations = newImageLocationsTable("haystack", "image_locations", "")
|
||||
|
||||
type imageLocationsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
LocationID postgres.ColumnString
|
||||
ImageID postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type ImageLocationsTable struct {
|
||||
imageLocationsTable
|
||||
|
||||
EXCLUDED imageLocationsTable
|
||||
}
|
||||
|
||||
// AS creates new ImageLocationsTable with assigned alias
|
||||
func (a ImageLocationsTable) AS(alias string) *ImageLocationsTable {
|
||||
return newImageLocationsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new ImageLocationsTable with assigned schema name
|
||||
func (a ImageLocationsTable) FromSchema(schemaName string) *ImageLocationsTable {
|
||||
return newImageLocationsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new ImageLocationsTable with assigned table prefix
|
||||
func (a ImageLocationsTable) WithPrefix(prefix string) *ImageLocationsTable {
|
||||
return newImageLocationsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new ImageLocationsTable with assigned table suffix
|
||||
func (a ImageLocationsTable) WithSuffix(suffix string) *ImageLocationsTable {
|
||||
return newImageLocationsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newImageLocationsTable(schemaName, tableName, alias string) *ImageLocationsTable {
|
||||
return &ImageLocationsTable{
|
||||
imageLocationsTable: newImageLocationsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newImageLocationsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newImageLocationsTableImpl(schemaName, tableName, alias string) imageLocationsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
LocationIDColumn = postgres.StringColumn("location_id")
|
||||
ImageIDColumn = postgres.StringColumn("image_id")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, LocationIDColumn, ImageIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{LocationIDColumn, ImageIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return imageLocationsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
LocationID: LocationIDColumn,
|
||||
ImageID: ImageIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
//
|
||||
// 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 Locations = newLocationsTable("haystack", "locations", "")
|
||||
|
||||
type locationsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
Name postgres.ColumnString
|
||||
Address postgres.ColumnString
|
||||
Description postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type LocationsTable struct {
|
||||
locationsTable
|
||||
|
||||
EXCLUDED locationsTable
|
||||
}
|
||||
|
||||
// AS creates new LocationsTable with assigned alias
|
||||
func (a LocationsTable) AS(alias string) *LocationsTable {
|
||||
return newLocationsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new LocationsTable with assigned schema name
|
||||
func (a LocationsTable) FromSchema(schemaName string) *LocationsTable {
|
||||
return newLocationsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new LocationsTable with assigned table prefix
|
||||
func (a LocationsTable) WithPrefix(prefix string) *LocationsTable {
|
||||
return newLocationsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new LocationsTable with assigned table suffix
|
||||
func (a LocationsTable) WithSuffix(suffix string) *LocationsTable {
|
||||
return newLocationsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newLocationsTable(schemaName, tableName, alias string) *LocationsTable {
|
||||
return &LocationsTable{
|
||||
locationsTable: newLocationsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newLocationsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newLocationsTableImpl(schemaName, tableName, alias string) locationsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
NameColumn = postgres.StringColumn("name")
|
||||
AddressColumn = postgres.StringColumn("address")
|
||||
DescriptionColumn = postgres.StringColumn("description")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, NameColumn, AddressColumn, DescriptionColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{NameColumn, AddressColumn, DescriptionColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return locationsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
Name: NameColumn,
|
||||
Address: AddressColumn,
|
||||
Description: DescriptionColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -10,23 +10,14 @@ package table
|
||||
// UseSchema sets a new schema name for all generated table SQL builder types. It is recommended to invoke
|
||||
// this method only once at the beginning of the program.
|
||||
func UseSchema(schema string) {
|
||||
Contacts = Contacts.FromSchema(schema)
|
||||
Events = Events.FromSchema(schema)
|
||||
Image = Image.FromSchema(schema)
|
||||
ImageContacts = ImageContacts.FromSchema(schema)
|
||||
ImageEvents = ImageEvents.FromSchema(schema)
|
||||
ImageLists = ImageLists.FromSchema(schema)
|
||||
ImageLocations = ImageLocations.FromSchema(schema)
|
||||
ImageSchemaItems = ImageSchemaItems.FromSchema(schema)
|
||||
Lists = Lists.FromSchema(schema)
|
||||
Locations = Locations.FromSchema(schema)
|
||||
Logs = Logs.FromSchema(schema)
|
||||
SchemaItems = SchemaItems.FromSchema(schema)
|
||||
Schemas = Schemas.FromSchema(schema)
|
||||
UserContacts = UserContacts.FromSchema(schema)
|
||||
UserEvents = UserEvents.FromSchema(schema)
|
||||
UserImages = UserImages.FromSchema(schema)
|
||||
UserImagesToProcess = UserImagesToProcess.FromSchema(schema)
|
||||
UserLocations = UserLocations.FromSchema(schema)
|
||||
Users = Users.FromSchema(schema)
|
||||
}
|
||||
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// 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 UserContacts = newUserContactsTable("haystack", "user_contacts", "")
|
||||
|
||||
type userContactsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
UserID postgres.ColumnString
|
||||
ContactID postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type UserContactsTable struct {
|
||||
userContactsTable
|
||||
|
||||
EXCLUDED userContactsTable
|
||||
}
|
||||
|
||||
// AS creates new UserContactsTable with assigned alias
|
||||
func (a UserContactsTable) AS(alias string) *UserContactsTable {
|
||||
return newUserContactsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new UserContactsTable with assigned schema name
|
||||
func (a UserContactsTable) FromSchema(schemaName string) *UserContactsTable {
|
||||
return newUserContactsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new UserContactsTable with assigned table prefix
|
||||
func (a UserContactsTable) WithPrefix(prefix string) *UserContactsTable {
|
||||
return newUserContactsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new UserContactsTable with assigned table suffix
|
||||
func (a UserContactsTable) WithSuffix(suffix string) *UserContactsTable {
|
||||
return newUserContactsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newUserContactsTable(schemaName, tableName, alias string) *UserContactsTable {
|
||||
return &UserContactsTable{
|
||||
userContactsTable: newUserContactsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newUserContactsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newUserContactsTableImpl(schemaName, tableName, alias string) userContactsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
UserIDColumn = postgres.StringColumn("user_id")
|
||||
ContactIDColumn = postgres.StringColumn("contact_id")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, UserIDColumn, ContactIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{UserIDColumn, ContactIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return userContactsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
UserID: UserIDColumn,
|
||||
ContactID: ContactIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
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")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, EventIDColumn, UserIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{EventIDColumn, UserIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return userEventsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
EventID: EventIDColumn,
|
||||
UserID: UserIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// 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 UserLocations = newUserLocationsTable("haystack", "user_locations", "")
|
||||
|
||||
type userLocationsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
LocationID postgres.ColumnString
|
||||
UserID postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
}
|
||||
|
||||
type UserLocationsTable struct {
|
||||
userLocationsTable
|
||||
|
||||
EXCLUDED userLocationsTable
|
||||
}
|
||||
|
||||
// AS creates new UserLocationsTable with assigned alias
|
||||
func (a UserLocationsTable) AS(alias string) *UserLocationsTable {
|
||||
return newUserLocationsTable(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new UserLocationsTable with assigned schema name
|
||||
func (a UserLocationsTable) FromSchema(schemaName string) *UserLocationsTable {
|
||||
return newUserLocationsTable(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new UserLocationsTable with assigned table prefix
|
||||
func (a UserLocationsTable) WithPrefix(prefix string) *UserLocationsTable {
|
||||
return newUserLocationsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new UserLocationsTable with assigned table suffix
|
||||
func (a UserLocationsTable) WithSuffix(suffix string) *UserLocationsTable {
|
||||
return newUserLocationsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func newUserLocationsTable(schemaName, tableName, alias string) *UserLocationsTable {
|
||||
return &UserLocationsTable{
|
||||
userLocationsTable: newUserLocationsTableImpl(schemaName, tableName, alias),
|
||||
EXCLUDED: newUserLocationsTableImpl("", "excluded", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func newUserLocationsTableImpl(schemaName, tableName, alias string) userLocationsTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
LocationIDColumn = postgres.StringColumn("location_id")
|
||||
UserIDColumn = postgres.StringColumn("user_id")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{IDColumn, LocationIDColumn, UserIDColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{LocationIDColumn, UserIDColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
return userLocationsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
LocationID: LocationIDColumn,
|
||||
UserID: UserIDColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"screenmark/screenmark/.gen/haystack/haystack/model"
|
||||
"screenmark/screenmark/agents/client"
|
||||
"screenmark/screenmark/models"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const contactPrompt = `
|
||||
**Role:** AI Contact Processor from Images.
|
||||
|
||||
**Goal:** Extract contacts from an image, check against existing list using listContacts, add *only* new contacts using createContact, and call stopAgent when finished. Avoid duplicates.
|
||||
|
||||
**Input:** Image potentially containing contact info (Name, Phone, Email, Address).
|
||||
|
||||
**Workflow:**
|
||||
1. **Scan Image:** Extract all contact details. If none, call stopAgent.
|
||||
2. **Think:** Using the think tool, you must layout your thoughts about the contacts on the image. If they are duplicates or not, and what your next action should be,
|
||||
3. **Check Duplicates:** If contacts found, *first* call listContacts. Compare extracted info to list. If all found contacts already exist, use createExistingContact.
|
||||
4. **Add New:** If you detect a new contact on the image, call createContact to create a new contact.
|
||||
5. **Finish:** Call stopAgent once all new contacts are created OR if steps 1 or 2 determined no action/creation was needed.
|
||||
|
||||
**Tools:**
|
||||
* listContacts: Check existing contacts (Use first if contacts found).
|
||||
* createContact: Add a NEW contact (Name required).
|
||||
* createExistingContact: Adds this image to an existing contact, if one is found in listContacts.
|
||||
* stopAgent: Signal task completion.
|
||||
`
|
||||
|
||||
const contactTools = `
|
||||
[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "think",
|
||||
"description": "Use this tool to think through the image, evaluating the contact and whether or not it exists in the users listContacts.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thought": {
|
||||
"type": "string",
|
||||
"description": "A singular thought about the image"
|
||||
}
|
||||
},
|
||||
"required": ["thought"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "listContacts",
|
||||
"description": "Retrieves the complete list of the user's currently saved contacts (e.g., names, phone numbers, emails if available in the stored data). This tool is essential and **must** be called *before* attempting to create a new contact if potential contact info is found in the image, to check if the person already exists and prevent duplicate entries.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "createContact",
|
||||
"description": "Saves a new contact to the user's contact list. Only use this function **after** confirming the contact does not already exist by checking the output of listContacts. Provide all available extracted information for the new contact. Process one new contact per call.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contactId": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the contact. You should only provide this IF you believe the contact already exists, from listContacts."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The full name of the person being added as a contact. This field is mandatory."
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "string",
|
||||
"description": "The contact's primary phone number, including area or country code if available. Provide this if extracted from the image. Only include this if you see a phone number, do not make it up."
|
||||
},
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "The complete physical mailing address of the contact (e.g., street number, street name, city, state/province, postal code, country). Provide this if extracted from the image. Only include this if you see an address. No not make it up."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "The contact's primary email address. Provide this if extracted from the image. Only include this if you see an email, do not make it up."
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "createExistingContact",
|
||||
"description": "Called when a contact already exists in the users list, from listContas. Only call this to indicate this image contains a duplicate.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contactId": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the contact"
|
||||
}
|
||||
},
|
||||
"required": ["contactId"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "stopAgent",
|
||||
"description": "Use this tool to signal that the contact processing for the current image is complete. Call this *only* when: 1) No contact info was found initially, OR 2) All found contacts were confirmed to already exist after calling listContacts, OR 3) All necessary createContact calls for new individuals have been completed.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`
|
||||
|
||||
type listContactsArguments struct{}
|
||||
type createContactsArguments struct {
|
||||
Name string `json:"name"`
|
||||
ContactID *string `json:"contactId"`
|
||||
PhoneNumber *string `json:"phoneNumber"`
|
||||
Address *string `json:"address"`
|
||||
Email *string `json:"email"`
|
||||
}
|
||||
type createExistingContactArguments struct {
|
||||
ContactID string `json:"contactId"`
|
||||
}
|
||||
|
||||
func NewContactAgent(log *log.Logger, contactModel models.ContactModel) client.AgentClient {
|
||||
agentClient := client.CreateAgentClient(client.CreateAgentClientOptions{
|
||||
SystemPrompt: contactPrompt,
|
||||
JsonTools: contactTools,
|
||||
Log: log,
|
||||
EndToolCall: "stopAgent",
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("think", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return "Thought", nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("listContacts", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return contactModel.List(context.Background(), info.UserId)
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("createContact", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
||||
args := createContactsArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return model.Contacts{}, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
contactId := uuid.Nil
|
||||
if args.ContactID != nil {
|
||||
contactUuid, err := uuid.Parse(*args.ContactID)
|
||||
if err != nil {
|
||||
return model.Contacts{}, err
|
||||
}
|
||||
|
||||
contactId = contactUuid
|
||||
}
|
||||
|
||||
contact, err := contactModel.Save(ctx, info.UserId, model.Contacts{
|
||||
ID: contactId,
|
||||
Name: args.Name,
|
||||
PhoneNumber: args.PhoneNumber,
|
||||
Email: args.Email,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return model.Contacts{}, err
|
||||
}
|
||||
|
||||
_, err = contactModel.SaveToImage(ctx, info.ImageId, contact.ID)
|
||||
if err != nil {
|
||||
return model.Contacts{}, err
|
||||
}
|
||||
|
||||
return contact, nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("createExistingContact", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
||||
args := createExistingContactArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
contactId, err := uuid.Parse(args.ContactID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = contactModel.SaveToImage(ctx, info.ImageId, contactId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
})
|
||||
|
||||
return agentClient
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"screenmark/screenmark/.gen/haystack/haystack/model"
|
||||
"screenmark/screenmark/agents/client"
|
||||
"screenmark/screenmark/models"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const eventPrompt = `
|
||||
**You are an AI processing events from images using internal thought.**
|
||||
|
||||
**Task:** Extract event details (Name, Date/Time, Location). Use think before deciding actions. Check duplicates with listEvents. Handle new events via getEventLocationId (if location exists) and createEvent. Use finish if no event or duplicate found.
|
||||
1. **Analyze Image & Think:** Extract details. Use think to confirm if a valid event exists. If not -> stopAgent.
|
||||
2. **Event Confirmed?** -> *Must* call listEvents, to check for existing events and prevent duplicates.
|
||||
3. **Detect Duplicates** -> If the input contains an event that already exists from listEvents, then you should call stopAgent.
|
||||
4. **New Events**
|
||||
* If you think the input contains a location, then you can use getEventLocationId to retrieve the ID of the location. Only use this IF the input contains a location.
|
||||
* Call createEvent.
|
||||
5. **Multiple Events:** Process sequentially using this logic.
|
||||
|
||||
**Tools:**
|
||||
* think: Internal reasoning/planning step.
|
||||
* listEvents: Check for duplicates (mandatory first step for found events).
|
||||
* getEventLocationId: Get ID for location text.
|
||||
* createEvent: Add new event (Name req.). Terminal action for new events.
|
||||
* stopAgent: Signal completion (no event/duplicate found). Terminal action.
|
||||
`
|
||||
|
||||
const eventTools = `
|
||||
[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "think",
|
||||
"description": "Use this tool to think through the image, evaluating the event and whether or not it exists in the users listEvents.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thought": {
|
||||
"type": "string",
|
||||
"description": "A singular thought about the image"
|
||||
}
|
||||
},
|
||||
"required": ["thought"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "listEvents",
|
||||
"description": "Retrieves the list of the user's currently scheduled events. Essential for checking if an event identified in the image already exists to prevent duplicates. Must be called before potentially creating an event.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "createEvent",
|
||||
"description": "Creates a new event in the user's calendar or list. Use only after listEvents confirms the event is new. Provide all extracted details.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name or title of the event. This field is mandatory."
|
||||
},
|
||||
"startDateTime": {
|
||||
"type": "string",
|
||||
"description": "The event's start date and time in ISO 8601 format (e.g., '2025-04-18T10:00:00Z'). Include if available."
|
||||
},
|
||||
"endDateTime": {
|
||||
"type": "string",
|
||||
"description": "The event's end date and time in ISO 8601 format. Optional, include if available and different from startDateTime."
|
||||
},
|
||||
"locationId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier (UUID or similar) for the event's location. Use this if available, do not invent it."
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "updateEvent",
|
||||
"description": "Updates an existing event record identified by its eventId. Use this tool when listEvents indicates a match for the event details found in the current input.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eventId": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the existing event"
|
||||
}
|
||||
},
|
||||
"required": ["eventId"]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "getEventLocationId",
|
||||
"description": "Retrieves a unique identifier for a location description associated with an event. Use this before createEvent if a new event specifies a location.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"locationDescription": {
|
||||
"type": "string",
|
||||
"description": "The text describing the location extracted from the image (e.g., 'Conference Room B', '123 Main St, Anytown', 'Zoom Link details')."
|
||||
}
|
||||
},
|
||||
"required": ["locationDescription"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "stopAgent",
|
||||
"description": "Call this tool only when event processing for the current image is fully complete. This occurs if: 1) No event info was found, OR 2) The found event already exists, OR 3) A new event has been successfully created.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]`
|
||||
|
||||
type listEventArguments struct{}
|
||||
type createEventArguments struct {
|
||||
Name string `json:"name"`
|
||||
StartDateTime *string `json:"startDateTime"`
|
||||
EndDateTime *string `json:"endDateTime"`
|
||||
OrganizerName *string `json:"organizerName"`
|
||||
LocationID *string `json:"locationId"`
|
||||
}
|
||||
type updateEventArguments struct {
|
||||
EventID string `json:"eventId"`
|
||||
}
|
||||
|
||||
const layout = "2006-01-02T15:04:05Z"
|
||||
|
||||
func getArguments(args createEventArguments) (model.Events, error) {
|
||||
event := model.Events{
|
||||
Name: args.Name,
|
||||
}
|
||||
|
||||
if args.StartDateTime != nil {
|
||||
startTime, err := time.Parse(layout, *args.StartDateTime)
|
||||
if err != nil {
|
||||
return event, err
|
||||
}
|
||||
|
||||
event.StartDateTime = &startTime
|
||||
}
|
||||
|
||||
if args.EndDateTime != nil {
|
||||
endTime, err := time.Parse(layout, *args.EndDateTime)
|
||||
if err != nil {
|
||||
return event, err
|
||||
}
|
||||
|
||||
event.EndDateTime = &endTime
|
||||
}
|
||||
|
||||
if args.LocationID != nil {
|
||||
locationId, err := uuid.Parse(*args.LocationID)
|
||||
if err != nil {
|
||||
return model.Events{}, err
|
||||
}
|
||||
|
||||
event.LocationID = &locationId
|
||||
}
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func NewEventAgent(log *log.Logger, eventsModel models.EventModel, locationModel models.LocationModel) client.AgentClient {
|
||||
agentClient := client.CreateAgentClient(client.CreateAgentClientOptions{
|
||||
SystemPrompt: eventPrompt,
|
||||
JsonTools: eventTools,
|
||||
Log: log,
|
||||
EndToolCall: "stopAgent",
|
||||
})
|
||||
|
||||
locationAgent := NewLocationAgentWithComm(log.WithPrefix("Events 📅 > Locations 📍"), locationModel)
|
||||
locationQuery := "Can you get me the ID of the location present in this image?"
|
||||
locationAgent.Options.Query = &locationQuery
|
||||
|
||||
agentClient.ToolHandler.AddTool("think", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return "Thought", nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("listEvents", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return eventsModel.List(context.Background(), info.UserId)
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("createEvent", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
||||
args := createEventArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return model.Events{}, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
event, err := getArguments(args)
|
||||
if err != nil {
|
||||
return model.Events{}, err
|
||||
}
|
||||
|
||||
events, err := eventsModel.Save(ctx, info.UserId, event)
|
||||
|
||||
if err != nil {
|
||||
return model.Events{}, err
|
||||
}
|
||||
|
||||
_, err = eventsModel.SaveToImage(ctx, info.ImageId, events.ID)
|
||||
if err != nil {
|
||||
return model.Events{}, err
|
||||
}
|
||||
|
||||
return events, nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("updateEvent", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
||||
args := updateEventArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
contactUuid, err := uuid.Parse(args.EventID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
eventsModel.SaveToImage(ctx, info.ImageId, contactUuid)
|
||||
return "Saved", nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("getEventLocationId", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
// TODO: reenable this when I'm creating the agent locally instead of getting it from above.
|
||||
locationAgent.RunAgent(info.UserId, info.ImageId, info.ImageName, *info.Image)
|
||||
|
||||
log.Debugf("Reply from location %s\n", locationAgent.Reply)
|
||||
return locationAgent.Reply, nil
|
||||
})
|
||||
|
||||
return agentClient
|
||||
}
|
@ -192,7 +192,7 @@ func NewListAgent(log *log.Logger, listModel models.ListModel) client.AgentClien
|
||||
args := createListArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return model.Events{}, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
@ -1,248 +0,0 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"screenmark/screenmark/.gen/haystack/haystack/model"
|
||||
"screenmark/screenmark/agents/client"
|
||||
"screenmark/screenmark/models"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const locationPrompt = `
|
||||
Role: Location AI Assistant
|
||||
|
||||
Objective: Identify locations from images/text, manage a saved list, and answer user queries about saved locations using the provided tools.
|
||||
The user does not want to have duplicate entries on their saved location list. So you should only create a new location if listLocation doesnt return
|
||||
what would be a duplicate.
|
||||
|
||||
Core Logic:
|
||||
|
||||
**Extract Location Details:** Attempt to extract location details (like InputName, InputAddress) from the user's input.
|
||||
* If no details can be extracted, inform the user and use stopAgent.
|
||||
|
||||
**Check for Existing Location:** If details *were* extracted:
|
||||
* Use listLocations with the extracted InputName and/or InputAddress to search for potentially matching locations already saved in the list.
|
||||
|
||||
Action loop:
|
||||
**Thinking**
|
||||
* Use the think tool to analytise the image.
|
||||
* You should think about whether listLocations already contains this location, or if it is a new location.
|
||||
* You should always call this after listLocations.
|
||||
* You must think about whether or not listLocations already has this location.
|
||||
|
||||
**Decide Action based on Search Results:**
|
||||
* If no existing location looks like the location on the input. You should use createLocation.
|
||||
* Do not use this tool if this location already exists.
|
||||
* If the input contains a location that already exists, you should use createExistingLocation.
|
||||
* If there is a similar location in listLocation, you should use this tool. It doesnt have to be an exact match.
|
||||
* Lastly, if the user asked a specific question about a location. You must do all the actions but also always use the reply tool to answer the user.
|
||||
* This is the only way you can communicate with the user if they asked a query.
|
||||
|
||||
You should repeat the action loop until all locations on the image are done.
|
||||
Once you are done, use stopAgent.
|
||||
`
|
||||
|
||||
const replyTool = `
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "reply",
|
||||
"description": "Signals intent to provide information about a specific known location in response to a user's query. Use only if the user asked a question and the location's ID was found via listLocations.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"locationId": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the saved location that the user is asking about."
|
||||
}
|
||||
},
|
||||
"required": ["locationId"]
|
||||
}
|
||||
}
|
||||
},`
|
||||
|
||||
const locationTools = `
|
||||
[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "think",
|
||||
"description": "Use this tool to think through the image, evaluating the location and whether or not it exists in the users listLocations. You should also ask yourself if the user has asked a query, and if you've used the correct tool to reply to them.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thought": {
|
||||
"type": "string",
|
||||
"description": "A singular thought about the image"
|
||||
}
|
||||
},
|
||||
"required": ["thought"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "listLocations",
|
||||
"description": "Retrieves the list of the user's currently saved locations (names, addresses, IDs). Use this first to check if a location from an image already exists, or to find the ID of a location the user is asking about.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "createLocation",
|
||||
"description": "Creates a new location with as much information as you can extract. Be precise. You should only add the parameters you can actually see on the image.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The primary name of the location"
|
||||
},
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "The address of the location"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "createExistingLocation",
|
||||
"description": "Called when a location already exists in the users list, from listLocations. Only call this to indicate this image contains a duplicate.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"locationId": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the location, from listLocations"
|
||||
}
|
||||
},
|
||||
"required": ["locationId"]
|
||||
}
|
||||
}
|
||||
},
|
||||
%s
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "stopAgent",
|
||||
"description": "Use this tool to signal that the contact processing for the current image is complete.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]`
|
||||
|
||||
func getLocationAgentTools(allowReply bool) string {
|
||||
if allowReply {
|
||||
return fmt.Sprintf(locationTools, replyTool)
|
||||
} else {
|
||||
return fmt.Sprintf(locationTools, "")
|
||||
}
|
||||
}
|
||||
|
||||
type listLocationArguments struct{}
|
||||
type createLocationArguments struct {
|
||||
Name string `json:"name"`
|
||||
Address *string `json:"address"`
|
||||
}
|
||||
type createExistingLocationArguments struct {
|
||||
LocationID string `json:"locationId"`
|
||||
}
|
||||
|
||||
func NewLocationAgentWithComm(log *log.Logger, locationModel models.LocationModel) client.AgentClient {
|
||||
client := NewLocationAgent(log, locationModel)
|
||||
|
||||
client.Options.JsonTools = getLocationAgentTools(true)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func NewLocationAgent(log *log.Logger, locationModel models.LocationModel) client.AgentClient {
|
||||
agentClient := client.CreateAgentClient(client.CreateAgentClientOptions{
|
||||
SystemPrompt: locationPrompt,
|
||||
JsonTools: getLocationAgentTools(false),
|
||||
Log: log,
|
||||
EndToolCall: "stopAgent",
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("listLocations", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return locationModel.List(context.Background(), info.UserId)
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("createLocation", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
||||
args := createLocationArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return model.Locations{}, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// TODO: this tool could be simplier, as the model could have a SaveToImage joined with the save.
|
||||
|
||||
location, err := locationModel.Save(ctx, info.UserId, model.Locations{
|
||||
Name: args.Name,
|
||||
Address: args.Address,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return model.Locations{}, err
|
||||
}
|
||||
|
||||
_, err = locationModel.SaveToImage(ctx, info.ImageId, location.ID)
|
||||
if err != nil {
|
||||
return model.Locations{}, err
|
||||
}
|
||||
|
||||
return location, nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("createExistingLocation", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
||||
args := createExistingLocationArguments{}
|
||||
err := json.Unmarshal([]byte(_args), &args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
locationId, err := uuid.Parse(args.LocationID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = locationModel.SaveToImage(ctx, info.ImageId, locationId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("reply", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return "ok", nil
|
||||
})
|
||||
|
||||
agentClient.ToolHandler.AddTool("think", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return "ok", nil
|
||||
})
|
||||
|
||||
return agentClient
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"screenmark/screenmark/agents/client"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
const orchestratorPrompt = `
|
||||
**Role:** You are an Orchestrator AI responsible for analyzing images provided by the user.
|
||||
|
||||
**Primary Task:** Examine the input image and determine which specialized AI agent(s), available as tool calls, should be invoked to process the relevant information within the image, or if no specialized processing is needed. Your goal is to either extract and structure useful information for the user by selecting the most appropriate tool(s) or explicitly indicate that no specific action is required.
|
||||
|
||||
**Analysis Process & Decision Logic:**
|
||||
|
||||
1. **Analyze Image Content:** Scrutinize the image for distinct types of information:
|
||||
* General text/writing (including code, formulas)
|
||||
* Information about a person or contact details
|
||||
* Information about a place, location, or address
|
||||
* Information about an event
|
||||
* Content that doesn't fit any specific category or lacks actionable information.
|
||||
|
||||
2. **Thinking**
|
||||
* You should use the think tool to allow you to think your way through the image.
|
||||
* You should call this as many times as you need to in order to describe and analyse the image correctly.
|
||||
|
||||
3. **Agent Selection - Determine ALL that apply:**
|
||||
* **contactAgent:** Is there information specifically related to a person or their contact details (e.g., business card, name/email/phone)?
|
||||
* **locationAgent:** Is there information specifically identifying a place, location, city, or address (e.g., map, street sign, address text)?
|
||||
* **eventAgent:** Is there information specifically related to an event (e.g., invitation, poster with date/time, schedule)?
|
||||
* **noteAgent** Does the image contain *any* text/writing (including code, formulas)?
|
||||
* **noAgent**: Call this when you are done working on this image.
|
||||
|
||||
* Call all applicable specialized agents (noteAgent, contactAgent, locationAgent, eventAgent) simultaneously (in parallel).
|
||||
* Do not call agents if you dont think they are relevant. It's better to be sound than complete.
|
||||
`
|
||||
|
||||
const orchestratorTools = `
|
||||
[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "think",
|
||||
"description": "Use to layout all your thoughts about the image, roughly describing it, and specially describing if the image contains anything relevant to your available agents",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thought": {
|
||||
"type": "string",
|
||||
"description": "A singular thought about the image"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "noteAgent",
|
||||
"description": "Extracts general textual content like handwritten notes, paragraphs in documents, presentation slides, code snippets, or mathematical formulas. Use this for significant text that isn't primarily contact details, an address, or specific event information.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "contactAgent",
|
||||
"description": "Extracts personal contact information. Use when the image clearly shows details like names, phone numbers, email addresses, job titles, or company names, especially from sources like business cards, email signatures, or contact lists.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "locationAgent",
|
||||
"description": "Use when the input has anything to do with a place. This could be a city, an address, a postcode, a virtual meeting location, or a geographical location.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "eventAgent",
|
||||
"description": "Extracts details related to scheduled events, appointments, or specific occasions. Use when the image contains information like event titles, dates, times, venues, agendas, or descriptions, typically found on invitations, posters, calendar entries, or schedules.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "noAgent",
|
||||
"description": "Extracts details related to scheduled events, appointments, or specific occasions. Use when the image contains information like event titles, dates, times, venues, agendas, or descriptions, typically found on invitations, posters, calendar entries, or schedules.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`
|
||||
|
||||
type OrchestratorAgent struct {
|
||||
Client client.AgentClient
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Ok bool `json:"ok"`
|
||||
}
|
||||
|
||||
func NewOrchestratorAgent(log *log.Logger, contactAgent client.AgentClient, locationAgent client.AgentClient, eventAgent client.AgentClient, imageName string, imageData []byte) client.AgentClient {
|
||||
agent := client.CreateAgentClient(client.CreateAgentClientOptions{
|
||||
SystemPrompt: orchestratorPrompt,
|
||||
JsonTools: orchestratorTools,
|
||||
Log: log,
|
||||
EndToolCall: "noAgent",
|
||||
})
|
||||
|
||||
agent.ToolHandler.AddTool("think", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return "Thought", nil
|
||||
})
|
||||
|
||||
agent.ToolHandler.AddTool("contactAgent", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
contactAgent.RunAgent(info.UserId, info.ImageId, imageName, imageData)
|
||||
|
||||
return "contactAgent called successfully", nil
|
||||
})
|
||||
|
||||
agent.ToolHandler.AddTool("locationAgent", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
locationAgent.RunAgent(info.UserId, info.ImageId, imageName, imageData)
|
||||
|
||||
return "locationAgent called successfully", nil
|
||||
})
|
||||
|
||||
agent.ToolHandler.AddTool("eventAgent", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
eventAgent.RunAgent(info.UserId, info.ImageId, imageName, imageData)
|
||||
|
||||
return "eventAgent called successfully", nil
|
||||
})
|
||||
|
||||
agent.ToolHandler.AddTool("noAgent", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
||||
return "ok", nil
|
||||
})
|
||||
|
||||
return agent
|
||||
}
|
@ -31,11 +31,7 @@ func ListenNewImageEvents(db *sql.DB, notifier *Notifier[Notification]) {
|
||||
})
|
||||
defer listener.Close()
|
||||
|
||||
locationModel := models.NewLocationModel(db)
|
||||
eventModel := models.NewEventModel(db)
|
||||
imageModel := models.NewImageModel(db)
|
||||
contactModel := models.NewContactModel(db)
|
||||
|
||||
listModel := models.NewListModel(db)
|
||||
|
||||
databaseEventLog := createLogger("Database Events 🤖", os.Stdout)
|
||||
@ -62,10 +58,6 @@ func ListenNewImageEvents(db *sql.DB, notifier *Notifier[Notification]) {
|
||||
|
||||
splitWriter := createDbStdoutWriter(db, image.ImageID)
|
||||
|
||||
contactAgent := agents.NewContactAgent(createLogger("Contacts 👥", splitWriter), contactModel)
|
||||
locationAgent := agents.NewLocationAgent(createLogger("Locations 📍", splitWriter), locationModel)
|
||||
eventAgent := agents.NewEventAgent(createLogger("Events 📅", splitWriter), eventModel, locationModel)
|
||||
|
||||
if err := imageModel.StartProcessing(ctx, image.ID); err != nil {
|
||||
databaseEventLog.Error("Failed to FinishProcessing", "error", err)
|
||||
return
|
||||
@ -81,9 +73,6 @@ func ListenNewImageEvents(db *sql.DB, notifier *Notifier[Notification]) {
|
||||
listAgent := agents.NewListAgent(createLogger("Lists 🖋️", splitWriter), listModel)
|
||||
listAgent.RunAgent(image.UserID, image.ImageID, image.Image.ImageName, image.Image.Image)
|
||||
|
||||
orchestrator := agents.NewOrchestratorAgent(createLogger("Orchestrator 🎼", splitWriter), contactAgent, locationAgent, eventAgent, image.Image.ImageName, image.Image.Image)
|
||||
orchestrator.RunAgent(image.UserID, image.ImageID, image.Image.ImageName, image.Image.Image)
|
||||
|
||||
_, err = imageModel.FinishProcessing(ctx, image.ID)
|
||||
if err != nil {
|
||||
databaseEventLog.Error("Failed to finish processing", "ImageID", imageId, "error", err)
|
||||
|
@ -119,14 +119,6 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
imageProperties, err := userModel.ListWithProperties(r.Context(), userId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Something went wrong")
|
||||
return
|
||||
}
|
||||
|
||||
images, err := userModel.GetUserImages(r.Context(), userId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -153,14 +145,12 @@ func main() {
|
||||
|
||||
type ImagesReturn struct {
|
||||
UserImages []models.UserImageWithImage
|
||||
ImageProperties []models.TypedProperties
|
||||
ProcessingImages []models.UserProcessingImage
|
||||
Lists []models.ListsWithImages
|
||||
}
|
||||
|
||||
imagesReturn := ImagesReturn{
|
||||
UserImages: images,
|
||||
ImageProperties: models.GetTypedImageProperties(imageProperties),
|
||||
ProcessingImages: processingImages,
|
||||
Lists: listsWithImages,
|
||||
}
|
||||
@ -176,36 +166,6 @@ func main() {
|
||||
w.Write(jsonImages)
|
||||
})
|
||||
|
||||
r.Get("/image-properties/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
userId := r.Context().Value(USER_ID).(uuid.UUID)
|
||||
stringImageId := r.PathValue("id")
|
||||
|
||||
imageId, err := uuid.Parse(stringImageId)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "You cannot read this")
|
||||
return
|
||||
}
|
||||
|
||||
image, err := userModel.ListImageWithProperties(r.Context(), userId, imageId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Something went wrong")
|
||||
return
|
||||
}
|
||||
|
||||
jsonImages, err := json.Marshal(models.GetTypedImageProperties([]models.ImageWithProperties{image})[0])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "Could not create JSON response for this image")
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(jsonImages)
|
||||
})
|
||||
|
||||
r.Post("/image/{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
imageName := r.PathValue("name")
|
||||
userId := r.Context().Value(USER_ID).(uuid.UUID)
|
||||
|
@ -1,117 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"screenmark/screenmark/.gen/haystack/haystack/model"
|
||||
. "screenmark/screenmark/.gen/haystack/haystack/table"
|
||||
|
||||
. "github.com/go-jet/jet/v2/postgres"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ContactModel struct {
|
||||
dbPool *sql.DB
|
||||
}
|
||||
|
||||
func (m ContactModel) List(ctx context.Context, userId uuid.UUID) ([]model.Contacts, error) {
|
||||
listContactsStmt := SELECT(Contacts.AllColumns).
|
||||
FROM(
|
||||
Contacts.
|
||||
INNER_JOIN(UserContacts, UserContacts.ContactID.EQ(Contacts.ID)),
|
||||
).
|
||||
WHERE(UserContacts.UserID.EQ(UUID(userId)))
|
||||
|
||||
locations := []model.Contacts{}
|
||||
|
||||
err := listContactsStmt.QueryContext(ctx, m.dbPool, &locations)
|
||||
return locations, err
|
||||
}
|
||||
|
||||
func (m ContactModel) Get(ctx context.Context, contactId uuid.UUID) (model.Contacts, error) {
|
||||
getContactStmt := Contacts.
|
||||
SELECT(Contacts.AllColumns).
|
||||
WHERE(Contacts.ID.EQ(UUID(contactId)))
|
||||
|
||||
contact := model.Contacts{}
|
||||
err := getContactStmt.QueryContext(ctx, m.dbPool, &contact)
|
||||
|
||||
return contact, err
|
||||
}
|
||||
|
||||
func (m ContactModel) Update(ctx context.Context, contact model.Contacts) (model.Contacts, error) {
|
||||
existingContact, err := m.Get(ctx, contact.ID)
|
||||
if err != nil {
|
||||
return model.Contacts{}, err
|
||||
}
|
||||
|
||||
existingContact.Name = contact.Name
|
||||
|
||||
if contact.Description != nil {
|
||||
existingContact.Description = contact.Description
|
||||
}
|
||||
|
||||
if contact.PhoneNumber != nil {
|
||||
existingContact.PhoneNumber = contact.PhoneNumber
|
||||
}
|
||||
|
||||
if contact.Email != nil {
|
||||
existingContact.Email = contact.Email
|
||||
}
|
||||
|
||||
updateContactStmt := Contacts.
|
||||
UPDATE(Contacts.MutableColumns).
|
||||
MODEL(existingContact).
|
||||
WHERE(Contacts.ID.EQ(UUID(contact.ID))).
|
||||
RETURNING(Contacts.AllColumns)
|
||||
|
||||
updatedContact := model.Contacts{}
|
||||
err = updateContactStmt.QueryContext(ctx, m.dbPool, &updatedContact)
|
||||
|
||||
return updatedContact, err
|
||||
}
|
||||
|
||||
func (m ContactModel) Save(ctx context.Context, userId uuid.UUID, contact model.Contacts) (model.Contacts, error) {
|
||||
// TODO: make this a transaction
|
||||
|
||||
if contact.ID != uuid.Nil {
|
||||
return m.Update(ctx, contact)
|
||||
}
|
||||
|
||||
insertContactStmt := Contacts.
|
||||
INSERT(Contacts.Name, Contacts.Description, Contacts.PhoneNumber, Contacts.Email).
|
||||
VALUES(contact.Name, contact.Description, contact.PhoneNumber, contact.Email).
|
||||
RETURNING(Contacts.AllColumns)
|
||||
|
||||
insertedContact := model.Contacts{}
|
||||
err := insertContactStmt.QueryContext(ctx, m.dbPool, &insertedContact)
|
||||
|
||||
if err != nil {
|
||||
return insertedContact, err
|
||||
}
|
||||
|
||||
insertUserContactStmt := UserContacts.
|
||||
INSERT(UserContacts.UserID, UserContacts.ContactID).
|
||||
VALUES(userId, insertedContact.ID)
|
||||
|
||||
_, err = insertUserContactStmt.ExecContext(ctx, m.dbPool)
|
||||
|
||||
return insertedContact, err
|
||||
}
|
||||
|
||||
func (m ContactModel) SaveToImage(ctx context.Context, imageId uuid.UUID, contactId uuid.UUID) (model.ImageContacts, error) {
|
||||
insertImageContactStmt := ImageContacts.
|
||||
INSERT(ImageContacts.ImageID, ImageContacts.ContactID).
|
||||
VALUES(imageId, contactId).
|
||||
RETURNING(ImageContacts.AllColumns)
|
||||
|
||||
imageContact := model.ImageContacts{}
|
||||
err := insertImageContactStmt.QueryContext(ctx, m.dbPool, &imageContact)
|
||||
|
||||
return imageContact, err
|
||||
}
|
||||
|
||||
func NewContactModel(db *sql.DB) ContactModel {
|
||||
return ContactModel{dbPool: db}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
. "github.com/go-jet/jet/v2/postgres"
|
||||
"screenmark/screenmark/.gen/haystack/haystack/model"
|
||||
. "screenmark/screenmark/.gen/haystack/haystack/table"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type EventModel struct {
|
||||
dbPool *sql.DB
|
||||
}
|
||||
|
||||
func (m EventModel) List(ctx context.Context, userId uuid.UUID) ([]model.Events, error) {
|
||||
listEventsStmt := SELECT(Events.AllColumns).
|
||||
FROM(
|
||||
Events.
|
||||
INNER_JOIN(UserEvents, UserEvents.EventID.EQ(Events.ID)),
|
||||
).
|
||||
WHERE(UserEvents.UserID.EQ(UUID(userId)))
|
||||
|
||||
events := []model.Events{}
|
||||
|
||||
err := listEventsStmt.QueryContext(ctx, m.dbPool, &events)
|
||||
return events, err
|
||||
}
|
||||
|
||||
func (m EventModel) Save(ctx context.Context, userId uuid.UUID, event model.Events) (model.Events, error) {
|
||||
// TODO tx here
|
||||
insertEventStmt := Events.
|
||||
INSERT(Events.MutableColumns).
|
||||
MODEL(event).
|
||||
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)
|
||||
|
||||
_, err = insertUserEventStmt.ExecContext(ctx, m.dbPool)
|
||||
|
||||
return insertedEvent, err
|
||||
}
|
||||
|
||||
func (m EventModel) SaveToImage(ctx context.Context, imageId uuid.UUID, eventId uuid.UUID) (model.ImageEvents, error) {
|
||||
insertImageEventStmt := ImageEvents.
|
||||
INSERT(ImageEvents.ImageID, ImageEvents.EventID).
|
||||
VALUES(imageId, eventId).
|
||||
RETURNING(ImageEvents.AllColumns)
|
||||
|
||||
imageEvent := model.ImageEvents{}
|
||||
err := insertImageEventStmt.QueryContext(ctx, m.dbPool, &imageEvent)
|
||||
|
||||
return imageEvent, 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 (m EventModel) UpdateOrganizer(ctx context.Context, eventId uuid.UUID, organizerId uuid.UUID) (model.Events, error) {
|
||||
updateEventContactStmt := Events.
|
||||
UPDATE(Events.OrganizerID).
|
||||
SET(organizerId).
|
||||
WHERE(Events.ID.EQ(UUID(eventId))).
|
||||
RETURNING(Events.AllColumns)
|
||||
|
||||
updatedEvent := model.Events{}
|
||||
err := updateEventContactStmt.QueryContext(ctx, m.dbPool, &updatedEvent)
|
||||
|
||||
return updatedEvent, err
|
||||
}
|
||||
|
||||
func NewEventModel(db *sql.DB) EventModel {
|
||||
return EventModel{dbPool: db}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"screenmark/screenmark/.gen/haystack/haystack/model"
|
||||
. "screenmark/screenmark/.gen/haystack/haystack/table"
|
||||
|
||||
. "github.com/go-jet/jet/v2/postgres"
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type LocationModel struct {
|
||||
dbPool *sql.DB
|
||||
}
|
||||
|
||||
func (m LocationModel) List(ctx context.Context, userId uuid.UUID) ([]model.Locations, error) {
|
||||
listLocationsStmt := SELECT(Locations.AllColumns).
|
||||
FROM(
|
||||
Locations.
|
||||
INNER_JOIN(UserLocations, UserLocations.LocationID.EQ(Locations.ID)),
|
||||
).
|
||||
WHERE(UserLocations.UserID.EQ(UUID(userId)))
|
||||
|
||||
locations := []model.Locations{}
|
||||
|
||||
err := listLocationsStmt.QueryContext(ctx, m.dbPool, &locations)
|
||||
return locations, err
|
||||
}
|
||||
|
||||
func (m LocationModel) Get(ctx context.Context, locationId uuid.UUID) (model.Locations, error) {
|
||||
getLocationStmt := Locations.
|
||||
SELECT(Locations.AllColumns).
|
||||
WHERE(Locations.ID.EQ(UUID(locationId)))
|
||||
|
||||
location := model.Locations{}
|
||||
err := getLocationStmt.QueryContext(ctx, m.dbPool, &location)
|
||||
|
||||
return location, err
|
||||
}
|
||||
|
||||
func (m LocationModel) Update(ctx context.Context, location model.Locations) (model.Locations, error) {
|
||||
existingLocation, err := m.Get(ctx, location.ID)
|
||||
if err != nil {
|
||||
return model.Locations{}, err
|
||||
}
|
||||
|
||||
existingLocation.Name = location.Name
|
||||
|
||||
if location.Description != nil {
|
||||
existingLocation.Description = location.Description
|
||||
}
|
||||
|
||||
if location.Address != nil {
|
||||
existingLocation.Address = location.Address
|
||||
}
|
||||
|
||||
updateLocationStmt := Locations.
|
||||
UPDATE(Locations.MutableColumns).
|
||||
MODEL(existingLocation).
|
||||
WHERE(Locations.ID.EQ(UUID(location.ID))).
|
||||
RETURNING(Locations.AllColumns)
|
||||
|
||||
updatedLocation := model.Locations{}
|
||||
err = updateLocationStmt.QueryContext(ctx, m.dbPool, &updatedLocation)
|
||||
|
||||
return updatedLocation, err
|
||||
}
|
||||
|
||||
func (m LocationModel) Save(ctx context.Context, userId uuid.UUID, location model.Locations) (model.Locations, error) {
|
||||
if location.ID != uuid.Nil {
|
||||
return m.Update(ctx, location)
|
||||
}
|
||||
|
||||
insertLocationStmt := Locations.
|
||||
INSERT(Locations.Name, Locations.Address, Locations.Description).
|
||||
VALUES(location.Name, location.Address, location.Description).
|
||||
RETURNING(Locations.AllColumns)
|
||||
|
||||
insertedLocation := model.Locations{}
|
||||
err := insertLocationStmt.QueryContext(ctx, m.dbPool, &insertedLocation)
|
||||
if err != nil {
|
||||
return model.Locations{}, err
|
||||
}
|
||||
|
||||
insertUserLocationStmt := UserLocations.
|
||||
INSERT(UserLocations.UserID, UserLocations.LocationID).
|
||||
VALUES(userId, insertedLocation.ID)
|
||||
|
||||
_, err = insertUserLocationStmt.ExecContext(ctx, m.dbPool)
|
||||
|
||||
return insertedLocation, err
|
||||
}
|
||||
|
||||
func (m LocationModel) SaveToImage(ctx context.Context, imageId uuid.UUID, locationId uuid.UUID) (model.ImageLocations, error) {
|
||||
imageLocation := model.ImageLocations{}
|
||||
|
||||
checkExistingStmt := ImageLocations.
|
||||
SELECT(ImageLocations.AllColumns).
|
||||
WHERE(
|
||||
ImageLocations.ImageID.EQ(UUID(imageId)).
|
||||
AND(ImageLocations.LocationID.EQ(UUID(locationId))),
|
||||
)
|
||||
|
||||
err := checkExistingStmt.QueryContext(ctx, m.dbPool, &imageLocation)
|
||||
if err != nil && err != qrm.ErrNoRows {
|
||||
// A real error
|
||||
return model.ImageLocations{}, err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// Already exists.
|
||||
return imageLocation, nil
|
||||
}
|
||||
|
||||
insertImageLocationStmt := ImageLocations.
|
||||
INSERT(ImageLocations.ImageID, ImageLocations.LocationID).
|
||||
VALUES(imageId, locationId).
|
||||
RETURNING(ImageLocations.AllColumns)
|
||||
|
||||
err = insertImageLocationStmt.QueryContext(ctx, m.dbPool, &imageLocation)
|
||||
|
||||
return imageLocation, err
|
||||
}
|
||||
|
||||
func NewLocationModel(db *sql.DB) LocationModel {
|
||||
return LocationModel{dbPool: db}
|
||||
}
|
@ -19,189 +19,6 @@ type ImageWithProperties struct {
|
||||
ID uuid.UUID
|
||||
|
||||
Image model.Image
|
||||
|
||||
Locations []model.Locations
|
||||
Events []model.Events
|
||||
Contacts []model.Contacts
|
||||
}
|
||||
|
||||
type PropertiesWithImage struct {
|
||||
Locations []struct {
|
||||
model.Locations
|
||||
|
||||
Images uuid.UUIDs
|
||||
}
|
||||
Contacts []struct {
|
||||
model.Contacts
|
||||
|
||||
Images uuid.UUIDs
|
||||
}
|
||||
Events []struct {
|
||||
model.Events
|
||||
|
||||
Images uuid.UUIDs
|
||||
}
|
||||
}
|
||||
|
||||
func transpose(imageProperties []ImageWithProperties) PropertiesWithImage {
|
||||
// EntityID -> []ImageIDs
|
||||
dependencies := make(map[uuid.UUID]uuid.UUIDs)
|
||||
|
||||
addDependency := func(entityId uuid.UUID, imageId uuid.UUID) {
|
||||
deps, exists := dependencies[entityId]
|
||||
if !exists {
|
||||
dep := uuid.UUIDs{imageId}
|
||||
dependencies[entityId] = dep
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
dependencies[entityId] = append(deps, imageId)
|
||||
}
|
||||
|
||||
contactMap := make(map[uuid.UUID]model.Contacts)
|
||||
locationMap := make(map[uuid.UUID]model.Locations)
|
||||
eventMap := make(map[uuid.UUID]model.Events)
|
||||
|
||||
for _, image := range imageProperties {
|
||||
for _, contact := range image.Contacts {
|
||||
contactMap[contact.ID] = contact
|
||||
addDependency(contact.ID, image.Image.ID)
|
||||
}
|
||||
for _, location := range image.Locations {
|
||||
locationMap[location.ID] = location
|
||||
addDependency(location.ID, image.Image.ID)
|
||||
}
|
||||
for _, event := range image.Events {
|
||||
eventMap[event.ID] = event
|
||||
addDependency(event.ID, image.Image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
properties := PropertiesWithImage{
|
||||
Contacts: make([]struct {
|
||||
model.Contacts
|
||||
Images uuid.UUIDs
|
||||
}, 0),
|
||||
Locations: make([]struct {
|
||||
model.Locations
|
||||
Images uuid.UUIDs
|
||||
}, 0),
|
||||
Events: make([]struct {
|
||||
model.Events
|
||||
Images uuid.UUIDs
|
||||
}, 0),
|
||||
}
|
||||
|
||||
for contactId, contact := range contactMap {
|
||||
properties.Contacts = append(properties.Contacts, struct {
|
||||
model.Contacts
|
||||
Images uuid.UUIDs
|
||||
}{
|
||||
Contacts: contact,
|
||||
Images: dependencies[contactId],
|
||||
})
|
||||
}
|
||||
|
||||
for locationId, location := range locationMap {
|
||||
properties.Locations = append(properties.Locations, struct {
|
||||
model.Locations
|
||||
Images uuid.UUIDs
|
||||
}{
|
||||
Locations: location,
|
||||
Images: dependencies[locationId],
|
||||
})
|
||||
}
|
||||
|
||||
for eventId, event := range eventMap {
|
||||
properties.Events = append(properties.Events, struct {
|
||||
model.Events
|
||||
Images uuid.UUIDs
|
||||
}{
|
||||
Events: event,
|
||||
Images: dependencies[eventId],
|
||||
})
|
||||
}
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
type TypedProperties struct {
|
||||
Type string `json:"type"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func propertiesToTypeArray(properties PropertiesWithImage) []TypedProperties {
|
||||
typedProperties := make([]TypedProperties, 0)
|
||||
|
||||
for _, location := range properties.Locations {
|
||||
typedProperties = append(typedProperties, TypedProperties{
|
||||
Type: "location",
|
||||
Data: location,
|
||||
})
|
||||
}
|
||||
|
||||
for _, contact := range properties.Contacts {
|
||||
typedProperties = append(typedProperties, TypedProperties{
|
||||
Type: "contact",
|
||||
Data: contact,
|
||||
})
|
||||
}
|
||||
|
||||
for _, event := range properties.Events {
|
||||
typedProperties = append(typedProperties, TypedProperties{
|
||||
Type: "event",
|
||||
Data: event,
|
||||
})
|
||||
}
|
||||
|
||||
return typedProperties
|
||||
}
|
||||
|
||||
func GetTypedImageProperties(imageProperties []ImageWithProperties) []TypedProperties {
|
||||
return propertiesToTypeArray(transpose(imageProperties))
|
||||
}
|
||||
|
||||
func getListImagesStmt() SelectStatement {
|
||||
return SELECT(
|
||||
UserImages.ID.AS("ImageWithProperties.ID"),
|
||||
Image.ID,
|
||||
Image.ImageName,
|
||||
ImageLocations.AllColumns,
|
||||
Locations.AllColumns,
|
||||
ImageEvents.AllColumns,
|
||||
Events.AllColumns,
|
||||
ImageContacts.AllColumns,
|
||||
Contacts.AllColumns,
|
||||
).
|
||||
FROM(
|
||||
UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID)).
|
||||
LEFT_JOIN(ImageLocations, ImageLocations.ImageID.EQ(UserImages.ImageID)).
|
||||
LEFT_JOIN(Locations, Locations.ID.EQ(ImageLocations.LocationID)).
|
||||
LEFT_JOIN(ImageEvents, ImageEvents.ImageID.EQ(UserImages.ImageID)).
|
||||
LEFT_JOIN(Events, Events.ID.EQ(ImageEvents.EventID)).
|
||||
LEFT_JOIN(ImageContacts, ImageContacts.ImageID.EQ(UserImages.ImageID)).
|
||||
LEFT_JOIN(Contacts, Contacts.ID.EQ(ImageContacts.ContactID)))
|
||||
}
|
||||
|
||||
func (m UserModel) ListImageWithProperties(ctx context.Context, userId uuid.UUID, imageId uuid.UUID) (ImageWithProperties, error) {
|
||||
listImagePropertiesStmt := getListImagesStmt().
|
||||
WHERE(UserImages.ImageID.EQ(UUID(imageId)))
|
||||
|
||||
image := ImageWithProperties{}
|
||||
err := listImagePropertiesStmt.QueryContext(ctx, m.dbPool, &image)
|
||||
|
||||
return image, err
|
||||
}
|
||||
|
||||
func (m UserModel) ListWithProperties(ctx context.Context, userId uuid.UUID) ([]ImageWithProperties, error) {
|
||||
listWithPropertiesStmt := getListImagesStmt().
|
||||
WHERE(UserImages.UserID.EQ(UUID(userId)))
|
||||
|
||||
images := []ImageWithProperties{}
|
||||
err := listWithPropertiesStmt.QueryContext(ctx, m.dbPool, &images)
|
||||
|
||||
return images, err
|
||||
}
|
||||
|
||||
func (m UserModel) GetUserIdFromEmail(ctx context.Context, email string) (uuid.UUID, error) {
|
||||
|
@ -35,92 +35,6 @@ CREATE TABLE haystack.user_images (
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.locations (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
description TEXT,
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.image_locations (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
location_id UUID NOT NULL REFERENCES haystack.locations (id),
|
||||
image_id UUID NOT NULL REFERENCES haystack.image (id),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.user_locations (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
location_id UUID NOT NULL REFERENCES haystack.locations (id),
|
||||
user_id UUID NOT NULL REFERENCES haystack.users (id),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.contacts (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- It seems name and description are frequent. We could use table inheritance.
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
phone_number TEXT,
|
||||
email TEXT,
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.user_contacts (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES haystack.users (id),
|
||||
contact_id UUID NOT NULL REFERENCES haystack.contacts (id),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.image_contacts (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
image_id UUID NOT NULL REFERENCES haystack.image (id),
|
||||
contact_id UUID NOT NULL REFERENCES haystack.contacts (id),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.events (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- It seems name and description are frequent. We could use table inheritance.
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
start_date_time TIMESTAMP,
|
||||
end_date_time TIMESTAMP,
|
||||
|
||||
location_id UUID REFERENCES haystack.locations (id),
|
||||
organizer_id UUID REFERENCES haystack.contacts (id),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.image_events (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
event_id UUID NOT NULL REFERENCES haystack.events (id),
|
||||
image_id UUID NOT NULL REFERENCES haystack.image (id),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
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),
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.logs (
|
||||
log TEXT NOT NULL,
|
||||
image_id UUID NOT NULL REFERENCES haystack.image (id),
|
||||
|
@ -2,11 +2,9 @@ import { Navigate, Route, Router } from "@solidjs/router";
|
||||
import { onAndroidMount } from "./mobile";
|
||||
import {
|
||||
FrontPage,
|
||||
Gallery,
|
||||
ImagePage,
|
||||
Login,
|
||||
Settings,
|
||||
Entity,
|
||||
SearchPage,
|
||||
AllImages,
|
||||
List,
|
||||
@ -36,8 +34,6 @@ export const App = () => {
|
||||
<Route path="/all-images" component={AllImages} />
|
||||
<Route path="/image/:imageId" component={ImagePage} />
|
||||
<Route path="/list/:listId" component={List} />
|
||||
<Route path="/entity/:entityId" component={Entity} />
|
||||
<Route path="/gallery/:entity" component={Gallery} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
</Route>
|
||||
</Route>
|
||||
|
@ -1,82 +0,0 @@
|
||||
import type { UserImage } from "../../network";
|
||||
import { Show, type Component } from "solid-js";
|
||||
import SolidjsMarkdown from "solidjs-markdown";
|
||||
|
||||
type Props = {
|
||||
item: UserImage;
|
||||
};
|
||||
|
||||
const NullableParagraph: Component<{
|
||||
item: string | null;
|
||||
itemTitle: string;
|
||||
}> = (props) => {
|
||||
return (
|
||||
<Show when={props.item}>
|
||||
{(item) => (
|
||||
<>
|
||||
<p class="font-semibold text-xl">{props.itemTitle}</p>
|
||||
<p class="text-md">{item()}</p>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const ConcreteItemModal: Component<Props> = (props) => {
|
||||
switch (props.item.type) {
|
||||
case "note":
|
||||
return (
|
||||
<SolidjsMarkdown>
|
||||
{props.item.data.Content.slice(
|
||||
"```markdown".length,
|
||||
props.item.data.Content.length - "```".length,
|
||||
)}
|
||||
</SolidjsMarkdown>
|
||||
);
|
||||
case "location":
|
||||
return (
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="font-semibold text-xl">Address</p>
|
||||
<p class="text-md">{props.item.data.Address}</p>
|
||||
</div>
|
||||
);
|
||||
case "event":
|
||||
return (
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="font-semibold text-xl">Event</p>
|
||||
<p class="text-md">{props.item.data.Name}</p>
|
||||
|
||||
<NullableParagraph
|
||||
itemTitle="Start Time"
|
||||
item={props.item.data.StartDateTime}
|
||||
/>
|
||||
<NullableParagraph
|
||||
itemTitle="End Time"
|
||||
item={props.item.data.EndDateTime}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "contact":
|
||||
return (
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="font-semibold text-xl">Contact</p>
|
||||
<p class="text-md">{props.item.data.Name}</p>
|
||||
|
||||
<NullableParagraph itemTitle="Email" item={props.item.data.Email} />
|
||||
|
||||
<NullableParagraph
|
||||
itemTitle="Phone Number"
|
||||
item={props.item.data.PhoneNumber}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const ItemModal: Component<Props> = (props) => {
|
||||
return (
|
||||
<div class="rounded-2xl p-4 bg-white border border-neutral-300 flex flex-col gap-2 mb-2">
|
||||
<ConcreteItemModal item={props.item} />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import type { UserImage } from "../../network";
|
||||
import { SearchCardContact } from "./SearchCardContact";
|
||||
import { SearchCardEvent } from "./SearchCardEvent";
|
||||
import { SearchCardLocation } from "./SearchCardLocation";
|
||||
|
||||
const UnwrappedSearchCard = (props: { item: UserImage }) => {
|
||||
const { item } = props;
|
||||
|
||||
switch (item.type) {
|
||||
case "location":
|
||||
return <SearchCardLocation item={item} />;
|
||||
case "event":
|
||||
return <SearchCardEvent item={item} />;
|
||||
case "contact":
|
||||
return <SearchCardContact item={item} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const SearchCard = (props: { item: UserImage }) => {
|
||||
return (
|
||||
<A
|
||||
href={`/entity/${props.item.data.ID}`}
|
||||
class="w-full h-[144px] border relative border-neutral-200 cursor-pointer overflow-hidden rounded-xl"
|
||||
>
|
||||
<UnwrappedSearchCard item={props.item} />
|
||||
</A>
|
||||
);
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import { IconUser } from "@tabler/icons-solidjs";
|
||||
import type { UserImage } from "../../network";
|
||||
|
||||
type Props = {
|
||||
item: Extract<UserImage, { type: "contact" }>;
|
||||
};
|
||||
|
||||
export const SearchCardContact = ({ item }: Props) => {
|
||||
const { data } = item;
|
||||
|
||||
return (
|
||||
<div class="h-full inset-0 p-3 bg-orange-50">
|
||||
<div class="flex mb-1 items-center gap-1">
|
||||
<IconUser size={14} class="text-neutral-500" />
|
||||
<p class="text-xs text-neutral-500">Contact</p>
|
||||
</div>
|
||||
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||
</p>
|
||||
<p class="text-xs text-neutral-700">Phone: {data.PhoneNumber}</p>
|
||||
<p class="text-xs text-neutral-700">Mail: {data.Email}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
import { IconCalendar } from "@tabler/icons-solidjs";
|
||||
import type { UserImage } from "../../network";
|
||||
|
||||
type Props = {
|
||||
item: Extract<UserImage, { type: "event" }>;
|
||||
};
|
||||
|
||||
export const SearchCardEvent = ({ item }: Props) => {
|
||||
const { data } = item;
|
||||
|
||||
return (
|
||||
<div class="h-full inset-0 p-3 bg-purple-50">
|
||||
<div class="flex mb-1 items-center gap-1">
|
||||
<IconCalendar size={14} class="text-neutral-500" />
|
||||
<p class="text-xs text-neutral-500">Event</p>
|
||||
</div>
|
||||
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||
</p>
|
||||
<p class="text-xs text-neutral-700">
|
||||
On{" "}
|
||||
{data.StartDateTime
|
||||
? new Date(data.StartDateTime).toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})
|
||||
: "unknown date"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import { IconMapPin } from "@tabler/icons-solidjs";
|
||||
import type { UserImage } from "../../network";
|
||||
|
||||
type Props = {
|
||||
item: Extract<UserImage, { type: "location" }>;
|
||||
};
|
||||
|
||||
export const SearchCardLocation = ({ item }: Props) => {
|
||||
const { data } = item;
|
||||
|
||||
return (
|
||||
<div class="h-full inset-0 p-3 bg-red-50">
|
||||
<div class="flex mb-1 items-center gap-1">
|
||||
<IconMapPin size={14} class="text-neutral-500" />
|
||||
<p class="text-xs text-neutral-500">Location</p>
|
||||
</div>
|
||||
<p class="text-sm text-neutral-900 font-bold mb-1">
|
||||
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
|
||||
</p>
|
||||
<p class="text-xs text-neutral-700">Address: {data.Address}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -7,26 +7,9 @@ import {
|
||||
createResource,
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
import {
|
||||
CategoryUnion,
|
||||
getUserImages,
|
||||
JustTheImageWhatAreTheseNames,
|
||||
List,
|
||||
UserImage,
|
||||
} from "../network";
|
||||
import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage";
|
||||
|
||||
type TaggedCategory<T extends CategoryUnion["type"]> = Extract<
|
||||
CategoryUnion,
|
||||
{ type: T }
|
||||
>["data"];
|
||||
|
||||
type CategoriesSpecificData = {
|
||||
[K in CategoryUnion["type"]]: Array<TaggedCategory<K>>;
|
||||
};
|
||||
import { getUserImages, JustTheImageWhatAreTheseNames } from "../network";
|
||||
|
||||
export type SearchImageStore = {
|
||||
images: Accessor<UserImage[]>;
|
||||
imagesByDate: Accessor<
|
||||
Array<{ date: Date; images: JustTheImageWhatAreTheseNames }>
|
||||
>;
|
||||
@ -35,13 +18,10 @@ export type SearchImageStore = {
|
||||
|
||||
userImages: Accessor<JustTheImageWhatAreTheseNames>;
|
||||
|
||||
imagesWithProperties: Accessor<ReturnType<typeof groupPropertiesWithImage>>;
|
||||
processingImages: Accessor<
|
||||
Awaited<ReturnType<typeof getUserImages>>["ProcessingImages"] | undefined
|
||||
>;
|
||||
|
||||
categories: Accessor<CategoriesSpecificData>;
|
||||
|
||||
onRefetchImages: () => void;
|
||||
};
|
||||
|
||||
@ -49,15 +29,6 @@ const SearchImageContext = createContext<SearchImageStore>();
|
||||
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
const [data, { refetch }] = createResource(getUserImages);
|
||||
|
||||
const imageData = createMemo(() => {
|
||||
const d = data();
|
||||
if (d == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return d.ImageProperties;
|
||||
});
|
||||
|
||||
const sortedImages = createMemo<ReturnType<SearchImageStore["imagesByDate"]>>(
|
||||
() => {
|
||||
const d = data();
|
||||
@ -89,42 +60,14 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
|
||||
const processingImages = () => data()?.ProcessingImages ?? [];
|
||||
|
||||
const imagesWithProperties = createMemo<
|
||||
ReturnType<typeof groupPropertiesWithImage>
|
||||
>(() => {
|
||||
const d = data();
|
||||
if (d == null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return groupPropertiesWithImage(d);
|
||||
});
|
||||
|
||||
const categories = createMemo(() => {
|
||||
const c: ReturnType<SearchImageStore["categories"]> = {
|
||||
contact: [],
|
||||
event: [],
|
||||
location: [],
|
||||
};
|
||||
|
||||
for (const category of data()?.ImageProperties ?? []) {
|
||||
c[category.type].push(category.data as any);
|
||||
}
|
||||
|
||||
return c;
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchImageContext.Provider
|
||||
value={{
|
||||
images: imageData,
|
||||
imagesByDate: sortedImages,
|
||||
lists: () => data()?.Lists ?? [],
|
||||
imagesWithProperties: imagesWithProperties,
|
||||
userImages: () => data()?.UserImages ?? [],
|
||||
processingImages,
|
||||
onRefetchImages: refetch,
|
||||
categories,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
string,
|
||||
union,
|
||||
uuid,
|
||||
variant,
|
||||
} from "valibot";
|
||||
|
||||
type BaseRequestParams = Partial<{
|
||||
@ -85,62 +84,6 @@ export const sendImage = async (
|
||||
return parse(sendImageResponseValidator, res);
|
||||
};
|
||||
|
||||
const locationValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
CreatedAt: pipe(string()),
|
||||
Name: string(),
|
||||
Address: nullable(string()),
|
||||
Description: nullable(string()),
|
||||
Images: array(pipe(string(), uuid())),
|
||||
});
|
||||
|
||||
const contactValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
CreatedAt: pipe(string()),
|
||||
Name: string(),
|
||||
Description: nullable(string()),
|
||||
PhoneNumber: nullable(string()),
|
||||
Email: nullable(string()),
|
||||
Images: array(pipe(string(), uuid())),
|
||||
});
|
||||
|
||||
const eventValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
CreatedAt: nullable(pipe(string())),
|
||||
Name: string(),
|
||||
StartDateTime: nullable(pipe(string())),
|
||||
EndDateTime: nullable(pipe(string())),
|
||||
Description: nullable(string()),
|
||||
LocationID: nullable(pipe(string(), uuid())),
|
||||
// Location: nullable(locationValidator),
|
||||
OrganizerID: nullable(pipe(string(), uuid())),
|
||||
// Organizer: nullable(contactValidator),
|
||||
Images: array(pipe(string(), uuid())),
|
||||
});
|
||||
|
||||
const locationDataType = strictObject({
|
||||
type: literal("location"),
|
||||
data: locationValidator,
|
||||
});
|
||||
|
||||
const eventDataType = strictObject({
|
||||
type: literal("event"),
|
||||
data: eventValidator,
|
||||
});
|
||||
|
||||
const contactDataType = strictObject({
|
||||
type: literal("contact"),
|
||||
data: contactValidator,
|
||||
});
|
||||
|
||||
const dataTypeValidator = variant("type", [
|
||||
locationDataType,
|
||||
eventDataType,
|
||||
contactDataType,
|
||||
]);
|
||||
|
||||
export type CategoryUnion = InferOutput<typeof dataTypeValidator>;
|
||||
|
||||
const imageMetaValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageName: string(),
|
||||
@ -168,8 +111,6 @@ const userProcessingImageValidator = strictObject({
|
||||
]),
|
||||
});
|
||||
|
||||
export type UserImage = InferOutput<typeof dataTypeValidator>;
|
||||
|
||||
const listValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
UserID: pipe(string(), uuid()),
|
||||
@ -212,7 +153,6 @@ export type List = InferOutput<typeof listValidator>;
|
||||
|
||||
const imageRequestValidator = strictObject({
|
||||
UserImages: array(userImageValidator),
|
||||
ImageProperties: array(dataTypeValidator),
|
||||
ProcessingImages: array(userProcessingImageValidator),
|
||||
Lists: array(listValidator),
|
||||
});
|
||||
@ -233,15 +173,6 @@ export const getUserImages = async (): Promise<
|
||||
return parse(imageRequestValidator, res);
|
||||
};
|
||||
|
||||
export const getImage = async (imageId: string): Promise<UserImage> => {
|
||||
const request = getBaseAuthorizedRequest({
|
||||
path: `image-properties/${imageId}`,
|
||||
});
|
||||
|
||||
const res = await fetch(request).then((res) => res.json());
|
||||
return parse(dataTypeValidator, res);
|
||||
};
|
||||
|
||||
export const postLogin = async (email: string): Promise<void> => {
|
||||
const request = getBaseRequest({
|
||||
path: "login",
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { ImageComponent } from "@components/image";
|
||||
import { ItemModal } from "@components/item-modal/ItemModal";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { Component, For, Show } from "solid-js";
|
||||
|
||||
export const Entity: Component = () => {
|
||||
const params = useParams<{ entityId: string }>();
|
||||
|
||||
const { images } = useSearchImageContext();
|
||||
|
||||
const entity = () => images().find((i) => i.data.ID === params.entityId);
|
||||
|
||||
return (
|
||||
<Show when={entity()} fallback={<>Sorry, this entity could not be found</>}>
|
||||
{(e) => (
|
||||
<div>
|
||||
<ItemModal item={e()} />
|
||||
<div class="w-full grid grid-cols-4 auto-rows-[minmax(100px,1fr)] gap-4 bg-white p-4 rounded-xl border border-neutral-200">
|
||||
<For each={e().data.Images}>
|
||||
{(imageId) => <ImageComponent ID={imageId} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
};
|
@ -1,21 +1,8 @@
|
||||
import { Component, For } from "solid-js";
|
||||
import { A } from "@solidjs/router";
|
||||
import {
|
||||
SearchImageStore,
|
||||
useSearchImageContext,
|
||||
} from "@contexts/SearchImageContext";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import fastHashCode from "../../utils/hash";
|
||||
|
||||
// TODO: lots of stuff to do with Entities, this could be seperated into a centralized place.
|
||||
const CategoryColor: Record<
|
||||
keyof ReturnType<SearchImageStore["categories"]>,
|
||||
string
|
||||
> = {
|
||||
contact: "bg-orange-50",
|
||||
location: "bg-red-50",
|
||||
event: "bg-purple-50",
|
||||
};
|
||||
|
||||
const colors = [
|
||||
"bg-emerald-50",
|
||||
"bg-lime-50",
|
||||
@ -31,30 +18,10 @@ const colors = [
|
||||
];
|
||||
|
||||
export const Categories: Component = () => {
|
||||
const { categories, lists } = useSearchImageContext();
|
||||
const { lists } = useSearchImageContext();
|
||||
|
||||
return (
|
||||
<div class="rounded-xl bg-white p-4 flex flex-col gap-2">
|
||||
<h2 class="text-xl font-bold">Entities</h2>
|
||||
<div class="w-full grid grid-cols-4 auto-rows-[minmax(100px,1fr)] gap-4">
|
||||
<For each={Object.entries(categories())}>
|
||||
{([category, group]) => (
|
||||
<A
|
||||
href={`/gallery/${category}`}
|
||||
class={
|
||||
"col-span-2 flex flex-col justify-center items-center rounded-lg p-4 border border-neutral-200 " +
|
||||
"capitalize " +
|
||||
CategoryColor[category as keyof typeof CategoryColor] +
|
||||
" " +
|
||||
(group.length === 0 ? "row-span-1 order-10" : "row-span-2")
|
||||
}
|
||||
>
|
||||
<p class="text-xl font-bold">{category}s</p>
|
||||
<p class="text-lg">{group.length}</p>
|
||||
</A>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<h2 class="text-xl font-bold">Generated Lists</h2>
|
||||
<div class="w-full grid grid-cols-3 auto-rows-[minmax(100px,1fr)] gap-4">
|
||||
<For each={lists()}>
|
||||
|
@ -1,52 +0,0 @@
|
||||
import { Component, For, Show } from "solid-js";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { union, literal, safeParse, InferOutput, parse } from "valibot";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { SearchCard } from "@components/search-card/SearchCard";
|
||||
|
||||
const entityValidator = union([
|
||||
literal("event"),
|
||||
literal("note"),
|
||||
literal("location"),
|
||||
literal("contact"),
|
||||
]);
|
||||
|
||||
const EntityGallery: Component<{
|
||||
entity: InferOutput<typeof entityValidator>;
|
||||
}> = (props) => {
|
||||
// Just to be doubly sure.
|
||||
parse(entityValidator, props.entity);
|
||||
|
||||
// These names are being silly. Entity or Category?
|
||||
const { images } = useSearchImageContext();
|
||||
|
||||
const filteredCategories = () =>
|
||||
images().filter((i) => i.type === props.entity);
|
||||
|
||||
return (
|
||||
<div class="w-full flex flex-col gap-4 capitalize bg-white rounded-xl p-4">
|
||||
<h2 class="font-bold text-xl">
|
||||
{props.entity}s ({filteredCategories().length})
|
||||
</h2>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<For each={filteredCategories()}>
|
||||
{(category) => <SearchCard item={category} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Gallery: Component = () => {
|
||||
const params = useParams();
|
||||
const validated = safeParse(entityValidator, params.entity);
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={validated.success}
|
||||
fallback={<p>Sorry, this entity is not supported</p>}
|
||||
>
|
||||
<EntityGallery entity={validated.output as any} />
|
||||
</Show>
|
||||
);
|
||||
};
|
@ -1,25 +1,16 @@
|
||||
import { ImageComponent } from "@components/image";
|
||||
import { SearchCard } from "@components/search-card/SearchCard";
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { UserImage } from "@network/index";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { createEffect, For, Show, type Component } from "solid-js";
|
||||
import { type Component } from "solid-js";
|
||||
import SolidjsMarkdown from "solidjs-markdown";
|
||||
|
||||
export const ImagePage: Component = () => {
|
||||
const { imageId } = useParams<{ imageId: string }>();
|
||||
|
||||
const { imagesWithProperties, userImages } = useSearchImageContext();
|
||||
const { userImages } = useSearchImageContext();
|
||||
|
||||
const image = () => userImages().find((i) => i.ImageID === imageId);
|
||||
|
||||
createEffect(() => {
|
||||
console.log(userImages());
|
||||
});
|
||||
|
||||
const imageProperties = (): UserImage[] | undefined =>
|
||||
Object.entries(imagesWithProperties()).find(([id]) => id === imageId)?.[1];
|
||||
|
||||
return (
|
||||
<main class="flex flex-col items-center gap-4">
|
||||
<div class="w-full bg-white rounded-xl p-4">
|
||||
@ -29,15 +20,7 @@ export const ImagePage: Component = () => {
|
||||
<h2 class="font-bold text-xl">Description</h2>
|
||||
<SolidjsMarkdown>{image()?.Image.Description}</SolidjsMarkdown>
|
||||
</div>
|
||||
<div class="w-full grid grid-cols-3 gap-2 grid-flow-row-dense p-4 bg-white rounded-xl">
|
||||
<Show when={imageProperties()}>
|
||||
{(image) => (
|
||||
<For each={image()}>
|
||||
{(property) => <SearchCard item={property} />}
|
||||
</For>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<div class="w-full grid grid-cols-3 gap-2 grid-flow-row-dense p-4 bg-white rounded-xl"></div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,7 @@
|
||||
export * from "./front";
|
||||
export * from "./gallery";
|
||||
export * from "./image";
|
||||
export * from "./settings";
|
||||
export * from "./login";
|
||||
export * from "./entity";
|
||||
export * from "./search";
|
||||
export * from "./all-images";
|
||||
export * from "./list";
|
||||
|
@ -2,13 +2,13 @@ import { Component, createSignal, For } from "solid-js";
|
||||
import { Search } from "@kobalte/core/search";
|
||||
import { IconSearch } from "@tabler/icons-solidjs";
|
||||
import { useSearch } from "./search";
|
||||
import { UserImage } from "@network/index";
|
||||
import { SearchCard } from "@components/search-card/SearchCard";
|
||||
import { JustTheImageWhatAreTheseNames } from "@network/index";
|
||||
|
||||
export const SearchPage: Component = () => {
|
||||
const fuse = useSearch();
|
||||
|
||||
const [searchItems, setSearchItems] = createSignal<UserImage[]>([]);
|
||||
const [searchItems, setSearchItems] =
|
||||
createSignal<JustTheImageWhatAreTheseNames>([]);
|
||||
|
||||
return (
|
||||
<Search
|
||||
@ -36,7 +36,9 @@ export const SearchPage: Component = () => {
|
||||
<Search.Portal>
|
||||
<Search.Content class="container relative w-full rounded-xl bg-white p-4 grid grid-cols-3 gap-4">
|
||||
<Search.Arrow />
|
||||
<For each={searchItems()}>{(item) => <SearchCard item={item} />}</For>
|
||||
<For each={searchItems()}>
|
||||
{(item) => <div>{item.Image.Description}</div>}
|
||||
</For>
|
||||
<Search.NoResult>No result found</Search.NoResult>
|
||||
</Search.Content>
|
||||
</Search.Portal>
|
||||
|
@ -1,46 +1,12 @@
|
||||
import { useSearchImageContext } from "@contexts/SearchImageContext";
|
||||
import { UserImage } from "@network/index";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
// This language is stupid. `keyof` only returns common keys but this somehow doesnt.
|
||||
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
||||
|
||||
const weightedTerms: Record<
|
||||
KeysOfUnion<UserImage["data"]>,
|
||||
number | undefined
|
||||
> = {
|
||||
ID: undefined,
|
||||
LocationID: undefined,
|
||||
OrganizerID: undefined,
|
||||
Images: undefined,
|
||||
|
||||
Description: 10,
|
||||
|
||||
Name: 5,
|
||||
Address: 2,
|
||||
|
||||
PhoneNumber: 2,
|
||||
Email: 2,
|
||||
|
||||
CreatedAt: 1,
|
||||
StartDateTime: 1,
|
||||
EndDateTime: 1,
|
||||
};
|
||||
|
||||
export const useSearch = () => {
|
||||
const { images, userImages } = useSearchImageContext();
|
||||
|
||||
const imageDescriptions = () =>
|
||||
userImages().map((i) => ({ data: { Description: i.Image.Description } }));
|
||||
const { userImages } = useSearchImageContext();
|
||||
|
||||
return () =>
|
||||
new Fuse([...images(), ...imageDescriptions()], {
|
||||
new Fuse(userImages(), {
|
||||
shouldSort: true,
|
||||
keys: Object.entries(weightedTerms)
|
||||
.filter(([, w]) => w != null)
|
||||
.map(([name, weight]) => ({
|
||||
name: `data.${name}`,
|
||||
weight,
|
||||
})),
|
||||
keys: ["Image.Description"],
|
||||
});
|
||||
};
|
||||
|
@ -1,16 +0,0 @@
|
||||
import type { getUserImages } from "../network";
|
||||
|
||||
export const groupPropertiesWithImage = ({
|
||||
UserImages,
|
||||
ImageProperties,
|
||||
}: Awaited<ReturnType<typeof getUserImages>>) => {
|
||||
const imageToProperties: Record<string, typeof ImageProperties> = {};
|
||||
|
||||
for (const image of UserImages) {
|
||||
imageToProperties[image.ImageID] = ImageProperties.filter((i) =>
|
||||
i.data.Images.includes(image.ImageID),
|
||||
);
|
||||
}
|
||||
|
||||
return imageToProperties;
|
||||
};
|
Reference in New Issue
Block a user