diff --git a/packages/chat-client/src/api/orchestration.js b/packages/chat-client/src/api/orchestration.js index 9214ef3..6fe6325 100644 --- a/packages/chat-client/src/api/orchestration.js +++ b/packages/chat-client/src/api/orchestration.js @@ -143,7 +143,7 @@ export async function fetchModels() { if(!res.ok) throw new Error(`Failted to fetch models: ${res.status}`); return res.json(); } - +/* export async function renameSession(sessionId, name) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { method: 'PATCH', @@ -153,6 +153,20 @@ export async function renameSession(sessionId, name) { if (!res.ok) throw new Error(`Failed to rename session: ${res.status}`); return res.json(); } +*/ +export async function updateSession(sessionId, { name, projectId } = {}) { + const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, projectId }), + }); + if (!res.ok) throw new Error(`Failed to update session: ${res.status}`); + return res.json(); +} + +export async function renameSession(sessionId, name) { + return updateSession(sessionId, {name}) +} export async function deleteSession(sessionId) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { @@ -190,4 +204,14 @@ export async function updateProject(id, { name, description, colour, icon }) { export async function deleteProject(id) { const res = await fetch(`${BASE_URL}/projects/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(`Failed to delete project: ${res.status}`); +} + +export async function updateSessionProject(sessionId, projectId) { + const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ projectId }), + }); + if (!res.ok) throw new Error(`Failed to update session project: ${res.status}`); + return res.json(); } \ No newline at end of file diff --git a/packages/chat-client/src/components/SessionModal.jsx b/packages/chat-client/src/components/SessionModal.jsx index 58bb865..c5f0ff7 100644 --- a/packages/chat-client/src/components/SessionModal.jsx +++ b/packages/chat-client/src/components/SessionModal.jsx @@ -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 ( -
-
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' ? ( - <> -

- Session Settings -

-
- - 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%', - }} - /> -
-
- - -
- - ) : ( - <> -

- Delete Session -

-

- Are you sure you want to delete{' '} - - {session.name || session.external_id} - - ? This will permanently remove all messages in this conversation. -

-
- - -
- - )} + if (!session) return null; + + return ( +
+
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' ? ( + <> +

+ Session Settings +

+ + {/* Name */} +
+ + 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%', + }} + />
-
- ); + + {/* Project assignment */} +
+ + +
+ +
+ + +
+ + ) : ( + <> +

+ Delete Session +

+

+ Are you sure you want to delete{' '} + + {session.name || session.external_id} + + ? This will permanently remove all messages in this conversation. +

+
+ + +
+ + )} +
+
+ ); } \ No newline at end of file diff --git a/packages/chat-client/src/components/Sidebar.jsx b/packages/chat-client/src/components/Sidebar.jsx index 2573eb6..e38cd08 100644 --- a/packages/chat-client/src/components/Sidebar.jsx +++ b/packages/chat-client/src/components/Sidebar.jsx @@ -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} /> )} diff --git a/packages/chat-client/src/hooks/useChat.js b/packages/chat-client/src/hooks/useChat.js index b5faee9..b9e9e33 100644 --- a/packages/chat-client/src/hooks/useChat.js +++ b/packages/chat-client/src/hooks/useChat.js @@ -53,6 +53,9 @@ export function useChat({ activeSession, appendMessage, updateLastMessage, refre // Refresh session list so new sessions appear in sidebar refreshSessions(); + + // Delayed refresh + setTimeout( () => refreshSessions(), 3000); }, onError: (err) => { diff --git a/packages/memory-service/src/episodic/index.js b/packages/memory-service/src/episodic/index.js index 0c7484a..440950f 100644 --- a/packages/memory-service/src/episodic/index.js +++ b/packages/memory-service/src/episodic/index.js @@ -52,13 +52,15 @@ function deleteSession(id) { db.prepare(`DELETE FROM sessions WHERE id = ?`).run(id); } -function updateSession(id, { name } = {}){ +function updateSession(id, { name, projectId } = {}) { const db = getDB(); db.prepare(` UPDATE sessions - SET name = ?, updated_at = unixepoch() + SET name = ?, + project_id = COALESCE(?, project_id), + updated_at = unixepoch() WHERE id = ? - `).run(name ?? null, id); + `).run(name ?? null, projectId ?? null, id); return getSession(id); } diff --git a/packages/memory-service/src/index.js b/packages/memory-service/src/index.js index 27b1c4b..5ebf93a 100644 --- a/packages/memory-service/src/index.js +++ b/packages/memory-service/src/index.js @@ -66,9 +66,9 @@ app.get('/sessions/:id', (req, res) => { }); app.patch('/sessions/by-external/:externalId', (req, res) => { - const { name } = req.body; + const { name, projectId } = req.body; try { - const session = episodic.updateSessionByExternalId(req.params.externalId, {name }); + const session = episodic.updateSessionByExternalId(req.params.externalId, {name, projectId }); res.json(session); } catch (err) { res.status(500).json({error: err.message }); diff --git a/packages/orchestration-service/src/routes/sessions.js b/packages/orchestration-service/src/routes/sessions.js index 16791fd..f1524ff 100644 --- a/packages/orchestration-service/src/routes/sessions.js +++ b/packages/orchestration-service/src/routes/sessions.js @@ -30,11 +30,11 @@ router.get('/', async (req, res) => { }) router.patch('/:sessionId', async (req, res) => { - const { name } = req.body; + const { name, projectId } = req.body; if (!name?.trim()) return res.status(400).json({ error: 'name is required' }); try { - const session = await memory.updateSession(req.params.sessionId, { name: name.trim() }); + const session = await memory.updateSession(req.params.sessionId, { name, projectId }); res.json(session); } catch (err) { res.status(500).json({ error: err.message }); diff --git a/packages/orchestration-service/src/services/memory.js b/packages/orchestration-service/src/services/memory.js index b4d99e4..f8e6df1 100644 --- a/packages/orchestration-service/src/services/memory.js +++ b/packages/orchestration-service/src/services/memory.js @@ -63,11 +63,11 @@ async function getSessions(limit = EPISODIC.DEFAULT_SESSIONS_LIMIT, offset = EPI return res.json(); } -async function updateSession(externalId, { name }) { +async function updateSession(externalId, { name, projectId }) { const res = await fetch(`${BASE_URL}/sessions/by-external/${externalId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name }), + body: JSON.stringify({ name, projectId }), }); if (!res.ok) throw new Error(`Failed to update session: ${res.status}`); return res.json();