Streaming AI Markdown Responses to Javascript Function Calls

When using Chilkat to interact with AI providers (like OpenAI or Anthropic) in streaming mode, the AI response arrives in small "chunks" rather than all at once. This response is often formatted in Markdown (using asterisks for bold, backticks for code, etc.).

Chilkat acts as an intelligent bridge between the raw AI data stream and your browser display. It parses the incoming Markdown in real-time and acts as a state machine. Instead of waiting for the full response, Chilkat translates the Markdown syntax immediately into specific JavaScript calls:

  1. Markdown Parsing: As chunks arrive, Chilkat determines if the text represents formatting (like ** or ###) or raw content.
  2. Function Selection:
    • If Chilkat detects Markdown formatting that needs to be rendered (like a <b> tag, a list item <li>, or a code block), it generates a call to appendHtmlBySelector.
    • If Chilkat detects plain content, it generates a call to appendTextBySelector.
  3. Real-Time Execution: These JavaScript calls are executed immediately by the browser (or WebView).

This allows the user to see the text "type out" and format itself on the screen instantly as the AI "thinks," rather than staring at a loading spinner until the request is finished.


The JavaScript Source Code

These are the client-side functions that Chilkat calls to (and then your application code runs the generated Javascript code). They must be present in the HTML loaded into your browser control.

window.appendHtmlBySelector = function(selector, html) {
    try {
        const targetElement = document.querySelector(selector);
        if (targetElement) {
            // Parses the string as HTML and inserts it inside the element
            targetElement.insertAdjacentHTML('beforeend', html);
            window.scrollTo(0, document.body.scrollHeight);
        } else {
            console.error('appendHtmlBySelector failed: Element matching selector "' + selector + '" not found.');
        }
    } catch (e) {
        console.error('appendHtmlBySelector failed:', e);
    }
};

window.appendTextBySelector = function(selector, text) {
    try {
        const targetElement = document.querySelector(selector);
        if (targetElement) {
            // Creates a safe text node (HTML tags are not rendered)
            targetElement.appendChild(document.createTextNode(text));
            window.scrollTo(0, document.body.scrollHeight);
        } else {
            console.error('appendTextBySelector failed: Element matching selector "' + selector + '" not found.');
        }
    } catch (e) {
        console.error('appendTextBySelector failed:', e);
    }
};

Understanding Selectors

Both functions require a selector argument. A selector is simply a string pattern used to tell the browser which HTML element you want to update.

  • ID Selector (#): Targets a specific element with a unique ID.
    • Example: '#response-container' targets <div id="response-container"></div>.
  • Class Selector (.): Targets elements with a specific class name.
    • Example: '.ai-message' targets <div class="ai-message"></div>.
  • Tag Selector: Targets elements by their HTML tag.
    • Example: 'body' targets the main body of the page.

Real-World Example Analysis

Here is the breakdown of how Chilkat processes the following markdown.

The Input (Markdown)

The AI sends this Markdown structure:

Here are the **four largest cities in California**, each with a one-sentence description:

1. **Los Angeles** — The state’s largest city, known for Hollywood, diverse neighborhoods, and its role as a global center for entertainment and culture.
2. **San Diego** — A coastal city famous for its beaches, mild climate, and major naval presence.
3. **San Jose** — The heart of Silicon Valley and a major hub for technology and innovation.
4. **San Francisco** — A historic, densely packed city known for its iconic landmarks, steep hills, and vibrant cultural scene.
The Output (JavaScript Calls)

Chilkat translates that Markdown into these specific calls. Notice how the Selector changes to ensure the HTML is built with the correct nesting.

Step 1: Initialize the Container

window.appendHtmlBySelector("#content", "<div class=\"response-content\">");
  • Action: Chilkat creates a new div for this specific response inside the main #content area.

Step 2: Add the Intro Paragraph

window.appendHtmlBySelector("div.response-content:last-of-type", "<p>Here are the <strong>four largest cities in California</strong>, each with a one-sentence description:");
  • Selector: div.response-content:last-of-type
  • Why: Chilkat targets the newest response div created in Step 1. It inserts the paragraph there.

Step 3: Create the List Structure

window.appendHtmlBySelector("div.response-content:last-of-type", "<ol>");
  • Action: It opens an Ordered List (<ol>) inside the current response div. At this point, the list is empty.

Step 4: Populate List Items (Deep Nesting)

window.appendHtmlBySelector("div.response-content:last-of-type > ol:last-child", "<li><strong>Los Angeles</strong> — The state’s largest city...");
window.appendHtmlBySelector("div.response-content:last-of-type > ol:last-child", "<li><strong>San Diego</strong> — A coastal city...");
// ... repeated for San Jose and San Francisco
  • Selector: div.response-content:last-of-type > ol:last-child
  • Why: This is the most critical part. Chilkat recognizes that these lines are items inside the list, not new paragraphs.
    • It looks for the current response div (last-of-type).
    • Inside that, it looks for the ol that was just created (ol:last-child).
    • It appends the <li> inside that ol.

Summary

By dynamically adjusting the selector, Chilkat builds a complex, nested HTML structure in real-time. The window.scrollTo command at the end of every function call ensures the user follows this construction line-by-line as it happens.


Back