diff --git a/default-themes/nebula-dawn.v2.css b/default-themes/nebula-dawn.v2.css index 112522d..9787ba7 100644 --- a/default-themes/nebula-dawn.v2.css +++ b/default-themes/nebula-dawn.v2.css @@ -466,6 +466,7 @@ body.chat-collapsed .chat-toggle { .chat-input-wrapper .chat-input { width: 100%; + padding-left: 36px; padding-right: 36px; } @@ -704,3 +705,112 @@ body.chat-collapsed .chat-toggle { #loadingOverlay { position: absolute; } .chat-submit:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .chat-input:disabled { opacity: 0.5; cursor: not-allowed; } + +/* ---- Attach button (left side of input) ---- */ +.attach-btn { + position: absolute; + left: 6px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s, background 0.2s; + z-index: 2; +} +.attach-btn:hover { + color: var(--accent-primary); + background: var(--accent-glow); +} +.light-mode .attach-btn { + color: var(--text-secondary); +} +.light-mode .attach-btn:hover { + color: var(--accent-primary); +} + +/* ---- Attach popup menu ---- */ +.attach-menu { + display: none; + flex-direction: column; + position: absolute; + bottom: calc(100% + 6px); + left: 0; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.12); + overflow: hidden; + z-index: 100; + min-width: 160px; +} +.attach-menu-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + font-size: 13px; + color: var(--text-primary); + cursor: pointer; + transition: background 0.15s; +} +.attach-menu-item:hover { + background: var(--accent-glow); +} + +/* ---- Attachment pills ---- */ +.attachment-pills { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 0 4px; + min-height: 0; +} +.attachment-pills:empty { display: none; } +.attachment-pill { + display: flex; + align-items: center; + gap: 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 4px 8px; + max-width: 180px; +} +.attachment-pill-remove { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + font-size: 14px; + padding: 0 2px; + line-height: 1; +} +.attachment-pill-remove:hover { + color: #ff6b6b; +} + +/* ---- Screenshot overlay ---- */ +.screenshot-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.15); + cursor: crosshair; + z-index: 1000; +} +.screenshot-rect { + display: none; + position: absolute; + border: 2px solid red; + background: rgba(255,0,0,0.05); + pointer-events: none; +} diff --git a/default-themes/nebula-dusk.v2.css b/default-themes/nebula-dusk.v2.css index d2bd17a..8e4c2cc 100644 --- a/default-themes/nebula-dusk.v2.css +++ b/default-themes/nebula-dusk.v2.css @@ -466,6 +466,7 @@ body.chat-collapsed .chat-toggle { .chat-input-wrapper .chat-input { width: 100%; + padding-left: 36px; padding-right: 36px; } @@ -696,3 +697,106 @@ body.chat-collapsed .chat-toggle { #loadingOverlay { position: absolute; } .chat-submit:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .chat-input:disabled { opacity: 0.5; cursor: not-allowed; } + +/* ---- Attach button (left side of input) ---- */ +.attach-btn { + position: absolute; + left: 6px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s, background 0.2s; + z-index: 2; +} +.attach-btn:hover { + color: var(--accent-primary); + background: var(--accent-glow); +} + +/* ---- Attach popup menu ---- */ +.attach-menu { + display: none; + flex-direction: column; + position: absolute; + bottom: calc(100% + 6px); + left: 0; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0,0,0,0.3); + overflow: hidden; + z-index: 100; + min-width: 160px; +} +.attach-menu-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + font-size: 13px; + color: var(--text-primary); + cursor: pointer; + transition: background 0.15s; +} +.attach-menu-item:hover { + background: var(--accent-glow); +} + +/* ---- Attachment pills ---- */ +.attachment-pills { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 0 4px; + min-height: 0; +} +.attachment-pills:empty { display: none; } +.attachment-pill { + display: flex; + align-items: center; + gap: 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 4px 8px; + max-width: 180px; +} +.attachment-pill-remove { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + font-size: 14px; + padding: 0 2px; + line-height: 1; +} +.attachment-pill-remove:hover { + color: #ff6b6b; +} + +/* ---- Screenshot overlay ---- */ +.screenshot-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.2); + cursor: crosshair; + z-index: 1000; +} +.screenshot-rect { + display: none; + position: absolute; + border: 2px solid red; + background: rgba(255,0,0,0.05); + pointer-events: none; +} diff --git a/page-scripts/page-v2.js b/page-scripts/page-v2.js index 90944b8..64b2371 100644 --- a/page-scripts/page-v2.js +++ b/page-scripts/page-v2.js @@ -99,24 +99,28 @@ var chatInput = document.getElementById('chatInput'); - // 2. Form submit handler — show overlay + disable inputs + // 2. Form submit handler — fetch+JSON with attachment support var chatForm = document.getElementById('chatForm'); if (chatForm) { - chatForm.addEventListener('submit', function() { + chatForm.addEventListener('submit', function(e) { + e.preventDefault(); + + var ci = document.getElementById('chatInput'); + var messageText = ci ? ci.value : ''; + // Append any captured console errors to the outgoing message var errors = window.__synthOSErrors; - if (errors && errors.length > 0) { - var ci = document.getElementById('chatInput'); - if (ci && ci.value.trim()) { - ci.value = ci.value + '\n\nCONSOLE_ERRORS:\n' + errors.join('\n---\n'); - window.__synthOSErrors = []; - } + if (errors && errors.length > 0 && messageText.trim()) { + messageText = messageText + '\n\nCONSOLE_ERRORS:\n' + errors.join('\n---\n'); + window.__synthOSErrors = []; } + + if (!messageText.trim()) return; + + // Show overlay and disable inputs var overlay = document.getElementById('loadingOverlay'); if (overlay) overlay.style.display = 'flex'; - chatForm.action = window.location.pathname; setTimeout(function() { - var ci = document.getElementById('chatInput'); if (ci) ci.disabled = true; var sb = document.querySelector('.chat-submit'); if (sb) sb.disabled = true; @@ -125,6 +129,41 @@ a.style.opacity = '0.5'; }); }, 50); + + // Build JSON body with optional attachments + var body = { message: messageText }; + var attachments = window.__synthOSAttachments; + if (attachments && attachments.length > 0) { + body.attachments = attachments.map(function(a) { + return { mediaType: a.mediaType, data: a.data, name: a.name }; + }); + } + + fetch(window.location.pathname, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + .then(function(res) { return res.text(); }) + .then(function(html) { + // Reset guard so page-v2.js re-initializes on the new DOM + window.__synthOSChatPanel = false; + document.open(); + document.write(html); + document.close(); + }) + .catch(function(err) { + console.error('Submit failed:', err); + if (overlay) overlay.style.display = 'none'; + if (ci) ci.disabled = false; + var sb = document.querySelector('.chat-submit'); + if (sb) sb.disabled = false; + }); + + // Clear attachments after submit + window.__synthOSAttachments = []; + var pillsContainer = document.querySelector('.attachment-pills'); + if (pillsContainer) pillsContainer.innerHTML = ''; }); } @@ -143,64 +182,64 @@ // --- Create save modal --- var modal = document.createElement('div'); - modal.id = 'saveModal'; + modal.id = 'synthos-saveModal'; modal.className = 'modal-overlay'; modal.innerHTML = '
'; document.body.appendChild(modal); // --- Create error modal --- var errorModal = document.createElement('div'); - errorModal.id = 'errorModal'; + errorModal.id = 'synthos-errorModal'; errorModal.className = 'modal-overlay'; errorModal.innerHTML = ''; document.body.appendChild(errorModal); // --- Element references --- - var titleInput = document.getElementById('saveTitleInput'); - var categoriesInput = document.getElementById('saveCategoriesInput'); - var greetingInput = document.getElementById('saveGreetingInput'); - var greetingHint = document.getElementById('saveGreetingHint'); - var titleError = document.getElementById('saveTitleError'); - var categoriesError = document.getElementById('saveCategoriesError'); + var titleInput = document.getElementById('synthos-saveTitleInput'); + var categoriesInput = document.getElementById('synthos-saveCategoriesInput'); + var greetingInput = document.getElementById('synthos-saveGreetingInput'); + var greetingHint = document.getElementById('synthos-saveGreetingHint'); + var titleError = document.getElementById('synthos-saveTitleError'); + var categoriesError = document.getElementById('synthos-saveCategoriesError'); // --- Greeting enable/disable based on title change --- titleInput.addEventListener('input', function() { @@ -240,7 +279,7 @@ } function showError(msg) { - document.getElementById('errorMessage').textContent = msg; + document.getElementById('synthos-errorMessage').textContent = msg; errorModal.classList.add('show'); } @@ -274,7 +313,7 @@ var categories = cats.split(',').map(function(c) { return c.trim(); }).filter(Boolean); // Disable button during save - var confirmBtn = document.getElementById('saveConfirmBtn'); + var confirmBtn = document.getElementById('synthos-saveConfirmBtn'); confirmBtn.disabled = true; confirmBtn.textContent = 'Saving...'; @@ -313,11 +352,11 @@ // --- Event listeners --- saveLink.addEventListener('click', openSaveModal); - document.getElementById('saveCloseBtn').addEventListener('click', closeSaveModal); - document.getElementById('saveCancelBtn').addEventListener('click', closeSaveModal); - document.getElementById('saveConfirmBtn').addEventListener('click', submitSave); - document.getElementById('errorCloseBtn').addEventListener('click', closeError); - document.getElementById('errorOkBtn').addEventListener('click', closeError); + document.getElementById('synthos-saveCloseBtn').addEventListener('click', closeSaveModal); + document.getElementById('synthos-saveCancelBtn').addEventListener('click', closeSaveModal); + document.getElementById('synthos-saveConfirmBtn').addEventListener('click', submitSave); + document.getElementById('synthos-errorCloseBtn').addEventListener('click', closeError); + document.getElementById('synthos-errorOkBtn').addEventListener('click', closeError); var saveModalMouseDownTarget = null; modal.addEventListener('mousedown', function(e) { saveModalMouseDownTarget = e.target; }); @@ -445,18 +484,18 @@ // --- Create brainstorm modal --- var modal = document.createElement('div'); - modal.id = 'brainstormModal'; + modal.id = 'synthos-brainstormModal'; modal.className = 'modal-overlay brainstorm-modal'; modal.innerHTML = ''; document.body.appendChild(modal); @@ -481,11 +520,11 @@ function closeBrainstorm() { modal.classList.remove('show'); brainstormHistory = []; - document.getElementById('brainstormMessages').innerHTML = ''; + document.getElementById('synthos-brainstormMessages').innerHTML = ''; } function scrollBrainstormToBottom() { - var el = document.getElementById('brainstormMessages'); + var el = document.getElementById('synthos-brainstormMessages'); el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }); } @@ -542,13 +581,13 @@ } else { div.textContent = text; } - document.getElementById('brainstormMessages').appendChild(div); + document.getElementById('synthos-brainstormMessages').appendChild(div); scrollBrainstormToBottom(); } function submitSuggestion(text) { // Disable old suggestion chips so they can't be double-clicked - var oldChips = document.querySelectorAll('#brainstormMessages .brainstorm-suggestion-chip'); + var oldChips = document.querySelectorAll('#synthos-brainstormMessages .brainstorm-suggestion-chip'); for (var i = 0; i < oldChips.length; i++) { oldChips[i].disabled = true; } @@ -571,7 +610,7 @@ // Send from the input field function sendBrainstormMessage() { - var input = document.getElementById('brainstormInput'); + var input = document.getElementById('synthos-brainstormInput'); var text = input.value.trim(); if (!text) return; input.value = ''; @@ -580,7 +619,7 @@ // Core fetch — isOpener=true means this is the initial call when brainstorm opens function sendBrainstormText(text, isOpener) { - var input = document.getElementById('brainstormInput'); + var input = document.getElementById('synthos-brainstormInput'); var userMsg = text || (isOpener ? 'Look at the conversation so far and suggest what we could build or improve.' : ''); if (!userMsg) return; @@ -592,13 +631,13 @@ var thinking = document.createElement('div'); thinking.className = 'brainstorm-thinking'; - thinking.id = 'brainstormThinking'; + thinking.id = 'synthos-brainstormThinking'; thinking.textContent = 'Thinking...'; - document.getElementById('brainstormMessages').appendChild(thinking); + document.getElementById('synthos-brainstormMessages').appendChild(thinking); scrollBrainstormToBottom(); input.disabled = true; - document.getElementById('brainstormSendBtn').disabled = true; + document.getElementById('synthos-brainstormSendBtn').disabled = true; fetch('/api/brainstorm', { method: 'POST', @@ -613,7 +652,7 @@ return res.json(); }) .then(function(data) { - var thinkingEl = document.getElementById('brainstormThinking'); + var thinkingEl = document.getElementById('synthos-brainstormThinking'); if (thinkingEl) thinkingEl.remove(); var response = data.response || 'Sorry, I didn\'t get a response.'; @@ -626,20 +665,20 @@ }); }) .catch(function(err) { - var thinkingEl = document.getElementById('brainstormThinking'); + var thinkingEl = document.getElementById('synthos-brainstormThinking'); if (thinkingEl) thinkingEl.remove(); appendBrainstormMessage('assistant', 'Something went wrong: ' + err.message); }) .finally(function() { input.disabled = false; - document.getElementById('brainstormSendBtn').disabled = false; + document.getElementById('synthos-brainstormSendBtn').disabled = false; input.focus(); }); } // --- Event listeners --- brainstormBtn.addEventListener('click', openBrainstorm); - document.getElementById('brainstormCloseBtn').addEventListener('click', closeBrainstorm); + document.getElementById('synthos-brainstormCloseBtn').addEventListener('click', closeBrainstorm); var brainstormMouseDownTarget = null; modal.addEventListener('mousedown', function(e) { brainstormMouseDownTarget = e.target; }); @@ -652,8 +691,8 @@ if (e.key === 'Escape' && modal.classList.contains('show')) closeBrainstorm(); }); - document.getElementById('brainstormSendBtn').addEventListener('click', sendBrainstormMessage); - document.getElementById('brainstormInput').addEventListener('keydown', function(e) { + document.getElementById('synthos-brainstormSendBtn').addEventListener('click', sendBrainstormMessage); + document.getElementById('synthos-brainstormInput').addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendBrainstormMessage(); @@ -661,6 +700,339 @@ }); })(); + // 9. Attach button — + menu, attachment pills, file picker, screenshot tool + (function() { + var chatInput = document.getElementById('chatInput'); + if (!chatInput) return; + var wrapper = chatInput.parentElement; + if (!wrapper || !wrapper.classList.contains('chat-input-wrapper')) return; + + // --- Attachments state --- + if (!window.__synthOSAttachments) window.__synthOSAttachments = []; + + // --- Create pills container (between .link-group and #chatForm) --- + var pillsContainer = document.createElement('div'); + pillsContainer.className = 'attachment-pills'; + var chatForm = document.getElementById('chatForm'); + if (chatForm && chatForm.parentNode) { + chatForm.parentNode.insertBefore(pillsContainer, chatForm); + } + + function renderPills() { + pillsContainer.innerHTML = ''; + var attachments = window.__synthOSAttachments; + if (!attachments || attachments.length === 0) return; + for (var i = 0; i < attachments.length; i++) { + (function(idx) { + var att = attachments[idx]; + var pill = document.createElement('div'); + pill.className = 'attachment-pill'; + + var thumb = document.createElement('img'); + thumb.src = 'data:' + att.mediaType + ';base64,' + att.data; + thumb.style.cssText = 'width:24px;height:24px;object-fit:cover;border-radius:3px;'; + pill.appendChild(thumb); + + var nameSpan = document.createElement('span'); + nameSpan.textContent = att.name || 'image'; + nameSpan.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;'; + pill.appendChild(nameSpan); + + var removeBtn = document.createElement('button'); + removeBtn.type = 'button'; + removeBtn.className = 'attachment-pill-remove'; + removeBtn.textContent = '\u00d7'; + removeBtn.addEventListener('click', function() { + window.__synthOSAttachments.splice(idx, 1); + renderPills(); + }); + pill.appendChild(removeBtn); + + pillsContainer.appendChild(pill); + })(i); + } + } + + // Expose for debugging + window.__synthOSRenderPills = renderPills; + + // --- Helper: add an image attachment from a data URL --- + function addImageFromDataUrl(dataUrl, name) { + var commaIdx = dataUrl.indexOf(','); + if (commaIdx === -1) return; + var meta = dataUrl.substring(0, commaIdx); // data:image/png;base64 + var base64 = dataUrl.substring(commaIdx + 1); + var mediaType = meta.replace('data:', '').replace(';base64', ''); + window.__synthOSAttachments.push({ + mediaType: mediaType, + data: base64, + name: name || 'image' + }); + renderPills(); + } + + // --- Hidden file input --- + var fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*'; + fileInput.style.display = 'none'; + document.body.appendChild(fileInput); + + fileInput.addEventListener('change', function() { + var file = fileInput.files && fileInput.files[0]; + if (!file) return; + var reader = new FileReader(); + reader.onload = function() { + addImageFromDataUrl(reader.result, file.name); + }; + reader.readAsDataURL(file); + fileInput.value = ''; + }); + + // --- Paste image from clipboard (document-level to catch all pastes) --- + document.addEventListener('paste', function(e) { + // Only handle pastes when chat input is focused or no other editable is focused + var active = document.activeElement; + var isEditable = active && (active.isContentEditable || active.tagName === 'TEXTAREA' || + (active.tagName === 'INPUT' && active !== chatInput)); + if (isEditable) return; + + var items = e.clipboardData && e.clipboardData.items; + if (!items) return; + for (var i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image/') === 0) { + var blob = items[i].getAsFile(); + if (!blob) continue; + e.preventDefault(); + var reader = new FileReader(); + reader.onload = function() { + addImageFromDataUrl(reader.result, 'pasted-image.png'); + }; + reader.readAsDataURL(blob); + return; // only handle the first image + } + } + }); + + // --- + button --- + var attachBtn = document.createElement('button'); + attachBtn.type = 'button'; + attachBtn.className = 'attach-btn'; + if (window.__synthOSTooltip) window.__synthOSTooltip(attachBtn, 'Attach file or screenshot'); + attachBtn.innerHTML = ''; + wrapper.insertBefore(attachBtn, chatInput); + + // --- Popup menu --- + var menu = document.createElement('div'); + menu.className = 'attach-menu'; + + var menuAttachFile = document.createElement('div'); + menuAttachFile.className = 'attach-menu-item'; + menuAttachFile.innerHTML = ' Attach File'; + menu.appendChild(menuAttachFile); + + var menuScreenshot = document.createElement('div'); + menuScreenshot.className = 'attach-menu-item'; + menuScreenshot.innerHTML = ' Screenshot'; + menu.appendChild(menuScreenshot); + + wrapper.appendChild(menu); + + var menuOpen = false; + function toggleMenu() { + menuOpen = !menuOpen; + menu.style.display = menuOpen ? 'flex' : 'none'; + } + function closeMenu() { + menuOpen = false; + menu.style.display = 'none'; + } + + attachBtn.addEventListener('click', function(e) { + e.stopPropagation(); + toggleMenu(); + }); + + document.addEventListener('click', function() { + if (menuOpen) closeMenu(); + }); + menu.addEventListener('click', function(e) { e.stopPropagation(); }); + + menuAttachFile.addEventListener('click', function() { + closeMenu(); + fileInput.click(); + }); + + // --- Screenshot annotation flow (multi-rectangle) --- + menuScreenshot.addEventListener('click', function() { + closeMenu(); + startScreenshotAnnotation(); + }); + + function startScreenshotAnnotation() { + var viewerPanel = document.getElementById('viewerPanel'); + if (!viewerPanel) return; + + // Create overlay + var overlay = document.createElement('div'); + overlay.className = 'screenshot-overlay'; + + // Instructions bar + var instrBar = document.createElement('div'); + instrBar.style.cssText = 'position:absolute;top:10px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:white;padding:6px 14px;border-radius:6px;font-size:13px;z-index:10;pointer-events:none;'; + instrBar.textContent = 'Draw rectangles to highlight areas, then click Capture'; + overlay.appendChild(instrBar); + + // Persistent action buttons (always visible) + var actions = document.createElement('div'); + actions.className = 'screenshot-actions'; + actions.style.cssText = 'position:absolute;bottom:16px;left:50%;transform:translateX(-50%);display:flex;gap:8px;z-index:10;'; + + var captureBtn = document.createElement('button'); + captureBtn.type = 'button'; + captureBtn.textContent = 'Capture'; + captureBtn.className = 'brainstorm-send-btn'; + captureBtn.style.cssText = 'padding:6px 16px;font-size:13px;'; + + var cancelBtn = document.createElement('button'); + cancelBtn.type = 'button'; + cancelBtn.textContent = 'Cancel'; + cancelBtn.className = 'brainstorm-send-btn'; + cancelBtn.style.cssText = 'padding:6px 16px;font-size:13px;background:transparent;border:1px solid rgba(255,255,255,0.3);color:white;'; + + actions.appendChild(captureBtn); + actions.appendChild(cancelBtn); + overlay.appendChild(actions); + + // Drawing state + var currentRect = null; + var startX, startY, isDrawing = false; + var allRects = []; // Array of {el, x, y, w, h} + + overlay.addEventListener('mousedown', function(e) { + if (e.target.tagName === 'BUTTON') return; + isDrawing = true; + var overlayBounds = overlay.getBoundingClientRect(); + startX = e.clientX - overlayBounds.left; + startY = e.clientY - overlayBounds.top; + + // Create a new rectangle element + currentRect = document.createElement('div'); + currentRect.className = 'screenshot-rect'; + currentRect.style.display = 'block'; + currentRect.style.left = startX + 'px'; + currentRect.style.top = startY + 'px'; + currentRect.style.width = '0'; + currentRect.style.height = '0'; + overlay.appendChild(currentRect); + }); + + overlay.addEventListener('mousemove', function(e) { + if (!isDrawing || !currentRect) return; + var overlayBounds = overlay.getBoundingClientRect(); + var curX = e.clientX - overlayBounds.left; + var curY = e.clientY - overlayBounds.top; + var x = Math.min(startX, curX); + var y = Math.min(startY, curY); + var w = Math.abs(curX - startX); + var h = Math.abs(curY - startY); + currentRect.style.left = x + 'px'; + currentRect.style.top = y + 'px'; + currentRect.style.width = w + 'px'; + currentRect.style.height = h + 'px'; + }); + + overlay.addEventListener('mouseup', function() { + if (!isDrawing || !currentRect) return; + isDrawing = false; + var w = parseInt(currentRect.style.width); + var h = parseInt(currentRect.style.height); + if (w < 10 || h < 10) { + // Too small — discard + currentRect.remove(); + currentRect = null; + return; + } + allRects.push({ + el: currentRect, + x: parseInt(currentRect.style.left), + y: parseInt(currentRect.style.top), + w: w, + h: h + }); + currentRect = null; + }); + + captureBtn.addEventListener('click', function() { + doCapture(); + }); + cancelBtn.addEventListener('click', function() { + cleanup(); + }); + + function doCapture() { + if (typeof html2canvas === 'undefined') { + console.error('html2canvas not loaded'); + cleanup(); + return; + } + + // Capture the full viewer panel without the overlay + overlay.style.visibility = 'hidden'; + + html2canvas(viewerPanel, { useCORS: true, logging: false }).then(function(fullCanvas) { + // Draw red rectangles onto the captured canvas + var vpRect = viewerPanel.getBoundingClientRect(); + var scaleX = fullCanvas.width / vpRect.width; + var scaleY = fullCanvas.height / vpRect.height; + + var ctx = fullCanvas.getContext('2d'); + ctx.strokeStyle = 'red'; + ctx.lineWidth = 3 * Math.max(scaleX, scaleY); + + for (var i = 0; i < allRects.length; i++) { + var r = allRects[i]; + ctx.strokeRect( + Math.round(r.x * scaleX), + Math.round(r.y * scaleY), + Math.round(r.w * scaleX), + Math.round(r.h * scaleY) + ); + } + + var dataUrl = fullCanvas.toDataURL('image/png'); + var base64 = dataUrl.split(',')[1]; + window.__synthOSAttachments.push({ + mediaType: 'image/png', + data: base64, + name: 'screenshot.png' + }); + renderPills(); + cleanup(); + }).catch(function(err) { + console.error('Screenshot capture failed:', err); + cleanup(); + }); + } + + function cleanup() { + if (overlay.parentNode) overlay.parentNode.removeChild(overlay); + document.removeEventListener('keydown', onKeyDown); + } + + // Escape to cancel + function onKeyDown(e) { + if (e.key === 'Escape') { + cleanup(); + } + } + document.addEventListener('keydown', onKeyDown); + + viewerPanel.style.position = 'relative'; + viewerPanel.appendChild(overlay); + } + })(); + // Initial focus — run after all setup (including chat-collapsed check) if (chatInput && !document.body.classList.contains('chat-collapsed')) { chatInput.focus(); diff --git a/required-pages/builder.html b/required-pages/builder.html index 247f1c4..9ecd883 100644 --- a/required-pages/builder.html +++ b/required-pages/builder.html @@ -8,6 +8,7 @@ + diff --git a/src/builders/anthropic.ts b/src/builders/anthropic.ts index 895143a..aa39512 100644 --- a/src/builders/anthropic.ts +++ b/src/builders/anthropic.ts @@ -1,6 +1,6 @@ -import { anthropic as createAnthropicModel, completePrompt } from '../models'; +import { anthropic as createAnthropicModel, completePrompt, ContentBlock } from '../models'; import { parseChangeList, getTransformInstr } from '../service/transformPage'; -import { Builder, BuilderResult, CHANGE_OPS_SCHEMA, ContextSection } from './types'; +import { Attachment, Builder, BuilderResult, CHANGE_OPS_SCHEMA, ContextSection } from './types'; // --------------------------------------------------------------------------- // Builder options — passed from the route handler @@ -97,19 +97,19 @@ export function createAnthropicBuilder( const name = productName ?? 'SynthOS'; return { - async run(currentPage, additionalSections, userMessage, newBuild): Promise