From e09858f9364f8e018cdc304f3bc0b1a55193cffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20F=2E=20R=C3=B8dseth?= Date: Wed, 21 Feb 2024 19:18:38 +0100 Subject: [PATCH] Add initial support for index.prompt files --- engine/between.go | 17 ++++++++++++++++ engine/dirhandler.go | 2 +- engine/handlers.go | 46 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 engine/between.go diff --git a/engine/between.go b/engine/between.go new file mode 100644 index 000000000..ed6f666ad --- /dev/null +++ b/engine/between.go @@ -0,0 +1,17 @@ +package engine + +import "strings" + +// Return what's between two strings, "a" and "b", in another string, +// but inclusively, so that "a" and "b" are also included in the return value. +func betweenInclusive(orig, a, b string) string { + if strings.Contains(orig, a) && strings.Contains(orig, b) { + posa := strings.Index(orig, a) + len(a) + posb := strings.LastIndex(orig, b) + if posa > posb { + return "" + } + return a + orig[posa:posb] + b + } + return "" +} diff --git a/engine/dirhandler.go b/engine/dirhandler.go index a52f30b86..914be1cbd 100644 --- a/engine/dirhandler.go +++ b/engine/dirhandler.go @@ -15,7 +15,7 @@ import ( ) // List of filenames that should be displayed instead of a directory listing -var indexFilenames = []string{"index.lua", "index.html", "index.md", "index.txt", "index.pongo2", "index.tmpl", "index.po2", "index.amber", "index.happ", "index.hyper", "index.hyper.js", "index.hyper.jsx", "index.tl"} +var indexFilenames = []string{"index.lua", "index.html", "index.md", "index.txt", "index.pongo2", "index.tmpl", "index.po2", "index.amber", "index.happ", "index.hyper", "index.hyper.js", "index.hyper.jsx", "index.tl", "index.prompt"} const ( dotSlash = "." + utils.Pathsep /* ./ */ diff --git a/engine/handlers.go b/engine/handlers.go index 8042bd8e1..0cc1932ff 100644 --- a/engine/handlers.go +++ b/engine/handlers.go @@ -17,6 +17,7 @@ import ( "github.com/xyproto/algernon/themes" "github.com/xyproto/algernon/utils" "github.com/xyproto/datablock" + "github.com/xyproto/ollamaclient" "github.com/xyproto/recwatch" "github.com/xyproto/sheepcounter" "github.com/xyproto/simpleform" @@ -167,7 +168,7 @@ func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, _ case ".md", ".markdown": w.Header().Add("Content-Type", "text/html;charset=utf-8") - if markdownblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // if no error + if markdownblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // success // Render the markdown page ac.MarkdownPage(w, req, markdownblock.Bytes(), filename) } @@ -317,7 +318,7 @@ func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, _ return case ".gcss": - if gcssblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // if no error + if gcssblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // success w.Header().Add("Content-Type", "text/css;charset=utf-8") // Render the GCSS page as CSS ac.GCSSPage(w, req, filename, gcssblock.Bytes()) @@ -325,7 +326,7 @@ func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, _ return case ".scss": - if scssblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // if no error + if scssblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // success // Render the SASS page (with .scss extension) as CSS w.Header().Add("Content-Type", "text/css;charset=utf-8") ac.SCSSPage(w, req, filename, scssblock.Bytes()) @@ -333,7 +334,7 @@ func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, _ return case ".happ", ".hyper", ".hyper.jsx", ".hyper.js": // hyperApp JSX -> JS, wrapped in HTML - if jsxblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // if no error + if jsxblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // success // Render the JSX page as HTML with embedded JavaScript w.Header().Add("Content-Type", "text/html;charset=utf-8") ac.HyperAppPage(w, req, filename, jsxblock.Bytes()) @@ -344,10 +345,43 @@ func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, _ // This case must come after the .hyper.jsx case case ".jsx": - if jsxblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // if no error + if jsxblock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // success // Render the JSX page as JavaScript w.Header().Add("Content-Type", "text/javascript;charset=utf-8") ac.JSXPage(w, req, filename, jsxblock.Bytes()) + } else { + log.Error("Error when serving " + filename + ":" + err.Error()) + } + return + + // .prompt files contains a content type and a prompt that is converted to data in a reproducible way, with a newline between them + case ".prompt": + if data, err := os.ReadFile(filename); err == nil { // success + lines := strings.Split(string(data), "\n") + if len(lines) < 3 { + log.Error(filename + " must contain a content type, a blank line and then a prompt to be usable") + } else if strings.TrimSpace(lines[1]) != "" { + log.Error(filename + " must contain a content type, a blank line and then a prompt to be usable") + } else { + contentType := strings.TrimSpace(lines[0]) + prompt := strings.TrimSpace(strings.Join(lines[2:], "\n")) + w.Header().Add("Conent-Type", contentType) + oc := ollamaclient.NewWithModel("tinyllama") + if err := oc.PullIfNeeded(true); err == nil { // success + if output, err := oc.GetOutput(prompt, true); err == nil { // success + if strings.Contains(output, "<") && strings.Contains(output, ">") { + output = betweenInclusive(output, "<", ">") + } + w.Write([]byte(output)) + } else { + log.Error("could not convert " + filename + " to content: " + err.Error()) + } + } else { + log.Error("could not convert " + filename + " to content: " + err.Error()) + } + } + } else { + log.Error("Error when serving " + filename + ":" + err.Error()) } return @@ -424,7 +458,7 @@ func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, _ } // Read the file (possibly in compressed format, straight from the cache) - if dataBlock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // if no error + if dataBlock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil { // success // Serve the file dataBlock.ToClient(w, req, filename, ac.ClientCanGzip(req), gzipThreshold) } else {