Add v2 rewrite: monorepo with desktop and web apps, shared packages, docs, and wireframes
This commit is contained in:
322
wireframe/app.js
Normal file
322
wireframe/app.js
Normal file
@@ -0,0 +1,322 @@
|
||||
// ─── Agent data ───
|
||||
const agentInfo = {
|
||||
cr: { name: 'Company Research Agent', status: 'Completed · 100%', action: 'Company overview complete' },
|
||||
fm: { name: 'Financial Modeling Agent', status: 'Running · 62% complete', action: 'Building revenue schedule…' },
|
||||
sf: { name: 'SEC Filings Agent', status: 'Running · 45% complete', action: 'Extracting segment data from 10-K…' },
|
||||
ec: { name: 'Earnings Call Agent', status: 'Running · 78% complete', action: 'Summarizing Q2 FY25 call…' },
|
||||
ci: { name: 'Competitive Intel Agent', status: 'Queued · Waiting for SEC Filings', action: 'Queued' },
|
||||
va: { name: 'Valuation Agent', status: 'Queued · Waiting for Financial Modeling', action: 'Queued' },
|
||||
rk: { name: 'Risk Agent', status: 'Idle', action: 'Idle' },
|
||||
mw: { name: 'Memo Writing Agent', status: 'Idle', action: 'Idle' },
|
||||
pa: { name: 'Presentation Agent', status: 'Idle', action: 'Idle' },
|
||||
mn: { name: 'Monitoring Agent', status: 'Idle', action: 'Idle' },
|
||||
sv: { name: 'Source Verification Agent', status: 'Idle', action: 'Idle' },
|
||||
rt: { name: 'Red Team Agent', status: 'Idle', action: 'Idle' },
|
||||
ex: { name: 'Export Agent', status: 'Idle', action: 'Idle' },
|
||||
qa: { name: 'Model QA Agent', status: 'Idle', action: 'Idle' },
|
||||
};
|
||||
|
||||
const activeAgents = ['sf', 'fm', 'ec'];
|
||||
let carouselIdx = 0;
|
||||
|
||||
// ─── Screen switching ───
|
||||
function switchScreen(id, btn) {
|
||||
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById('screen-' + id).classList.add('active');
|
||||
document.querySelectorAll('.screen-tabs button').forEach(b => b.classList.remove('active'));
|
||||
if (btn && btn.classList) btn.classList.add('active');
|
||||
// Show/hide ticker group on home vs other screens
|
||||
const tickerGroup = document.getElementById('tickerGroup');
|
||||
const navBtn = document.getElementById('navOpenBtn');
|
||||
if (id === 'home') {
|
||||
if (tickerGroup) tickerGroup.style.display = 'none';
|
||||
} else {
|
||||
if (tickerGroup) tickerGroup.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Collapsible nav (close completely) ───
|
||||
function toggleNav(navId) {
|
||||
const nav = document.getElementById(navId);
|
||||
nav.classList.toggle('collapsed');
|
||||
const btn = nav.querySelector('.collapse-btn');
|
||||
if (nav.classList.contains('collapsed')) {
|
||||
btn.textContent = '▶';
|
||||
} else {
|
||||
btn.textContent = '◀';
|
||||
}
|
||||
}
|
||||
function openNav() {
|
||||
const wsNav = document.getElementById('ws-nav');
|
||||
const memoNav = document.getElementById('memo-nav');
|
||||
[wsNav, memoNav].forEach(nav => {
|
||||
if (nav && nav.classList.contains('collapsed')) {
|
||||
nav.classList.remove('collapsed');
|
||||
nav.querySelector('.collapse-btn').textContent = '◀';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Left nav button selection ───
|
||||
document.querySelectorAll('#ws-nav button').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('#ws-nav button').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Memo outline selection ───
|
||||
document.querySelectorAll('.memo-outline li').forEach(li => {
|
||||
li.addEventListener('click', function() {
|
||||
document.querySelectorAll('.memo-outline li').forEach(l => l.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Agent selection + detail panel ───
|
||||
function selectAgent(tile, id) {
|
||||
document.querySelectorAll('.agent-tile').forEach(t => t.classList.remove('selected'));
|
||||
tile.classList.add('selected');
|
||||
const detail = document.getElementById('agentDetail');
|
||||
detail.classList.add('open');
|
||||
document.getElementById('detailAgentName').textContent = agentInfo[id].name;
|
||||
document.getElementById('detailAgentStatus').textContent = agentInfo[id].status;
|
||||
}
|
||||
function closeAgentDetail() {
|
||||
document.getElementById('agentDetail').classList.remove('open');
|
||||
document.querySelectorAll('.agent-tile').forEach(t => t.classList.remove('selected'));
|
||||
}
|
||||
|
||||
// ─── Agent carousel ───
|
||||
function updateCarousel() {
|
||||
const agent = agentInfo[activeAgents[carouselIdx]];
|
||||
document.getElementById('carouselName').textContent = agent.name;
|
||||
document.getElementById('carouselAction').textContent = agent.action;
|
||||
document.getElementById('carouselCounter').textContent = (carouselIdx + 1) + ' / ' + activeAgents.length;
|
||||
document.getElementById('carouselPrev').disabled = carouselIdx === 0;
|
||||
document.getElementById('carouselNext').disabled = carouselIdx === activeAgents.length - 1;
|
||||
}
|
||||
function carouselPrev() {
|
||||
if (carouselIdx > 0) { carouselIdx--; updateCarousel(); }
|
||||
}
|
||||
function carouselNext() {
|
||||
if (carouselIdx < activeAgents.length - 1) { carouselIdx++; updateCarousel(); }
|
||||
}
|
||||
updateCarousel();
|
||||
|
||||
// ─── Agent fullscreen overlay ───
|
||||
function openAgentOverlay(agentId) {
|
||||
const id = agentId || activeAgents[carouselIdx];
|
||||
const agent = agentInfo[id];
|
||||
document.getElementById('overlayAgentName').textContent = agent.name;
|
||||
document.getElementById('overlayAgentStatus').textContent = agent.status;
|
||||
// Activate the matching tab
|
||||
document.querySelectorAll('.overlay-agent-tabs .oat-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
if (btn.textContent.trim().includes(agent.name.split(' ')[0])) btn.classList.add('active');
|
||||
});
|
||||
document.getElementById('agentOverlay').classList.add('open');
|
||||
}
|
||||
function closeAgentOverlay() {
|
||||
document.getElementById('agentOverlay').classList.remove('open');
|
||||
}
|
||||
|
||||
// ─── Settings overlay ───
|
||||
function openSettings() { document.getElementById('settingsOverlay').classList.add('open'); }
|
||||
function closeSettings() { document.getElementById('settingsOverlay').classList.remove('open'); }
|
||||
|
||||
// ─── Settings nav ───
|
||||
document.querySelectorAll('.settings-nav button').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.settings-nav button').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Review mode + annotations ───
|
||||
let reviewMode = false;
|
||||
let annotationMode = null;
|
||||
|
||||
function toggleReviewMode() {
|
||||
reviewMode = !reviewMode;
|
||||
const btn = document.getElementById('reviewToggle');
|
||||
btn.textContent = reviewMode ? 'Exit Review' : 'Review Mode';
|
||||
btn.style.background = reviewMode ? 'var(--accent)' : '';
|
||||
btn.style.color = reviewMode ? 'var(--surface)' : '';
|
||||
btn.style.borderColor = reviewMode ? 'var(--accent)' : '';
|
||||
if (!reviewMode) {
|
||||
annotationMode = null;
|
||||
document.querySelectorAll('#modeHighlightBtn,#modeCommentBtn,#modeBoxBtn').forEach(b => {
|
||||
b.style.background = ''; b.style.color = ''; b.style.borderColor = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setAnnotationMode(mode) {
|
||||
annotationMode = mode;
|
||||
const btns = { highlight: 'modeHighlightBtn', comment: 'modeCommentBtn', box: 'modeBoxBtn' };
|
||||
Object.entries(btns).forEach(([m, bid]) => {
|
||||
const b = document.getElementById(bid);
|
||||
b.style.background = m === mode ? 'var(--accent)' : '';
|
||||
b.style.color = m === mode ? 'var(--surface)' : '';
|
||||
b.style.borderColor = m === mode ? 'var(--accent)' : '';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', function(e) {
|
||||
const toolbar = document.getElementById('annotationToolbar');
|
||||
if (!reviewMode || !e.target.closest('#screen-memo .center')) {
|
||||
toolbar.classList.remove('open'); return;
|
||||
}
|
||||
const sel = window.getSelection();
|
||||
if (sel && sel.toString().trim().length > 0) {
|
||||
const range = sel.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
const center = e.target.closest('.center');
|
||||
const cRect = center.getBoundingClientRect();
|
||||
toolbar.style.top = (rect.top - cRect.top - 36 + center.scrollTop) + 'px';
|
||||
toolbar.style.left = (rect.left - cRect.left + center.scrollLeft) + 'px';
|
||||
toolbar.classList.add('open');
|
||||
} else {
|
||||
toolbar.classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
function annotateHighlight() {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.toString().trim().length === 0) return;
|
||||
const range = sel.getRangeAt(0);
|
||||
const span = document.createElement('span');
|
||||
span.className = 'highlight-annotation';
|
||||
span.setAttribute('data-comment', 'Highlighted by reviewer');
|
||||
try { range.surroundContents(span); } catch(e) { span.textContent = sel.toString(); range.deleteContents(); range.insertNode(span); }
|
||||
sel.removeAllRanges();
|
||||
document.getElementById('annotationToolbar').classList.remove('open');
|
||||
}
|
||||
|
||||
function annotateComment() {
|
||||
const comment = prompt('Add a review comment:');
|
||||
if (!comment) return;
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.toString().trim().length === 0) return;
|
||||
const range = sel.getRangeAt(0);
|
||||
const span = document.createElement('span');
|
||||
span.className = 'highlight-annotation';
|
||||
span.setAttribute('data-comment', comment);
|
||||
try { range.surroundContents(span); } catch(e) { span.textContent = sel.toString(); range.deleteContents(); range.insertNode(span); }
|
||||
sel.removeAllRanges();
|
||||
document.getElementById('annotationToolbar').classList.remove('open');
|
||||
}
|
||||
|
||||
function annotateStrikethrough() {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.toString().trim().length === 0) return;
|
||||
const range = sel.getRangeAt(0);
|
||||
const span = document.createElement('span');
|
||||
span.style.textDecoration = 'line-through';
|
||||
span.style.color = 'var(--red)';
|
||||
span.style.opacity = '0.6';
|
||||
try { range.surroundContents(span); } catch(e) { span.textContent = sel.toString(); range.deleteContents(); range.insertNode(span); }
|
||||
sel.removeAllRanges();
|
||||
document.getElementById('annotationToolbar').classList.remove('open');
|
||||
}
|
||||
|
||||
function annotateBox() {
|
||||
document.getElementById('annotationToolbar').classList.remove('open');
|
||||
const center = document.querySelector('#screen-memo .center');
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;z-index:150;cursor:crosshair;';
|
||||
center.style.position = 'relative';
|
||||
center.appendChild(overlay);
|
||||
let startX, startY;
|
||||
overlay.addEventListener('mousedown', function(e) {
|
||||
const rect = overlay.getBoundingClientRect();
|
||||
startX = e.clientX - rect.left + center.scrollLeft;
|
||||
startY = e.clientY - rect.top + center.scrollTop;
|
||||
const box = document.createElement('div');
|
||||
box.className = 'box-annotation';
|
||||
box.style.left = startX + 'px'; box.style.top = startY + 'px';
|
||||
box.style.width = '0px'; box.style.height = '0px';
|
||||
center.appendChild(box);
|
||||
function onMove(ev) {
|
||||
const cx = ev.clientX - rect.left + center.scrollLeft;
|
||||
const cy = ev.clientY - rect.top + center.scrollTop;
|
||||
box.style.width = Math.abs(cx - startX) + 'px';
|
||||
box.style.height = Math.abs(cy - startY) + 'px';
|
||||
box.style.left = Math.min(cx, startX) + 'px';
|
||||
box.style.top = Math.min(cy, startY) + 'px';
|
||||
}
|
||||
function onUp() {
|
||||
overlay.remove();
|
||||
const label = document.createElement('div');
|
||||
label.className = 'box-label';
|
||||
label.textContent = 'Review needed';
|
||||
label.onclick = function() { const note = prompt('Add note:', label.textContent); if (note) label.textContent = note; };
|
||||
box.appendChild(label);
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Model tab switching ───
|
||||
function switchModelTab(tab) {
|
||||
const spreadsheet = document.querySelector('#screen-model .spreadsheet');
|
||||
const spreadsheetInfo = spreadsheet ? spreadsheet.parentElement.querySelector('div:last-of-type') : null;
|
||||
const chartsPanel = document.getElementById('chartsPanel');
|
||||
const allTables = document.querySelectorAll('#screen-model .center > *');
|
||||
|
||||
if (tab === 'charts') {
|
||||
// Hide spreadsheet area, show charts
|
||||
const center = document.querySelector('#screen-model .center');
|
||||
if (center) {
|
||||
// Hide everything except charts panel
|
||||
Array.from(center.children).forEach(child => {
|
||||
if (child.id !== 'chartsPanel') child.style.display = 'none';
|
||||
});
|
||||
}
|
||||
if (chartsPanel) chartsPanel.style.display = 'grid';
|
||||
} else {
|
||||
// Show spreadsheet area, hide charts
|
||||
const center = document.querySelector('#screen-model .center');
|
||||
if (center) {
|
||||
Array.from(center.children).forEach(child => {
|
||||
if (child.id !== 'chartsPanel') child.style.display = '';
|
||||
});
|
||||
}
|
||||
if (chartsPanel) chartsPanel.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Overlay tab switching (new tab-based layout) ───
|
||||
function switchOverlayTab(btn, id) {
|
||||
document.querySelectorAll('.overlay-agent-tabs .oat-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
const agent = agentInfo[id];
|
||||
document.getElementById('overlayAgentName').textContent = agent.name;
|
||||
document.getElementById('overlayAgentStatus').textContent = agent.status;
|
||||
}
|
||||
|
||||
// ─── Dep tree tab switching ───
|
||||
function switchDepTab(btn, ticker) {
|
||||
btn.parentElement.querySelectorAll('button').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
// Future: swap dep tree content per ticker
|
||||
}
|
||||
|
||||
// ─── Profile editor ───
|
||||
function openProfile() { document.getElementById('profileOverlay').classList.add('open'); }
|
||||
function closeProfile() { document.getElementById('profileOverlay').classList.remove('open'); }
|
||||
function saveProfile() {
|
||||
const name = document.getElementById('profileName').value;
|
||||
const initials = name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2);
|
||||
document.querySelector('.topbar .avatar').textContent = initials;
|
||||
document.getElementById('profileDisplayName').textContent = name;
|
||||
document.querySelector('.profile-overlay .profile-avatar').textContent = initials;
|
||||
closeProfile();
|
||||
}
|
||||
|
||||
// ─── Init: hide ticker on home screen ───
|
||||
document.getElementById('tickerGroup').style.display = 'none';
|
||||
Reference in New Issue
Block a user