// This file holds the HTML rendering helpers for session export (US-008, #124). // The output is a single self-contained document: all CSS is inlined in a //

pigo session โ€” %s

%s

`, html.EscapeString(title), html.EscapeString(title), strings.Join(meta, " ยท ")) } // htmlFoot closes the container and document. func htmlFoot() string { return `
` } // renderEntryHTML renders one entry as a role-colored message block. Text and // tool arguments are escaped so no session content can inject markup. func renderEntryHTML(e Entry) string { switch m := e.Message.(type) { case agentcore.UserMessage: return msgBlock("user", "User", html.EscapeString(agentcore.ContentToText(m.Content)), "") case agentcore.AssistantMessage: var tools strings.Builder for _, c := range m.ToolCalls() { args := strings.TrimSpace(string(c.Arguments)) tools.WriteString(fmt.Sprintf(`
โ†’ %s %s
`, html.EscapeString(c.Name), html.EscapeString(args))) } return msgBlock("assistant", "Assistant", html.EscapeString(agentcore.ContentToText(m.Content)), tools.String()) case agentcore.ToolResultMessage: label := "Tool Result" if m.ToolName != "" { label = "Tool Result: " + m.ToolName } return msgBlock("tool", html.EscapeString(label), html.EscapeString(agentcore.ContentToText(m.Content)), "") case agentcore.CompactionMessage: return msgBlock("compaction", "Compaction", html.EscapeString(m.Summary), "") default: return msgBlock("assistant", html.EscapeString(e.Message.Role()), "", "") } } // msgBlock assembles one .msg block with a role class, a role label, the escaped // body text, and optional pre-rendered tool-call HTML. Callers MUST pass already // escaped text/label; extra is trusted HTML built here from escaped parts. func msgBlock(class, label, escapedText, extra string) string { var b strings.Builder b.WriteString(`
`) b.WriteString(label) b.WriteString(`
`) if escapedText != "" { b.WriteString(`
`) b.WriteString(escapedText) b.WriteString(`
`) } b.WriteString(extra) b.WriteString("
\n") return b.String() }