markup icon indicating copy to clipboard operation
markup copied to clipboard

In box-ia

Open daddabachir002-ui opened this issue 1 month ago • 0 comments

{ "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" } }

Inbox
:root{ --accent: #00d1ff; --muted: #9aa4a8; --bg: #000000; --panel: #0b0b0b; --card: #111111; --text: #e6eef2; }

*{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 =>

${m.senderName} ${m.replied ? '· ✅ Replied' : ''}
${m.senderEmail} • ${new Date(m.date).toLocaleString()}
${m.preview}
).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 `
${message.subject || 'No subject'}
${message.senderName} • ${message.senderEmail} • ${new Date(message.date).toLocaleString()}
  <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:

  1. Provide a short (2-4 sentence) summary of the message.
  2. 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.
  3. 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

  1. 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)
  2. Install and run:

    npm install
    npm run start
    

daddabachir002-ui avatar Nov 12 '25 12:11 daddabachir002-ui