aipackage/cezen-portal/teams.html
2026-06-30 10:51:41 +05:30

556 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Teams — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<style>
.tm-layout { display: grid; grid-template-columns: 1fr 380px; min-height: calc(100vh - 64px); }
@media(max-width:960px){ .tm-layout { grid-template-columns: 1fr; } .tm-detail { display:none; } }
/* Left — team list */
.tm-list-panel { padding: 28px 28px; border-right: 1px solid var(--bdr); overflow-y: auto; }
.tm-header { display: flex; align-items: center; gap: 14px; margin-bottom: 22px; }
.tm-title { font-size: 22px; font-weight: 800; color: var(--ink); flex: 1; }
.tm-add-btn {
padding: 9px 18px; border-radius: 9px; background: var(--purple);
color: white; border: none; font-family: inherit; font-size: 13px;
font-weight: 700; cursor: pointer; transition: .15s;
}
.tm-add-btn:hover { background: #6d28d9; }
.tm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.tm-card {
background: var(--navy2); border: 1.5px solid var(--bdr);
border-radius: 14px; padding: 18px 18px 14px; cursor: pointer;
transition: .15s;
}
.tm-card:hover, .tm-card.active { border-color: var(--purple); }
.tm-card.active { background: rgba(124,58,237,.04); }
.tm-card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.tm-avatar {
width: 42px; height: 42px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 20px; font-weight: 800; color: white; flex-shrink: 0;
}
.tm-card-name { font-size: 15px; font-weight: 800; color: var(--ink); }
.tm-card-desc { font-size: 12px; color: var(--lt); margin-top: 2px; }
.tm-card-stats { display: flex; gap: 14px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--bdr); }
.tm-card-stat { font-size: 12px; color: var(--med); }
.tm-card-stat strong { color: var(--purple); }
.tm-card-actions { display: flex; gap: 6px; margin-top: 12px; }
.tm-sm-btn {
padding: 5px 12px; border-radius: 7px; font-family: inherit;
font-size: 12px; font-weight: 600; cursor: pointer; transition: .15s;
border: 1.5px solid var(--bdr); background: none; color: var(--med);
}
.tm-sm-btn:hover { border-color: var(--purple); color: var(--purple); }
.tm-sm-btn.danger:hover { border-color: #ef4444; color: #ef4444; }
/* Right — detail panel */
.tm-detail {
background: var(--navy2); border-left: 1px solid var(--bdr);
display: flex; flex-direction: column; overflow: hidden;
}
.tm-detail-header {
padding: 18px 20px; border-bottom: 1px solid var(--bdr);
display: flex; align-items: center; gap: 10px;
}
.tm-detail-title { font-size: 16px; font-weight: 800; color: var(--ink); flex: 1; }
.tm-detail-body { flex: 1; overflow-y: auto; padding: 16px 20px; display: flex; flex-direction: column; gap: 18px; }
.tm-detail-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--lt); gap: 10px; }
.tm-section-title { font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 10px; }
/* Members */
.tm-member-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px; border-radius: 9px;
border: 1px solid var(--bdr); margin-bottom: 6px; background: var(--bg);
}
.tm-member-ava {
width: 32px; height: 32px; border-radius: 8px; background: rgba(124,58,237,.15);
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 800; color: var(--purple); flex-shrink: 0;
}
.tm-member-name { font-size: 13px; font-weight: 700; color: var(--ink); flex: 1; }
.tm-member-email { font-size: 11px; color: var(--lt); }
.tm-role-badge {
font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 20px;
background: rgba(124,58,237,.1); color: var(--purple);
}
.tm-role-badge.lead { background: rgba(16,185,129,.1); color: #059669; }
.tm-remove-btn {
background: none; border: none; cursor: pointer; color: var(--lt);
font-size: 13px; padding: 3px 6px; border-radius: 5px; transition: .12s;
}
.tm-remove-btn:hover { color: #ef4444; }
/* Add member row */
.tm-add-member { display: flex; gap: 8px; margin-top: 8px; }
.tm-add-member select {
flex: 1; padding: 7px 10px; border-radius: 8px;
border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: inherit; font-size: 13px;
}
.tm-add-member select:focus { outline: none; border-color: var(--purple); }
.tm-add-member button {
padding: 7px 14px; border-radius: 8px; background: var(--purple);
color: white; border: none; font-family: inherit; font-size: 13px;
font-weight: 600; cursor: pointer;
}
/* Settings */
.tm-setting-row { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.tm-setting-row label { font-size: 12px; font-weight: 700; color: var(--med); }
.tm-setting-row select, .tm-setting-row input {
width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 8px;
border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: inherit; font-size: 13px;
}
.tm-setting-row select:focus, .tm-setting-row input:focus { outline: none; border-color: var(--purple); }
.tm-save-settings-btn {
width: 100%; padding: 9px; border-radius: 9px;
background: var(--purple); color: white; border: none;
font-family: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
}
.tm-save-settings-btn:hover { background: #6d28d9; }
/* Modal */
.tm-modal-bg {
display: none; position: fixed; inset: 0; background: rgba(22,13,53,.5);
z-index: 1000; align-items: center; justify-content: center;
}
.tm-modal-bg.open { display: flex; }
.tm-modal {
background: var(--navy2); border-radius: 16px; padding: 28px;
width: 100%; max-width: 440px; border: 1.5px solid var(--bdr);
}
.tm-modal h2 { font-size: 18px; font-weight: 800; color: var(--ink); margin-bottom: 18px; }
.tm-form-field { margin-bottom: 14px; }
.tm-form-field label { display: block; font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 6px; }
.tm-form-field input, .tm-form-field textarea {
width: 100%; box-sizing: border-box; padding: 9px 12px; border-radius: 9px;
border: 1.5px solid var(--bdr); background: var(--bg);
color: var(--ink); font-family: inherit; font-size: 13px;
}
.tm-form-field input:focus, .tm-form-field textarea:focus { outline: none; border-color: var(--purple); }
.tm-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
.tm-modal-btn {
padding: 9px 20px; border-radius: 9px; font-family: inherit;
font-size: 13px; font-weight: 700; cursor: pointer; transition: .15s;
}
.tm-modal-btn.cancel { background: none; border: 1.5px solid var(--bdr); color: var(--med); }
.tm-modal-btn.create { background: var(--purple); border: none; color: white; }
/* Toast */
#tm-toast { position:fixed;bottom:24px;right:24px;z-index:9999;background:var(--ink);color:white;padding:10px 18px;border-radius:10px;font-size:13px;font-weight:600;opacity:0;transform:translateY(8px);transition:.25s;pointer-events:none; }
#tm-toast.show { opacity:1;transform:translateY(0); }
</style>
</head>
<body>
<header class="topnav">
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
<nav>
<a href="index.html">Home</a>
<a href="quickstart.html">Quick Start</a>
<a href="prompts.html">Prompt Library</a>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security & Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Audit Log</a>
<a href="feedback.html">Feedback & Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html" class="active">Teams</a>
<a href="models-admin.html">Model Manager</a>
<a href="training.html">Training</a>
<a href="knowledge.html">Knowledge Base</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="benchmark.html">Benchmarking</a>
<a href="model-compare.html">Model Compare</a>
<a href="api-playground.html">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<a href="rag-quality.html">RAG Quality</a>
<a href="router.html">Model Router</a>
<a href="connectors.html">Connectors</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="console.html">Console</a>
<a href="settings.html">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">Multimodal Chat</a>
<a href="prompt-studio.html">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Builder</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Automation</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
<a href="notifications.html" style="position:relative">🔔</a>
</nav>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<div class="tm-layout">
<!-- Left: Team list -->
<div class="tm-list-panel">
<div class="tm-header">
<div class="tm-title">👥 Teams &amp; Departments</div>
<button class="tm-add-btn" onclick="openCreateModal()">+ New Team</button>
</div>
<div class="tm-grid" id="tm-grid">
<p style="color:var(--lt)">Loading teams…</p>
</div>
</div>
<!-- Right: Detail -->
<div class="tm-detail" id="tm-detail">
<div class="tm-detail-empty">
<div style="font-size:38px;opacity:.3">👥</div>
<p style="font-size:14px;color:var(--lt)">Select a team to manage it</p>
</div>
</div>
</div>
<!-- Create Modal -->
<div class="tm-modal-bg" id="tm-modal">
<div class="tm-modal">
<h2>Create Team</h2>
<div class="tm-form-field">
<label>Team Name</label>
<input id="new-team-name" type="text" placeholder="e.g. Finance, Legal, Engineering">
</div>
<div class="tm-form-field">
<label>Description</label>
<textarea id="new-team-desc" rows="2" placeholder="What does this team use the AI for?"></textarea>
</div>
<div class="tm-modal-actions">
<button class="tm-modal-btn cancel" onclick="closeModal()">Cancel</button>
<button class="tm-modal-btn create" onclick="createTeam()">Create Team</button>
</div>
</div>
</div>
<div id="tm-toast"></div>
<script>
const TEAM_COLORS = ['#7C3AED','#0EA5E9','#10B981','#F59E0B','#EC4899','#6366F1','#14B8A6','#EF4444'];
const MOCK_TEAMS = [
{ id:1, name:'Finance & Accounts', description:'Budget analysis, expense classification, vendor invoice review', created_by:'admin', created_at:'2026-05-10T09:00:00Z',
members:[{id:2,username:'ravi.kumar',email:'ravi.k@iimb.ac.in',user_role:'user',team_role:'lead'},{id:3,username:'priya.nair',email:'priya.n@iimb.ac.in',user_role:'user',team_role:'member'}],
member_count:2, settings:{allowed_models:'["llama3.1:70b","mistral:7b"]',kb_collections:'["1"]',monthly_token_quota:500000} },
{ id:2, name:'Legal & Procurement', description:'Contract review, tender analysis, compliance checks', created_by:'admin', created_at:'2026-05-12T10:00:00Z',
members:[{id:4,username:'anand.m',email:'anand.m@iimb.ac.in',user_role:'user',team_role:'lead'}],
member_count:1, settings:{allowed_models:'["llama3.1:70b","codellama:34b"]',kb_collections:'["2"]',monthly_token_quota:750000} },
{ id:3, name:'Research & Faculty', description:'Academic research assistance, literature review, grant writing', created_by:'admin', created_at:'2026-05-15T11:00:00Z',
members:[],
member_count:0, settings:{allowed_models:'[]',kb_collections:'[]',monthly_token_quota:0} },
];
const MOCK_USERS = [
{id:2,username:'ravi.kumar',email:'ravi.k@iimb.ac.in'},{id:3,username:'priya.nair',email:'priya.n@iimb.ac.in'},
{id:4,username:'anand.m',email:'anand.m@iimb.ac.in'},{id:5,username:'deepa.v',email:'deepa.v@iimb.ac.in'},
{id:6,username:'kiran.rao',email:'kiran.rao@iimb.ac.in'},
];
let teams = [];
let allUsers = [];
let selectedTeamId = null;
async function loadTeams() {
try {
const r = await fetch('/api/teams');
if (!r.ok) throw new Error();
teams = await r.json();
} catch { teams = MOCK_TEAMS; }
renderGrid();
}
async function loadUsers() {
try {
const r = await fetch('/api/users');
if (!r.ok) throw new Error();
const d = await r.json();
allUsers = d.users || d;
} catch { allUsers = MOCK_USERS; }
}
function teamColor(id) { return TEAM_COLORS[(id - 1) % TEAM_COLORS.length]; }
function teamInitial(name) { return name.split(/\s+/).map(w=>w[0]).join('').slice(0,2).toUpperCase(); }
function renderGrid() {
const grid = document.getElementById('tm-grid');
if (!teams.length) {
grid.innerHTML = `<p style="color:var(--lt);grid-column:1/-1">No teams yet. Click <strong>+ New Team</strong> to create one.</p>`;
return;
}
grid.innerHTML = teams.map(t => {
const models = JSON.parse(t.settings?.allowed_models || '[]');
const kbs = JSON.parse(t.settings?.kb_collections || '[]');
const quota = t.settings?.monthly_token_quota || 0;
return `
<div class="tm-card ${t.id === selectedTeamId ? 'active' : ''}" onclick="selectTeam(${t.id})">
<div class="tm-card-header">
<div class="tm-avatar" style="background:${teamColor(t.id)}">${teamInitial(t.name)}</div>
<div>
<div class="tm-card-name">${t.name}</div>
<div class="tm-card-desc">${t.description || 'No description'}</div>
</div>
</div>
<div class="tm-card-stats">
<div class="tm-card-stat">👤 <strong>${t.member_count}</strong> members</div>
<div class="tm-card-stat">🤖 <strong>${models.length || 'All'}</strong> models</div>
<div class="tm-card-stat">📚 <strong>${kbs.length || 'All'}</strong> KBs</div>
${quota ? `<div class="tm-card-stat">🔤 <strong>${(quota/1000).toFixed(0)}k</strong> tok/mo</div>` : ''}
</div>
<div class="tm-card-actions">
<button class="tm-sm-btn" onclick="event.stopPropagation();selectTeam(${t.id})">Manage</button>
<button class="tm-sm-btn danger" onclick="event.stopPropagation();deleteTeam(${t.id})">Delete</button>
</div>
</div>`;
}).join('');
}
function selectTeam(id) {
selectedTeamId = id;
renderGrid();
const t = teams.find(x => x.id === id);
if (!t) return;
renderDetail(t);
}
function renderDetail(t) {
const models = JSON.parse(t.settings?.allowed_models || '[]');
const kbs = JSON.parse(t.settings?.kb_collections || '[]');
const quota = t.settings?.monthly_token_quota || 0;
const detailEl = document.getElementById('tm-detail');
const notInTeam = allUsers.filter(u => !t.members.find(m => m.id === u.id));
detailEl.innerHTML = `
<div class="tm-detail-header">
<div class="tm-avatar" style="background:${teamColor(t.id)};width:36px;height:36px;border-radius:10px;font-size:16px;display:flex;align-items:center;justify-content:center;color:white;font-weight:800;flex-shrink:0">${teamInitial(t.name)}</div>
<div class="tm-detail-title">${t.name}</div>
</div>
<div class="tm-detail-body">
<!-- Members -->
<div>
<div class="tm-section-title">Members (${t.members.length})</div>
${t.members.length ? t.members.map(m => `
<div class="tm-member-row">
<div class="tm-member-ava">${m.username[0].toUpperCase()}</div>
<div style="flex:1;min-width:0">
<div class="tm-member-name">${m.username}</div>
<div class="tm-member-email">${m.email}</div>
</div>
<span class="tm-role-badge ${m.team_role}">${m.team_role}</span>
<button class="tm-remove-btn" onclick="removeMember(${t.id},${m.id})" title="Remove">✕</button>
</div>
`).join('') : '<p style="font-size:13px;color:var(--lt)">No members yet.</p>'}
<div class="tm-add-member">
<select id="add-user-sel">
<option value="">+ Add member…</option>
${notInTeam.map(u => `<option value="${u.id}">${u.username}${u.email}</option>`).join('')}
</select>
<select id="add-role-sel" style="width:100px">
<option value="member">Member</option>
<option value="lead">Lead</option>
</select>
<button onclick="addMember(${t.id})">Add</button>
</div>
</div>
<!-- Model Access -->
<div>
<div class="tm-section-title">Model Access</div>
<div class="tm-setting-row">
<label>Allowed Models <span style="font-weight:400;text-transform:none;letter-spacing:0">(leave blank for all)</span></label>
<select id="tm-models" multiple style="height:110px">
${['llama3.1:8b','llama3.1:70b','mistral:7b','codellama:34b','llava:13b','gemma2:9b','llama3.2:3b'].map(m =>
`<option value="${m}" ${models.includes(m)?'selected':''}>${m}</option>`
).join('')}
</select>
</div>
</div>
<!-- KB Access -->
<div>
<div class="tm-section-title">Knowledge Base Collections</div>
<div class="tm-setting-row">
<label>Allowed Collections <span style="font-weight:400;text-transform:none;letter-spacing:0">(leave blank for all)</span></label>
<select id="tm-kbs" multiple style="height:80px">
<option value="1" ${kbs.includes('1')?'selected':''}>Company Policies</option>
<option value="2" ${kbs.includes('2')?'selected':''}>Legal Documents</option>
<option value="3" ${kbs.includes('3')?'selected':''}>HR Handbook</option>
<option value="4" ${kbs.includes('4')?'selected':''}>Technical Docs</option>
</select>
</div>
</div>
<!-- Usage Quota -->
<div>
<div class="tm-section-title">Usage Quota</div>
<div class="tm-setting-row">
<label>Monthly Token Limit (0 = unlimited)</label>
<input id="tm-quota" type="number" min="0" step="50000" value="${quota}" placeholder="e.g. 1000000">
</div>
<button class="tm-save-settings-btn" onclick="saveSettings(${t.id})">💾 Save Settings</button>
</div>
</div>`;
}
async function addMember(teamId) {
const sel = document.getElementById('add-user-sel');
const role = document.getElementById('add-role-sel').value;
const userId = parseInt(sel.value);
if (!userId) { showToast('Select a user'); return; }
try {
await fetch(`/api/teams/${teamId}/members`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({user_id: userId, role})
});
} catch {}
const user = allUsers.find(u => u.id === userId);
const team = teams.find(t => t.id === teamId);
if (team && user) {
team.members.push({...user, team_role: role});
team.member_count++;
}
renderGrid();
renderDetail(team);
showToast('Member added');
}
async function removeMember(teamId, userId) {
try {
await fetch(`/api/teams/${teamId}/members/${userId}`, { method:'DELETE' });
} catch {}
const team = teams.find(t => t.id === teamId);
if (team) {
team.members = team.members.filter(m => m.id !== userId);
team.member_count--;
}
renderGrid();
renderDetail(team);
showToast('Member removed');
}
async function saveSettings(teamId) {
const modelsSel = document.getElementById('tm-models');
const kbsSel = document.getElementById('tm-kbs');
const quota = parseInt(document.getElementById('tm-quota').value) || 0;
const models = [...modelsSel.selectedOptions].map(o => o.value);
const kbs = [...kbsSel.selectedOptions].map(o => o.value);
try {
await fetch(`/api/teams/${teamId}`, {
method:'PUT',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({
allowed_models: JSON.stringify(models),
kb_collections: JSON.stringify(kbs),
monthly_token_quota: quota
})
});
} catch {}
const team = teams.find(t => t.id === teamId);
if (team && team.settings) {
team.settings.allowed_models = JSON.stringify(models);
team.settings.kb_collections = JSON.stringify(kbs);
team.settings.monthly_token_quota = quota;
}
renderGrid();
showToast('Settings saved');
}
async function deleteTeam(id) {
const team = teams.find(t => t.id === id);
if (!confirm(`Delete team "${team?.name}"? This cannot be undone.`)) return;
try { await fetch(`/api/teams/${id}`, { method:'DELETE' }); } catch {}
teams = teams.filter(t => t.id !== id);
if (selectedTeamId === id) {
selectedTeamId = null;
document.getElementById('tm-detail').innerHTML = `<div class="tm-detail-empty"><div style="font-size:38px;opacity:.3">👥</div><p style="font-size:14px;color:var(--lt)">Select a team to manage it</p></div>`;
}
renderGrid();
showToast('Team deleted');
}
function openCreateModal() { document.getElementById('tm-modal').classList.add('open'); }
function closeModal() { document.getElementById('tm-modal').classList.remove('open'); }
async function createTeam() {
const name = document.getElementById('new-team-name').value.trim();
const desc = document.getElementById('new-team-desc').value.trim();
if (!name) { showToast('Enter a team name'); return; }
try {
const r = await fetch('/api/teams', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({name, description: desc})
});
const d = await r.json();
teams.push({id: d.id, name, description: desc, created_by:'admin', created_at: new Date().toISOString(), members:[], member_count:0, settings:{allowed_models:'[]',kb_collections:'[]',monthly_token_quota:0}});
} catch {
const id = Date.now() % 10000;
teams.push({id, name, description: desc, created_by:'admin', created_at: new Date().toISOString(), members:[], member_count:0, settings:{allowed_models:'[]',kb_collections:'[]',monthly_token_quota:0}});
}
closeModal();
document.getElementById('new-team-name').value = '';
document.getElementById('new-team-desc').value = '';
renderGrid();
showToast('Team created');
}
function showToast(msg) {
const t = document.getElementById('tm-toast');
t.textContent = msg; t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2200);
}
loadTeams();
loadUsers();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
</body>
</html>