mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-03 11:05:35 +08:00
refactor: move fetching messages to module
This commit is contained in:
@@ -70,7 +70,6 @@ import userRoutes from './routes/user.js';
|
|||||||
import codexRoutes from './routes/codex.js';
|
import codexRoutes from './routes/codex.js';
|
||||||
import geminiRoutes from './routes/gemini.js';
|
import geminiRoutes from './routes/gemini.js';
|
||||||
import pluginsRoutes from './routes/plugins.js';
|
import pluginsRoutes from './routes/plugins.js';
|
||||||
import messagesRoutes from './routes/messages.js';
|
|
||||||
import providerRoutes from './modules/providers/provider.routes.js';
|
import providerRoutes from './modules/providers/provider.routes.js';
|
||||||
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
||||||
import { initializeDatabase, sessionsDb } from './modules/database/index.js';
|
import { initializeDatabase, sessionsDb } from './modules/database/index.js';
|
||||||
@@ -194,9 +193,6 @@ app.use('/api/gemini', authenticateToken, geminiRoutes);
|
|||||||
// Plugins API Routes (protected)
|
// Plugins API Routes (protected)
|
||||||
app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
||||||
|
|
||||||
// Unified session messages route (protected)
|
|
||||||
app.use('/api/sessions', authenticateToken, messagesRoutes);
|
|
||||||
|
|
||||||
// Unified provider MCP routes (protected)
|
// Unified provider MCP routes (protected)
|
||||||
app.use('/api/providers', authenticateToken, providerRoutes);
|
app.use('/api/providers', authenticateToken, providerRoutes);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import fsp from 'node:fs/promises';
|
import fsp from 'node:fs/promises';
|
||||||
import os from 'node:os';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import readline from 'node:readline';
|
import readline from 'node:readline';
|
||||||
|
|
||||||
@@ -103,13 +102,10 @@ async function parseAgentTools(filePath: string): Promise<AnyRecord[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getSessionMessages(
|
async function getSessionMessages(
|
||||||
projectName: string,
|
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
limit: number | null,
|
limit: number | null,
|
||||||
offset: number,
|
offset: number,
|
||||||
): Promise<ClaudeHistoryMessagesResult> {
|
): Promise<ClaudeHistoryMessagesResult> {
|
||||||
const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jsonLPath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
|
const jsonLPath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
|
||||||
|
|
||||||
@@ -117,6 +113,7 @@ async function getSessionMessages(
|
|||||||
return { messages: [], total: 0, hasMore: false };
|
return { messages: [], total: 0, hasMore: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const projectDir = path.dirname(jsonLPath);
|
||||||
const files = await fsp.readdir(projectDir);
|
const files = await fsp.readdir(projectDir);
|
||||||
const agentFiles = files.filter((file) => file.endsWith('.jsonl') && file.startsWith('agent-'));
|
const agentFiles = files.filter((file) => file.endsWith('.jsonl') && file.startsWith('agent-'));
|
||||||
|
|
||||||
@@ -413,14 +410,11 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|||||||
sessionId: string,
|
sessionId: string,
|
||||||
options: FetchHistoryOptions = {},
|
options: FetchHistoryOptions = {},
|
||||||
): Promise<FetchHistoryResult> {
|
): Promise<FetchHistoryResult> {
|
||||||
const { projectName, limit = null, offset = 0 } = options;
|
const { limit = null, offset = 0 } = options;
|
||||||
if (!projectName) {
|
|
||||||
return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: ClaudeHistoryResult;
|
let result: ClaudeHistoryResult;
|
||||||
try {
|
try {
|
||||||
result = await getSessionMessages(projectName, sessionId, limit, offset);
|
result = await getSessionMessages(sessionId, limit, offset);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
console.warn(`[ClaudeProvider] Failed to load session ${sessionId}:`, message);
|
console.warn(`[ClaudeProvider] Failed to load session ${sessionId}:`, message);
|
||||||
|
|||||||
@@ -49,6 +49,29 @@ const readOptionalQueryString = (value: unknown): string | undefined => {
|
|||||||
return normalized.length > 0 ? normalized : undefined;
|
return normalized.length > 0 ? normalized : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseOptionalBooleanQuery = (value: unknown, name: string): boolean | undefined => {
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = readOptionalQueryString(value);
|
||||||
|
if (!normalized) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppError(`${name} must be "true" or "false".`, {
|
||||||
|
code: 'INVALID_QUERY_PARAMETER',
|
||||||
|
statusCode: 400,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const parseMcpScope = (value: unknown): McpScope | undefined => {
|
const parseMcpScope = (value: unknown): McpScope | undefined => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -260,7 +283,8 @@ router.delete(
|
|||||||
'/sessions/:sessionId',
|
'/sessions/:sessionId',
|
||||||
asyncHandler(async (req: Request, res: Response) => {
|
asyncHandler(async (req: Request, res: Response) => {
|
||||||
const sessionId = parseSessionId(req.params.sessionId);
|
const sessionId = parseSessionId(req.params.sessionId);
|
||||||
const result = await sessionsService.deleteSessionById(sessionId);
|
const deletedFromDisk = parseOptionalBooleanQuery(req.query.deletedFromDisk, 'deletedFromDisk') ?? false;
|
||||||
|
const result = await sessionsService.deleteSessionById(sessionId, deletedFromDisk);
|
||||||
res.json(createApiSuccessResponse(result));
|
res.json(createApiSuccessResponse(result));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -275,4 +299,36 @@ router.put(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/sessions/:sessionId/messages',
|
||||||
|
asyncHandler(async (req: Request, res: Response) => {
|
||||||
|
const sessionId = parseSessionId(req.params.sessionId);
|
||||||
|
const limitRaw = readOptionalQueryString(req.query.limit);
|
||||||
|
const offsetRaw = readOptionalQueryString(req.query.offset);
|
||||||
|
|
||||||
|
const limit = limitRaw === undefined ? null : Number.parseInt(limitRaw, 10);
|
||||||
|
const offset = offsetRaw === undefined ? 0 : Number.parseInt(offsetRaw, 10);
|
||||||
|
|
||||||
|
if (limitRaw !== undefined && Number.isNaN(limit)) {
|
||||||
|
throw new AppError('limit must be a valid integer.', {
|
||||||
|
code: 'INVALID_QUERY_PARAMETER',
|
||||||
|
statusCode: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offsetRaw !== undefined && Number.isNaN(offset)) {
|
||||||
|
throw new AppError('offset must be a valid integer.', {
|
||||||
|
code: 'INVALID_QUERY_PARAMETER',
|
||||||
|
statusCode: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await sessionsService.fetchHistory(sessionId, {
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
});
|
||||||
|
res.json(result);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type {
|
|||||||
} from '@/shared/types.js';
|
} from '@/shared/types.js';
|
||||||
import { AppError } from '@/shared/utils.js';
|
import { AppError } from '@/shared/utils.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes one file if it exists.
|
* Removes one file if it exists.
|
||||||
*/
|
*/
|
||||||
@@ -54,20 +53,54 @@ export const sessionsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches normalized persisted session history for one provider/session pair.
|
* Fetches persisted history by session id.
|
||||||
|
*
|
||||||
|
* Provider and provider-specific lookup hints are resolved from the indexed
|
||||||
|
* session metadata in the database.
|
||||||
*/
|
*/
|
||||||
fetchHistory(
|
fetchHistory(
|
||||||
providerName: string,
|
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
options?: FetchHistoryOptions,
|
options: Pick<FetchHistoryOptions, 'limit' | 'offset'> = {},
|
||||||
): Promise<FetchHistoryResult> {
|
): Promise<FetchHistoryResult> {
|
||||||
return providerRegistry.resolveProvider(providerName).sessions.fetchHistory(sessionId, options);
|
const session = sessionsDb.getSessionById(sessionId);
|
||||||
|
if (!session) {
|
||||||
|
throw new AppError(`Session "${sessionId}" was not found.`, {
|
||||||
|
code: 'SESSION_NOT_FOUND',
|
||||||
|
statusCode: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = session.provider as LLMProvider;
|
||||||
|
return providerRegistry.resolveProvider(provider).sessions.fetchHistory(sessionId, {
|
||||||
|
limit: options.limit ?? null,
|
||||||
|
offset: options.offset ?? 0,
|
||||||
|
projectPath: session.project_path ?? '',
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes one persisted session row by id.
|
* Deletes one persisted session row by id.
|
||||||
|
*
|
||||||
|
* When `deletedFromDisk` is true and a session `jsonl_path` exists, the path
|
||||||
|
* is deleted from disk before the DB row is removed.
|
||||||
*/
|
*/
|
||||||
deleteSessionById(sessionId: string): { sessionId: string } {
|
async deleteSessionById(
|
||||||
|
sessionId: string,
|
||||||
|
deletedFromDisk = false,
|
||||||
|
): Promise<{ sessionId: string; deletedFromDisk: boolean }> {
|
||||||
|
const session = sessionsDb.getSessionById(sessionId);
|
||||||
|
if (!session) {
|
||||||
|
throw new AppError(`Session "${sessionId}" was not found.`, {
|
||||||
|
code: 'SESSION_NOT_FOUND',
|
||||||
|
statusCode: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let removedFromDisk = false;
|
||||||
|
if (deletedFromDisk && session.jsonl_path) {
|
||||||
|
removedFromDisk = await removeFileIfExists(session.jsonl_path);
|
||||||
|
}
|
||||||
|
|
||||||
const deleted = sessionsDb.deleteSessionById(sessionId);
|
const deleted = sessionsDb.deleteSessionById(sessionId);
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
throw new AppError(`Session "${sessionId}" was not found.`, {
|
throw new AppError(`Session "${sessionId}" was not found.`, {
|
||||||
@@ -76,7 +109,7 @@ export const sessionsService = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { sessionId };
|
return { sessionId, deletedFromDisk: removedFromDisk };
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* The filesystem-aware helpers kept in this module serve the remaining
|
* The filesystem-aware helpers kept in this module serve the remaining
|
||||||
* features that still need on-disk data:
|
* features that still need on-disk data:
|
||||||
* - Session message reads for each provider (Claude/Codex/Gemini) for
|
* - Session message reads for each provider (Claude/Codex/Gemini) for
|
||||||
* `GET /api/sessions/:sessionId/messages`.
|
* `GET /api/providers/sessions/:sessionId/messages`.
|
||||||
* - Conversation search (`searchConversations`) which scans JSONL history.
|
* - Conversation search (`searchConversations`) which scans JSONL history.
|
||||||
* - (Project row removal / JSONL cleanup is handled in
|
* - (Project row removal / JSONL cleanup is handled in
|
||||||
* `modules/projects/services/project-delete.service.ts`.)
|
* `modules/projects/services/project-delete.service.ts`.)
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
/**
|
|
||||||
* Unified messages endpoint.
|
|
||||||
*
|
|
||||||
* GET /api/sessions/:sessionId/messages?provider=claude&projectId=<id>&limit=50&offset=0
|
|
||||||
*
|
|
||||||
* Replaces the four provider-specific session message endpoints with a single route
|
|
||||||
* that delegates to the appropriate adapter via the provider registry.
|
|
||||||
*
|
|
||||||
* After the projectName → projectId migration, Claude history is located via the
|
|
||||||
* DB-backed project path lookup; the route accepts `projectId` (preferred) and
|
|
||||||
* resolves it to the underlying Claude folder name for the downstream adapter.
|
|
||||||
*
|
|
||||||
* @module routes/messages
|
|
||||||
*/
|
|
||||||
|
|
||||||
import express from 'express';
|
|
||||||
import { sessionsService } from '../modules/providers/services/sessions.service.js';
|
|
||||||
import { getProjectPathById, claudeFolderNameFromPath } from '../projects.js';
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/sessions/:sessionId/messages
|
|
||||||
*
|
|
||||||
* Auth: authenticateToken applied at mount level in index.js
|
|
||||||
*
|
|
||||||
* Query params:
|
|
||||||
* provider - 'claude' | 'cursor' | 'codex' | 'gemini' (default: 'claude')
|
|
||||||
* projectId - DB primary key of the project (required for claude provider)
|
|
||||||
* projectPath - required for cursor provider (absolute path used for cwdId hash)
|
|
||||||
* limit - page size (omit or null for all)
|
|
||||||
* offset - pagination offset (default: 0)
|
|
||||||
*/
|
|
||||||
router.get('/:sessionId/messages', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { sessionId } = req.params;
|
|
||||||
const provider = String(req.query.provider || 'claude').trim().toLowerCase();
|
|
||||||
const projectId = req.query.projectId || '';
|
|
||||||
const projectPath = req.query.projectPath || '';
|
|
||||||
const limitParam = req.query.limit;
|
|
||||||
const limit = limitParam !== undefined && limitParam !== null && limitParam !== ''
|
|
||||||
? parseInt(limitParam, 10)
|
|
||||||
: null;
|
|
||||||
const offset = parseInt(req.query.offset || '0', 10);
|
|
||||||
|
|
||||||
const availableProviders = sessionsService.listProviderIds();
|
|
||||||
if (!availableProviders.includes(provider)) {
|
|
||||||
const available = availableProviders.join(', ');
|
|
||||||
return res.status(400).json({ error: `Unknown provider: ${provider}. Available: ${available}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Claude adapter still reads sessions from ~/.claude/projects/<folder>/,
|
|
||||||
// so we translate the caller's projectId into the encoded folder name via
|
|
||||||
// the DB-stored project path before delegating to the adapter.
|
|
||||||
let claudeProjectName = '';
|
|
||||||
if (provider === 'claude' && projectId) {
|
|
||||||
const resolvedPath = await getProjectPathById(projectId);
|
|
||||||
if (!resolvedPath) {
|
|
||||||
return res.status(404).json({ error: 'Project not found' });
|
|
||||||
}
|
|
||||||
claudeProjectName = claudeFolderNameFromPath(resolvedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await sessionsService.fetchHistory(provider, sessionId, {
|
|
||||||
projectName: claudeProjectName,
|
|
||||||
projectPath,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.json(result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching unified messages:', error);
|
|
||||||
return res.status(500).json({ error: 'Failed to fetch messages' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -133,11 +133,10 @@ export type NormalizedMessage = {
|
|||||||
/**
|
/**
|
||||||
* Shared options used to fetch historical provider messages.
|
* Shared options used to fetch historical provider messages.
|
||||||
*
|
*
|
||||||
* Consumers should pass provider-specific lookup hints (`projectName`, `projectPath`)
|
* Consumers should pass provider-specific lookup hints (`projectPath`) only
|
||||||
* only when the selected provider requires them.
|
* when the selected provider requires them.
|
||||||
*/
|
*/
|
||||||
export type FetchHistoryOptions = {
|
export type FetchHistoryOptions = {
|
||||||
projectName?: string;
|
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
limit?: number | null;
|
limit?: number | null;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import type { LLMProvider } from '../types/app';
|
|
||||||
import { authenticatedFetch } from '../utils/api';
|
import { authenticatedFetch } from '../utils/api';
|
||||||
|
import type { LLMProvider } from '../types/app';
|
||||||
|
|
||||||
// ─── NormalizedMessage (mirrors server/adapters/types.js) ────────────────────
|
// ─── NormalizedMessage (mirrors server/adapters/types.js) ────────────────────
|
||||||
|
|
||||||
@@ -164,11 +165,9 @@ export function useSessionStore() {
|
|||||||
const has = useCallback((sessionId: string) => storeRef.current.has(sessionId), []);
|
const has = useCallback((sessionId: string) => storeRef.current.has(sessionId), []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch messages from the unified endpoint and populate serverMessages.
|
* Fetch messages from the provider sessions endpoint and populate serverMessages.
|
||||||
*
|
*
|
||||||
* `projectId` is the DB-assigned identifier used by the backend to resolve
|
* Provider and project metadata are resolved server-side from `sessionId`.
|
||||||
* the project's on-disk directory; it replaces the legacy `projectName`
|
|
||||||
* Claude folder encoding that callers used to pass.
|
|
||||||
*/
|
*/
|
||||||
const fetchFromServer = useCallback(async (
|
const fetchFromServer = useCallback(async (
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
@@ -186,16 +185,13 @@ export function useSessionStore() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (opts.provider) params.append('provider', opts.provider);
|
|
||||||
if (opts.projectId) params.append('projectId', opts.projectId);
|
|
||||||
if (opts.projectPath) params.append('projectPath', opts.projectPath);
|
|
||||||
if (opts.limit !== null && opts.limit !== undefined) {
|
if (opts.limit !== null && opts.limit !== undefined) {
|
||||||
params.append('limit', String(opts.limit));
|
params.append('limit', String(opts.limit));
|
||||||
params.append('offset', String(opts.offset ?? 0));
|
params.append('offset', String(opts.offset ?? 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
const url = `/api/sessions/${encodeURIComponent(sessionId)}/messages${qs ? `?${qs}` : ''}`;
|
const url = `/api/providers/sessions/${encodeURIComponent(sessionId)}/messages${qs ? `?${qs}` : ''}`;
|
||||||
const response = await authenticatedFetch(url);
|
const response = await authenticatedFetch(url);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -228,9 +224,6 @@ export function useSessionStore() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load older (paginated) messages and prepend to serverMessages.
|
* Load older (paginated) messages and prepend to serverMessages.
|
||||||
*
|
|
||||||
* Accepts `projectId` (the DB primary key) so the unified messages endpoint
|
|
||||||
* can resolve the project path through the database.
|
|
||||||
*/
|
*/
|
||||||
const fetchMore = useCallback(async (
|
const fetchMore = useCallback(async (
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
@@ -245,15 +238,12 @@ export function useSessionStore() {
|
|||||||
if (!slot.hasMore) return slot;
|
if (!slot.hasMore) return slot;
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (opts.provider) params.append('provider', opts.provider);
|
|
||||||
if (opts.projectId) params.append('projectId', opts.projectId);
|
|
||||||
if (opts.projectPath) params.append('projectPath', opts.projectPath);
|
|
||||||
const limit = opts.limit ?? 20;
|
const limit = opts.limit ?? 20;
|
||||||
params.append('limit', String(limit));
|
params.append('limit', String(limit));
|
||||||
params.append('offset', String(slot.offset));
|
params.append('offset', String(slot.offset));
|
||||||
|
|
||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
const url = `/api/sessions/${encodeURIComponent(sessionId)}/messages${qs ? `?${qs}` : ''}`;
|
const url = `/api/providers/sessions/${encodeURIComponent(sessionId)}/messages${qs ? `?${qs}` : ''}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch(url);
|
const response = await authenticatedFetch(url);
|
||||||
@@ -305,14 +295,11 @@ export function useSessionStore() {
|
|||||||
}, [getSlot, notify]);
|
}, [getSlot, notify]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-fetch serverMessages from the unified endpoint (e.g., on projects_updated).
|
* Re-fetch serverMessages from the provider sessions endpoint.
|
||||||
*
|
|
||||||
* Uses the DB-assigned `projectId`; the legacy folder-derived projectName
|
|
||||||
* is no longer accepted here.
|
|
||||||
*/
|
*/
|
||||||
const refreshFromServer = useCallback(async (
|
const refreshFromServer = useCallback(async (
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
opts: {
|
_opts: {
|
||||||
provider?: LLMProvider;
|
provider?: LLMProvider;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
@@ -321,12 +308,9 @@ export function useSessionStore() {
|
|||||||
const slot = getSlot(sessionId);
|
const slot = getSlot(sessionId);
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (opts.provider) params.append('provider', opts.provider);
|
|
||||||
if (opts.projectId) params.append('projectId', opts.projectId);
|
|
||||||
if (opts.projectPath) params.append('projectPath', opts.projectPath);
|
|
||||||
|
|
||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
const url = `/api/sessions/${encodeURIComponent(sessionId)}/messages${qs ? `?${qs}` : ''}`;
|
const url = `/api/providers/sessions/${encodeURIComponent(sessionId)}/messages${qs ? `?${qs}` : ''}`;
|
||||||
const response = await authenticatedFetch(url);
|
const response = await authenticatedFetch(url);
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
|||||||
@@ -56,20 +56,16 @@ export const api = {
|
|||||||
projects: () => authenticatedFetch('/api/projects'),
|
projects: () => authenticatedFetch('/api/projects'),
|
||||||
projectTaskmaster: (projectId) =>
|
projectTaskmaster: (projectId) =>
|
||||||
authenticatedFetch(`/api/projects/${encodeURIComponent(projectId)}/taskmaster`),
|
authenticatedFetch(`/api/projects/${encodeURIComponent(projectId)}/taskmaster`),
|
||||||
// Unified endpoint — all providers through one URL. The legacy `projectName`
|
// Unified endpoint for persisted session messages.
|
||||||
// query parameter is preserved on the wire (routes/messages.js still reads
|
// Provider/project metadata are resolved by the backend from sessionId.
|
||||||
// it) but it now carries a projectId value supplied by the caller.
|
unifiedSessionMessages: (sessionId, _provider = 'claude', { limit = null, offset = 0 } = {}) => {
|
||||||
unifiedSessionMessages: (sessionId, provider = 'claude', { projectId = '', projectPath = '', limit = null, offset = 0 } = {}) => {
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('provider', provider);
|
|
||||||
if (projectId) params.append('projectId', projectId);
|
|
||||||
if (projectPath) params.append('projectPath', projectPath);
|
|
||||||
if (limit !== null) {
|
if (limit !== null) {
|
||||||
params.append('limit', String(limit));
|
params.append('limit', String(limit));
|
||||||
params.append('offset', String(offset));
|
params.append('offset', String(offset));
|
||||||
}
|
}
|
||||||
const queryString = params.toString();
|
const queryString = params.toString();
|
||||||
return authenticatedFetch(`/api/sessions/${encodeURIComponent(sessionId)}/messages${queryString ? `?${queryString}` : ''}`);
|
return authenticatedFetch(`/api/providers/sessions/${encodeURIComponent(sessionId)}/messages${queryString ? `?${queryString}` : ''}`);
|
||||||
},
|
},
|
||||||
renameProject: (projectId, displayName) =>
|
renameProject: (projectId, displayName) =>
|
||||||
authenticatedFetch(`/api/projects/${projectId}/rename`, {
|
authenticatedFetch(`/api/projects/${projectId}/rename`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user