mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-20 19:03:51 +08:00
fix: improve CodexLens env defaults, self-exclusion, and route handling
- Adjust env defaults (embed batch 64, workers 2) and add HNSW/chunking params - Exclude .codexlens directory from indexing and file watching - Expand codexlens-routes with improved validation and error handling - Enhance integration tests for broader route coverage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,7 +85,7 @@ const API_ONLY_KEYS = new Set([
|
||||
const FIELD_DEFAULTS: Record<string, string> = {
|
||||
CODEXLENS_EMBED_API_MODEL: 'text-embedding-3-small',
|
||||
CODEXLENS_EMBED_DIM: '1536',
|
||||
CODEXLENS_EMBED_BATCH_SIZE: '512',
|
||||
CODEXLENS_EMBED_BATCH_SIZE: '64',
|
||||
CODEXLENS_EMBED_API_CONCURRENCY: '4',
|
||||
CODEXLENS_BINARY_TOP_K: '200',
|
||||
CODEXLENS_ANN_TOP_K: '50',
|
||||
@@ -93,7 +93,11 @@ const FIELD_DEFAULTS: Record<string, string> = {
|
||||
CODEXLENS_FUSION_K: '60',
|
||||
CODEXLENS_RERANKER_TOP_K: '20',
|
||||
CODEXLENS_RERANKER_BATCH_SIZE: '32',
|
||||
CODEXLENS_INDEX_WORKERS: '4',
|
||||
CODEXLENS_INDEX_WORKERS: '2',
|
||||
CODEXLENS_CODE_AWARE_CHUNKING: 'true',
|
||||
CODEXLENS_MAX_FILE_SIZE: '1000000',
|
||||
CODEXLENS_HNSW_EF: '150',
|
||||
CODEXLENS_HNSW_M: '32',
|
||||
};
|
||||
|
||||
// Collect all keys
|
||||
@@ -103,6 +107,15 @@ function buildEmptyEnv(): Record<string, string> {
|
||||
return Object.fromEntries(ALL_KEYS.map((k) => [k, '']));
|
||||
}
|
||||
|
||||
function buildEffectiveEnv(
|
||||
values: Record<string, string>,
|
||||
defaults: Record<string, string>,
|
||||
): Record<string, string> {
|
||||
return Object.fromEntries(
|
||||
ALL_KEYS.map((key) => [key, values[key] ?? defaults[key] ?? '']),
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Sensitive field with show/hide toggle
|
||||
// ========================================
|
||||
@@ -142,30 +155,28 @@ function SensitiveInput({ value, onChange, id }: SensitiveInputProps) {
|
||||
|
||||
export function EnvSettingsTab() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { data: serverEnv, isLoading } = useCodexLensEnv();
|
||||
const { data: envData, isLoading } = useCodexLensEnv();
|
||||
const { saveEnv, isSaving } = useSaveCodexLensEnv();
|
||||
|
||||
const [embedMode, setEmbedMode] = useState<EmbedMode>('local');
|
||||
const [localEnv, setLocalEnv] = useState<Record<string, string>>(buildEmptyEnv);
|
||||
|
||||
const serverValues = envData?.values ?? {};
|
||||
const serverDefaults = { ...FIELD_DEFAULTS, ...(envData?.defaults ?? {}) };
|
||||
const serverRecord = buildEffectiveEnv(serverValues, serverDefaults);
|
||||
|
||||
// Sync server state into local when loaded and detect embed mode
|
||||
useEffect(() => {
|
||||
if (serverEnv) {
|
||||
setLocalEnv((prev) => {
|
||||
const next = { ...prev };
|
||||
ALL_KEYS.forEach((k) => {
|
||||
next[k] = serverEnv[k] ?? '';
|
||||
});
|
||||
return next;
|
||||
});
|
||||
if (envData) {
|
||||
const nextDefaults = { ...FIELD_DEFAULTS, ...(envData.defaults ?? {}) };
|
||||
const nextValues = envData.values ?? {};
|
||||
setLocalEnv(buildEffectiveEnv(nextValues, nextDefaults));
|
||||
// Auto-detect mode from saved env
|
||||
if (serverEnv.CODEXLENS_EMBED_API_URL) {
|
||||
if (nextValues.CODEXLENS_EMBED_API_URL) {
|
||||
setEmbedMode('api');
|
||||
}
|
||||
}
|
||||
}, [serverEnv]);
|
||||
|
||||
const serverRecord = serverEnv ?? {};
|
||||
}, [envData]);
|
||||
|
||||
const isDirty = ALL_KEYS.some((k) => localEnv[k] !== (serverRecord[k] ?? ''));
|
||||
|
||||
@@ -174,7 +185,17 @@ export function EnvSettingsTab() {
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
await saveEnv(localEnv);
|
||||
const payload = Object.fromEntries(
|
||||
Object.entries(localEnv).flatMap(([key, value]) => {
|
||||
const trimmed = value.trim();
|
||||
const defaultValue = serverDefaults[key] ?? '';
|
||||
if (!trimmed || trimmed === defaultValue) {
|
||||
return [];
|
||||
}
|
||||
return [[key, trimmed]];
|
||||
}),
|
||||
);
|
||||
await saveEnv(payload);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
@@ -252,7 +273,7 @@ export function EnvSettingsTab() {
|
||||
<Input
|
||||
id={field.key}
|
||||
value={localEnv[field.key] ?? ''}
|
||||
placeholder={FIELD_DEFAULTS[field.key] ?? ''}
|
||||
placeholder={serverDefaults[field.key] ?? ''}
|
||||
onChange={(e) => handleChange(field.key, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useCodexLensMcpConfig, useCodexLensEnv } from '@/hooks/useCodexLens';
|
||||
import { installMcpTemplate } from '@/lib/api';
|
||||
import { addGlobalMcpServer, copyMcpServerToProject } from '@/lib/api';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
|
||||
export function McpConfigTab() {
|
||||
@@ -26,16 +26,34 @@ export function McpConfigTab() {
|
||||
setInstalling(true);
|
||||
setInstallResult(null);
|
||||
try {
|
||||
const res = await installMcpTemplate({
|
||||
templateName: 'codexlens',
|
||||
scope,
|
||||
projectPath: scope === 'project' ? projectPath : undefined,
|
||||
});
|
||||
const mcpServers = mcpConfig?.['mcpServers'];
|
||||
const serverConfig = mcpServers && typeof mcpServers === 'object'
|
||||
? (mcpServers as Record<string, unknown>).codexlens
|
||||
: undefined;
|
||||
|
||||
if (!serverConfig || typeof serverConfig !== 'object') {
|
||||
throw new Error(formatMessage({ id: 'codexlens.mcp.noConfig' }));
|
||||
}
|
||||
|
||||
const typedConfig = serverConfig as {
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
if (scope === 'project') {
|
||||
if (!projectPath) {
|
||||
throw new Error(formatMessage({ id: 'codexlens.mcp.installError' }));
|
||||
}
|
||||
await copyMcpServerToProject('codexlens', typedConfig, projectPath);
|
||||
} else {
|
||||
await addGlobalMcpServer('codexlens', typedConfig);
|
||||
}
|
||||
|
||||
setInstallResult({
|
||||
ok: !!res.success,
|
||||
msg: res.success
|
||||
? formatMessage({ id: 'codexlens.mcp.installSuccess' })
|
||||
: (res.error ?? formatMessage({ id: 'codexlens.mcp.installError' })),
|
||||
ok: true,
|
||||
msg: formatMessage({ id: 'codexlens.mcp.installSuccess' }),
|
||||
});
|
||||
} catch (err) {
|
||||
setInstallResult({ ok: false, msg: (err as Error).message });
|
||||
@@ -44,7 +62,7 @@ export function McpConfigTab() {
|
||||
}
|
||||
};
|
||||
|
||||
const hasApiUrl = !!(envData?.CODEXLENS_EMBED_API_URL);
|
||||
const hasApiUrl = !!(envData?.values.CODEXLENS_EMBED_API_URL);
|
||||
const embedMode = hasApiUrl ? 'API' : 'Local fastembed';
|
||||
|
||||
const configJson = mcpConfig ? JSON.stringify(mcpConfig, null, 2) : '';
|
||||
|
||||
@@ -23,7 +23,7 @@ export function ModelManagerTab() {
|
||||
const { downloadModel, isDownloading } = useDownloadModel();
|
||||
const { deleteModel, isDeleting } = useDeleteModel();
|
||||
|
||||
const hasApiUrl = !!(envData?.CODEXLENS_EMBED_API_URL);
|
||||
const hasApiUrl = !!(envData?.values.CODEXLENS_EMBED_API_URL);
|
||||
const embedMode = hasApiUrl ? 'API' : 'Local fastembed';
|
||||
|
||||
const models: ModelEntry[] = modelsData ?? [];
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// TanStack Query hooks for CodexLens v2 API management
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { fetchApi } from '@/lib/api';
|
||||
|
||||
// ========================================
|
||||
// Domain types (exported for component use)
|
||||
@@ -25,11 +26,19 @@ export interface IndexStatusData {
|
||||
}
|
||||
|
||||
export type McpConfigData = Record<string, unknown>;
|
||||
export interface CodexLensEnvData {
|
||||
values: Record<string, string>;
|
||||
defaults: Record<string, string>;
|
||||
}
|
||||
|
||||
// Internal API response wrappers
|
||||
interface ModelsResponse { success: boolean; models: ModelEntry[] }
|
||||
interface IndexStatusResponse { success: boolean; status: IndexStatusData }
|
||||
interface EnvResponse { success: boolean; env: Record<string, string> }
|
||||
interface EnvResponse {
|
||||
success: boolean;
|
||||
env: Record<string, string>;
|
||||
defaults?: Record<string, string>;
|
||||
}
|
||||
interface McpConfigResponse { success: boolean; config: McpConfigData }
|
||||
|
||||
// ========================================
|
||||
@@ -44,27 +53,6 @@ export const codexLensKeys = {
|
||||
mcpConfig: () => [...codexLensKeys.all, 'mcpConfig'] as const,
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// Internal fetch helper (mirrors fetchApi pattern from api.ts)
|
||||
// ========================================
|
||||
|
||||
async function fetchApi<T>(url: string, options: RequestInit = {}): Promise<T> {
|
||||
const headers = new Headers(options.headers);
|
||||
if (options.body && typeof options.body === 'string') {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => response.statusText);
|
||||
throw new Error(text || `Request failed: ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Models Hooks
|
||||
// ========================================
|
||||
@@ -174,7 +162,11 @@ export function useRebuildIndex() {
|
||||
export function useCodexLensEnv() {
|
||||
return useQuery({
|
||||
queryKey: codexLensKeys.env(),
|
||||
queryFn: () => fetchApi<EnvResponse>('/api/codexlens/env').then(r => r.env),
|
||||
queryFn: () =>
|
||||
fetchApi<EnvResponse>('/api/codexlens/env').then((r): CodexLensEnvData => ({
|
||||
values: r.env ?? {},
|
||||
defaults: r.defaults ?? {},
|
||||
})),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ export async function initializeCsrfToken(): Promise<void> {
|
||||
/**
|
||||
* Base fetch wrapper with CSRF token and error handling
|
||||
*/
|
||||
async function fetchApi<T>(
|
||||
export async function fetchApi<T>(
|
||||
url: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
|
||||
Reference in New Issue
Block a user