internal/gaby: quick fix for markdown rendering error

Add a fix for a specific markdown error (made by the LLM) found in
the overview of issue 70471, in which a citation was placed on the
same line as the backticks indicating the end of a code block.

If we notice numerous markdown errors in the future, we should
come up with a more robust solution.

(To aid in future debugging, this CL adds a UI element that allows
the user to view the raw, unfixed output of the LLM).

Change-Id: I7d225e0304d97c75cd5f3de7c6f7b513a21d070d
Reviewed-on: https://go-review.googlesource.com/c/oscar/+/630697
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/gaby/overview.go b/internal/gaby/overview.go
index 8d759a5..9cd797b 100644
--- a/internal/gaby/overview.go
+++ b/internal/gaby/overview.go
@@ -13,6 +13,7 @@
 	"strings"
 	"time"
 
+	"github.com/google/safehtml"
 	"github.com/google/safehtml/template"
 	"golang.org/x/oscar/internal/github"
 	"golang.org/x/oscar/internal/htmlutil"
@@ -62,9 +63,19 @@
 	handlePage(w, g.populateOverviewPage(r), overviewPageTmpl)
 }
 
+// fixMarkdown fixes mistakes that we have observed the LLM make
+// when generating markdown.
+func fixMarkdown(text string) string {
+	// add newline after backticks followed by a space
+	return strings.ReplaceAll(text, "``` ", "```\n")
+}
+
 var overviewPageTmpl = newTemplate(overviewPageTmplFile, template.FuncMap{
-	"fmttime":  fmtTimeString,
-	"safehtml": htmlutil.MarkdownToSafeHTML,
+	"fmttime": fmtTimeString,
+	"safehtml": func(md string) safehtml.HTML {
+		md = fixMarkdown(md)
+		return htmlutil.MarkdownToSafeHTML(md)
+	},
 })
 
 // fmtTimeString formats an [time.RFC3339]-encoded time string
diff --git a/internal/gaby/static/overview.css b/internal/gaby/static/overview.css
index c12b568..953d832 100644
--- a/internal/gaby/static/overview.css
+++ b/internal/gaby/static/overview.css
@@ -11,7 +11,7 @@
     min-width: fit-content;
     width: 1em;
 }
-#prompt {
+.start-hidden {
     display: none;
 }
 #prompt ul {
diff --git a/internal/gaby/tmpl/overviewpage.tmpl b/internal/gaby/tmpl/overviewpage.tmpl
index a2b3d3f..a66a92e 100644
--- a/internal/gaby/tmpl/overviewpage.tmpl
+++ b/internal/gaby/tmpl/overviewpage.tmpl
@@ -12,10 +12,23 @@
   </body>
 </html>
 
+{{define "show-rawoutput"}}
+<div class="toggle" onclick="toggleRawOutput()">[show raw LLM output]</div>
+<div id="rawoutput" class="start-hidden">
+	<pre>{{.Overview.Overview}}</pre>
+</div>
+<script>
+function toggleRawOutput() {
+	var x = document.getElementById("rawoutput");
+	toggle(x)
+}
+</script>
+{{end}}
+
 
 {{define "show-prompt"}}
 <div class="toggle" onclick="togglePrompt()">[show prompt]</div>
-<div id="prompt">
+<div id="prompt" class="start-hidden">
 	<ul>
 		{{- range .Overview.Prompt -}}
 	<li>
@@ -45,6 +58,7 @@
 		<p>AI-generated overview{{if .Overview.Cached}} (cached){{end}}:</p>
 		<div id="overview">{{safehtml .Overview.Overview}}</div>
 	</div>
+	{{template "show-rawoutput" .}}
 	{{template "show-prompt" .}}
 {{- else }}
 	{{if .Params.Query}}<p>No result.</p>{{end}}