feat(api-settings): add CCW-LiteLLM installation progress overlay and related localization

This commit is contained in:
catlog22
2026-02-18 13:56:05 +08:00
parent 3441a3c06c
commit 7e8fb3d2de
10 changed files with 371 additions and 312 deletions

View File

@@ -23,11 +23,13 @@ import {
useUninstallCcwLitellm, useUninstallCcwLitellm,
} from '@/hooks/useApiSettings'; } from '@/hooks/useApiSettings';
import { useNotifications } from '@/hooks/useNotifications'; import { useNotifications } from '@/hooks/useNotifications';
import { LitellmInstallProgressOverlay } from './LitellmInstallProgressOverlay';
export function CcwLitellmStatus() { export function CcwLitellmStatus() {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { success, error: notifyError } = useNotifications(); const { success, error: notifyError } = useNotifications();
const [refresh, setRefresh] = useState(false); const [refresh, setRefresh] = useState(false);
const [isInstallOverlayOpen, setIsInstallOverlayOpen] = useState(false);
const { data: status, isLoading, refetch } = useCcwLitellmStatus({ refresh }); const { data: status, isLoading, refetch } = useCcwLitellmStatus({ refresh });
const { install, isInstalling } = useInstallCcwLitellm(); const { install, isInstalling } = useInstallCcwLitellm();
@@ -35,17 +37,22 @@ export function CcwLitellmStatus() {
const isBusy = isInstalling || isUninstalling; const isBusy = isInstalling || isUninstalling;
const handleInstall = async () => { const handleInstallViaOverlay = async (): Promise<{ success: boolean }> => {
try { try {
await install(); await install();
success(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installSuccess' })); success(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installSuccess' }));
setRefresh(true); return { success: true };
refetch();
} catch { } catch {
notifyError(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installFailed' })); notifyError(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installFailed' }));
return { success: false };
} }
}; };
const handleInstallSuccess = () => {
setRefresh(true);
refetch();
};
const handleUninstall = async () => { const handleUninstall = async () => {
try { try {
await uninstall(); await uninstall();
@@ -66,6 +73,7 @@ export function CcwLitellmStatus() {
const version = status?.version; const version = status?.version;
return ( return (
<>
<Card> <Card>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -131,7 +139,7 @@ export function CcwLitellmStatus() {
<Button <Button
variant="default" variant="default"
size="sm" size="sm"
onClick={handleInstall} onClick={() => setIsInstallOverlayOpen(true)}
disabled={isBusy} disabled={isBusy}
> >
{isInstalling ? ( {isInstalling ? (
@@ -146,6 +154,15 @@ export function CcwLitellmStatus() {
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Install Progress Overlay */}
<LitellmInstallProgressOverlay
open={isInstallOverlayOpen}
onOpenChange={setIsInstallOverlayOpen}
onInstall={handleInstallViaOverlay}
onSuccess={handleInstallSuccess}
/>
</>
); );
} }

View File

@@ -0,0 +1,267 @@
// ========================================
// CCW-LiteLLM Install Progress Overlay
// ========================================
// Dialog overlay showing staged progress during ccw-litellm installation
// Adapted from CodexLens InstallProgressOverlay pattern
import { useState, useEffect, useRef, useCallback } from 'react';
import { useIntl } from 'react-intl';
import {
Check,
Download,
Info,
Loader2,
} from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/Dialog';
import { Button } from '@/components/ui/Button';
import { Progress } from '@/components/ui/Progress';
import { Card, CardContent } from '@/components/ui/Card';
// ----------------------------------------
// Types
// ----------------------------------------
interface InstallStage {
progress: number;
messageId: string;
}
interface LitellmInstallProgressOverlayProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onInstall: () => Promise<{ success: boolean }>;
onSuccess?: () => void;
}
// ----------------------------------------
// Constants
// ----------------------------------------
const INSTALL_STAGES: InstallStage[] = [
{ progress: 10, messageId: 'apiSettings.ccwLitellm.install.stage.checkingVenv' },
{ progress: 25, messageId: 'apiSettings.ccwLitellm.install.stage.detectingPackage' },
{ progress: 45, messageId: 'apiSettings.ccwLitellm.install.stage.installingDeps' },
{ progress: 65, messageId: 'apiSettings.ccwLitellm.install.stage.installingPackage' },
{ progress: 85, messageId: 'apiSettings.ccwLitellm.install.stage.verifying' },
];
const STAGE_INTERVAL_MS = 2000;
// ----------------------------------------
// Checklist items
// ----------------------------------------
interface ChecklistItem {
labelId: string;
descId: string;
}
const CHECKLIST_ITEMS: ChecklistItem[] = [
{ labelId: 'apiSettings.ccwLitellm.install.codexlensVenv', descId: 'apiSettings.ccwLitellm.install.codexlensVenvDesc' },
{ labelId: 'apiSettings.ccwLitellm.install.litellmPackage', descId: 'apiSettings.ccwLitellm.install.litellmPackageDesc' },
{ labelId: 'apiSettings.ccwLitellm.install.embeddingSupport', descId: 'apiSettings.ccwLitellm.install.embeddingSupportDesc' },
];
// ----------------------------------------
// Component
// ----------------------------------------
export function LitellmInstallProgressOverlay({
open,
onOpenChange,
onInstall,
onSuccess,
}: LitellmInstallProgressOverlayProps) {
const { formatMessage } = useIntl();
const [isInstalling, setIsInstalling] = useState(false);
const [progress, setProgress] = useState(0);
const [stageText, setStageText] = useState('');
const [isComplete, setIsComplete] = useState(false);
const [errorText, setErrorText] = useState('');
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const clearStageInterval = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, []);
// Reset state when dialog closes
useEffect(() => {
if (!open) {
setIsInstalling(false);
setProgress(0);
setStageText('');
setIsComplete(false);
setErrorText('');
clearStageInterval();
}
}, [open, clearStageInterval]);
// Cleanup on unmount
useEffect(() => {
return () => clearStageInterval();
}, [clearStageInterval]);
const handleInstall = async () => {
setIsInstalling(true);
setProgress(0);
setIsComplete(false);
setErrorText('');
// Start stage simulation
let currentStage = 0;
setStageText(formatMessage({ id: INSTALL_STAGES[0].messageId }));
setProgress(INSTALL_STAGES[0].progress);
currentStage = 1;
intervalRef.current = setInterval(() => {
if (currentStage < INSTALL_STAGES.length) {
setStageText(formatMessage({ id: INSTALL_STAGES[currentStage].messageId }));
setProgress(INSTALL_STAGES[currentStage].progress);
currentStage++;
}
}, STAGE_INTERVAL_MS);
try {
const result = await onInstall();
clearStageInterval();
if (result.success) {
setProgress(100);
setStageText(formatMessage({ id: 'apiSettings.ccwLitellm.install.stage.complete' }));
setIsComplete(true);
// Auto-close after showing completion
setTimeout(() => {
onOpenChange(false);
onSuccess?.();
}, 1200);
} else {
setIsInstalling(false);
setProgress(0);
setStageText('');
setErrorText(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installFailed' }));
}
} catch {
clearStageInterval();
setIsInstalling(false);
setProgress(0);
setStageText('');
setErrorText(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installFailed' }));
}
};
return (
<Dialog open={open} onOpenChange={isInstalling ? undefined : onOpenChange}>
<DialogContent className="max-w-lg" onPointerDownOutside={isInstalling ? (e) => e.preventDefault() : undefined}>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Download className="w-5 h-5 text-primary" />
{formatMessage({ id: 'apiSettings.ccwLitellm.install.title' })}
</DialogTitle>
<DialogDescription>
{formatMessage({ id: 'apiSettings.ccwLitellm.install.description' })}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Install Checklist */}
<div>
<h4 className="text-sm font-medium mb-2">
{formatMessage({ id: 'apiSettings.ccwLitellm.install.checklist' })}
</h4>
<ul className="space-y-2">
{CHECKLIST_ITEMS.map((item) => (
<li key={item.labelId} className="flex items-start gap-2 text-sm">
<Check className="w-4 h-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>
<strong>{formatMessage({ id: item.labelId })}</strong>
{' - '}
{formatMessage({ id: item.descId })}
</span>
</li>
))}
</ul>
</div>
{/* Install Info */}
<Card className="bg-primary/5 border-primary/20">
<CardContent className="p-3 flex items-start gap-2">
<Info className="w-4 h-4 text-primary mt-0.5 flex-shrink-0" />
<div className="text-sm text-muted-foreground">
<p className="font-medium text-foreground">
{formatMessage({ id: 'apiSettings.ccwLitellm.install.location' })}
</p>
<p className="mt-1">
<code className="bg-muted px-1.5 py-0.5 rounded text-xs">
{formatMessage({ id: 'apiSettings.ccwLitellm.install.locationPath' })}
</code>
</p>
<p className="mt-1">
{formatMessage({ id: 'apiSettings.ccwLitellm.install.timeEstimate' })}
</p>
</div>
</CardContent>
</Card>
{/* Progress Section - shown during install */}
{isInstalling && (
<div className="space-y-2">
<div className="flex items-center gap-3">
{isComplete ? (
<Check className="w-5 h-5 text-green-500" />
) : (
<Loader2 className="w-5 h-5 text-primary animate-spin" />
)}
<span className="text-sm">{stageText}</span>
</div>
<Progress value={progress} className="h-2" />
</div>
)}
{/* Error message */}
{errorText && !isInstalling && (
<p className="text-sm text-destructive">{errorText}</p>
)}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isInstalling}
>
{formatMessage({ id: 'apiSettings.common.cancel' })}
</Button>
<Button
onClick={handleInstall}
disabled={isInstalling}
>
{isInstalling ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
{formatMessage({ id: 'apiSettings.ccwLitellm.install.installing' })}
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
{formatMessage({ id: 'apiSettings.ccwLitellm.install.installNow' })}
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export default LitellmInstallProgressOverlay;

View File

@@ -18,8 +18,6 @@ vi.mock('@/hooks', async (importOriginal) => {
useCodexLensEnv: vi.fn(), useCodexLensEnv: vi.fn(),
useUpdateCodexLensEnv: vi.fn(), useUpdateCodexLensEnv: vi.fn(),
useCodexLensModels: vi.fn(), useCodexLensModels: vi.fn(),
useCodexLensRerankerConfig: vi.fn(),
useUpdateRerankerConfig: vi.fn(),
useNotifications: vi.fn(() => ({ useNotifications: vi.fn(() => ({
toasts: [], toasts: [],
wsStatus: 'disconnected' as const, wsStatus: 'disconnected' as const,
@@ -50,8 +48,6 @@ import {
useCodexLensEnv, useCodexLensEnv,
useUpdateCodexLensEnv, useUpdateCodexLensEnv,
useCodexLensModels, useCodexLensModels,
useCodexLensRerankerConfig,
useUpdateRerankerConfig,
useNotifications, useNotifications,
} from '@/hooks'; } from '@/hooks';
@@ -106,25 +102,6 @@ function setupDefaultMocks() {
error: null, error: null,
refetch: vi.fn(), refetch: vi.fn(),
}); });
vi.mocked(useCodexLensRerankerConfig).mockReturnValue({
data: undefined,
backend: 'fastembed',
modelName: '',
apiProvider: '',
apiKeySet: false,
availableBackends: ['onnx', 'api', 'litellm', 'legacy'],
apiProviders: ['siliconflow', 'cohere', 'jina'],
litellmModels: undefined,
configSource: 'default',
isLoading: false,
error: null,
refetch: vi.fn(),
});
vi.mocked(useUpdateRerankerConfig).mockReturnValue({
updateConfig: vi.fn().mockResolvedValue({ success: true, message: 'Saved' }),
isUpdating: false,
error: null,
});
} }
describe('SettingsTab', () => { describe('SettingsTab', () => {
@@ -347,25 +324,6 @@ describe('SettingsTab', () => {
error: null, error: null,
refetch: vi.fn(), refetch: vi.fn(),
}); });
vi.mocked(useCodexLensRerankerConfig).mockReturnValue({
data: undefined,
backend: 'fastembed',
modelName: '',
apiProvider: '',
apiKeySet: false,
availableBackends: [],
apiProviders: [],
litellmModels: undefined,
configSource: 'default',
isLoading: false,
error: null,
refetch: vi.fn(),
});
vi.mocked(useUpdateRerankerConfig).mockReturnValue({
updateConfig: vi.fn().mockResolvedValue({ success: true }),
isUpdating: false,
error: null,
});
render(<SettingsTab enabled={true} />); render(<SettingsTab enabled={true} />);

View File

@@ -7,263 +7,22 @@
import { useState, useEffect, useCallback, useMemo } from 'react'; import { useState, useEffect, useCallback, useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { Save, RefreshCw, Loader2 } from 'lucide-react'; import { Save, RefreshCw } from 'lucide-react';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Label } from '@/components/ui/Label'; import { Label } from '@/components/ui/Label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
SelectGroup,
SelectLabel,
} from '@/components/ui/Select';
import { import {
useCodexLensConfig, useCodexLensConfig,
useCodexLensEnv, useCodexLensEnv,
useUpdateCodexLensEnv, useUpdateCodexLensEnv,
useCodexLensModels, useCodexLensModels,
useCodexLensRerankerConfig,
useUpdateRerankerConfig,
} from '@/hooks'; } from '@/hooks';
import { useNotifications } from '@/hooks'; import { useNotifications } from '@/hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { SchemaFormRenderer } from './SchemaFormRenderer'; import { SchemaFormRenderer } from './SchemaFormRenderer';
import { envVarGroupsSchema, getSchemaDefaults } from './envVarSchema'; import { envVarGroupsSchema, getSchemaDefaults } from './envVarSchema';
// ========== Reranker Configuration Card ==========
interface RerankerConfigCardProps {
enabled?: boolean;
}
function RerankerConfigCard({ enabled = true }: RerankerConfigCardProps) {
const { formatMessage } = useIntl();
const { success: showSuccess, error: showError } = useNotifications();
const {
backend: serverBackend,
modelName: serverModelName,
apiProvider: serverApiProvider,
apiKeySet,
availableBackends,
apiProviders,
litellmModels,
configSource,
isLoading,
} = useCodexLensRerankerConfig({ enabled });
const { updateConfig, isUpdating } = useUpdateRerankerConfig();
const [backend, setBackend] = useState('');
const [modelName, setModelName] = useState('');
const [apiProvider, setApiProvider] = useState('');
const [hasChanges, setHasChanges] = useState(false);
// Initialize form from server data
useEffect(() => {
setBackend(serverBackend);
setModelName(serverModelName);
setApiProvider(serverApiProvider);
setHasChanges(false);
}, [serverBackend, serverModelName, serverApiProvider]);
// Detect changes
useEffect(() => {
const changed =
backend !== serverBackend ||
modelName !== serverModelName ||
apiProvider !== serverApiProvider;
setHasChanges(changed);
}, [backend, modelName, apiProvider, serverBackend, serverModelName, serverApiProvider]);
const handleSave = async () => {
try {
const request: Record<string, string> = {};
if (backend !== serverBackend) request.backend = backend;
if (modelName !== serverModelName) {
// When backend is litellm, model_name is sent as litellm_endpoint
if (backend === 'litellm') {
request.litellm_endpoint = modelName;
} else {
request.model_name = modelName;
}
}
if (apiProvider !== serverApiProvider) request.api_provider = apiProvider;
const result = await updateConfig(request);
if (result.success) {
showSuccess(
formatMessage({ id: 'codexlens.reranker.saveSuccess' }),
result.message || ''
);
} else {
showError(
formatMessage({ id: 'codexlens.reranker.saveFailed' }),
result.error || ''
);
}
} catch (err) {
showError(
formatMessage({ id: 'codexlens.reranker.saveFailed' }),
err instanceof Error ? err.message : ''
);
}
};
// Determine whether to show litellm model dropdown or text input
const showLitellmModelSelect = backend === 'litellm' && litellmModels && litellmModels.length > 0;
// Show provider dropdown only for api backend
const showProviderSelect = backend === 'api';
if (isLoading) {
return (
<Card className="p-6">
<div className="flex items-center gap-2 text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin" />
<span>{formatMessage({ id: 'common.loading' })}</span>
</div>
</Card>
);
}
return (
<Card className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-1">
{formatMessage({ id: 'codexlens.reranker.title' })}
</h3>
<p className="text-sm text-muted-foreground mb-4">
{formatMessage({ id: 'codexlens.reranker.description' })}
</p>
<div className="space-y-4">
{/* Backend Select */}
<div className="space-y-2">
<Label>{formatMessage({ id: 'codexlens.reranker.backend' })}</Label>
<Select value={backend} onValueChange={setBackend}>
<SelectTrigger>
<SelectValue placeholder={formatMessage({ id: 'codexlens.reranker.selectBackend' })} />
</SelectTrigger>
<SelectContent>
{availableBackends.length > 0 ? (
availableBackends.map((b) => (
<SelectItem key={b} value={b}>
{b}
</SelectItem>
))
) : (
<SelectItem value="_none" disabled>
{formatMessage({ id: 'codexlens.reranker.noBackends' })}
</SelectItem>
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'codexlens.reranker.backendHint' })}
</p>
</div>
{/* Model - Select for litellm, Input for others */}
<div className="space-y-2">
<Label>{formatMessage({ id: 'codexlens.reranker.model' })}</Label>
{showLitellmModelSelect ? (
<Select value={modelName} onValueChange={setModelName}>
<SelectTrigger>
<SelectValue placeholder={formatMessage({ id: 'codexlens.reranker.selectModel' })} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{formatMessage({ id: 'codexlens.reranker.litellmModels' })}
</SelectLabel>
{litellmModels!.map((m) => (
<SelectItem key={m.modelId} value={m.modelId}>
{m.modelName}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
) : (
<Input
value={modelName}
onChange={(e) => setModelName(e.target.value)}
placeholder={formatMessage({ id: 'codexlens.reranker.selectModel' })}
/>
)}
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'codexlens.reranker.modelHint' })}
</p>
</div>
{/* Provider Select (only for api backend) */}
{showProviderSelect && (
<div className="space-y-2">
<Label>{formatMessage({ id: 'codexlens.reranker.provider' })}</Label>
<Select value={apiProvider} onValueChange={setApiProvider}>
<SelectTrigger>
<SelectValue placeholder={formatMessage({ id: 'codexlens.reranker.selectProvider' })} />
</SelectTrigger>
<SelectContent>
{apiProviders.length > 0 ? (
apiProviders.map((p) => (
<SelectItem key={p} value={p}>
{p}
</SelectItem>
))
) : (
<SelectItem value="_none" disabled>
{formatMessage({ id: 'codexlens.reranker.noProviders' })}
</SelectItem>
)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'codexlens.reranker.providerHint' })}
</p>
</div>
)}
{/* Status Row */}
<div className="flex items-center gap-4 text-sm text-muted-foreground pt-2 border-t">
<span>
{formatMessage({ id: 'codexlens.reranker.apiKeyStatus' })}:{' '}
<span className={apiKeySet ? 'text-green-600' : 'text-yellow-600'}>
{apiKeySet
? formatMessage({ id: 'codexlens.reranker.apiKeySet' })
: formatMessage({ id: 'codexlens.reranker.apiKeyNotSet' })}
</span>
</span>
<span>
{formatMessage({ id: 'codexlens.reranker.configSource' })}: {configSource}
</span>
</div>
{/* Save Button */}
<div className="flex items-center gap-2 pt-2">
<Button
onClick={handleSave}
disabled={isUpdating || !hasChanges}
size="sm"
>
{isUpdating ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<Save className="w-4 h-4 mr-2" />
)}
{isUpdating
? formatMessage({ id: 'codexlens.reranker.saving' })
: formatMessage({ id: 'codexlens.reranker.save' })}
</Button>
</div>
</div>
</Card>
);
}
// ========== Settings Tab ========== // ========== Settings Tab ==========
interface SettingsTabProps { interface SettingsTabProps {
@@ -462,9 +221,6 @@ export function SettingsTab({ enabled = true }: SettingsTabProps) {
</div> </div>
</Card> </Card>
{/* Reranker Configuration */}
<RerankerConfigCard enabled={enabled} />
{/* General Configuration */} {/* General Configuration */}
<Card className="p-6"> <Card className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-4"> <h3 className="text-lg font-semibold text-foreground mb-4">

View File

@@ -650,14 +650,14 @@ function WorkflowTaskWidgetComponent({ className }: WorkflowTaskWidgetProps) {
onClick={() => handleSessionClick(currentSession.session_id)} onClick={() => handleSessionClick(currentSession.session_id)}
> >
{/* Session Header */} {/* Session Header */}
<div className="mb-2 pb-2 border-b border-border shrink-0"> <div className="mb-2 pb-2 border-b border-border shrink-0 min-w-0">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2 min-w-0">
<div className={cn('px-2 py-1 rounded text-xs font-medium shrink-0', sessionStatusColors[currentSession.status]?.bg || 'bg-muted', sessionStatusColors[currentSession.status]?.text || 'text-muted-foreground')}> <div className={cn('px-2 py-1 rounded text-xs font-medium shrink-0', sessionStatusColors[currentSession.status]?.bg || 'bg-muted', sessionStatusColors[currentSession.status]?.text || 'text-muted-foreground')}>
{formatMessage({ id: `common.status.${currentSession.status === 'in_progress' ? 'inProgress' : currentSession.status}` })} {formatMessage({ id: `common.status.${currentSession.status === 'in_progress' ? 'inProgress' : currentSession.status}` })}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0 overflow-hidden">
<p className="text-sm font-medium text-foreground truncate">{currentSession.title || currentSession.session_id}</p> <p className="text-sm font-medium text-foreground truncate">{currentSession.title || currentSession.session_id}</p>
<p className="text-xs text-muted-foreground">{currentSession.session_id}</p> <p className="text-xs text-muted-foreground truncate">{currentSession.session_id}</p>
</div> </div>
</div> </div>
{/* Description */} {/* Description */}

View File

@@ -337,6 +337,30 @@
"installFailed": "Failed to install ccw-litellm", "installFailed": "Failed to install ccw-litellm",
"uninstallSuccess": "ccw-litellm uninstalled successfully", "uninstallSuccess": "ccw-litellm uninstalled successfully",
"uninstallFailed": "Failed to uninstall ccw-litellm" "uninstallFailed": "Failed to uninstall ccw-litellm"
},
"install": {
"title": "Install CCW-LiteLLM",
"description": "Install ccw-litellm into the CodexLens Python virtual environment for embedding support.",
"checklist": "What will be installed",
"codexlensVenv": "CodexLens Venv",
"codexlensVenvDesc": "Ensures CodexLens Python virtual environment is ready",
"litellmPackage": "ccw-litellm Package",
"litellmPackageDesc": "LiteLLM-based embedding client for multi-provider support",
"embeddingSupport": "Embedding Support",
"embeddingSupportDesc": "Enables LiteLLM embedding backend for semantic search",
"location": "Install Location",
"locationPath": "~/.codexlens/venv",
"timeEstimate": "Installation may take 1-3 minutes depending on network speed.",
"installNow": "Install Now",
"installing": "Installing...",
"stage": {
"checkingVenv": "Checking CodexLens virtual environment...",
"detectingPackage": "Detecting ccw-litellm package source...",
"installingDeps": "Installing dependencies...",
"installingPackage": "Installing ccw-litellm package...",
"verifying": "Verifying installation...",
"complete": "Installation complete!"
}
} }
}, },
"common": { "common": {

View File

@@ -337,6 +337,30 @@
"installFailed": "ccw-litellm 安装失败", "installFailed": "ccw-litellm 安装失败",
"uninstallSuccess": "ccw-litellm 卸载成功", "uninstallSuccess": "ccw-litellm 卸载成功",
"uninstallFailed": "ccw-litellm 卸载失败" "uninstallFailed": "ccw-litellm 卸载失败"
},
"install": {
"title": "安装 CCW-LiteLLM",
"description": "将 ccw-litellm 安装到 CodexLens Python 虚拟环境中以支持嵌入功能。",
"checklist": "将要安装的内容",
"codexlensVenv": "CodexLens 虚拟环境",
"codexlensVenvDesc": "确保 CodexLens Python 虚拟环境就绪",
"litellmPackage": "ccw-litellm 包",
"litellmPackageDesc": "基于 LiteLLM 的嵌入客户端,支持多提供商",
"embeddingSupport": "嵌入支持",
"embeddingSupportDesc": "启用 LiteLLM 嵌入后端用于语义搜索",
"location": "安装位置",
"locationPath": "~/.codexlens/venv",
"timeEstimate": "安装可能需要 1-3 分钟,取决于网络速度。",
"installNow": "立即安装",
"installing": "安装中...",
"stage": {
"checkingVenv": "正在检查 CodexLens 虚拟环境...",
"detectingPackage": "正在检测 ccw-litellm 包来源...",
"installingDeps": "正在安装依赖项...",
"installingPackage": "正在安装 ccw-litellm 包...",
"verifying": "正在验证安装...",
"complete": "安装完成!"
}
} }
}, },
"common": { "common": {

View File

@@ -17,6 +17,7 @@ Requires-Dist: tree-sitter-javascript>=0.25
Requires-Dist: tree-sitter-typescript>=0.23 Requires-Dist: tree-sitter-typescript>=0.23
Requires-Dist: pathspec>=0.11 Requires-Dist: pathspec>=0.11
Requires-Dist: watchdog>=3.0 Requires-Dist: watchdog>=3.0
Requires-Dist: ast-grep-py>=0.40.0
Provides-Extra: semantic Provides-Extra: semantic
Requires-Dist: numpy>=1.24; extra == "semantic" Requires-Dist: numpy>=1.24; extra == "semantic"
Requires-Dist: fastembed>=0.2; extra == "semantic" Requires-Dist: fastembed>=0.2; extra == "semantic"
@@ -45,12 +46,6 @@ Provides-Extra: reranker
Requires-Dist: optimum>=1.16; extra == "reranker" Requires-Dist: optimum>=1.16; extra == "reranker"
Requires-Dist: onnxruntime>=1.15; extra == "reranker" Requires-Dist: onnxruntime>=1.15; extra == "reranker"
Requires-Dist: transformers>=4.36; extra == "reranker" Requires-Dist: transformers>=4.36; extra == "reranker"
Provides-Extra: splade
Requires-Dist: transformers>=4.36; extra == "splade"
Requires-Dist: optimum[onnxruntime]>=1.16; extra == "splade"
Provides-Extra: splade-gpu
Requires-Dist: transformers>=4.36; extra == "splade-gpu"
Requires-Dist: optimum[onnxruntime-gpu]>=1.16; extra == "splade-gpu"
Provides-Extra: encoding Provides-Extra: encoding
Requires-Dist: chardet>=5.0; extra == "encoding" Requires-Dist: chardet>=5.0; extra == "encoding"
Provides-Extra: clustering Provides-Extra: clustering

View File

@@ -17,6 +17,7 @@ src/codexlens/api/__init__.py
src/codexlens/api/definition.py src/codexlens/api/definition.py
src/codexlens/api/file_context.py src/codexlens/api/file_context.py
src/codexlens/api/hover.py src/codexlens/api/hover.py
src/codexlens/api/lsp_lifecycle.py
src/codexlens/api/models.py src/codexlens/api/models.py
src/codexlens/api/references.py src/codexlens/api/references.py
src/codexlens/api/semantic.py src/codexlens/api/semantic.py
@@ -34,6 +35,7 @@ src/codexlens/indexing/embedding.py
src/codexlens/indexing/symbol_extractor.py src/codexlens/indexing/symbol_extractor.py
src/codexlens/lsp/__init__.py src/codexlens/lsp/__init__.py
src/codexlens/lsp/handlers.py src/codexlens/lsp/handlers.py
src/codexlens/lsp/keepalive_bridge.py
src/codexlens/lsp/lsp_bridge.py src/codexlens/lsp/lsp_bridge.py
src/codexlens/lsp/lsp_graph_builder.py src/codexlens/lsp/lsp_graph_builder.py
src/codexlens/lsp/providers.py src/codexlens/lsp/providers.py
@@ -44,14 +46,22 @@ src/codexlens/mcp/hooks.py
src/codexlens/mcp/provider.py src/codexlens/mcp/provider.py
src/codexlens/mcp/schema.py src/codexlens/mcp/schema.py
src/codexlens/parsers/__init__.py src/codexlens/parsers/__init__.py
src/codexlens/parsers/astgrep_binding.py
src/codexlens/parsers/astgrep_js_ts_processor.py
src/codexlens/parsers/astgrep_processor.py
src/codexlens/parsers/encoding.py src/codexlens/parsers/encoding.py
src/codexlens/parsers/factory.py src/codexlens/parsers/factory.py
src/codexlens/parsers/tokenizer.py src/codexlens/parsers/tokenizer.py
src/codexlens/parsers/treesitter_parser.py src/codexlens/parsers/treesitter_parser.py
src/codexlens/parsers/patterns/__init__.py
src/codexlens/parsers/patterns/javascript/__init__.py
src/codexlens/parsers/patterns/python/__init__.py
src/codexlens/parsers/patterns/typescript/__init__.py
src/codexlens/search/__init__.py src/codexlens/search/__init__.py
src/codexlens/search/binary_searcher.py src/codexlens/search/binary_searcher.py
src/codexlens/search/chain_search.py src/codexlens/search/chain_search.py
src/codexlens/search/enrichment.py src/codexlens/search/enrichment.py
src/codexlens/search/global_graph_expander.py
src/codexlens/search/graph_expander.py src/codexlens/search/graph_expander.py
src/codexlens/search/hybrid_search.py src/codexlens/search/hybrid_search.py
src/codexlens/search/query_parser.py src/codexlens/search/query_parser.py
@@ -77,7 +87,6 @@ src/codexlens/semantic/factory.py
src/codexlens/semantic/gpu_support.py src/codexlens/semantic/gpu_support.py
src/codexlens/semantic/litellm_embedder.py src/codexlens/semantic/litellm_embedder.py
src/codexlens/semantic/rotational_embedder.py src/codexlens/semantic/rotational_embedder.py
src/codexlens/semantic/splade_encoder.py
src/codexlens/semantic/vector_store.py src/codexlens/semantic/vector_store.py
src/codexlens/semantic/reranker/__init__.py src/codexlens/semantic/reranker/__init__.py
src/codexlens/semantic/reranker/api_reranker.py src/codexlens/semantic/reranker/api_reranker.py
@@ -96,7 +105,6 @@ src/codexlens/storage/merkle_tree.py
src/codexlens/storage/migration_manager.py src/codexlens/storage/migration_manager.py
src/codexlens/storage/path_mapper.py src/codexlens/storage/path_mapper.py
src/codexlens/storage/registry.py src/codexlens/storage/registry.py
src/codexlens/storage/splade_index.py
src/codexlens/storage/sqlite_store.py src/codexlens/storage/sqlite_store.py
src/codexlens/storage/sqlite_utils.py src/codexlens/storage/sqlite_utils.py
src/codexlens/storage/vector_meta_store.py src/codexlens/storage/vector_meta_store.py
@@ -108,7 +116,6 @@ src/codexlens/storage/migrations/migration_005_cleanup_unused_fields.py
src/codexlens/storage/migrations/migration_006_enhance_relationships.py src/codexlens/storage/migrations/migration_006_enhance_relationships.py
src/codexlens/storage/migrations/migration_007_add_graph_neighbors.py src/codexlens/storage/migrations/migration_007_add_graph_neighbors.py
src/codexlens/storage/migrations/migration_008_add_merkle_hashes.py src/codexlens/storage/migrations/migration_008_add_merkle_hashes.py
src/codexlens/storage/migrations/migration_009_add_splade.py
src/codexlens/storage/migrations/migration_010_add_multi_vector_chunks.py src/codexlens/storage/migrations/migration_010_add_multi_vector_chunks.py
src/codexlens/watcher/__init__.py src/codexlens/watcher/__init__.py
src/codexlens/watcher/events.py src/codexlens/watcher/events.py
@@ -118,12 +125,17 @@ src/codexlens/watcher/manager.py
tests/test_ann_index.py tests/test_ann_index.py
tests/test_api_reranker.py tests/test_api_reranker.py
tests/test_association_tree.py tests/test_association_tree.py
tests/test_astgrep_binding.py
tests/test_binary_searcher.py
tests/test_cascade_strategies.py
tests/test_chain_search.py tests/test_chain_search.py
tests/test_cli_hybrid_search.py tests/test_cli_hybrid_search.py
tests/test_cli_output.py tests/test_cli_output.py
tests/test_clustering_strategies.py tests/test_clustering_strategies.py
tests/test_code_extractor.py tests/test_code_extractor.py
tests/test_config.py tests/test_config.py
tests/test_config_cascade.py
tests/test_config_staged_env_overrides.py
tests/test_dual_fts.py tests/test_dual_fts.py
tests/test_embedder.py tests/test_embedder.py
tests/test_embedding_backend_availability.py tests/test_embedding_backend_availability.py
@@ -132,20 +144,26 @@ tests/test_enrichment.py
tests/test_entities.py tests/test_entities.py
tests/test_errors.py tests/test_errors.py
tests/test_file_cache.py tests/test_file_cache.py
tests/test_global_graph_expander.py
tests/test_global_index.py tests/test_global_index.py
tests/test_global_relationships.py
tests/test_global_symbol_index.py tests/test_global_symbol_index.py
tests/test_graph_expansion.py tests/test_graph_expansion.py
tests/test_hybrid_chunker.py tests/test_hybrid_chunker.py
tests/test_hybrid_search_e2e.py tests/test_hybrid_search_e2e.py
tests/test_hybrid_search_reranker_backend.py tests/test_hybrid_search_reranker_backend.py
tests/test_hybrid_search_unit.py
tests/test_incremental_indexing.py tests/test_incremental_indexing.py
tests/test_litellm_reranker.py tests/test_litellm_reranker.py
tests/test_lsp_graph_builder_depth.py
tests/test_merkle_detection.py tests/test_merkle_detection.py
tests/test_parser_integration.py tests/test_parser_integration.py
tests/test_parsers.py tests/test_parsers.py
tests/test_path_mapper_windows_drive.py
tests/test_performance_optimizations.py tests/test_performance_optimizations.py
tests/test_pure_vector_search.py tests/test_pure_vector_search.py
tests/test_query_parser.py tests/test_query_parser.py
tests/test_ranking.py
tests/test_recursive_splitting.py tests/test_recursive_splitting.py
tests/test_registry.py tests/test_registry.py
tests/test_reranker_backends.py tests/test_reranker_backends.py
@@ -160,7 +178,14 @@ tests/test_search_performance.py
tests/test_semantic.py tests/test_semantic.py
tests/test_semantic_search.py tests/test_semantic_search.py
tests/test_sqlite_store.py tests/test_sqlite_store.py
tests/test_stage1_binary_search_uses_chunk_lines.py
tests/test_staged_cascade.py tests/test_staged_cascade.py
tests/test_staged_cascade_lsp_depth.py
tests/test_staged_cascade_realtime_lsp.py
tests/test_staged_stage1_fallback_seed.py
tests/test_staged_stage3_fast_strategies.py
tests/test_standalone_lsp_manager_open_document_cache.py
tests/test_static_graph_integration.py
tests/test_storage.py tests/test_storage.py
tests/test_storage_concurrency.py tests/test_storage_concurrency.py
tests/test_symbol_extractor.py tests/test_symbol_extractor.py

View File

@@ -7,6 +7,7 @@ tree-sitter-javascript>=0.25
tree-sitter-typescript>=0.23 tree-sitter-typescript>=0.23
pathspec>=0.11 pathspec>=0.11
watchdog>=3.0 watchdog>=3.0
ast-grep-py>=0.40.0
[clustering] [clustering]
hdbscan>=0.8.1 hdbscan>=0.8.1
@@ -56,11 +57,3 @@ numpy>=1.24
fastembed>=0.2 fastembed>=0.2
hnswlib>=0.8.0 hnswlib>=0.8.0
onnxruntime-gpu>=1.15.0 onnxruntime-gpu>=1.15.0
[splade]
transformers>=4.36
optimum[onnxruntime]>=1.16
[splade-gpu]
transformers>=4.36
optimum[onnxruntime-gpu]>=1.16