From ae3ef9bc39f0ab30d5e465ea4c518327bac22bdf Mon Sep 17 00:00:00 2001 From: Benson Wong Date: Mon, 23 Dec 2024 19:48:59 -0800 Subject: [PATCH] Refactor UI (#33) - add html to / instead of 404 - add client side regex to /logs --- proxy/html/index.html | 14 ++++ proxy/html/logs.html | 134 +++++++++++++++++++++++++----- proxy/html_files.go | 10 +++ proxy/proxymanager.go | 33 +++++--- proxy/proxymanager_loghandlers.go | 19 +++-- 5 files changed, 169 insertions(+), 41 deletions(-) create mode 100644 proxy/html/index.html create mode 100644 proxy/html_files.go diff --git a/proxy/html/index.html b/proxy/html/index.html new file mode 100644 index 0000000..2f9dfd2 --- /dev/null +++ b/proxy/html/index.html @@ -0,0 +1,14 @@ + + + + + + llama-swap + + +

llama-swap

+

+ view logs | configured models | github +

+ + diff --git a/proxy/html/logs.html b/proxy/html/logs.html index 49026f1..c9f82cc 100644 --- a/proxy/html/logs.html +++ b/proxy/html/logs.html @@ -12,42 +12,134 @@ flex-direction: column; font-family: "Courier New", Courier, monospace; } + #log-controls { + margin: 0.5em; + display: flex; + align-items: center; + justify-content: space-between; /* Spaces out elements evenly */ + } + #log-controls input { + flex: 1; + } + #log-controls input:focus { + outline: none; /* Ensures no outline is shown when the input is focused */ + } #log-stream { flex: 1; - margin: 1em; - padding: 10px; + margin: 0.5em; + padding: 1em; background: #f4f4f4; overflow-y: auto; white-space: pre-wrap; /* Ensures line wrapping */ word-wrap: break-word; /* Ensures long words wrap */ } + + .regex-error { + background-color: #ff0000 !important; + } + + /* Dark mode styles */ + @media (prefers-color-scheme: dark) { + body { + background-color: #333; + color: #fff; + } + + #log-stream { + background: #444; + color: #fff; + } + + #log-controls input { + background: #555; + color: #fff; + border: 1px solid #777; + } + + #log-controls button { + background: #555; + color: #fff; + border: 1px solid #777; + } + } -
Waiting for logs...
-
- +
Waiting for logs...
+
+ + +
\ No newline at end of file diff --git a/proxy/html_files.go b/proxy/html_files.go new file mode 100644 index 0000000..c5fa042 --- /dev/null +++ b/proxy/html_files.go @@ -0,0 +1,10 @@ +package proxy + +import "embed" + +//go:embed html +var htmlFiles embed.FS + +func getHTMLFile(path string) ([]byte, error) { + return htmlFiles.ReadFile("html/" + path) +} diff --git a/proxy/proxymanager.go b/proxy/proxymanager.go index 161b98d..45c568b 100644 --- a/proxy/proxymanager.go +++ b/proxy/proxymanager.go @@ -2,7 +2,6 @@ package proxy import ( "bytes" - "embed" "encoding/json" "fmt" "io" @@ -20,15 +19,6 @@ const ( PROFILE_SPLIT_CHAR = ":" ) -//go:embed html/favicon.ico -var faviconData []byte - -//go:embed html/logs.html -var logsHTML []byte - -// make sure embed is kept there by the IDE auto-package importer -var _ = embed.FS{} - type ProxyManager struct { sync.Mutex @@ -98,8 +88,29 @@ func New(config *Config) *ProxyManager { pm.ginEngine.GET("/upstream", pm.upstreamIndex) pm.ginEngine.Any("/upstream/:model_id/*upstreamPath", pm.proxyToUpstream) + pm.ginEngine.GET("/", func(c *gin.Context) { + // Set the Content-Type header to text/html + c.Header("Content-Type", "text/html") + + // Write the embedded HTML content to the response + htmlData, err := getHTMLFile("index.html") + if err != nil { + c.String(http.StatusInternalServerError, err.Error()) + return + } + _, err = c.Writer.Write(htmlData) + if err != nil { + c.String(http.StatusInternalServerError, fmt.Sprintf("failed to write response: %v", err)) + return + } + }) + pm.ginEngine.GET("/favicon.ico", func(c *gin.Context) { - c.Data(http.StatusOK, "image/x-icon", faviconData) + if data, err := getHTMLFile("favicon.ico"); err == nil { + c.Data(http.StatusOK, "image/x-icon", data) + } else { + c.String(http.StatusInternalServerError, err.Error()) + } }) // Disable console color for testing diff --git a/proxy/proxymanager_loghandlers.go b/proxy/proxymanager_loghandlers.go index 36cea1c..56efbf6 100644 --- a/proxy/proxymanager_loghandlers.go +++ b/proxy/proxymanager_loghandlers.go @@ -16,9 +16,14 @@ func (pm *ProxyManager) sendLogsHandlers(c *gin.Context) { c.Header("Content-Type", "text/html") // Write the embedded HTML content to the response - _, err := c.Writer.Write(logsHTML) + logsHTML, err := getHTMLFile("logs.html") if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to write response: %v", err)) + c.String(http.StatusInternalServerError, err.Error()) + return + } + _, err = c.Writer.Write(logsHTML) + if err != nil { + c.String(http.StatusInternalServerError, fmt.Sprintf("failed to write response: %v", err)) return } } else { @@ -43,7 +48,7 @@ func (pm *ProxyManager) streamLogsHandler(c *gin.Context) { notify := c.Request.Context().Done() flusher, ok := c.Writer.(http.Flusher) if !ok { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Streaming unsupported")) + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("streaming unsupported")) return } @@ -53,11 +58,7 @@ func (pm *ProxyManager) streamLogsHandler(c *gin.Context) { if !skipHistory { history := pm.logMonitor.GetHistory() if len(history) != 0 { - _, err := c.Writer.Write(history) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } + c.Writer.Write(history) flusher.Flush() } } @@ -68,7 +69,7 @@ func (pm *ProxyManager) streamLogsHandler(c *gin.Context) { case msg := <-ch: _, err := c.Writer.Write(msg) if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) + // just break the loop if we can't write for some reason return } flusher.Flush()