Files
client/plans/ai-tv-page.md
2026-04-17 16:52:51 -04:00

12 KiB
Raw Permalink Blame History

AI-TV Page Architecture Plan

Concept

AI-TV transforms the standard AI chat interface into a "generative web browser" — every AI response is a full HTML page rendered in an iframe "TV screen". The user sends prompts, and instead of receiving markdown chat bubbles, they see a fully rendered web page appear in the main viewport.

Layout

┌─────────────────────────────────────────────────────────────┐
│  Header Bar: [Provider ▼] [Model ▼]              Balance    │
├──────────────┬──────────────────────────────────────────────┤
│              │                                              │
│  Past Pages  │           iframe "TV Screen"                 │
│  Strip       │                                              │
│              │   (sandboxed iframe rendering the             │
│  ┌────────┐  │    AI-generated HTML page)                   │
│  │ Page 3 │  │                                              │
│  │ active  │  │                                              │
│  └────────┘  │                                              │
│  ┌────────┐  │                                              │
│  │ Page 2 │  │                                              │
│  └────────┘  │                                              │
│  ┌────────┐  │                                              │
│  │ Page 1 │  │                                              │
│  └────────┘  │                                              │
│              │                                              │
│  [+ New]     │                                              │
├──────────────┴──────────────────────────────────────────────┤
│  ▶ System Prompt (collapsible)                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ You are a web page generator. Respond only in HTML...│   │
│  └──────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│  [Prompt textarea                              ] [Send]     │
├─────────────────────────────────────────────────────────────┤
│  Status bar                                                 │
└─────────────────────────────────────────────────────────────┘

Architecture Diagram

graph TD
    A[AI-TV Page] --> B[Header Bar]
    A --> C[Main Content Area]
    A --> D[Input Section]
    A --> E[Footer]

    B --> B1[Provider Select]
    B --> B2[Model Dropdown]
    B --> B3[Balance Display]

    C --> C1[Past Pages Strip - left sidebar]
    C --> C2[iframe TV Screen - main area]

    C1 --> C1a[Page cards with title + timestamp]
    C1 --> C1b[Click to load page into viewer]
    C1 --> C1c[Delete page button]
    C1 --> C1d[New Session button]

    C2 --> C2a[sandbox=allow-scripts]
    C2 --> C2b[srcdoc set from AI response]

    D --> D1[Collapsible system prompt section]
    D --> D2[Prompt textarea]
    D --> D3[Send button]

    D1 --> D1a[Default: HTML-only response instruction]
    D1 --> D1b[Editable per session]

    subgraph Response Pipeline
        R1[User sends prompt] --> R2[System prompt prepended to messages]
        R2 --> R3[API call via callOpenAICompatibleChat]
        R3 --> R4[Extract content from response]
        R4 --> R5[extractHtmlFromResponse - strip code fences]
        R5 --> R6[Store raw HTML in conversation messages array]
        R6 --> R7[Set iframe.srcdoc = cleanHtml]
        R7 --> R8[Add page card to Past Pages strip]
    end

Detailed Changes from ai.html

1. HTML Structure Changes

Remove:

  • #divAiMessages — the chat bubbles container
  • Chat bubble CSS classes (.aiMessageRow, .aiMessageBubble, .aiMessageContent, .aiMessageMeta, .aiTyping, .aiTypingDot)
  • Image attachment UI in messages (keep pending attachments for sending)

Transform #divAiConversationsPane into Past Pages Strip:

  • Rename to #divAiPagesPane
  • Each item shows: page title (auto-generated from prompt), timestamp, delete button
  • Active page highlighted
  • + New Session button at bottom (clears conversation, starts fresh)

Add:

  • <iframe id="iframeAiTv"> inside #divAiChatPane where #divAiMessages was
  • Collapsible #divAiSystemPromptSection above the input area
  • Loading overlay on the iframe while waiting for response

2. iframe Configuration

<iframe
  id="iframeAiTv"
  sandbox="allow-scripts allow-same-origin"
  style="width: 100%; flex: 1; border: none; border-radius: 8px; background: white;"
  srcdoc="<html><body style='display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;color:#888;'><p>Send a prompt to generate a page...</p></body></html>"
></iframe>

Sandbox notes:

  • allow-scripts — lets the generated page run JS for interactivity
  • allow-same-origin — needed if the generated page wants to use localStorage or similar (optional, could be removed for stricter isolation)
  • No allow-top-navigation — prevents the generated page from navigating the parent
  • No allow-popups — prevents unwanted popups

3. System Prompt Default

You are a web page generator. You MUST respond to ALL user requests with a complete, valid HTML page. Your entire response must be ONLY the HTML document — no markdown, no explanations, no code fences, no commentary before or after the HTML.

Rules:
- Start with <!DOCTYPE html> and include <html>, <head>, and <body> tags
- All CSS must be inline in a <style> tag in the <head>
- All JavaScript must be inline in <script> tags
- Do not reference external resources (CDNs are acceptable)
- Make the page visually appealing with good typography and layout
- The page should be responsive and work at any viewport size
- Use modern CSS features (flexbox, grid, custom properties)

4. Response Processing

New function extractHtmlFromResponse(rawContent):

function extractHtmlFromResponse(rawContent) {
  let html = String(rawContent || '').trim();
  
  // Strip markdown code fences: ```html ... ``` or ``` ... ```
  const fencePattern = /^```(?:html)?\s*\n?([\s\S]*?)\n?\s*```$/;
  const match = html.match(fencePattern);
  if (match) {
    html = match[1].trim();
  }
  
  // Validate it looks like HTML
  if (!html.toLowerCase().includes('<html') && !html.toLowerCase().includes('<!doctype')) {
    // Wrap bare content in a basic HTML page
    html = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
  }
  
  return html;
}

5. Conversation Storage Changes

The existing conversation storage structure works well. Each message in convo.messages[] already has:

  • role — 'user' or 'assistant'
  • content — the raw text (for assistant, this will be the full HTML)
  • createdAt — timestamp

For the Past Pages strip, we derive page cards from assistant messages:

function getPageCards(convo) {
  return convo.messages
    .filter(m => m.role === 'assistant' && !m.typing)
    .map((m, index) => ({
      messageId: m.id,
      title: getPageTitle(m, index),
      timestamp: m.createdAt,
      html: m.content
    }));
}

function getPageTitle(msg, index) {
  // Try to extract <title> from the HTML
  const titleMatch = String(msg.content || '').match(/<title[^>]*>(.*?)<\/title>/i);
  if (titleMatch && titleMatch[1].trim()) return titleMatch[1].trim();
  return `Page ${index + 1}`;
}

6. Past Pages Strip UI

Each page card in the strip:

<div class="aiPageItem active" data-message-id="...">
  <div class="aiPageHeader">
    <div class="aiPageTitle">My Generated Page</div>
    <button class="aiDeletePageBtn" title="Delete">×</button>
  </div>
  <div class="aiPagePreview">3:42 PM</div>
</div>

Clicking a card sets iframeAiTv.srcdoc to that message's HTML content.

7. Collapsible System Prompt Section

<div id="divAiSystemPromptSection">
  <button id="btnToggleSystemPrompt" class="aiSystemPromptToggle">
    ▶ System Prompt
  </button>
  <div id="divAiSystemPromptBody" style="display: none;">
    <textarea id="taAiSystemPrompt" class="aiTextarea">...</textarea>
  </div>
</div>

Toggle shows/hides the textarea. The default value is the HTML-only instruction from section 3.

8. Send Flow Changes

The sendPrompt() function changes:

  1. User message is added to conversation (same as before)
  2. Instead of adding a typing bubble, show a loading overlay on the iframe
  3. Call callOpenAICompatibleChat() (same API call)
  4. On response: extractHtmlFromResponse(result.content)
  5. Store the raw HTML as the assistant message content
  6. Set iframeAiTv.srcdoc = extractedHtml
  7. Add a new page card to the Past Pages strip
  8. Remove loading overlay

9. CSS Changes

New styles needed:

  • #iframeAiTv — full flex area, no border, white background default
  • .aiPageItem — page card in the strip (similar to .aiConversationItem but adapted)
  • .aiPageItem.active — highlighted state
  • #divAiSystemPromptSection — collapsible section styling
  • .aiLoadingOverlay — semi-transparent overlay with spinner on the iframe area

Remove:

  • .aiMessageRow, .aiMessageBubble, .aiMessageContent, .aiMessageMeta
  • .aiTyping, .aiTypingDot, @keyframes aiPulse
  • .aiMessageAttachments, .aiMessageAttachmentImg

10. What Stays the Same

  • Header bar with provider/model selection
  • Sidebar config (provider settings, API keys, payment methods)
  • All Routstr/ppq.ai payment and balance logic
  • Conversation persistence in localStorage
  • Image attachment sending (vision models can still receive images)
  • Footer with relay status
  • Hamburger menu and sidebar navigation
  • Theme toggle, logout

Files to Modify

File Change
www/ai-tv.html Full restructure: layout, CSS, JavaScript

No other files need modification — this is a self-contained page.

Risk Considerations

  • Large HTML in localStorage — Generated pages could be large. Consider truncating stored HTML or adding a max page count.
  • Malicious generated content — The iframe sandbox mitigates this, but allow-scripts means generated JS will run. This is intentional for interactivity but worth noting.
  • Models wrapping in code fences — The extractHtmlFromResponse function handles this, but edge cases may exist (multiple code blocks, partial fences).
  • Models adding commentary — Some models may add text before/after the HTML despite instructions. The extraction function should handle this by finding the HTML document boundaries.