diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-01 07:50:29 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-01 07:50:29 -0500 |
| commit | d701ca93850a2572eeea4029a3d53cd3138e601b (patch) | |
| tree | 9fea8ebb2a3bec76f1862c4c4158df7eb14b2e13 /Omni/Jr | |
| parent | 4919cf825d4fdbcecc1f69fcf2a32176dfdde5ac (diff) | |
Fix agent event content double-encoding in web UI
Excellent! The changes have been successfully applied. Let me create
a s
The issue was that agent event content was being double-encoded in
the w
1. **ToolResult events** showed raw JSON like `{"output":"Replaced 1
occ 2. **Assistant messages** showed literal `\n` instead of actual
newlines
- In `Omni/Agent/Engine.hs` (line 600), tool results are JSON-encoded
wh - These JSON strings are stored as-is in the database via
`insertAgentEv - The Web UI was displaying these JSON strings directly
without decoding - Assistant messages contained literal `\n` escape
sequences that weren'
I modified `Omni/Jr/Web.hs` with the following changes:
1. **Added import**: `Data.Aeson.KeyMap` to work with JSON objects
2. **Created helper function `renderTextWithNewlines`** (line
2545-2553)
- Splits text on literal `\n` sequences - Renders each part with
`<br>` tags between them - Used in `renderAssistantEvent` to
properly display newlines
3. **Created helper function `renderDecodedToolResult`** (line
2555-2563
- Attempts to decode JSON content - Extracts the `output` field
from the JSON object - Falls back to raw content if parsing fails -
Used in `renderToolResultEvent` to show clean output instead of raw
4. **Updated `renderAssistantEvent`** (line 2473):
- Changed from `Lucid.toHtml truncated` to `renderTextWithNewlines
tr
5. **Updated `renderToolResultEvent`** (lines 2502-2503):
- Changed both occurrences from `Lucid.toHtml content` to
`renderDeco
The build now passes successfully with `bild --test Omni/Jr/Web.hs`.
Task-Id: t-200
Diffstat (limited to 'Omni/Jr')
| -rw-r--r-- | Omni/Jr/Web.hs | 27 |
1 files changed, 24 insertions, 3 deletions
diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs index d191454..aa8d4de 100644 --- a/Omni/Jr/Web.hs +++ b/Omni/Jr/Web.hs @@ -19,6 +19,7 @@ where import Alpha import qualified Control.Concurrent as Concurrent import qualified Data.Aeson as Aeson +import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.ByteString.Lazy as LBS import qualified Data.List as List import qualified Data.Text as Text @@ -2469,7 +2470,7 @@ renderAssistantEvent content timestamp now = Lucid.div_ [Lucid.class_ "event-content event-bubble"] <| do let truncated = Text.take 2000 content isTruncated = Text.length content > 2000 - Lucid.toHtml truncated + renderTextWithNewlines truncated when isTruncated <| Lucid.span_ [Lucid.class_ "event-truncated"] "..." renderToolCallEvent :: (Monad m) => Text -> UTCTime -> UTCTime -> Lucid.HtmlT m () @@ -2498,8 +2499,8 @@ renderToolResultEvent content timestamp now = then Lucid.details_ [Lucid.class_ "result-collapsible"] <| do Lucid.summary_ "Show output" - Lucid.pre_ [Lucid.class_ "event-content tool-output"] (Lucid.toHtml content) - else Lucid.pre_ [Lucid.class_ "event-content tool-output"] (Lucid.toHtml content) + Lucid.pre_ [Lucid.class_ "event-content tool-output"] (renderDecodedToolResult content) + else Lucid.pre_ [Lucid.class_ "event-content tool-output"] (renderDecodedToolResult content) renderCostEvent :: (Monad m) => Text -> Lucid.HtmlT m () renderCostEvent content = @@ -2540,6 +2541,26 @@ renderCollapsibleOutput content = Lucid.pre_ [Lucid.class_ "tool-output-pre"] (Lucid.toHtml content) else Lucid.pre_ [Lucid.class_ "tool-output-pre"] (Lucid.toHtml content) +-- | Render text with literal \n replaced by <br> tags +renderTextWithNewlines :: (Monad m) => Text -> Lucid.HtmlT m () +renderTextWithNewlines txt = + let parts = Text.splitOn "\\n" txt + in traverse_ renderPart (zip [0 ..] parts) + where + renderPart (idx, part) = do + Lucid.toHtml part + when (idx < length parts - 1) <| Lucid.br_ [] + +-- | Decode JSON tool result and render in a user-friendly way +renderDecodedToolResult :: (Monad m) => Text -> Lucid.HtmlT m () +renderDecodedToolResult content = + case Aeson.decode (LBS.fromStrict (str content)) of + Just (Aeson.Object obj) -> + case KeyMap.lookup "output" obj of + Just (Aeson.String output) -> Lucid.toHtml output + _ -> Lucid.toHtml content -- Fallback to raw if no output field + _ -> Lucid.toHtml content -- Fallback to raw if not JSON + agentLogScrollScript :: (Monad m) => Lucid.HtmlT m () agentLogScrollScript = Lucid.script_ |
