added being able to assign sessions to projects via the sessions modal
This commit is contained in:
@@ -1,116 +1,128 @@
|
||||
// SessionModal.jsx
|
||||
import React, {useState, useEffect, useRef} from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { updateSession } from '../api/orchestration';
|
||||
|
||||
export default function SessionModal({ session, mode = 'settings', onRename, onDelete, onClose }) {
|
||||
const [name, setName] = useState(session?.name || '');
|
||||
const inputRef = useRef(null);
|
||||
export default function SessionModal({ session, mode = 'settings', onRename, onDelete, onClose, projects = [] }) {
|
||||
const [name, setName] = useState(session?.name || '');
|
||||
const [projectId, setProjectId] = useState(session?.project_id ?? '');
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === 'settings') {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
function handleSubmit() {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return;
|
||||
onRename(session, trimmed);
|
||||
onClose();
|
||||
useEffect(() => {
|
||||
if (mode === 'settings') {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Enter' && mode === 'settings') handleSubmit();
|
||||
if (e.key === 'Escape') onClose();
|
||||
}
|
||||
function handleSubmit() {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return;
|
||||
onRename(session, trimmed, projectId || null);
|
||||
onClose();
|
||||
}
|
||||
|
||||
if (!session) return null;
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Enter' && mode === 'settings') handleSubmit();
|
||||
if (e.key === 'Escape') onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={onClose} style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 100,
|
||||
}}>
|
||||
<div onClick={e => e.stopPropagation()} onKeyDown={handleKeyDown} style={{
|
||||
background: 'var(--bg-surface)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-lg)',
|
||||
padding: '24px',
|
||||
width: '360px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
}}>
|
||||
{mode === 'settings' ? (
|
||||
<>
|
||||
<h2 style={{ fontSize: '15px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
||||
Session Settings
|
||||
</h2>
|
||||
<div className="flex-col" style={{ gap: '8px' }}>
|
||||
<label className="label-upper">Name</label>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder="Enter session name..."
|
||||
style={{
|
||||
background: 'var(--bg-elevated)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
padding: '8px 12px',
|
||||
color: 'var(--text-primary)',
|
||||
fontSize: '14px',
|
||||
outline: 'none',
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex" style={{ gap: '8px', justifyContent: 'flex-end' }}>
|
||||
<button className="btn-reset text-base text-muted"
|
||||
onClick={onClose}
|
||||
style={{ padding: '8px 14px', borderRadius: 'var(--radius-md)' }}
|
||||
>Cancel</button>
|
||||
<button className="btn-primary" onClick={handleSubmit}
|
||||
disabled={!name.trim()}
|
||||
style={{ padding: '8px 16px' }}
|
||||
>Save</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 style={{ fontSize: '15px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
||||
Delete Session
|
||||
</h2>
|
||||
<p className="text-sm text-secondary">
|
||||
Are you sure you want to delete{' '}
|
||||
<span style={{ color: 'var(--text-primary)', fontWeight: 500 }}>
|
||||
{session.name || session.external_id}
|
||||
</span>
|
||||
? This will permanently remove all messages in this conversation.
|
||||
</p>
|
||||
<div className="flex" style={{ gap: '8px', justifyContent: 'flex-end' }}>
|
||||
<button className="btn-reset text-base text-muted"
|
||||
onClick={onClose}
|
||||
style={{ padding: '8px 14px', borderRadius: 'var(--radius-md)' }}
|
||||
>Cancel</button>
|
||||
<button className="btn-reset text-base"
|
||||
onClick={() => { onDelete(session); onClose(); }}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
background: '#c0392b',
|
||||
color: 'white',
|
||||
}}
|
||||
>Delete</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
if (!session) return null;
|
||||
|
||||
return (
|
||||
<div onClick={onClose} style={{
|
||||
position: 'fixed', inset: 0,
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
zIndex: 100,
|
||||
}}>
|
||||
<div onClick={e => e.stopPropagation()} onKeyDown={handleKeyDown} style={{
|
||||
background: 'var(--bg-surface)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-lg)',
|
||||
padding: '24px', width: '360px',
|
||||
display: 'flex', flexDirection: 'column', gap: '16px',
|
||||
}}>
|
||||
{mode === 'settings' ? (
|
||||
<>
|
||||
<h2 style={{ fontSize: '15px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
||||
Session Settings
|
||||
</h2>
|
||||
|
||||
{/* Name */}
|
||||
<div className="flex-col" style={{ gap: '6px' }}>
|
||||
<label className="label-upper">Name</label>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder="Enter session name..."
|
||||
style={{
|
||||
background: 'var(--bg-elevated)', border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-md)', padding: '8px 12px',
|
||||
color: 'var(--text-primary)', fontSize: '14px', outline: 'none', width: '100%',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
{/* Project assignment */}
|
||||
<div className="flex-col" style={{ gap: '6px' }}>
|
||||
<label className="label-upper">Project <span style={{ opacity: 0.5 }}>(optional)</span></label>
|
||||
<select
|
||||
value={projectId}
|
||||
onChange={e => setProjectId(e.target.value)}
|
||||
style={{
|
||||
width: '100%', padding: '8px 10px',
|
||||
background: 'var(--bg-elevated)', border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-md)', color: 'var(--text-primary)',
|
||||
fontSize: '13px', cursor: 'pointer', outline: 'none',
|
||||
}}
|
||||
>
|
||||
<option value=''>No project</option>
|
||||
{projects.map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex" style={{ gap: '8px', justifyContent: 'flex-end' }}>
|
||||
<button className="btn-reset text-base text-muted"
|
||||
onClick={onClose}
|
||||
style={{ padding: '8px 14px', borderRadius: 'var(--radius-md)' }}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn-primary" onClick={handleSubmit}
|
||||
disabled={!name.trim()}
|
||||
style={{ padding: '8px 16px' }}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 style={{ fontSize: '15px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
||||
Delete Session
|
||||
</h2>
|
||||
<p className="text-sm text-secondary">
|
||||
Are you sure you want to delete{' '}
|
||||
<span style={{ color: 'var(--text-primary)', fontWeight: 500 }}>
|
||||
{session.name || session.external_id}
|
||||
</span>
|
||||
? This will permanently remove all messages in this conversation.
|
||||
</p>
|
||||
<div className="flex" style={{ gap: '8px', justifyContent: 'flex-end' }}>
|
||||
<button className="btn-reset text-base text-muted"
|
||||
onClick={onClose}
|
||||
style={{ padding: '8px 14px', borderRadius: 'var(--radius-md)' }}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn-reset text-base"
|
||||
onClick={() => { onDelete(session); onClose(); }}
|
||||
style={{ padding: '8px 16px', borderRadius: 'var(--radius-md)', background: '#c0392b', color: 'white' }}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import SessionModal from './SessionModal';
|
||||
import { useContextMenu } from '../hooks/useContextMenu';
|
||||
import { renameSession, deleteSession } from '../api/orchestration';
|
||||
import { renameSession, deleteSession, updateSession } from '../api/orchestration';
|
||||
|
||||
|
||||
export default function Sidebar({
|
||||
@@ -26,9 +26,9 @@ export default function Sidebar({
|
||||
|
||||
// ── Handlers ────────────────────────────────────────────
|
||||
|
||||
async function handleRename(session, name) {
|
||||
async function handleRename(session, name, projectId) {
|
||||
try {
|
||||
await renameSession(session.external_id, name);
|
||||
await updateSession(session.external_id, { name, projectId });
|
||||
onSessionsChange();
|
||||
} catch (err) {
|
||||
console.error('[Sidebar] Rename failed:', err.message);
|
||||
@@ -273,6 +273,7 @@ export default function Sidebar({
|
||||
onRename={handleRename}
|
||||
onDelete={handleDelete}
|
||||
onClose={() => setModalSession(null)}
|
||||
projects={projects}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user