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")) +}