Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

In box-ia #2012

Open
Open
@daddabachir002-ui

Description

@daddabachir002-ui

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

<title>Inbox</title>
<script type="module" src="./app.jv"></script>: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> <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 =>


${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>    <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:

  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 installnpm run start

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp