diff --git a/app/go.mod b/app/go.mod
index 097a2b2c..770d0734 100644
--- a/app/go.mod
+++ b/app/go.mod
@@ -5,6 +5,7 @@ go 1.22.1
replace github.com/jlewi/foyle/protos/go => ../protos/go
require (
+ connectrpc.com/connect v1.16.1
github.com/Kunde21/markdownfmt/v3 v3.1.0
github.com/agnivade/levenshtein v1.1.1
github.com/cockroachdb/pebble v1.1.0
@@ -37,9 +38,12 @@ require (
go.opentelemetry.io/otel/trace v1.25.0
go.uber.org/zap v1.27.0
gonum.org/v1/gonum v0.15.0
+ google.golang.org/api v0.162.0
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1
+ k8s.io/apimachinery v0.27.3
+ sigs.k8s.io/kustomize/kyaml v0.13.9
)
require (
@@ -189,7 +193,6 @@ require (
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.15.0 // indirect
- google.golang.org/api v0.162.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
@@ -198,13 +201,11 @@ require (
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- k8s.io/apimachinery v0.27.3 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
- sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
diff --git a/app/go.sum b/app/go.sum
index e8d18c8b..687bed91 100644
--- a/app/go.sum
+++ b/app/go.sum
@@ -48,6 +48,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis=
+connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@@ -348,8 +350,6 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
-github.com/jlewi/hydros v0.0.6 h1:gylMzSH5VvO+uTtz20JUtJPPr3ZLAQxvH3j/0FHK4FE=
-github.com/jlewi/hydros v0.0.6/go.mod h1:4fV+JUCnexPY2ZbKzdfV/RsyrfralN832MsUSq/7FqE=
github.com/jlewi/hydros v0.0.7-0.20240503183011-8f99ead373fb h1:2G2k606S3Qcg40czr7gnkeIG5KgQ2wXJ1BMxAuC+P3I=
github.com/jlewi/hydros v0.0.7-0.20240503183011-8f99ead373fb/go.mod h1:4fV+JUCnexPY2ZbKzdfV/RsyrfralN832MsUSq/7FqE=
github.com/jlewi/monogo v0.0.0-20240123191147-401afe194d74 h1:pbOw/rOMs0AZ494bGnI6DieGKwqoJQEjHWaJZrvxsJo=
diff --git a/app/pkg/eval/evaluator.go b/app/pkg/eval/evaluator.go
index 3329649f..99e30fd4 100644
--- a/app/pkg/eval/evaluator.go
+++ b/app/pkg/eval/evaluator.go
@@ -501,6 +501,8 @@ func (e *Evaluator) loadFoyleFiles(ctx context.Context, db *pebble.DB, files []s
continue
}
+ // TODO(https://github.com/jlewi/foyle/issues/95): We should assign an ID to each example that is stable
+ // across experiments.
id := uuid.NewString()
example := &v1alpha1.Example{
Id: id,
diff --git a/app/pkg/eval/service.go b/app/pkg/eval/service.go
new file mode 100644
index 00000000..464ff8e0
--- /dev/null
+++ b/app/pkg/eval/service.go
@@ -0,0 +1,71 @@
+package eval
+
+import (
+ "context"
+
+ "connectrpc.com/connect"
+ "github.com/cockroachdb/pebble"
+ "github.com/jlewi/foyle/app/pkg/logs"
+ "github.com/jlewi/foyle/protos/go/foyle/v1alpha1"
+ "github.com/jlewi/monogo/helpers"
+ "github.com/pkg/errors"
+ "google.golang.org/protobuf/proto"
+)
+
+// EvalServer is the server that implements the Eval service interface.
+// This is used to make results available to the frontend.
+type EvalServer struct{}
+
+func (s *EvalServer) List(
+ ctx context.Context,
+ req *connect.Request[v1alpha1.EvalResultListRequest],
+) (*connect.Response[v1alpha1.EvalResultListResponse], error) {
+ log := logs.FromContext(ctx)
+
+ if req.Msg.GetDatabase() == "" {
+ err := connect.NewError(connect.CodeInvalidArgument, errors.New("Request is missing database"))
+ log.Error(err, "Invalid EvalResultListRequest")
+ return nil, err
+ }
+
+ db, err := pebble.Open(req.Msg.GetDatabase(), &pebble.Options{})
+ if err != nil {
+ log.Error(err, "Failed to open database")
+ return nil, connect.NewError(connect.CodeInternal, err)
+ }
+ defer helpers.DeferIgnoreError(db.Close)
+
+ iter, err := db.NewIterWithContext(ctx, nil)
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, err)
+ }
+ defer iter.Close()
+
+ results := &v1alpha1.EvalResultListResponse{
+ Items: make([]*v1alpha1.EvalResult, 0, 100),
+ }
+
+ for iter.First(); iter.Valid(); iter.Next() {
+ key := iter.Key()
+ if key == nil {
+ break
+ }
+
+ value, err := iter.ValueAndErr()
+ if err != nil {
+ log.Error(err, "Failed to read value for key", "key", string(key))
+ continue
+ }
+
+ result := &v1alpha1.EvalResult{}
+ if err := proto.Unmarshal(value, result); err != nil {
+ log.Error(err, "Failed to unmarshal value for", "key", string(key))
+ continue
+ }
+ results.Items = append(results.Items, result)
+ }
+
+ res := connect.NewResponse(results)
+ res.Header().Set("Eval-Version", "v1alpha1")
+ return res, nil
+}
diff --git a/app/pkg/logsviewer/app.go b/app/pkg/logsviewer/app.go
new file mode 100644
index 00000000..e803a30c
--- /dev/null
+++ b/app/pkg/logsviewer/app.go
@@ -0,0 +1,99 @@
+package logsviewer
+
+import (
+ "github.com/go-logr/zapr"
+ "github.com/maxence-charriere/go-app/v9/pkg/app"
+ "github.com/pkg/errors"
+ "go.uber.org/zap"
+)
+
+type page string
+type view string
+
+const (
+ getAction = "/get"
+ setPage = "/setPage"
+
+ errorView view = "error"
+ generatedBlockView view = "generatedBlock"
+ executedBlockView view = "executedBlock"
+ rawView view = "raw"
+
+ blockLogsView page = "blockLogs"
+ evalsView page = "evals"
+
+ getErrorState = "/getError"
+ blockLogState = "/blocklog"
+)
+
+// MainApp is the main window of the application.
+//
+// The main application consists of a left hand navigation bar and a right hand side component that is the page
+// to display. When you click on one of the left hand navigation buttons it fires of an action setPage to change the
+// view. The handler for this action loads the appropriate page and sets MainApp.page to the component for that
+// page.
+type MainApp struct {
+ app.Compo
+ // Page keeps track of the page to display in the right hand side.
+ page app.UI
+}
+
+func (m *MainApp) Render() app.UI {
+ if m.page == nil {
+ // TODO(jeremy): Could we keep track of the last view so if we refresh we show the same data?
+ // One way to do that is to update the URL with query arguments containing the relevant state information.
+ // Then when we click refresh we could get the information directly from the URL
+ m.page = &BlockViewer{}
+ }
+ return app.Div().Class("main-layout").Body(
+ app.Div().Class("content").Body(
+ app.Div().Class("sidebar").Body(
+ &navigationBar{},
+ ),
+ app.Div().Class("page-window").Body(
+ m.page,
+ ),
+ ), &StatusBar{},
+ )
+}
+
+func (m *MainApp) OnMount(ctx app.Context) {
+ // register to handle the setPage action
+ ctx.Handle(setPage, m.handleSetPage)
+}
+
+// handleSetPage handles the setPage action. The event will tell us which view to display.
+func (m *MainApp) handleSetPage(ctx app.Context, action app.Action) {
+ log := zapr.NewLogger(zap.L())
+ pageValue, ok := action.Value.(page)
+ if !ok {
+ log.Error(errors.New("No page provided"), "Invalid action")
+ return
+ }
+ log.Info("Handling set page action", "page", pageValue)
+ switch pageValue {
+ case blockLogsView:
+ if _, ok := m.page.(*BlockViewer); !ok {
+ log.Info("Setting page to BlockViewer")
+ m.page = &BlockViewer{}
+ }
+ case evalsView:
+ if _, ok := m.page.(*EvalViewer); !ok {
+ log.Info("Setting page to EvalViewer")
+ m.page = &EvalViewer{}
+ }
+ }
+ // We need to call update to trigger a re-render of the component.
+ m.Update()
+}
+
+// StatusBar at the bottom of the page. Inspired by the vscode/intellij status bar.
+// We use this to show useful information like the version number.
+type StatusBar struct {
+ app.Compo
+}
+
+func (s *StatusBar) Render() app.UI {
+ version := app.Getenv("GOAPP_VERSION")
+ return app.Div().Class("status-bar").Text("goapp version: " + version)
+}
diff --git a/app/pkg/logsviewer/mainwindow.go b/app/pkg/logsviewer/block_viewer.go
similarity index 54%
rename from app/pkg/logsviewer/mainwindow.go
rename to app/pkg/logsviewer/block_viewer.go
index 664cc9c0..f1f5a807 100644
--- a/app/pkg/logsviewer/mainwindow.go
+++ b/app/pkg/logsviewer/block_viewer.go
@@ -11,28 +11,85 @@ import (
"go.uber.org/zap"
)
-// mainWindow is the main window of the application.
+// BlockViewer is the page that displays the block logs.
+//
+// How it works:
+// Clicking load fetches the blocklog from the server.
+// The log is then stored in the application context (https://go-app.dev/states)
+// this allows other components to use it. Load then fires off an UpdateView event to trigger
+// the blockLogView to update its content.
+// The UpdateView event takes a string argument which is what view should be rendered.
+// There is a left hand navigation bar with buttons to display different views of the current log.
+// Changing the view is achieved by sending UpdateView events to change the view
+type BlockViewer struct {
+ app.Compo
+ main *blockLogView
+}
+
+func (c *BlockViewer) Render() app.UI {
+ if c.main == nil {
+ c.main = &blockLogView{}
+ }
+ return app.Div().Class("main-layout").Body(
+ app.Div().Class("header").Body(
+ &blockSelector{},
+ ),
+ app.Div().Class("content").Body(
+ app.Div().Class("sidebar").Body(
+ &sideBar{},
+ ),
+ app.Div().Class("main-window").Body(
+ c.main,
+ ),
+ ),
+ )
+}
+
+// sideBar adds a navigation bar between the views to the left side.
+type sideBar struct {
+ app.Compo
+}
+
+func (s *sideBar) Render() app.UI {
+ return app.Div().Body(
+ // Each button needs to be enclosed in a div. Otherwise events get triggered for all the buttons.
+ app.Div().Body(
+ app.Button().Text("Generated Block").OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(getAction, generatedBlockView)
+ }),
+ ),
+ app.Div().Body(
+ app.Button().Text("Executed Block")).OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(getAction, executedBlockView)
+ }),
+ app.Div().Body(
+ app.Button().Text("Raw")).OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(getAction, rawView)
+ }))
+}
+
+// blockLogView is the main window of the application.
// What it displays will change depending on the view selected.
// The content of the main window is HTML which gets set by the action handler for different events.
//
// The main window registers a handler for the getAction event. The getAction event is triggered when ever
// a blockLog is loaded. The handler for the getAction event will set the HTML content of the main windowß
-type mainWindow struct {
+type blockLogView struct {
app.Compo
HTMLContent string
}
-func (m *mainWindow) Render() app.UI {
+func (m *blockLogView) Render() app.UI {
// Raw requires the value to have a single root element. So we enclose the HTML content in a div to ensure
// that is all ways true.
return app.Raw("
" + m.HTMLContent + "
")
}
-func (m *mainWindow) OnMount(ctx app.Context) {
+func (m *blockLogView) OnMount(ctx app.Context) {
ctx.Handle(getAction, m.handleGetAction)
}
-func (m *mainWindow) handleGetAction(ctx app.Context, action app.Action) {
+func (m *blockLogView) handleGetAction(ctx app.Context, action app.Action) {
log := zapr.NewLogger(zap.L())
viewValue, ok := action.Value.(view) // Checks if a name was given.
if !ok {
diff --git a/app/pkg/logsviewer/eval_viewer.go b/app/pkg/logsviewer/eval_viewer.go
new file mode 100644
index 00000000..f25339ea
--- /dev/null
+++ b/app/pkg/logsviewer/eval_viewer.go
@@ -0,0 +1,332 @@
+package logsviewer
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "connectrpc.com/connect"
+ "github.com/go-logr/zapr"
+ "github.com/jlewi/foyle/protos/go/foyle/v1alpha1"
+ "github.com/jlewi/foyle/protos/go/foyle/v1alpha1/v1alpha1connect"
+ "github.com/maxence-charriere/go-app/v9/pkg/app"
+ "github.com/pkg/errors"
+ "go.uber.org/zap"
+ "google.golang.org/protobuf/encoding/protojson"
+)
+
+type evalViews string
+
+const (
+ loadEvalResults = "/loadEvalResults"
+ databaseInputID = "databaseInput"
+
+ setEvalView = "/setEvalView"
+
+ evalQueryView evalViews = "evalQueryView"
+ evalActualAnswerView evalViews = "evalActualAnswerView"
+ evalExpectedAnswerView evalViews = "evalExpectedAnswerView"
+ evalRawView evalViews = "evalRawView"
+)
+
+var (
+ // resultSet keeps track of the current loaded result set. This allows us to easily access it from multiple
+ // elements. I guess we could also pass it around using go-app context but this seems easier.
+ resultSet *ResultSet
+)
+
+// EvalViewer is the page that displays an eval result.
+type EvalViewer struct {
+ app.Compo
+ main *evalView
+ resultsTable *EvalResultsTable
+}
+
+func (c *EvalViewer) Render() app.UI {
+ if c.main == nil {
+ c.main = &evalView{}
+ }
+ if c.resultsTable == nil {
+ c.resultsTable = &EvalResultsTable{}
+ }
+ loadButton := app.Button().
+ Text("Load").
+ OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewAction(loadEvalResults)
+ })
+
+ // The top row will contain the input box to select the database
+ // and the results table to scroll though them.
+ // These will be arranged vertically in the row
+ topRow := app.Div().Class("row").Body(
+ app.Div().Body(
+ app.Input().
+ Type("text").
+ ID(databaseInputID).
+ Value("/Users/jlewi/foyle_experiments/learning"),
+ loadButton,
+ ),
+ app.Div().Body(
+ c.resultsTable,
+ ))
+
+ // The bottom row will contain the main window.
+ bottomRow := app.Div().Class("row").Body(
+ app.Div().Class("sidebar").Body(
+ &evalSideBar{},
+ ),
+ app.Div().Class("main-window").Body(
+ c.main,
+ ),
+ )
+ return app.Div().Body(topRow, bottomRow)
+}
+
+// evalSideBar adds a navigation bar between the views to the left side.
+type evalSideBar struct {
+ app.Compo
+}
+
+func (s *evalSideBar) Render() app.UI {
+ return app.Div().Body(
+ // Each button needs to be enclosed in a div. Otherwise events get triggered for all the buttons.
+ app.Div().Body(
+ app.Button().Text("Query").OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(setEvalView, evalQueryView)
+ }),
+ ),
+ app.Div().Body(
+ app.Button().Text("Actual Answer").OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(setEvalView, evalActualAnswerView)
+ }),
+ ),
+ app.Div().Body(
+ app.Button().Text("Expected Answer")).OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(setEvalView, evalExpectedAnswerView)
+ }),
+ app.Div().Body(
+ app.Button().Text("Raw")).OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(setEvalView, evalRawView)
+ }))
+}
+
+type EvalResultsTable struct {
+ app.Compo
+ SelectedRow int
+}
+
+func (c *EvalResultsTable) OnMount(ctx app.Context) {
+ ctx.Handle(loadEvalResults, c.handleLoadEvalResults)
+}
+
+func (c *EvalResultsTable) handleLoadEvalResults(ctx app.Context, action app.Action) {
+ log := zapr.NewLogger(zap.L())
+ log.Info("Handling loadEvalResults")
+
+ if resultSet == nil {
+ resultSet = &ResultSet{}
+ }
+
+ database := app.Window().GetElementByID(databaseInputID).Get("value").String()
+ database = strings.TrimSpace(database)
+ if database == "" {
+ // TODO(jeremy): We should surface an error message. We could use the status bar to show the error message
+ log.Info("No database provided; can't load eval results")
+ return
+ }
+
+ // TODO(jeremy): We should cache the client; see GetClient in block_viewer.go for an example.
+ client := v1alpha1connect.NewEvalServiceClient(
+ http.DefaultClient,
+ // TODO(jeremy): How should we make this configurable?
+ "http://localhost:8080/api",
+ )
+
+ listRequest := &v1alpha1.EvalResultListRequest{
+ // TODO(jeremy): We need a UI element to let you enter this
+ Database: database,
+ }
+
+ res, err := client.List(
+ context.Background(),
+ connect.NewRequest(listRequest),
+ )
+
+ if err != nil {
+ log.Error(err, "Error listing eval results")
+ return
+ }
+
+ resultSet.data = res.Msg.Items
+ resultSet.selected = 0
+ log.Info("Loaded eval results", "numResults", len(resultSet.data), "instance", c)
+ c.SelectedRow = 1
+ c.Update()
+}
+
+func (c *EvalResultsTable) Render() app.UI {
+ log := zapr.NewLogger(zap.L())
+ log.Info("Rendering EvalResultsTable", "instance", c)
+ if resultSet == nil {
+ log.Info("Data is nil", "instance", c)
+ resultSet = &ResultSet{}
+ }
+
+ if resultSet.data == nil {
+ resultSet.data = make([]*v1alpha1.EvalResult, 0)
+ }
+
+ table := app.Table().Body(
+ app.Tr().Body(
+ app.Th().Text("ID"),
+ app.Th().Text("File"),
+ app.Th().Text("Distance"),
+ app.Th().Text("Normalized Distance"),
+ ),
+ app.Range(resultSet.data).Slice(func(i int) app.UI {
+ rowStyle := ""
+ if i == c.SelectedRow {
+ rowStyle = "selected-row" // This is a CSS class that you need to define
+ }
+ row := app.Tr().Class(rowStyle).Body(
+ app.Td().Text(resultSet.data[i].GetExample().GetId()),
+ app.Td().Text(resultSet.data[i].GetExampleFile()),
+ app.Td().Text(resultSet.data[i].GetDistance()),
+ app.Td().Text(resultSet.data[i].GetNormalizedDistance()),
+ )
+
+ // For each row we add a click handler to display the corresponding example.
+ row.OnClick(func(ctx app.Context, e app.Event) {
+ log := zapr.NewLogger(zap.L())
+ log.Info("Selected row", "row", i)
+ // Mark the selected row and trigger the update.
+ // This will redraw the table and change the style on the selected row.
+ c.SelectedRow = i
+ resultSet.selected = i
+ c.Update()
+
+ // TODO(jeremy): We should fire an event and change the context to display the evaluation result.
+ })
+ return row
+ }),
+ )
+ div := app.Div().Class("scrollable-table").Body(table)
+ return div
+}
+
+// evalView is the main viewer of the evaluation viewer.
+// What it displays will change depending on the view selected.
+// The content of the window is HTML which gets set by the action handler for different events.
+//
+// The view registers a handler for the setEvalViewAction event. The setEvalViewAction event is triggered when ever
+// the view needs to be changed; e.g. because the view has changed or the selected data has changed
+type evalView struct {
+ app.Compo
+ HTMLContent string
+}
+
+func (m *evalView) Render() app.UI {
+ // Raw requires the value to have a single root element. So we enclose the HTML content in a div to ensure
+ // that is all ways true.
+ return app.Raw("" + m.HTMLContent + "
")
+}
+
+func (m *evalView) OnMount(ctx app.Context) {
+ ctx.Handle(setEvalView, m.handleSetEvalView)
+}
+
+func (m *evalView) handleSetEvalView(ctx app.Context, action app.Action) {
+ log := zapr.NewLogger(zap.L())
+ viewValue, ok := action.Value.(evalViews) // Checks if a name was given.
+ if !ok {
+ log.Error(errors.New("No view provided"), "Invalid action")
+ return
+ }
+ log.Info("Handling get action", "view", viewValue)
+ switch viewValue {
+ case evalQueryView:
+ current := resultSet.GetSelected()
+ if current == nil {
+ m.HTMLContent = "No evaluation result is currently selected"
+ break
+ }
+ value, err := docToHTML(current.Example.Query)
+ if err == nil {
+ m.HTMLContent = value
+ } else {
+ log.Error(err, "Failed to convert generated block to html")
+ m.HTMLContent = fmt.Sprintf("Failed to convert generated block to html : error %+v", err)
+ }
+ case evalActualAnswerView:
+ current := resultSet.GetSelected()
+ if current == nil {
+ m.HTMLContent = "No evaluation result is currently selected"
+ break
+ }
+ doc := &v1alpha1.Doc{
+ Blocks: current.Actual,
+ }
+ value, err := docToHTML(doc)
+ if err == nil {
+ m.HTMLContent = value
+ } else {
+ log.Error(err, "Failed to convert actual answer to html")
+ m.HTMLContent = fmt.Sprintf("Failed to convert actual answer to html : error %+v", err)
+ }
+ case evalExpectedAnswerView:
+ current := resultSet.GetSelected()
+ if current == nil {
+ m.HTMLContent = "No evaluation result is currently selected"
+ break
+ }
+ doc := &v1alpha1.Doc{
+ Blocks: current.Example.Answer,
+ }
+ value, err := docToHTML(doc)
+ if err == nil {
+ m.HTMLContent = value
+ } else {
+ log.Error(err, "Failed to convert expected blocks to html")
+ m.HTMLContent = fmt.Sprintf("Failed to convert expected blocks to html : error %+v", err)
+ }
+ case evalRawView:
+ current := resultSet.GetSelected()
+ if current == nil {
+ m.HTMLContent = "No evaluation result is currently selected"
+ break
+ }
+ marshaler := protojson.MarshalOptions{
+ Indent: " ",
+ }
+ blockJson, err := marshaler.Marshal(current)
+ if err != nil {
+ log.Error(err, "Failed to turn result into json")
+ m.HTMLContent = fmt.Sprintf("Failed to turn blocklog into json: error %+v", err)
+ } else {
+ raw := "" + string(blockJson) + "
"
+ m.HTMLContent = raw
+ }
+ default:
+ m.HTMLContent = "Unknown view: " + string(viewValue)
+ }
+ // We need to call update to trigger a re-render of the component.
+ m.Update()
+}
+
+// ResultSet keeps track of the current loaded result set. This allows us to easily access it from multiple components.
+// N.B. we also might want to wrap the data with accessors so we can access data in a thread safe way
+type ResultSet struct {
+ data []*v1alpha1.EvalResult
+ selected int
+}
+
+func (c *ResultSet) GetSelected() *v1alpha1.EvalResult {
+ if c.data == nil || len(c.data) == 0 {
+ return nil
+ }
+ if c.selected < 0 || c.selected >= len(c.data) {
+ return nil
+ }
+ return c.data[c.selected]
+}
diff --git a/app/pkg/logsviewer/navigation.go b/app/pkg/logsviewer/navigation.go
new file mode 100644
index 00000000..82343e09
--- /dev/null
+++ b/app/pkg/logsviewer/navigation.go
@@ -0,0 +1,24 @@
+package logsviewer
+
+import "github.com/maxence-charriere/go-app/v9/pkg/app"
+
+// navigationBar is the left hand side navigation bar.
+// It is used to select the different pages in the application
+type navigationBar struct {
+ app.Compo
+}
+
+func (s *navigationBar) Render() app.UI {
+ return app.Div().Body(
+ // Each button needs to be enclosed in a div. Otherwise events get triggered for all the buttons.
+ app.Div().Body(
+ app.Button().Text("Block Logs").OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(setPage, blockLogsView)
+ }),
+ ),
+ app.Div().Body(
+ app.Button().Text("Eval Results")).OnClick(func(ctx app.Context, e app.Event) {
+ ctx.NewActionWithValue(setPage, evalsView)
+ }),
+ )
+}
diff --git a/app/pkg/logsviewer/viewer.go b/app/pkg/logsviewer/viewer.go
deleted file mode 100644
index e7d6e3ff..00000000
--- a/app/pkg/logsviewer/viewer.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package logsviewer
-
-import (
- "github.com/maxence-charriere/go-app/v9/pkg/app"
-)
-
-type view string
-
-const (
- getAction = "/et"
-
- errorView view = "error"
- generatedBlockView view = "generatedBlock"
- executedBlockView view = "executedBlock"
- rawView view = "raw"
-
- getErrorState = "/getError"
- blockLogState = "/blocklog"
-)
-
-// How it works:
-// Clicking load fetches the blocklog from the server.
-// The log is then stored in the application context (https://go-app.dev/states)
-// this allows other components to use it. Load then fires off an UpdateView event to trigger
-// the mainWindow to update its content.
-// The UpdateView event takes a string argument which is what view should be rendered.
-// There is a left hand navigation bar with buttons to display different views of the current log.
-// Changing the view is achieved by sending UpdateView events to change the view
-
-// MainApp is the main window of the application.
-type MainApp struct {
- app.Compo
- main *mainWindow
-}
-
-func (c *MainApp) Render() app.UI {
- if c.main == nil {
- c.main = &mainWindow{}
- }
- return app.Div().Class("main-layout").Body(
- app.Div().Class("header").Body(
- &blockSelector{},
- ),
- app.Div().Class("content").Body(
- app.Div().Class("sidebar").Body(
- &sideBar{},
- ),
- app.Div().Class("main-window").Body(
- c.main,
- ),
- ),
- )
-}
-
-// sideBar adds a navigation bar between the views to the left side.
-type sideBar struct {
- app.Compo
-}
-
-func (s *sideBar) Render() app.UI {
- return app.Div().Body(
- // Each button needs to be enclosed in a div. Otherwise events get triggered for all the buttons.
- app.Div().Body(
- app.Button().Text("Generated Block").OnClick(func(ctx app.Context, e app.Event) {
- ctx.NewActionWithValue(getAction, generatedBlockView)
- }),
- ),
- app.Div().Body(
- app.Button().Text("Executed Block")).OnClick(func(ctx app.Context, e app.Event) {
- ctx.NewActionWithValue(getAction, executedBlockView)
- }),
- app.Div().Body(
- app.Button().Text("Raw")).OnClick(func(ctx app.Context, e app.Event) {
- ctx.NewActionWithValue(getAction, rawView)
- }))
-}
diff --git a/app/pkg/logsviewer/views.go b/app/pkg/logsviewer/views.go
index 6143e907..deef8c50 100644
--- a/app/pkg/logsviewer/views.go
+++ b/app/pkg/logsviewer/views.go
@@ -3,6 +3,9 @@ package logsviewer
import (
"bytes"
+ "github.com/jlewi/foyle/app/pkg/docs"
+ "github.com/jlewi/foyle/protos/go/foyle/v1alpha1"
+
"github.com/go-logr/zapr"
"github.com/jlewi/foyle/app/api"
"github.com/pkg/errors"
@@ -49,3 +52,23 @@ func renderExecutedBlock(block *api.BlockLog) (string, error) {
return buf.String(), nil
}
+
+// docToHTML returns the dock as html
+func docToHTML(doc *v1alpha1.Doc) (string, error) {
+ if doc == nil {
+ return "", errors.New("doc is nil")
+ }
+ log := zapr.NewLogger(zap.L())
+
+ // Convert it to markdown
+ md := docs.DocToMarkdown(doc)
+
+ // Conver the markdown to html
+ var buf bytes.Buffer
+ if err := goldmark.Convert([]byte(md), &buf); err != nil {
+ log.Error(err, "Failed to convert markdown")
+ return "", err
+ }
+
+ return buf.String(), nil
+}
diff --git a/app/pkg/server/server.go b/app/pkg/server/server.go
index f1d30342..d13fe03b 100644
--- a/app/pkg/server/server.go
+++ b/app/pkg/server/server.go
@@ -6,6 +6,9 @@ import (
"fmt"
"time"
+ "github.com/jlewi/foyle/app/pkg/eval"
+ "github.com/jlewi/foyle/protos/go/foyle/v1alpha1/v1alpha1connect"
+
"github.com/jlewi/foyle/app/pkg/analyze"
"github.com/jlewi/foyle/app/pkg/logsviewer"
"github.com/maxence-charriere/go-app/v9/pkg/app"
@@ -218,9 +221,31 @@ func (s *Server) createGinEngine() error {
router.Use(corsMiddleWare)
}
+ // N.B. don't include leading or trailing slashes in the prefix because the code below assumes there isn't any
+ apiPrefix := "api"
// Add REST handlers for blocklogs
- router.GET("api/blocklogs/:id", s.logsCrud.GetBlockLog)
+ // TODO(jeremy): We should probably standardize on connect-rpc
+ router.GET(apiPrefix+"/blocklogs/:id", s.logsCrud.GetBlockLog)
+
+ // Set up the connect-rpc handlers for the EvalServer
+ path, handler := v1alpha1connect.NewEvalServiceHandler(&eval.EvalServer{})
+ log.Info("Setting up eval service", "path", path)
+ // Since we want to add the prefix apiPrefix we need to strip it before passing it to the connect-rpc handler
+ // Refer to https://connectrpc.com/docs/go/routing#prefixing-routes. Note that grpc-go clients don't
+ // support prefixes.
+ router.Any(apiPrefix+"/"+path+"*any", gin.WrapH(http.StripPrefix("/"+apiPrefix, handler)))
+ s.engine = router
+ // Setup the logs viewer
+ if err := s.setupViewerApp(router); err != nil {
+ return err
+ }
+ return nil
+}
+
+// setupViewerApp sets up the viewer app
+func (s *Server) setupViewerApp(router *gin.Engine) error {
+ log := zapr.NewLogger(zap.L())
app.Route("/", &logsviewer.MainApp{})
if strings.HasSuffix(logsviewer.AppPath, "/") {
@@ -233,6 +258,7 @@ func (s *Server) createGinEngine() error {
endpoint := fmt.Sprintf("http://%s:%d", s.config.Server.BindAddress, s.config.Server.HttpPort)
log.Info("Setting up logs viewer", "endpoint", endpoint, "path", logsviewer.AppPath)
+
viewerApp := &app.Handler{
Name: "FoyleLogsViewer",
Description: "View Foyle Logs",
@@ -240,16 +266,17 @@ func (s *Server) createGinEngine() error {
Resources: app.CustomProvider("", logsviewer.AppPath),
Styles: []string{
"/web/viewer.css", // Loads traceSelector.css file.
+ "/web/table.css", // Loads table.css file.
},
Env: map[string]string{
logsviewer.EndpointEnvVar: endpoint,
},
}
+
// N.B. We need a trailing slash for the relativePath passed to router. Any but not in the stripprefix
// because we need to leave the final slash in the path so that the route ends up matching.
router.Any(logsviewer.AppPath+"/*any", gin.WrapH(http.StripPrefix(logsviewer.AppPath, viewerApp)))
- s.engine = router
return nil
}
diff --git a/app/pwa/main.go b/app/pwa/main.go
index 4c64ac1c..7189d649 100644
--- a/app/pwa/main.go
+++ b/app/pwa/main.go
@@ -51,7 +51,8 @@ func main() {
Description: "An Hello World! example",
Resources: app.CustomProvider("", "/viewer"),
Styles: []string{
- "/web/viewer.css", // Loads traceSelector.css file.
+ "/web/table.css",
+ "/web/viewer.css",
},
Env: map[string]string{
logsviewer.EndpointEnvVar: "http://localhost:8000",
diff --git a/app/web/table.css b/app/web/table.css
new file mode 100644
index 00000000..5cd0b9c3
--- /dev/null
+++ b/app/web/table.css
@@ -0,0 +1,32 @@
+/* We want to dive the eval panel into a column with two rows.
+ The top row will be a table with the evaluation results, and the bottom row will be the viewer
+ showing the current selected row.
+ We will use flexbox to achieve this and specify the heights in percentages.
+ */
+.row {
+ flex: 1; /* Take up all available space */
+ display: flex;
+ flex-direction: column;
+ border: 1px solid #ccc;
+ box-sizing: border-box;
+}
+
+.row:first-child {
+ flex: 0 0 10%; /* 10% height, no grow, no shrink */
+}
+
+.row:last-child {
+ flex: 0 0 90%; /* 90% height, no grow, no shrink */
+}
+
+
+/* make the table scrollable */
+.scrollable-table {
+ height: 300px; /* Adjust as needed */
+ overflow-y: auto;
+}
+
+/* This styles the selected row in a table */
+.selected-row {
+ background-color: #f0f0f0;
+}
\ No newline at end of file
diff --git a/app/web/viewer.css b/app/web/viewer.css
index 8442ce2e..0457160b 100644
--- a/app/web/viewer.css
+++ b/app/web/viewer.css
@@ -19,7 +19,22 @@
background-color: #57d2ed;
}
+.page-window {
+ flex: 1;
+ background-color: #fff;
+}
+
.main-window {
flex: 1;
background-color: #fff;
+}
+
+/* style the status bar at the bottom */
+.status-bar {
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ background-color: #f0f0f0;
+ text-align: center;
+ padding: 10px 0;
}
\ No newline at end of file
diff --git a/frontend/foyle/src/gen/foyle/v1alpha1/eval_pb.ts b/frontend/foyle/src/gen/foyle/v1alpha1/eval_pb.ts
index d476139b..6e067676 100644
--- a/frontend/foyle/src/gen/foyle/v1alpha1/eval_pb.ts
+++ b/frontend/foyle/src/gen/foyle/v1alpha1/eval_pb.ts
@@ -119,3 +119,79 @@ export class EvalResult extends Message {
}
}
+/**
+ * @generated from message EvalResultListRequest
+ */
+export class EvalResultListRequest extends Message {
+ /**
+ * The path of the database to fetch results for
+ *
+ * @generated from field: string database = 1;
+ */
+ database = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "EvalResultListRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "database", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): EvalResultListRequest {
+ return new EvalResultListRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): EvalResultListRequest {
+ return new EvalResultListRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): EvalResultListRequest {
+ return new EvalResultListRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: EvalResultListRequest | PlainMessage | undefined, b: EvalResultListRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(EvalResultListRequest, a, b);
+ }
+}
+
+/**
+ * @generated from message EvalResultListResponse
+ */
+export class EvalResultListResponse extends Message {
+ /**
+ * @generated from field: repeated EvalResult items = 1;
+ */
+ items: EvalResult[] = [];
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "EvalResultListResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "items", kind: "message", T: EvalResult, repeated: true },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): EvalResultListResponse {
+ return new EvalResultListResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): EvalResultListResponse {
+ return new EvalResultListResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): EvalResultListResponse {
+ return new EvalResultListResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: EvalResultListResponse | PlainMessage | undefined, b: EvalResultListResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(EvalResultListResponse, a, b);
+ }
+}
+
diff --git a/protos/README.md b/protos/README.md
index ab94d030..b51745a4 100644
--- a/protos/README.md
+++ b/protos/README.md
@@ -46,6 +46,14 @@ More documentation can be found [here](https://github.com/bufbuild/protobuf-es/b
We [grpc-gateway](https://grpc-ecosystem.github.io/grpc-gateway/) to generate RESTful services from our grpc services.
+## connect-go
+
+To install the connect-go plugin
+
+```
+ go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
+```
+
## Zap Marshalers
We use [zap marshalers](https://pkg.go.dev/go.uber.org/zap#hdr-JSON) to generate MarshalLogObject methods for our types.
diff --git a/protos/buf.gen.yaml b/protos/buf.gen.yaml
index 42137951..b0ee41a9 100644
--- a/protos/buf.gen.yaml
+++ b/protos/buf.gen.yaml
@@ -26,5 +26,8 @@ plugins:
- import_extension=none
out: ../frontend/foyle/src/gen
- name: zap-marshaler
+ out: go/
+ opt: paths=source_relative
+ - plugin: connect-go
out: go/
opt: paths=source_relative
\ No newline at end of file
diff --git a/protos/foyle/v1alpha1/agent.proto b/protos/foyle/v1alpha1/agent.proto
index 18edeeb3..c1a3ae15 100644
--- a/protos/foyle/v1alpha1/agent.proto
+++ b/protos/foyle/v1alpha1/agent.proto
@@ -3,7 +3,7 @@ syntax = "proto3";
import "google/protobuf/struct.proto";
import "foyle/v1alpha1/doc.proto";
-option go_package = "github.com/jlewi/foyle/protos/go/v1alpha1";
+option go_package = "github.com/jlewi/foyle/protos/go/foyle/v1alpha1";
// grpc-gateway
// https://github.com/grpc-ecosystem/grpc-gateway
diff --git a/protos/foyle/v1alpha1/doc.proto b/protos/foyle/v1alpha1/doc.proto
index 3a7530bd..2c0fa8bb 100644
--- a/protos/foyle/v1alpha1/doc.proto
+++ b/protos/foyle/v1alpha1/doc.proto
@@ -2,7 +2,7 @@ syntax = "proto3";
import "google/protobuf/struct.proto";
-option go_package = "github.com/jlewi/foyle/protos/go/v1alpha1";
+option go_package = "github.com/jlewi/foyle/protos/go/foyle/v1alpha1";
// Doc represents a document in the editor.
message Doc {
diff --git a/protos/foyle/v1alpha1/eval.proto b/protos/foyle/v1alpha1/eval.proto
index 5a2c5c32..5599c524 100644
--- a/protos/foyle/v1alpha1/eval.proto
+++ b/protos/foyle/v1alpha1/eval.proto
@@ -5,7 +5,7 @@ import "foyle/v1alpha1/trainer.proto";
import "google/protobuf/struct.proto";
-option go_package = "github.com/jlewi/foyle/protos/go/v1alpha1";
+option go_package = "github.com/jlewi/foyle/protos/go/foyle/v1alpha1";
enum EvalResultStatus {
UNKNOWN_EVAL_RESULT_STATUS = 0;
@@ -32,3 +32,17 @@ message EvalResult {
// Status of the evaluation
EvalResultStatus status = 6;
}
+
+message EvalResultListRequest {
+ // The path of the database to fetch results for
+ string database = 1;
+}
+
+message EvalResultListResponse {
+ repeated EvalResult items = 1;
+}
+
+
+service EvalService {
+ rpc List(EvalResultListRequest) returns (EvalResultListResponse) {}
+}
\ No newline at end of file
diff --git a/protos/foyle/v1alpha1/trainer.proto b/protos/foyle/v1alpha1/trainer.proto
index b9a33cf5..3dff4241 100644
--- a/protos/foyle/v1alpha1/trainer.proto
+++ b/protos/foyle/v1alpha1/trainer.proto
@@ -4,7 +4,7 @@ import "foyle/v1alpha1/doc.proto";
import "google/protobuf/struct.proto";
-option go_package = "github.com/jlewi/foyle/protos/go/v1alpha1";
+option go_package = "github.com/jlewi/foyle/protos/go/foyle/v1alpha1";
// Example represents an example to be used in few shot learning
message Example {
diff --git a/protos/go/foyle/v1alpha1/agent.pb.go b/protos/go/foyle/v1alpha1/agent.pb.go
index e5c01613..be8d3fb1 100644
--- a/protos/go/foyle/v1alpha1/agent.pb.go
+++ b/protos/go/foyle/v1alpha1/agent.pb.go
@@ -245,10 +245,10 @@ var file_foyle_v1alpha1_agent_proto_rawDesc = []byte{
0x74, 0x1a, 0x10, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x65, 0x78,
- 0x65, 0x63, 0x75, 0x74, 0x65, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+ 0x65, 0x63, 0x75, 0x74, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x6c, 0x65, 0x77, 0x69, 0x2f, 0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f,
- 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
- 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f,
+ 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/protos/go/foyle/v1alpha1/doc.pb.go b/protos/go/foyle/v1alpha1/doc.pb.go
index 6c45247c..d1ae5f4c 100644
--- a/protos/go/foyle/v1alpha1/doc.pb.go
+++ b/protos/go/foyle/v1alpha1/doc.pb.go
@@ -364,11 +364,11 @@ var file_foyle_v1alpha1_doc_proto_rawDesc = []byte{
0x61, 0x2a, 0x39, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x16,
0x0a, 0x12, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f,
0x4b, 0x49, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x41, 0x52, 0x4b, 0x55, 0x50,
- 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x42, 0x2b, 0x5a, 0x29,
+ 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x42, 0x31, 0x5a, 0x2f,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x6c, 0x65, 0x77, 0x69,
0x2f, 0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x67, 0x6f,
- 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
- 0x33,
+ 0x2f, 0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62,
+ 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/protos/go/foyle/v1alpha1/eval.pb.go b/protos/go/foyle/v1alpha1/eval.pb.go
index 8e4d9f5d..1784138d 100644
--- a/protos/go/foyle/v1alpha1/eval.pb.go
+++ b/protos/go/foyle/v1alpha1/eval.pb.go
@@ -171,6 +171,101 @@ func (x *EvalResult) GetStatus() EvalResultStatus {
return EvalResultStatus_UNKNOWN_EVAL_RESULT_STATUS
}
+type EvalResultListRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // The path of the database to fetch results for
+ Database string `protobuf:"bytes,1,opt,name=database,proto3" json:"database,omitempty"`
+}
+
+func (x *EvalResultListRequest) Reset() {
+ *x = EvalResultListRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_foyle_v1alpha1_eval_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *EvalResultListRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EvalResultListRequest) ProtoMessage() {}
+
+func (x *EvalResultListRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_foyle_v1alpha1_eval_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use EvalResultListRequest.ProtoReflect.Descriptor instead.
+func (*EvalResultListRequest) Descriptor() ([]byte, []int) {
+ return file_foyle_v1alpha1_eval_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *EvalResultListRequest) GetDatabase() string {
+ if x != nil {
+ return x.Database
+ }
+ return ""
+}
+
+type EvalResultListResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Items []*EvalResult `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
+}
+
+func (x *EvalResultListResponse) Reset() {
+ *x = EvalResultListResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_foyle_v1alpha1_eval_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *EvalResultListResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EvalResultListResponse) ProtoMessage() {}
+
+func (x *EvalResultListResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_foyle_v1alpha1_eval_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use EvalResultListResponse.ProtoReflect.Descriptor instead.
+func (*EvalResultListResponse) Descriptor() ([]byte, []int) {
+ return file_foyle_v1alpha1_eval_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *EvalResultListResponse) GetItems() []*EvalResult {
+ if x != nil {
+ return x.Items
+ }
+ return nil
+}
+
var File_foyle_v1alpha1_eval_proto protoreflect.FileDescriptor
var file_foyle_v1alpha1_eval_proto_rawDesc = []byte{
@@ -197,15 +292,27 @@ var file_foyle_v1alpha1_eval_proto_rawDesc = []byte{
0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x45, 0x76, 0x61,
0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73,
- 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x47, 0x0a, 0x10, 0x45, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73,
- 0x75, 0x6c, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x4b,
- 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54,
- 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x4f, 0x4e,
- 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x42, 0x2b,
- 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x6c, 0x65,
- 0x77, 0x69, 0x2f, 0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
- 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f,
- 0x74, 0x6f, 0x33,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x33, 0x0a, 0x15, 0x45, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73,
+ 0x75, 0x6c, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a,
+ 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x22, 0x3b, 0x0a, 0x16, 0x45, 0x76,
+ 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20,
+ 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
+ 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2a, 0x47, 0x0a, 0x10, 0x45, 0x76, 0x61, 0x6c, 0x52,
+ 0x65, 0x73, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x1a, 0x55,
+ 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x5f, 0x52, 0x45, 0x53, 0x55,
+ 0x4c, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44,
+ 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02,
+ 0x32, 0x48, 0x0a, 0x0b, 0x45, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
+ 0x39, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x52, 0x65,
+ 0x73, 0x75, 0x6c, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+ 0x17, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x4c, 0x69, 0x73, 0x74,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69,
+ 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x6c, 0x65, 0x77, 0x69, 0x2f, 0x66,
+ 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x66,
+ 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -221,22 +328,27 @@ func file_foyle_v1alpha1_eval_proto_rawDescGZIP() []byte {
}
var file_foyle_v1alpha1_eval_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_foyle_v1alpha1_eval_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_foyle_v1alpha1_eval_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_foyle_v1alpha1_eval_proto_goTypes = []interface{}{
- (EvalResultStatus)(0), // 0: EvalResultStatus
- (*EvalResult)(nil), // 1: EvalResult
- (*Example)(nil), // 2: Example
- (*Block)(nil), // 3: Block
+ (EvalResultStatus)(0), // 0: EvalResultStatus
+ (*EvalResult)(nil), // 1: EvalResult
+ (*EvalResultListRequest)(nil), // 2: EvalResultListRequest
+ (*EvalResultListResponse)(nil), // 3: EvalResultListResponse
+ (*Example)(nil), // 4: Example
+ (*Block)(nil), // 5: Block
}
var file_foyle_v1alpha1_eval_proto_depIdxs = []int32{
- 2, // 0: EvalResult.example:type_name -> Example
- 3, // 1: EvalResult.actual:type_name -> Block
+ 4, // 0: EvalResult.example:type_name -> Example
+ 5, // 1: EvalResult.actual:type_name -> Block
0, // 2: EvalResult.status:type_name -> EvalResultStatus
- 3, // [3:3] is the sub-list for method output_type
- 3, // [3:3] is the sub-list for method input_type
- 3, // [3:3] is the sub-list for extension type_name
- 3, // [3:3] is the sub-list for extension extendee
- 0, // [0:3] is the sub-list for field type_name
+ 1, // 3: EvalResultListResponse.items:type_name -> EvalResult
+ 2, // 4: EvalService.List:input_type -> EvalResultListRequest
+ 3, // 5: EvalService.List:output_type -> EvalResultListResponse
+ 5, // [5:6] is the sub-list for method output_type
+ 4, // [4:5] is the sub-list for method input_type
+ 4, // [4:4] is the sub-list for extension type_name
+ 4, // [4:4] is the sub-list for extension extendee
+ 0, // [0:4] is the sub-list for field type_name
}
func init() { file_foyle_v1alpha1_eval_proto_init() }
@@ -259,6 +371,30 @@ func file_foyle_v1alpha1_eval_proto_init() {
return nil
}
}
+ file_foyle_v1alpha1_eval_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*EvalResultListRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_foyle_v1alpha1_eval_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*EvalResultListResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -266,9 +402,9 @@ func file_foyle_v1alpha1_eval_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_foyle_v1alpha1_eval_proto_rawDesc,
NumEnums: 1,
- NumMessages: 1,
+ NumMessages: 3,
NumExtensions: 0,
- NumServices: 0,
+ NumServices: 1,
},
GoTypes: file_foyle_v1alpha1_eval_proto_goTypes,
DependencyIndexes: file_foyle_v1alpha1_eval_proto_depIdxs,
diff --git a/protos/go/foyle/v1alpha1/eval.zap.go b/protos/go/foyle/v1alpha1/eval.zap.go
index ee20ea54..f6c9494a 100644
--- a/protos/go/foyle/v1alpha1/eval.zap.go
+++ b/protos/go/foyle/v1alpha1/eval.zap.go
@@ -63,3 +63,42 @@ func (m *EvalResult) MarshalLogObject(enc go_uber_org_zap_zapcore.ObjectEncoder)
return nil
}
+
+func (m *EvalResultListRequest) MarshalLogObject(enc go_uber_org_zap_zapcore.ObjectEncoder) error {
+ var keyName string
+ _ = keyName
+
+ if m == nil {
+ return nil
+ }
+
+ keyName = "database" // field database = 1
+ enc.AddString(keyName, m.Database)
+
+ return nil
+}
+
+func (m *EvalResultListResponse) MarshalLogObject(enc go_uber_org_zap_zapcore.ObjectEncoder) error {
+ var keyName string
+ _ = keyName
+
+ if m == nil {
+ return nil
+ }
+
+ keyName = "items" // field items = 1
+ enc.AddArray(keyName, go_uber_org_zap_zapcore.ArrayMarshalerFunc(func(aenc go_uber_org_zap_zapcore.ArrayEncoder) error {
+ for _, rv := range m.Items {
+ _ = rv
+ if rv != nil {
+ var vv interface{} = rv
+ if marshaler, ok := vv.(go_uber_org_zap_zapcore.ObjectMarshaler); ok {
+ aenc.AppendObject(marshaler)
+ }
+ }
+ }
+ return nil
+ }))
+
+ return nil
+}
diff --git a/protos/go/foyle/v1alpha1/eval_grpc.pb.go b/protos/go/foyle/v1alpha1/eval_grpc.pb.go
new file mode 100644
index 00000000..13687545
--- /dev/null
+++ b/protos/go/foyle/v1alpha1/eval_grpc.pb.go
@@ -0,0 +1,105 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc (unknown)
+// source: foyle/v1alpha1/eval.proto
+
+package v1alpha1
+
+import (
+ context "context"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// EvalServiceClient is the client API for EvalService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type EvalServiceClient interface {
+ List(ctx context.Context, in *EvalResultListRequest, opts ...grpc.CallOption) (*EvalResultListResponse, error)
+}
+
+type evalServiceClient struct {
+ cc grpc.ClientConnInterface
+}
+
+func NewEvalServiceClient(cc grpc.ClientConnInterface) EvalServiceClient {
+ return &evalServiceClient{cc}
+}
+
+func (c *evalServiceClient) List(ctx context.Context, in *EvalResultListRequest, opts ...grpc.CallOption) (*EvalResultListResponse, error) {
+ out := new(EvalResultListResponse)
+ err := c.cc.Invoke(ctx, "/EvalService/List", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// EvalServiceServer is the server API for EvalService service.
+// All implementations must embed UnimplementedEvalServiceServer
+// for forward compatibility
+type EvalServiceServer interface {
+ List(context.Context, *EvalResultListRequest) (*EvalResultListResponse, error)
+ mustEmbedUnimplementedEvalServiceServer()
+}
+
+// UnimplementedEvalServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedEvalServiceServer struct {
+}
+
+func (UnimplementedEvalServiceServer) List(context.Context, *EvalResultListRequest) (*EvalResultListResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
+}
+func (UnimplementedEvalServiceServer) mustEmbedUnimplementedEvalServiceServer() {}
+
+// UnsafeEvalServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to EvalServiceServer will
+// result in compilation errors.
+type UnsafeEvalServiceServer interface {
+ mustEmbedUnimplementedEvalServiceServer()
+}
+
+func RegisterEvalServiceServer(s grpc.ServiceRegistrar, srv EvalServiceServer) {
+ s.RegisterService(&EvalService_ServiceDesc, srv)
+}
+
+func _EvalService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(EvalResultListRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(EvalServiceServer).List(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/EvalService/List",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(EvalServiceServer).List(ctx, req.(*EvalResultListRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+// EvalService_ServiceDesc is the grpc.ServiceDesc for EvalService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var EvalService_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "EvalService",
+ HandlerType: (*EvalServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "List",
+ Handler: _EvalService_List_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "foyle/v1alpha1/eval.proto",
+}
diff --git a/protos/go/foyle/v1alpha1/trainer.pb.go b/protos/go/foyle/v1alpha1/trainer.pb.go
index a1e0642f..71e98c34 100644
--- a/protos/go/foyle/v1alpha1/trainer.pb.go
+++ b/protos/go/foyle/v1alpha1/trainer.pb.go
@@ -108,10 +108,11 @@ var file_foyle_v1alpha1_trainer_proto_rawDesc = []byte{
0x1a, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04,
0x2e, 0x44, 0x6f, 0x63, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1e, 0x0a, 0x06, 0x61,
0x6e, 0x73, 0x77, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x42, 0x6c,
- 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x42, 0x2b, 0x5a, 0x29, 0x67,
+ 0x6f, 0x63, 0x6b, 0x52, 0x06, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x42, 0x31, 0x5a, 0x2f, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x6c, 0x65, 0x77, 0x69, 0x2f,
0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x67, 0x6f, 0x2f,
- 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x66, 0x6f, 0x79, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/protos/go/foyle/v1alpha1/v1alpha1connect/agent.connect.go b/protos/go/foyle/v1alpha1/v1alpha1connect/agent.connect.go
new file mode 100644
index 00000000..1d453ed4
--- /dev/null
+++ b/protos/go/foyle/v1alpha1/v1alpha1connect/agent.connect.go
@@ -0,0 +1,191 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: foyle/v1alpha1/agent.proto
+
+package v1alpha1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1alpha1 "github.com/jlewi/foyle/protos/go/foyle/v1alpha1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // GenerateServiceName is the fully-qualified name of the GenerateService service.
+ GenerateServiceName = "GenerateService"
+ // ExecuteServiceName is the fully-qualified name of the ExecuteService service.
+ ExecuteServiceName = "ExecuteService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // GenerateServiceGenerateProcedure is the fully-qualified name of the GenerateService's Generate
+ // RPC.
+ GenerateServiceGenerateProcedure = "/GenerateService/Generate"
+ // ExecuteServiceExecuteProcedure is the fully-qualified name of the ExecuteService's Execute RPC.
+ ExecuteServiceExecuteProcedure = "/ExecuteService/Execute"
+)
+
+// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
+var (
+ generateServiceServiceDescriptor = v1alpha1.File_foyle_v1alpha1_agent_proto.Services().ByName("GenerateService")
+ generateServiceGenerateMethodDescriptor = generateServiceServiceDescriptor.Methods().ByName("Generate")
+ executeServiceServiceDescriptor = v1alpha1.File_foyle_v1alpha1_agent_proto.Services().ByName("ExecuteService")
+ executeServiceExecuteMethodDescriptor = executeServiceServiceDescriptor.Methods().ByName("Execute")
+)
+
+// GenerateServiceClient is a client for the GenerateService service.
+type GenerateServiceClient interface {
+ // Generate generates new cells given an existing document.
+ Generate(context.Context, *connect.Request[v1alpha1.GenerateRequest]) (*connect.Response[v1alpha1.GenerateResponse], error)
+}
+
+// NewGenerateServiceClient constructs a client for the GenerateService service. By default, it uses
+// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
+// connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewGenerateServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GenerateServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ return &generateServiceClient{
+ generate: connect.NewClient[v1alpha1.GenerateRequest, v1alpha1.GenerateResponse](
+ httpClient,
+ baseURL+GenerateServiceGenerateProcedure,
+ connect.WithSchema(generateServiceGenerateMethodDescriptor),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// generateServiceClient implements GenerateServiceClient.
+type generateServiceClient struct {
+ generate *connect.Client[v1alpha1.GenerateRequest, v1alpha1.GenerateResponse]
+}
+
+// Generate calls GenerateService.Generate.
+func (c *generateServiceClient) Generate(ctx context.Context, req *connect.Request[v1alpha1.GenerateRequest]) (*connect.Response[v1alpha1.GenerateResponse], error) {
+ return c.generate.CallUnary(ctx, req)
+}
+
+// GenerateServiceHandler is an implementation of the GenerateService service.
+type GenerateServiceHandler interface {
+ // Generate generates new cells given an existing document.
+ Generate(context.Context, *connect.Request[v1alpha1.GenerateRequest]) (*connect.Response[v1alpha1.GenerateResponse], error)
+}
+
+// NewGenerateServiceHandler builds an HTTP handler from the service implementation. It returns the
+// path on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewGenerateServiceHandler(svc GenerateServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ generateServiceGenerateHandler := connect.NewUnaryHandler(
+ GenerateServiceGenerateProcedure,
+ svc.Generate,
+ connect.WithSchema(generateServiceGenerateMethodDescriptor),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/GenerateService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case GenerateServiceGenerateProcedure:
+ generateServiceGenerateHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedGenerateServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedGenerateServiceHandler struct{}
+
+func (UnimplementedGenerateServiceHandler) Generate(context.Context, *connect.Request[v1alpha1.GenerateRequest]) (*connect.Response[v1alpha1.GenerateResponse], error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("GenerateService.Generate is not implemented"))
+}
+
+// ExecuteServiceClient is a client for the ExecuteService service.
+type ExecuteServiceClient interface {
+ // Execute executes a cell in an existing document.
+ Execute(context.Context, *connect.Request[v1alpha1.ExecuteRequest]) (*connect.Response[v1alpha1.ExecuteResponse], error)
+}
+
+// NewExecuteServiceClient constructs a client for the ExecuteService service. By default, it uses
+// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
+// connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewExecuteServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ExecuteServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ return &executeServiceClient{
+ execute: connect.NewClient[v1alpha1.ExecuteRequest, v1alpha1.ExecuteResponse](
+ httpClient,
+ baseURL+ExecuteServiceExecuteProcedure,
+ connect.WithSchema(executeServiceExecuteMethodDescriptor),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// executeServiceClient implements ExecuteServiceClient.
+type executeServiceClient struct {
+ execute *connect.Client[v1alpha1.ExecuteRequest, v1alpha1.ExecuteResponse]
+}
+
+// Execute calls ExecuteService.Execute.
+func (c *executeServiceClient) Execute(ctx context.Context, req *connect.Request[v1alpha1.ExecuteRequest]) (*connect.Response[v1alpha1.ExecuteResponse], error) {
+ return c.execute.CallUnary(ctx, req)
+}
+
+// ExecuteServiceHandler is an implementation of the ExecuteService service.
+type ExecuteServiceHandler interface {
+ // Execute executes a cell in an existing document.
+ Execute(context.Context, *connect.Request[v1alpha1.ExecuteRequest]) (*connect.Response[v1alpha1.ExecuteResponse], error)
+}
+
+// NewExecuteServiceHandler builds an HTTP handler from the service implementation. It returns the
+// path on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewExecuteServiceHandler(svc ExecuteServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ executeServiceExecuteHandler := connect.NewUnaryHandler(
+ ExecuteServiceExecuteProcedure,
+ svc.Execute,
+ connect.WithSchema(executeServiceExecuteMethodDescriptor),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/ExecuteService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case ExecuteServiceExecuteProcedure:
+ executeServiceExecuteHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedExecuteServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedExecuteServiceHandler struct{}
+
+func (UnimplementedExecuteServiceHandler) Execute(context.Context, *connect.Request[v1alpha1.ExecuteRequest]) (*connect.Response[v1alpha1.ExecuteResponse], error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ExecuteService.Execute is not implemented"))
+}
diff --git a/protos/go/foyle/v1alpha1/v1alpha1connect/eval.connect.go b/protos/go/foyle/v1alpha1/v1alpha1connect/eval.connect.go
new file mode 100644
index 00000000..5cedccc5
--- /dev/null
+++ b/protos/go/foyle/v1alpha1/v1alpha1connect/eval.connect.go
@@ -0,0 +1,112 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: foyle/v1alpha1/eval.proto
+
+package v1alpha1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1alpha1 "github.com/jlewi/foyle/protos/go/foyle/v1alpha1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // EvalServiceName is the fully-qualified name of the EvalService service.
+ EvalServiceName = "EvalService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // EvalServiceListProcedure is the fully-qualified name of the EvalService's List RPC.
+ EvalServiceListProcedure = "/EvalService/List"
+)
+
+// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
+var (
+ evalServiceServiceDescriptor = v1alpha1.File_foyle_v1alpha1_eval_proto.Services().ByName("EvalService")
+ evalServiceListMethodDescriptor = evalServiceServiceDescriptor.Methods().ByName("List")
+)
+
+// EvalServiceClient is a client for the EvalService service.
+type EvalServiceClient interface {
+ List(context.Context, *connect.Request[v1alpha1.EvalResultListRequest]) (*connect.Response[v1alpha1.EvalResultListResponse], error)
+}
+
+// NewEvalServiceClient constructs a client for the EvalService service. By default, it uses the
+// Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
+// connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewEvalServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) EvalServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ return &evalServiceClient{
+ list: connect.NewClient[v1alpha1.EvalResultListRequest, v1alpha1.EvalResultListResponse](
+ httpClient,
+ baseURL+EvalServiceListProcedure,
+ connect.WithSchema(evalServiceListMethodDescriptor),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// evalServiceClient implements EvalServiceClient.
+type evalServiceClient struct {
+ list *connect.Client[v1alpha1.EvalResultListRequest, v1alpha1.EvalResultListResponse]
+}
+
+// List calls EvalService.List.
+func (c *evalServiceClient) List(ctx context.Context, req *connect.Request[v1alpha1.EvalResultListRequest]) (*connect.Response[v1alpha1.EvalResultListResponse], error) {
+ return c.list.CallUnary(ctx, req)
+}
+
+// EvalServiceHandler is an implementation of the EvalService service.
+type EvalServiceHandler interface {
+ List(context.Context, *connect.Request[v1alpha1.EvalResultListRequest]) (*connect.Response[v1alpha1.EvalResultListResponse], error)
+}
+
+// NewEvalServiceHandler builds an HTTP handler from the service implementation. It returns the path
+// on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewEvalServiceHandler(svc EvalServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ evalServiceListHandler := connect.NewUnaryHandler(
+ EvalServiceListProcedure,
+ svc.List,
+ connect.WithSchema(evalServiceListMethodDescriptor),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/EvalService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case EvalServiceListProcedure:
+ evalServiceListHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedEvalServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedEvalServiceHandler struct{}
+
+func (UnimplementedEvalServiceHandler) List(context.Context, *connect.Request[v1alpha1.EvalResultListRequest]) (*connect.Response[v1alpha1.EvalResultListResponse], error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("EvalService.List is not implemented"))
+}