- Notifications
You must be signed in to change notification settings - Fork3.4k
Description
{
"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"
}
}
<!doctype html>
*{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> <div> <div>I</div> Inbox </div> <div>AI-powered replies • Weekly scheduling</div> </div> <div> <div></div> <div></div> </div>;
const sidebarEl = document.getElementById('sidebar');const contentEl = document.getElementById('content');// Render inbox listsidebarEl.innerHTML = InboxList({ messages: state.messages, selectedId: state.selectedMessageId, onSelect: onSelectMessage});// Render message view or placeholderif(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>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>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> <div>Message summary</div> <div>${escapeHtml(summary)}</div> </div> <div> <div>Your weekly schedule (used to shape reply)</div> <div> ${(schedule && schedule.length) ? schedule.map(s => `<div>${s.day}: ${s.items.join(', ')}</div>`).join('') : '<div>No schedule yet — set it in settings.</div>'} </div> </div> <div> <div> <button>Generate reply (AI)</button> <div>AI not run yet</div> </div> <textarea placeholder="Generated reply will appear here..."></textarea> <div> <button>Send reply</button> <button>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: 'alice@example.com',
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: 'support@vendor.com',
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 installnpm run start