Introduction

Since I’m using AI to help me more quickly brain dump my non-linear thoughts into a more readable format, I thought it would sometimes be handy to display Open WebUI chats in Hugo sites. This is where the renderchat shortcode comes in.

Installation

To use the renderchat shortcode, you can do this by creating a file in your site, layouts/shortcodes/renderchat.html, and adding the following code:

{{/* renderchat.html */}}
{{ $file := .Get "file" }}
{{ if eq $file "" }}
    <p>No chat log file specified.</p>
    {{ return }}
{{ end }}

{{ $jsonResource := .Page.Resources.GetMatch $file }}
{{ if not $jsonResource }}
    <p>Could not find the chat log file: {{ $file }}</p>
    {{ return }}
{{ end }}

{{ $json := $jsonResource | transform.Unmarshal }}
{{ if not $json }}
    <p>Failed to parse the chat log JSON file.</p>
    {{ return }}
{{ end }}

{{ range $json }}
    <div class="chat-log">
        <h2 class="chat-title">{{ .title }}</h2>
        <div class="chat-messages">
            {{ $messages := slice }}
            {{ range .chat.history.messages }}
                {{ $messages = $messages | append . }}
            {{ end }}

            <!-- Create a map of messages by ID -->
            {{ $messageMap := dict }}
            {{ range $messages }}
                {{ $messageMap = merge $messageMap (dict (printf "%s" .id) .) }}
            {{ end }}

            <!-- Render root messages -->
            {{ range (where $messages "parentId" "==" nil) }}
                {{ template "renderMessageWithChildren" dict "message" . "map" $messageMap }}
            {{ end }}
        </div>
    </div>
{{ end }}

{{/* Recursive template to render messages */}}
{{ define "renderMessageWithChildren" }}
    {{ $message := .message }}
    {{ $map := .map }}

    <!-- Render current message -->
    <div class="chat-message {{ $message.role }}">
        <strong>{{ if eq $message.role "user" }}User{{ else }}{{ $message.model }}{{ end }}:</strong>
        <div style="white-space: pre-wrap;">{{ $message.content }}</div>
    </div>

    <!-- Render children recursively -->
    {{ range $message.childrenIds }}
        {{ $child := index $map . }}
        {{ template "renderMessageWithChildren" dict "message" $child "map" $map }}
    {{ end }}
{{ end }}

Now we’ll need CSS code. My theme will load any CSS that I put in assets/css/extended/ so I added a file in there called renderchat.css. Here’s the CSS code for the chat log:

/* Chat container */
.chat-log {
    margin: 20px 0;
    padding: 10px;
    border: 1px solid var(--hugo-chat-border-color, #444);
    border-radius: 5px;
    background-color: var(--hugo-chat-bg, #222); /* Dark-friendly */
    color: var(--hugo-chat-text-color, #ddd); /* Text color */
}

/* Title styling */
.chat-title {
    font-size: 1.5rem;
    margin-bottom: 10px;
    text-align: center;
    color: var(--hugo-chat-title-color, #fff);
}

/* Chat messages container */
.chat-messages {
    display: flex;
    flex-direction: column;
    gap: 10px; /* Spacing between messages */
}

/* Chat message bubbles */
.chat-message {
    max-width: 60%;
    padding: 10px 15px;
    border-radius: 15px;
    display: inline-block;
    line-height: 1.5;
    background-color: var(--hugo-chat-message-bg, rgba(255, 255, 255, 0.1)); /* Semi-transparent */
    color: var(--hugo-chat-message-color, inherit);
    text-align: left;
}

/* User-specific styling */
.chat-message.user {
    align-self: flex-end;
    font-weight: bold;
    background-color: var(--hugo-chat-user-bg, rgba(0, 150, 136, 0.2)); /* Soft teal with transparency */
    color: var(--hugo-chat-user-color, #fff); /* White text */
}

/* Assistant-specific styling */
.chat-message.assistant {
    align-self: flex-start;
    font-style: italic;
    background-color: var(--hugo-chat-assistant-bg, rgba(100, 100, 100, 0.2)); /* Gray with transparency */
    color: var(--hugo-chat-assistant-color, #fff); /* White text */
}

Example

I’m using page bundles with this site, which makes it easy to just drop a chat log file downloaded straight from Open WebUI into my bundle, and referencing it with a shortcode like this (and note how I switched models partway through the chat, and the shortcode picked up on this):

🐙 Octopus Limerick Fun 😊

User:
Write me a limerick about an octopus with an extra tentacle
llama3.2:latest:
There once was an octopus named Pete, Whose extra tentacle couldn't be beat. He'd wave it with flair, And show off with care, But sometimes it caused him to trip on the street.
User:
Write me a sequel to this limerick about how Pete lost his extra tentacle.
phi4:latest:
With a jingle and jig in the deep blue sea lane, Pete danced till he met a great seahorse named Jane. She swished with a flip, And the tentacle slipped, Leaving Pete to wonder where it had been lain. He searched through the coral, quite wide-eyed, With his friends of all shapes by his side. A crab with some glee, Said he saw it flee, To join an octopus band playing hide and slide. So Pete learned a lesson quite grand: Extra tentacles aren't always as planned. But with friends to help him cope, He kept up good hope, And embraced the adventure of his oceanic land.