In box-ia
{ "name": "inbox-ai", "version": "0.1.0", "scripts": { "start": "node server/server.jv", "dev": "node server/server.jv" }, "dependencies": { "express": "^4.18.2", "body-parser": "^1.20.2", "cors": "^2.8.5", "node-fetch": "^3.3.2" } }
*{box-sizing:border-box} body { margin:0; background: linear-gradient(180deg,#000000 0%, #050505 100%); font-family: Inter, Roboto, system-ui, sans-serif; color:var(--text); height:100vh; }
.header{ display:flex; align-items:center; justify-content:space-between; padding:18px 24px; border-bottom:1px solid rgba(255,255,255,0.04); background:transparent; }
.brand { display:flex; align-items:center; gap:14px; font-weight:700; font-size:20px; color:var(--text); }
.app { display:flex; height:calc(100vh - 64px); }
.sidebar { width:360px; border-right:1px solid rgba(255,255,255,0.03); background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent); padding:18px; overflow:auto; }
.content { flex:1; padding:28px; overflow:auto; }
.message-card { background:linear-gradient(180deg, rgba(255,255,255,0.01), rgba(255,255,255,0.02)); border:1px solid rgba(255,255,255,0.03); padding:12px; border-radius:10px; margin-bottom:12px; cursor:pointer; }
.message-card:hover { transform:translateY(-2px); transition:transform .12s ease-out; }
.sender { font-weight:600; font-size:15px; } .meta { font-size:12px; color:var(--muted); margin-top:6px; } .summary { margin-top:8px; color:#cfd8db; font-size:13px; }
.detail-header { display:flex; gap:18px; align-items:center; margin-bottom:10px; } .detail-meta { color:var(--muted); font-size:13px; }
.reply-box { margin-top:16px; display:flex; gap:12px; flex-direction:column; }
.btn { background:var(--accent); color:#000; border:none; padding:10px 14px; border-radius:8px; font-weight:700; cursor:pointer; align-self:flex-start; }
.small { font-size:13px; padding:8px 10px; border-radius:8px; background:#0f0f0f; color:var(--muted); } textarea { width:100%; min-height:140px; background:#070707; border:1px solid rgba(255,255,255,0.03); padding:12px; color:var(--text); resize:vertical; border-radius:8px; } // app.jv import InboxList from './components/InboxList.jv'; import MessageView from './components/MessageView.jv';
const root = document.getElementById('root');
function App() { const state = { messages: [], selectedMessageId: null, schedule: [] // user's weekly schedule };
// Fetch initial data from backend async function loadInitial() { const msgs = await fetch('/api/messages').then(r => r.json()); const schedule = await fetch('/api/schedule').then(r => r.json()); // sort newest -> oldest by date desc msgs.sort((a,b)=> new Date(b.date) - new Date(a.date)); state.messages = msgs; state.schedule = schedule; render(); }
function onSelectMessage(id) { state.selectedMessageId = id; render(); }
async function onSendReply(messageId, replyText) { // send to backend to actually send or store const res = await fetch('/api/send-reply', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ messageId, replyText }) }); const result = await res.json(); if(result.success){ // update local message as replied const msg = state.messages.find(m=>m.id===messageId); if(msg) msg.replied = true; render(); } else { alert('Failed to send reply: ' + (result.error||'unknown')); } }
function render(){
root.innerHTML = <div class="header"> <div class="brand"> <div style="width:36px;height:36px;border-radius:8px;background:linear-gradient(135deg,var(--accent),#0066ff);display:flex;align-items:center;justify-content:center;font-weight:800;color:#000">I</div> Inbox </div> <div class="small">AI-powered replies • Weekly scheduling</div> </div> <div class="app"> <div class="sidebar" id="sidebar"></div> <div class="content" id="content"></div> </div>;
const sidebarEl = document.getElementById('sidebar');
const contentEl = document.getElementById('content');
// Render inbox list
sidebarEl.innerHTML = InboxList({
messages: state.messages,
selectedId: state.selectedMessageId,
onSelect: onSelectMessage
});
// Render message view or placeholder
if(state.selectedMessageId){
const msg = state.messages.find(m=>m.id===state.selectedMessageId);
contentEl.innerHTML = MessageView({
message: msg,
schedule: state.schedule,
onRequestReply: async () => {
// request AI-generated reply
contentEl.querySelector('.reply-status').textContent = 'Generating...';
const aiRes = await fetch('/api/generate-reply', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ messageId: msg.id })
}).then(r=>r.json());
if(aiRes.success){
const textarea = contentEl.querySelector('textarea');
textarea.value = aiRes.reply;
contentEl.querySelector('.reply-status').textContent = 'AI reply generated';
} else {
contentEl.querySelector('.reply-status').textContent = 'AI error: ' + (aiRes.error||'unknown');
}
},
onSend: (replyText) => onSendReply(msg.id, replyText)
});
} else {
contentEl.innerHTML = `<div style="color:var(--muted);font-size:16px">Select a message to view details and generate a reply.</div>`;
}
}
loadInitial(); }
App();
// InboxList.jv
export default function InboxList({ messages, selectedId, onSelect }) {
if(!messages) messages = [];
return <div style="font-size:13px;color:var(--muted);margin-bottom:8px">Incoming</div> ${messages.map(m =>
).join('')} <script> // Hook up window-level handler to call onSelect in app window._onSelect = ${onSelect.toString()}; </script> ;
}
// MessageView.jv
export default function MessageView({ message, schedule, onRequestReply, onSend }) {
if(!message) return <div>No message</div>;
const summary = message.summary || message.preview || (message.body||'').slice(0,280) + '...';
return `
<div style="margin-top:12px; color:#cfd8db;">
<div style="font-weight:600;margin-bottom:6px">Message summary</div>
<div>${escapeHtml(summary)}</div>
</div>
<div style="margin-top:16px">
<div style="font-weight:600;margin-bottom:6px">Your weekly schedule (used to shape reply)</div>
<div style="color:var(--muted);font-size:13px">
${(schedule && schedule.length) ? schedule.map(s => `<div>${s.day}: ${s.items.join(', ')}</div>`).join('') : '<div class="small">No schedule yet — set it in settings.</div>'}
</div>
</div>
<div class="reply-box">
<div style="display:flex;align-items:center;gap:12px;">
<button class="btn" onclick="(function(){ ${onRequestReply.toString()}(); })()">Generate reply (AI)</button>
<div class="reply-status small">AI not run yet</div>
</div>
<textarea placeholder="Generated reply will appear here..."></textarea>
<div style="display:flex;gap:8px;">
<button class="btn" onclick="(function(){ const t=document.querySelector('textarea'); const val=t.value; (${onSend.toString()})(val); })()">Send reply</button>
<button class="small" onclick="(function(){ const t=document.querySelector('textarea'); t.value=''; })()">Clear</button>
</div>
</div>
</div>
`; }
function escapeHtml(text) { return (text || '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } // server/server.jv const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const fetch = require('node-fetch');
const app = express(); app.use(cors()); app.use(bodyParser.json()); app.use(express.static('src'));
// ----- In-memory sample data (replace with DB) ----- let messages = [ { id: 'm1', senderName: 'Alice Johnson', senderEmail: '[email protected]', date: new Date().toISOString(), subject: 'Meeting request for Q4', body: 'Hi — can we meet next week to discuss Q4 targets? I am available Tue/Wed mornings.', preview: 'Hi — can we meet next week to discuss Q4 targets? ...', summary: 'Requesting a meeting next week to discuss Q4 targets; available Tue/Wed mornings.', replied: false }, { id: 'm2', senderName: 'Vendor Support', senderEmail: '[email protected]', date: new Date(Date.now() - 3600100024).toISOString(), subject: 'Invoice #12345', body: 'Please find attached invoice #12345 for services rendered in September.', preview: 'Please find attached invoice #12345...', summary: 'Invoice #12345 for September services.', replied: false } ];
let schedule = [ { day: 'Monday', items: ['9:00-10:00 Team sync', '15:00-17:00 Focus work'] }, { day: 'Tuesday', items: ['10:00-11:30 Client meeting'] }, { day: 'Wednesday', items: ['Free', '15:00 Gym'] }, { day: 'Thursday', items: ['All-day off'] }, { day: 'Friday', items: ['09:00-10:00 Weekly wrap'] } ];
// ----- Routes ----- app.get('/api/messages', (req,res) => { res.json(messages); });
app.get('/api/schedule', (req,res) => { res.json(schedule); });
app.post('/api/generate-reply', async (req,res) => { const { messageId } = req.body; const message = messages.find(m=>m.id===messageId); if(!message) return res.json({ success:false, error:'message not found' });
// Build prompt: include message summary + schedule context + formatting instructions const prompt = buildPrompt(message, schedule);
try { // Replace with your AI provider (OpenAI, etc.) const AI_PROVIDER_API_URL = process.env.AI_PROVIDER_API_URL || 'https://api.openai.com/v1/chat/completions'; const AI_API_KEY = process.env.AI_API_KEY || 'REPLACE_WITH_KEY';
const aiResponse = await fetch(AI_PROVIDER_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-5-mini', // placeholder — replace with actual model id your provider uses
messages: [
{ role:'system', content: 'You are an assistant that writes professional email replies adapted to the user schedule.' },
{ role:'user', content: prompt }
],
temperature: 0.2,
max_tokens: 600
})
}).then(r => r.json());
// parse response depending on provider shape:
const replyText = (aiResponse.choices && aiResponse.choices[0] && aiResponse.choices[0].message && aiResponse.choices[0].message.content)
|| aiResponse.output_text
|| null;
if(!replyText) throw new Error('No reply from AI');
return res.json({ success:true, reply: replyText });
} catch(e) { console.error('AI generation error', e); return res.json({ success:false, error: e.message || 'AI error' }); } });
app.post('/api/send-reply', (req,res) => { const { messageId, replyText } = req.body; const message = messages.find(m=>m.id===messageId); if(!message) return res.json({ success:false, error:'message not found' });
// In a real app: send email via SMTP/SendGrid/Gmail API here. // For demo, we mark as replied and store reply meta. message.replied = true; message.lastReply = { text: replyText, date: new Date().toISOString() };
res.json({ success:true }); });
// Utilities
function buildPrompt(message, schedule){
// Include short schedule bullet points
const scheduleLines = schedule.map(s => ${s.day}: ${s.items.join('; ')}).join('\n');
return ` Message subject: ${message.subject || '(none)'} Message from: ${message.senderName} <${message.senderEmail}> Message summary: ${message.summary || message.preview || message.body.slice(0,280)}
User weekly schedule: ${scheduleLines}
Task:
- Provide a short (2-4 sentence) summary of the message.
- Generate a professional reply email that:
- Confirms reception.
- Proposes 2–3 meeting times compatible with the user's schedule above, or explains when the user is available if scheduling is not needed.
- Keeps tone professional, concise, and friendly.
- Also produce a brief 'formatting' section with subject line suggestion and sign-off.
Output strictly in this JSON structure: { "summary":"...short summary...", "subject":"...suggested subject...", "body":"...full reply body text..." } `;}
const PORT = process.env.PORT || 3000; app.listen(PORT, ()=>console.log('Server running on port', PORT));
Inbox — AI-powered messaging (prototype)
Quick start
-
Put your AI provider URL and key in environment:
AI_PROVIDER_API_URL(e.g. OpenAI chat completions endpoint)AI_API_KEY(your API key)
-
Install and run:
npm install npm run start