@makemore/agent-frontend 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makemore/agent-frontend",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "A lightweight chat widget for AI agents. Use as an embeddable script tag or import directly into React/Preact projects.",
5
5
  "type": "module",
6
6
  "main": "dist/chat-widget.cjs.js",
@@ -243,21 +243,23 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
243
243
  />
244
244
  `}
245
245
 
246
- <${Header}
247
- config=${config}
248
- debugMode=${debugMode}
249
- isExpanded=${isExpanded}
250
- isSpeaking=${isSpeaking}
251
- messagesCount=${chat.messages.length}
252
- isLoading=${chat.isLoading}
253
- currentAgent=${currentAgent}
254
- onClose=${() => setIsOpen(false)}
255
- onToggleExpand=${() => setIsExpanded(!isExpanded)}
256
- onToggleDebug=${() => setDebugMode(!debugMode)}
257
- onToggleTTS=${() => setEnableTTS(!enableTTS)}
258
- onClear=${chat.clearMessages}
259
- onToggleSidebar=${handleToggleSidebar}
260
- />
246
+ ${config.showHeader !== false && html`
247
+ <${Header}
248
+ config=${config}
249
+ debugMode=${debugMode}
250
+ isExpanded=${isExpanded}
251
+ isSpeaking=${isSpeaking}
252
+ messagesCount=${chat.messages.length}
253
+ isLoading=${chat.isLoading}
254
+ currentAgent=${currentAgent}
255
+ onClose=${() => setIsOpen(false)}
256
+ onToggleExpand=${() => setIsExpanded(!isExpanded)}
257
+ onToggleDebug=${() => setDebugMode(!debugMode)}
258
+ onToggleTTS=${() => setEnableTTS(!enableTTS)}
259
+ onClear=${chat.clearMessages}
260
+ onToggleSidebar=${handleToggleSidebar}
261
+ />
262
+ `}
261
263
 
262
264
  ${config.showDevTools && html`
263
265
  <${DevToolbar}
@@ -329,6 +331,7 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
329
331
  placeholder=${config.placeholder}
330
332
  primaryColor=${config.primaryColor}
331
333
  enableVoice=${config.enableVoice}
334
+ enableFiles=${config.showFileAttachment !== false}
332
335
  />
333
336
  ` : html`
334
337
  <${TaskList}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * ContentBlocks - renders structured content blocks from tool results.
3
+ *
4
+ * Each block type has a dedicated renderer. Unknown types are silently
5
+ * skipped so the system is forward-compatible with new block types that
6
+ * only newer clients understand.
7
+ */
8
+
9
+ import { html } from 'htm/preact';
10
+ import { useState } from 'preact/hooks';
11
+ import { escapeHtml, parseMarkdown } from '../utils/helpers.js';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Individual block components
15
+ // ---------------------------------------------------------------------------
16
+
17
+ function CardBlock({ block, onAction, markdownParser }) {
18
+ return html`
19
+ <div class="cw-block-card">
20
+ ${block.image && html`<img class="cw-block-card-image" src=${block.image} alt=${block.title || ''} />`}
21
+ <div class="cw-block-card-body">
22
+ ${block.badge && html`<span class="cw-block-card-badge">${block.badge}</span>`}
23
+ ${block.title && html`<div class="cw-block-card-title">${block.title}</div>`}
24
+ ${block.subtitle && html`<div class="cw-block-card-subtitle">${block.subtitle}</div>`}
25
+ ${block.metadata && block.metadata.length > 0 && html`
26
+ <div class="cw-block-card-meta">
27
+ ${block.metadata.map(m => html`
28
+ <span class="cw-block-meta-pair">
29
+ <span class="cw-block-meta-label">${m.label}:</span> ${m.value}
30
+ </span>
31
+ `)}
32
+ </div>
33
+ `}
34
+ ${block.actions && block.actions.length > 0 && html`
35
+ <div class="cw-block-card-actions">
36
+ ${block.actions.map(a => html`<${ActionButton} action=${a} onAction=${onAction} />`)}
37
+ </div>
38
+ `}
39
+ </div>
40
+ </div>
41
+ `;
42
+ }
43
+
44
+ function CardListBlock({ block, onAction, markdownParser }) {
45
+ const layout = block.layout || 'vertical';
46
+ return html`
47
+ <div class="cw-block-card-list cw-block-card-list-${layout}">
48
+ ${(block.items || []).map(item => html`
49
+ <${CardBlock} block=${{ type: 'card', ...item }} onAction=${onAction} markdownParser=${markdownParser} />
50
+ `)}
51
+ </div>
52
+ `;
53
+ }
54
+
55
+ function ActionButton({ action, onAction }) {
56
+ const style = action.style || 'primary';
57
+ const handleClick = () => {
58
+ if (!onAction) return;
59
+ onAction(action);
60
+ };
61
+
62
+ if (action.type === 'link') {
63
+ return html`<a class="cw-block-btn cw-block-btn-${style}" href=${action.url} target="_blank" rel="noopener">${action.label}</a>`;
64
+ }
65
+ return html`<button class="cw-block-btn cw-block-btn-${style}" onClick=${handleClick}>${action.label}</button>`;
66
+ }
67
+
68
+ function ActionButtonsBlock({ block, onAction }) {
69
+ return html`
70
+ <div class="cw-block-action-buttons">
71
+ ${(block.buttons || []).map(a => html`<${ActionButton} action=${a} onAction=${onAction} />`)}
72
+ </div>
73
+ `;
74
+ }
75
+
76
+ function CalloutBlock({ block }) {
77
+ const style = block.style || 'info';
78
+ const icons = { info: 'ℹ️', success: '✅', warning: '⚠️' };
79
+ return html`
80
+ <div class="cw-block-callout cw-block-callout-${style}">
81
+ <span class="cw-block-callout-icon">${icons[style] || 'ℹ️'}</span>
82
+ <div class="cw-block-callout-content">
83
+ ${block.title && html`<strong>${block.title}</strong>`}
84
+ ${block.body && html`<span>${block.body}</span>`}
85
+ </div>
86
+ </div>
87
+ `;
88
+ }
89
+
90
+ function ImageBlock({ block }) {
91
+ return html`
92
+ <figure class="cw-block-image">
93
+ <img src=${block.url} alt=${block.alt || ''} />
94
+ ${block.caption && html`<figcaption>${block.caption}</figcaption>`}
95
+ </figure>
96
+ `;
97
+ }
98
+
99
+ function DividerBlock() {
100
+ return html`<hr class="cw-block-divider" />`;
101
+ }
102
+
103
+ function TableBlock({ block }) {
104
+ return html`
105
+ <div class="cw-block-table-wrapper">
106
+ <table class="cw-block-table">
107
+ ${block.headers && block.headers.length > 0 && html`
108
+ <thead><tr>${block.headers.map(h => html`<th>${h}</th>`)}</tr></thead>
109
+ `}
110
+ <tbody>
111
+ ${(block.rows || []).map(row => html`
112
+ <tr>${row.map(cell => html`<td>${cell}</td>`)}</tr>
113
+ `)}
114
+ </tbody>
115
+ </table>
116
+ </div>
117
+ `;
118
+ }
119
+
120
+ function CodeBlock({ block }) {
121
+ const [copied, setCopied] = useState(false);
122
+ const handleCopy = () => {
123
+ navigator.clipboard.writeText(block.code).then(() => {
124
+ setCopied(true);
125
+ setTimeout(() => setCopied(false), 1500);
126
+ });
127
+ };
128
+ return html`
129
+ <div class="cw-block-code">
130
+ ${block.filename && html`<div class="cw-block-code-filename">${block.filename}</div>`}
131
+ <pre><code>${escapeHtml(block.code)}</code></pre>
132
+ ${block.copyable !== false && html`
133
+ <button class="cw-block-code-copy" onClick=${handleCopy}>${copied ? '✓' : '⎘'}</button>
134
+ `}
135
+ </div>
136
+ `;
137
+ }
138
+
139
+ function CollapsibleBlock({ block }) {
140
+ const [open, setOpen] = useState(block.defaultOpen || false);
141
+ return html`
142
+ <details class="cw-block-collapsible" open=${open} onClick=${(e) => { e.preventDefault(); setOpen(!open); }}>
143
+ <summary>${block.title}</summary>
144
+ <div class="cw-block-collapsible-body">${block.body}</div>
145
+ </details>
146
+ `;
147
+ }
148
+
149
+ function StatusBlock({ block }) {
150
+ const icons = { loading: '⏳', success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' };
151
+ return html`
152
+ <div class="cw-block-status cw-block-status-${block.state || 'info'}">
153
+ <span class="cw-block-status-icon">${icons[block.state] || 'ℹ️'}</span>
154
+ <div>
155
+ <strong>${block.title}</strong>
156
+ ${block.body && html`<div>${block.body}</div>`}
157
+ ${block.progress != null && html`
158
+ <div class="cw-block-progress"><div class="cw-block-progress-bar" style=${{ width: `${block.progress * 100}%` }}></div></div>
159
+ `}
160
+ </div>
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Registry & top-level renderer
167
+ // ---------------------------------------------------------------------------
168
+
169
+ const BLOCK_RENDERERS = {
170
+ card: CardBlock,
171
+ cardList: CardListBlock,
172
+ actionButtons: ActionButtonsBlock,
173
+ callout: CalloutBlock,
174
+ image: ImageBlock,
175
+ divider: DividerBlock,
176
+ table: TableBlock,
177
+ code: CodeBlock,
178
+ collapsible: CollapsibleBlock,
179
+ status: StatusBlock,
180
+ };
181
+
182
+ /**
183
+ * Render a list of content blocks.
184
+ *
185
+ * @param {Object} props
186
+ * @param {Array} props.blocks - Array of block objects with a `type` key
187
+ * @param {Function} props.onAction - Callback for message/callback actions
188
+ * @param {Object} props.markdownParser - Optional markdown parser instance
189
+ */
190
+ export function BlockRenderer({ blocks, onAction, markdownParser }) {
191
+ if (!blocks || blocks.length === 0) return null;
192
+
193
+ return html`
194
+ <div class="cw-content-blocks">
195
+ ${blocks.map((block, i) => {
196
+ const Comp = BLOCK_RENDERERS[block.type];
197
+ if (!Comp) return null;
198
+ return html`<${Comp} key=${i} block=${block} onAction=${onAction} markdownParser=${markdownParser} />`;
199
+ })}
200
+ </div>
201
+ `;
202
+ }
203
+
@@ -5,6 +5,7 @@
5
5
  import { html } from 'htm/preact';
6
6
  import { useState, useRef, useEffect } from 'preact/hooks';
7
7
  import { escapeHtml, parseMarkdown, formatFileSize, getFileTypeIcon } from '../utils/helpers.js';
8
+ import { BlockRenderer } from './ContentBlocks.js';
8
9
 
9
10
  // Debug payload viewer component
10
11
  function DebugPayload({ msg, show, onToggle }) {
@@ -152,6 +153,28 @@ export function Message({ msg, debugMode, markdownParser, onEdit, onRetry, isLoa
152
153
  `;
153
154
  }
154
155
 
156
+ // Content blocks: render rich structured UI elements
157
+ if (msg.type === 'content_blocks' && msg.metadata?.blocks) {
158
+ const handleBlockAction = (action) => {
159
+ if (action.type === 'message' && msg._onSendMessage) {
160
+ msg._onSendMessage(action.message);
161
+ }
162
+ if (action.type === 'callback' && msg._onCallback) {
163
+ msg._onCallback(action.callbackId);
164
+ }
165
+ };
166
+ return html`
167
+ <div class="cw-message-row" style="position: relative;">
168
+ <${BlockRenderer}
169
+ blocks=${msg.metadata.blocks}
170
+ onAction=${handleBlockAction}
171
+ markdownParser=${markdownParser}
172
+ />
173
+ ${debugMode && html`<${DebugPayload} msg=${msg} show=${showPayload} onToggle=${() => setShowPayload(!showPayload)} />`}
174
+ </div>
175
+ `;
176
+ }
177
+
155
178
  // Tool call/result: show compact inline version
156
179
  if (isToolCall || isToolResult) {
157
180
  const hasDetails = msg.metadata?.arguments || msg.metadata?.result;
@@ -169,6 +169,26 @@ export function useChat(config, api, storage) {
169
169
  } catch (err) { console.error('[ChatWidget] Parse error:', err); }
170
170
  });
171
171
 
172
+ // Handle content blocks from tool results (rich UI elements)
173
+ eventSource.addEventListener('content.blocks', (event) => {
174
+ try {
175
+ const data = JSON.parse(event.data);
176
+ if (config.onEvent) config.onEvent('content.blocks', data.payload);
177
+ setMessages(prev => [...prev, {
178
+ id: 'content-blocks-' + Date.now(),
179
+ role: 'assistant',
180
+ content: '',
181
+ timestamp: new Date(),
182
+ type: 'content_blocks',
183
+ metadata: {
184
+ toolName: data.payload.tool_name,
185
+ toolCallId: data.payload.tool_call_id,
186
+ blocks: data.payload.blocks,
187
+ },
188
+ }]);
189
+ } catch (err) { console.error('[ChatWidget] Parse error:', err); }
190
+ });
191
+
172
192
  const handleTerminal = (event) => {
173
193
  try {
174
194
  const data = JSON.parse(event.data);
@@ -55,6 +55,9 @@ export const DEFAULT_CONFIG = {
55
55
  theme: 'light',
56
56
 
57
57
  // UI options
58
+ showHeader: true, // Show the header bar (title, action buttons). Set false for minimal chat.
59
+ showFileAttachment: true, // Show the file attachment button in the input area
60
+ showTasksTab: true, // Show the Chat/Tasks tab switcher
58
61
  showConversationSidebar: true,
59
62
  showClearButton: true,
60
63
  showDebugButton: true,