chat sessions in project view
This commit is contained in:
146
packages/chat-client/src/components/ProjectView.jsx
Normal file
146
packages/chat-client/src/components/ProjectView.jsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { fetchSessions, updateSession } from '../api/orchestration';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export default function ProjectView({ project, onNavigate, onSelectSession, onNewProjectChat }) {
|
||||
const [sessions, setSessions] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => { load(); }, [project.id]);
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
try {
|
||||
setSessions(await fetchSessions(50, 0, project.id));
|
||||
} catch (err) {
|
||||
console.error('[ProjectView] Failed to load sessions:', err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTimestamp(ts) {
|
||||
if (!ts) return '—';
|
||||
const date = new Date(ts * 1000);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
if (diffMins < 1) return 'Just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays === 1) return 'Yesterday';
|
||||
return date.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-col flex-1 overflow-hidden" style={{ background: 'var(--bg-base)' }}>
|
||||
|
||||
{/* Colour accent bar */}
|
||||
<div style={{
|
||||
height: '3px', flexShrink: 0,
|
||||
background: project.colour ?? 'var(--accent)',
|
||||
}} />
|
||||
|
||||
{/* Header */}
|
||||
<div className="panel-header" style={{ padding: '0 24px', justifyContent: 'space-between' }}>
|
||||
<button
|
||||
className="btn-reset text-xs text-muted"
|
||||
onClick={() => onNavigate('all-projects')}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = 'var(--text-secondary)'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = 'var(--text-muted)'}
|
||||
>
|
||||
← All Projects
|
||||
</button>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={onNewProjectChat}
|
||||
style={{ padding: '5px 12px', fontSize: '12px' }}
|
||||
>
|
||||
+ New Chat
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 scroll-y" style={{ padding: '32px 24px' }}>
|
||||
|
||||
{/* Project title + meta */}
|
||||
<div style={{ marginBottom: '32px' }}>
|
||||
<div className="flex items-center" style={{ gap: '10px', marginBottom: '8px' }}>
|
||||
<h1 style={{ fontSize: '22px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
||||
{project.name}
|
||||
</h1>
|
||||
{project.projectOnly === 1 && (
|
||||
<span style={{
|
||||
fontSize: '11px', fontWeight: 500,
|
||||
padding: '2px 8px', borderRadius: '99px',
|
||||
background: 'var(--bg-elevated)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text-muted)',
|
||||
}}>
|
||||
Project Only
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{project.description && (
|
||||
<p className="text-sm" style={{ color: 'var(--text-secondary)', maxWidth: '560px', lineHeight: 1.6 }}>
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sessions list */}
|
||||
<div>
|
||||
<p className="label-upper" style={{ marginBottom: '12px' }}>Conversations</p>
|
||||
{loading ? (
|
||||
<div className="text-sm text-muted">Loading...</div>
|
||||
) : sessions.length === 0 ? (
|
||||
<div style={{
|
||||
padding: '32px', textAlign: 'center',
|
||||
border: '1px dashed var(--border)', borderRadius: 'var(--radius-lg)',
|
||||
color: 'var(--text-muted)',
|
||||
}}>
|
||||
<p className="text-sm">No conversations in this project yet</p>
|
||||
<p className="text-xs" style={{ marginTop: '6px' }}>
|
||||
Click "+ New Chat" to start one
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{sessions.map((session, i) => (
|
||||
<button
|
||||
key={session.external_id}
|
||||
className="btn-reset"
|
||||
onClick={() => { onSelectSession(session); onNavigate('chat'); }}
|
||||
style={{
|
||||
padding: '12px 16px',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
borderBottom: i < sessions.length - 1 ? '1px solid var(--border)' : 'none',
|
||||
borderRadius: i === 0
|
||||
? 'var(--radius-md) var(--radius-md) 0 0'
|
||||
: i === sessions.length - 1
|
||||
? '0 0 var(--radius-md) var(--radius-md)'
|
||||
: '0',
|
||||
background: 'var(--bg-surface)',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-elevated)'}
|
||||
onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-surface)'}
|
||||
>
|
||||
<span className="text-base" style={{ color: 'var(--text-primary)' }}>
|
||||
{session.name || session.external_id}
|
||||
</span>
|
||||
<span className="text-xs text-muted" style={{ flexShrink: 0, marginLeft: '16px' }}>
|
||||
{formatTimestamp(session.updated_at)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user