mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-17 01:22:45 +00:00
feat: show session name and workspace path in heading
This commit is contained in:
@@ -3,6 +3,7 @@ import fsp, { readFile } from 'node:fs/promises';
|
||||
|
||||
import { scanStateDb } from '@/shared/database/repositories/scan-state.db.js';
|
||||
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
|
||||
import type { LLMProvider } from '@/shared/types/app.js';
|
||||
import { AppError } from '@/shared/utils/app-error.js';
|
||||
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
|
||||
@@ -118,7 +119,11 @@ export const llmSessionsService = {
|
||||
});
|
||||
}
|
||||
|
||||
return session;
|
||||
const workspace = workspaceOriginalPathsDb.getWorkspacePath(session.workspace_path);
|
||||
return {
|
||||
...session,
|
||||
workspace_id: workspace?.workspace_id ?? null,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import test from 'node:test';
|
||||
import { AppError } from '@/shared/utils/app-error.js';
|
||||
import { scanStateDb } from '@/shared/database/repositories/scan-state.db.js';
|
||||
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
|
||||
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
|
||||
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
|
||||
import { conversationSearchService } from '@/modules/conversations/conversation-search.service.js';
|
||||
@@ -102,6 +103,7 @@ test('llmSessionsService.updateSessionCustomName validates existence before upda
|
||||
provider: 'claude',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: null,
|
||||
custom_name: null,
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-01T00:00:00.000Z',
|
||||
}
|
||||
@@ -140,11 +142,22 @@ test('llmSessionsService.getIndexedSession returns DB session metadata', { concu
|
||||
provider: 'claude',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: '/tmp/workspace/session.jsonl',
|
||||
custom_name: 'Custom Session Name',
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-02T00:00:00.000Z',
|
||||
}
|
||||
: null
|
||||
));
|
||||
const restoreGetWorkspacePath = patchMethod(workspaceOriginalPathsDb, 'getWorkspacePath', (workspacePath: string) => (
|
||||
workspacePath === '/tmp/workspace'
|
||||
? {
|
||||
workspace_id: 'workspace-123',
|
||||
workspace_path: workspacePath,
|
||||
custom_workspace_name: 'Workspace Custom Name',
|
||||
isStarred: 0,
|
||||
}
|
||||
: null
|
||||
));
|
||||
|
||||
try {
|
||||
const session = llmSessionsService.getIndexedSession('known-session');
|
||||
@@ -153,8 +166,10 @@ test('llmSessionsService.getIndexedSession returns DB session metadata', { concu
|
||||
provider: 'claude',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: '/tmp/workspace/session.jsonl',
|
||||
custom_name: 'Custom Session Name',
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-02T00:00:00.000Z',
|
||||
workspace_id: 'workspace-123',
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
@@ -165,6 +180,7 @@ test('llmSessionsService.getIndexedSession returns DB session metadata', { concu
|
||||
error.statusCode === 404,
|
||||
);
|
||||
} finally {
|
||||
restoreGetWorkspacePath();
|
||||
restoreGetById();
|
||||
}
|
||||
});
|
||||
@@ -183,6 +199,7 @@ test('llmSessionsService.deleteSessionArtifacts validates ids and deletes disk/d
|
||||
provider: 'cursor',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: transcriptPath,
|
||||
custom_name: null,
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-01T00:00:00.000Z',
|
||||
}
|
||||
@@ -233,6 +250,7 @@ test('llmSessionsService.getSessionHistory parses JSONL and Gemini JSON correctl
|
||||
provider: 'cursor',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: jsonlPath,
|
||||
custom_name: null,
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-01T00:00:00.000Z',
|
||||
};
|
||||
@@ -244,6 +262,7 @@ test('llmSessionsService.getSessionHistory parses JSONL and Gemini JSON correctl
|
||||
provider: 'gemini',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: jsonPath,
|
||||
custom_name: null,
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-01T00:00:00.000Z',
|
||||
};
|
||||
@@ -255,6 +274,7 @@ test('llmSessionsService.getSessionHistory parses JSONL and Gemini JSON correctl
|
||||
provider: 'claude',
|
||||
workspace_path: '/tmp/workspace',
|
||||
jsonl_path: null,
|
||||
custom_name: null,
|
||||
created_at: '2026-04-01T00:00:00.000Z',
|
||||
updated_at: '2026-04-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
@@ -29,6 +29,18 @@ const parseWorkspaceIdFromBody = (req: Request): string => {
|
||||
return workspaceId;
|
||||
};
|
||||
|
||||
const parseWorkspaceIdFromParams = (req: Request): string => {
|
||||
const workspaceId = getTrimmedString(req.params.workspaceId);
|
||||
if (!workspaceId) {
|
||||
throw new AppError('workspaceId is required.', {
|
||||
code: 'WORKSPACE_ID_REQUIRED',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return workspaceId;
|
||||
};
|
||||
|
||||
const parseWorkspaceCustomNameFromBody = (req: Request): string | null => {
|
||||
const body = req.body as Record<string, unknown> | undefined;
|
||||
const customName = getTrimmedString(body?.workspaceCustomName);
|
||||
@@ -43,6 +55,15 @@ router.get(
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:workspaceId',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const workspaceId = parseWorkspaceIdFromParams(req);
|
||||
const workspace = workspaceService.getWorkspaceById(workspaceId);
|
||||
res.json(createApiSuccessResponse({ workspace }));
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/star',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
|
||||
@@ -86,6 +86,30 @@ const sortWorkspacesByLastActivity = (
|
||||
return left.workspaceDisplayName.localeCompare(right.workspaceDisplayName);
|
||||
});
|
||||
|
||||
const toWorkspaceRecord = (
|
||||
workspaceId: string,
|
||||
workspacePath: string,
|
||||
workspaceCustomName: string | null,
|
||||
isStarred: boolean,
|
||||
sessions: WorkspaceSessionRecord[],
|
||||
): WorkspaceRecord => {
|
||||
const sortedSessions = sortSessionsByLastActivity(sessions);
|
||||
const lastActivity = sortedSessions[0]?.lastActivity || null;
|
||||
|
||||
return {
|
||||
workspaceId,
|
||||
workspaceOriginalPath: workspacePath,
|
||||
workspaceCustomName,
|
||||
workspaceDisplayName:
|
||||
workspaceCustomName ||
|
||||
path.basename(workspacePath) ||
|
||||
workspacePath,
|
||||
isStarred,
|
||||
lastActivity,
|
||||
sessions: sortedSessions,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Groups indexed sessions by workspace and returns a deterministic catalog shape.
|
||||
*/
|
||||
@@ -102,23 +126,14 @@ const buildWorkspaceSessionCollection = (): WorkspaceRecord[] => {
|
||||
}
|
||||
|
||||
const workspaceRecords = workspaceRows.map((workspaceRow) => {
|
||||
const sessions = sortSessionsByLastActivity(
|
||||
sessionsByWorkspace.get(workspaceRow.workspace_path) || [],
|
||||
);
|
||||
const lastActivity = sessions[0]?.lastActivity || null;
|
||||
|
||||
return {
|
||||
workspaceId: workspaceRow.workspace_id,
|
||||
workspaceOriginalPath: workspaceRow.workspace_path,
|
||||
workspaceCustomName: workspaceRow.custom_workspace_name,
|
||||
workspaceDisplayName:
|
||||
workspaceRow.custom_workspace_name ||
|
||||
path.basename(workspaceRow.workspace_path) ||
|
||||
workspaceRow.workspace_path,
|
||||
isStarred: workspaceRow.isStarred === 1,
|
||||
lastActivity,
|
||||
const sessions = sessionsByWorkspace.get(workspaceRow.workspace_path) || [];
|
||||
return toWorkspaceRecord(
|
||||
workspaceRow.workspace_id,
|
||||
workspaceRow.workspace_path,
|
||||
workspaceRow.custom_workspace_name,
|
||||
workspaceRow.isStarred === 1,
|
||||
sessions,
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
return sortWorkspacesByLastActivity(workspaceRecords);
|
||||
@@ -132,6 +147,28 @@ export const workspaceService = {
|
||||
return buildWorkspaceSessionCollection();
|
||||
},
|
||||
|
||||
getWorkspaceById(workspaceId: string): WorkspaceRecord {
|
||||
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
|
||||
if (!workspaceRow) {
|
||||
throw new AppError('Workspace not found.', {
|
||||
code: 'WORKSPACE_NOT_FOUND',
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const sessions = sessionsDb
|
||||
.getSessionsByWorkspacePath(workspaceRow.workspace_path)
|
||||
.map(toWorkspaceSessionRecord);
|
||||
|
||||
return toWorkspaceRecord(
|
||||
workspaceRow.workspace_id,
|
||||
workspaceRow.workspace_path,
|
||||
workspaceRow.custom_workspace_name,
|
||||
workspaceRow.isStarred === 1,
|
||||
sessions,
|
||||
);
|
||||
},
|
||||
|
||||
toggleWorkspaceStar(workspaceId: string): boolean {
|
||||
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
|
||||
if (!workspaceRow) {
|
||||
|
||||
@@ -14,7 +14,7 @@ type SessionNameLookupRow = {
|
||||
|
||||
type SessionMetadataLookupRow = Pick<
|
||||
SessionsRow,
|
||||
'session_id' | 'provider' | 'workspace_path' | 'jsonl_path' | 'created_at' | 'updated_at'
|
||||
'session_id' | 'provider' | 'workspace_path' | 'jsonl_path' | 'custom_name' | 'created_at' | 'updated_at'
|
||||
>;
|
||||
|
||||
function normalizeTimestamp(value?: string): string | null {
|
||||
@@ -119,7 +119,7 @@ export const sessionsDb = {
|
||||
const db = getConnection();
|
||||
const row = db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, workspace_path, jsonl_path, created_at, updated_at
|
||||
`SELECT session_id, provider, workspace_path, jsonl_path, custom_name, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE session_id = ?`
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user