12 KiB
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 Sessionbutton at bottom (clears conversation, starts fresh)
Add:
<iframe id="iframeAiTv">inside#divAiChatPanewhere#divAiMessageswas- Collapsible
#divAiSystemPromptSectionabove 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 interactivityallow-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:
- User message is added to conversation (same as before)
- Instead of adding a typing bubble, show a loading overlay on the iframe
- Call
callOpenAICompatibleChat()(same API call) - On response:
extractHtmlFromResponse(result.content) - Store the raw HTML as the assistant message content
- Set
iframeAiTv.srcdoc = extractedHtml - Add a new page card to the Past Pages strip
- 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.aiConversationItembut 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-scriptsmeans generated JS will run. This is intentional for interactivity but worth noting. - Models wrapping in code fences — The
extractHtmlFromResponsefunction 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.