summaryrefslogtreecommitdiff
path: root/Omni/Ava/Web.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-19 21:54:54 -0500
committerBen Sima <ben@bensima.com>2025-12-19 21:54:54 -0500
commitfcb8629182fa1552e4a840ccd4ec0aa2b8042cc0 (patch)
treee389479cf9349fbdab107da739bceef11cf8e7ee /Omni/Ava/Web.hs
parente856c766584ed933bed0b79c7ef47b6d98b0fb7e (diff)
feat(ava): add tool trace viewer mini-app
- Add SQLite storage for tool traces (Omni/Ava/Trace.hs) - Add web server to serve trace viewer (Omni/Ava/Web.hs) - Add HTML/CSS/JS trace viewer UI (Omni/Ava/Web/trace.html) - Integrate trace storage into Engine.hs tool execution callback - Add trace links to Telegram responses when AVA_WEB_URL is set - Configure Tailscale Funnel for public access - Fix pre-push hook variable scope bug - Add direnv, bash, nix to Ava service PATH - Add mustache dep to Ava.hs for template rendering Epic: t-272
Diffstat (limited to 'Omni/Ava/Web.hs')
-rw-r--r--Omni/Ava/Web.hs84
1 files changed, 84 insertions, 0 deletions
diff --git a/Omni/Ava/Web.hs b/Omni/Ava/Web.hs
new file mode 100644
index 0000000..86a8280
--- /dev/null
+++ b/Omni/Ava/Web.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-- | Web server for Ava trace viewer.
+--
+-- Serves the trace viewer UI for debugging tool executions.
+--
+-- : out omni-ava-web
+-- : dep warp
+-- : dep wai
+-- : dep http-types
+-- : dep aeson
+-- : dep text
+-- : dep bytestring
+-- : dep sqlite-simple
+module Omni.Ava.Web
+ ( startWebServer,
+ app,
+ defaultPort,
+ )
+where
+
+import Alpha
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Lazy as LBS
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Encoding
+import qualified Data.Text.IO as TextIO
+import qualified Database.SQLite.Simple as SQL
+import qualified Network.HTTP.Types as HTTP
+import qualified Network.Wai as Wai
+import qualified Network.Wai.Handler.Warp as Warp
+import qualified Omni.Ava.Trace as Trace
+import qualified System.Environment as Environment
+
+defaultPort :: Int
+defaultPort = 8079
+
+startWebServer :: Int -> FilePath -> IO ()
+startWebServer port dbPath = do
+ putText <| "Starting Ava web server on port " <> tshow port
+ Warp.run port (app dbPath)
+
+app :: FilePath -> Wai.Application
+app dbPath request respond = do
+ case Wai.pathInfo request of
+ ["trace", tid] -> serveTracePage dbPath tid request respond
+ ["api", "trace", tid] -> serveTraceJson dbPath tid request respond
+ ["health"] -> respond <| Wai.responseLBS HTTP.status200 [(HTTP.hContentType, "text/plain")] "ok"
+ _ -> respond <| Wai.responseLBS HTTP.status404 [(HTTP.hContentType, "text/plain")] "Not found"
+
+serveTracePage :: FilePath -> Text -> Wai.Request -> (Wai.Response -> IO Wai.ResponseReceived) -> IO Wai.ResponseReceived
+serveTracePage dbPath tid _req respond = do
+ SQL.withConnection dbPath <| \conn -> do
+ maybeRec <- Trace.getTrace conn tid
+ case maybeRec of
+ Nothing -> respond <| Wai.responseLBS HTTP.status404 [(HTTP.hContentType, "text/plain")] "Trace not found"
+ Just rec -> do
+ html <- renderTraceHtml rec
+ respond <| Wai.responseLBS HTTP.status200 [(HTTP.hContentType, "text/html; charset=utf-8")] (LBS.fromStrict <| Encoding.encodeUtf8 html)
+
+serveTraceJson :: FilePath -> Text -> Wai.Request -> (Wai.Response -> IO Wai.ResponseReceived) -> IO Wai.ResponseReceived
+serveTraceJson dbPath tid _req respond = do
+ SQL.withConnection dbPath <| \conn -> do
+ maybeRec <- Trace.getTrace conn tid
+ case maybeRec of
+ Nothing -> respond <| Wai.responseLBS HTTP.status404 [(HTTP.hContentType, "application/json")] "{\"error\":\"not found\"}"
+ Just rec -> do
+ let json = Aeson.encode rec
+ respond <| Wai.responseLBS HTTP.status200 [(HTTP.hContentType, "application/json")] json
+
+renderTraceHtml :: Trace.TraceRecord -> IO Text
+renderTraceHtml rec = do
+ coderoot <- Environment.getEnv "CODEROOT"
+ let templatePath = coderoot <> "/Omni/Ava/Web/trace.html"
+ template <- TextIO.readFile templatePath
+ pure
+ <| Text.replace "{{trace_id}}" (Trace.trcId rec)
+ <| Text.replace "{{tool_name}}" (Trace.trcToolName rec)
+ <| Text.replace "{{created_at}}" (Trace.trcCreatedAt rec)
+ <| Text.replace "{{duration_ms}}" (tshow <| Trace.trcDurationMs rec)
+ <| Text.replace "{{input_json}}" (Trace.trcInput rec)
+ <| Text.replace "{{output_json}}" (Trace.trcOutput rec)
+ <| template