mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat(api-settings): add CCW-LiteLLM installation progress overlay and related localization
This commit is contained in:
@@ -23,11 +23,13 @@ import {
|
||||
useUninstallCcwLitellm,
|
||||
} from '@/hooks/useApiSettings';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { LitellmInstallProgressOverlay } from './LitellmInstallProgressOverlay';
|
||||
|
||||
export function CcwLitellmStatus() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { success, error: notifyError } = useNotifications();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [isInstallOverlayOpen, setIsInstallOverlayOpen] = useState(false);
|
||||
|
||||
const { data: status, isLoading, refetch } = useCcwLitellmStatus({ refresh });
|
||||
const { install, isInstalling } = useInstallCcwLitellm();
|
||||
@@ -35,17 +37,22 @@ export function CcwLitellmStatus() {
|
||||
|
||||
const isBusy = isInstalling || isUninstalling;
|
||||
|
||||
const handleInstall = async () => {
|
||||
const handleInstallViaOverlay = async (): Promise<{ success: boolean }> => {
|
||||
try {
|
||||
await install();
|
||||
success(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installSuccess' }));
|
||||
setRefresh(true);
|
||||
refetch();
|
||||
return { success: true };
|
||||
} catch {
|
||||
notifyError(formatMessage({ id: 'apiSettings.ccwLitellm.messages.installFailed' }));
|
||||
return { success: false };
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallSuccess = () => {
|
||||
setRefresh(true);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleUninstall = async () => {
|
||||
try {
|
||||
await uninstall();
|
||||
@@ -66,6 +73,7 @@ export function CcwLitellmStatus() {
|
||||
const version = status?.version;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -131,7 +139,7 @@ export function CcwLitellmStatus() {
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleInstall}
|
||||
onClick={() => setIsInstallOverlayOpen(true)}
|
||||
disabled={isBusy}
|
||||
>
|
||||
{isInstalling ? (
|
||||
@@ -146,6 +154,15 @@ export function CcwLitellmStatus() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Install Progress Overlay */}
|
||||
<LitellmInstallProgressOverlay
|
||||
open={isInstallOverlayOpen}
|
||||
onOpenChange={setIsInstallOverlayOpen}
|
||||
onInstall={handleInstallViaOverlay}
|
||||
onSuccess={handleInstallSuccess}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -18,8 +18,6 @@ vi.mock('@/hooks', async (importOriginal) => {
|
||||
useCodexLensEnv: vi.fn(),
|
||||
useUpdateCodexLensEnv: vi.fn(),
|
||||
useCodexLensModels: vi.fn(),
|
||||
useCodexLensRerankerConfig: vi.fn(),
|
||||
useUpdateRerankerConfig: vi.fn(),
|
||||
useNotifications: vi.fn(() => ({
|
||||
toasts: [],
|
||||
wsStatus: 'disconnected' as const,
|
||||
@@ -50,8 +48,6 @@ import {
|
||||
useCodexLensEnv,
|
||||
useUpdateCodexLensEnv,
|
||||
useCodexLensModels,
|
||||
useCodexLensRerankerConfig,
|
||||
useUpdateRerankerConfig,
|
||||
useNotifications,
|
||||
} from '@/hooks';
|
||||
|
||||
@@ -106,25 +102,6 @@ function setupDefaultMocks() {
|
||||
error: null,
|
||||
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', () => {
|
||||
@@ -347,25 +324,6 @@ describe('SettingsTab', () => {
|
||||
error: null,
|
||||
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} />);
|
||||
|
||||
|
||||
@@ -7,263 +7,22 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
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 { Input } from '@/components/ui/Input';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Label } from '@/components/ui/Label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectGroup,
|
||||
SelectLabel,
|
||||
} from '@/components/ui/Select';
|
||||
import {
|
||||
useCodexLensConfig,
|
||||
useCodexLensEnv,
|
||||
useUpdateCodexLensEnv,
|
||||
useCodexLensModels,
|
||||
useCodexLensRerankerConfig,
|
||||
useUpdateRerankerConfig,
|
||||
} from '@/hooks';
|
||||
import { useNotifications } from '@/hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SchemaFormRenderer } from './SchemaFormRenderer';
|
||||
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 ==========
|
||||
|
||||
interface SettingsTabProps {
|
||||
@@ -462,9 +221,6 @@ export function SettingsTab({ enabled = true }: SettingsTabProps) {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Reranker Configuration */}
|
||||
<RerankerConfigCard enabled={enabled} />
|
||||
|
||||
{/* General Configuration */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-foreground mb-4">
|
||||
|
||||
@@ -650,14 +650,14 @@ function WorkflowTaskWidgetComponent({ className }: WorkflowTaskWidgetProps) {
|
||||
onClick={() => handleSessionClick(currentSession.session_id)}
|
||||
>
|
||||
{/* Session Header */}
|
||||
<div className="mb-2 pb-2 border-b border-border shrink-0">
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="mb-2 pb-2 border-b border-border shrink-0 min-w-0">
|
||||
<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')}>
|
||||
{formatMessage({ id: `common.status.${currentSession.status === 'in_progress' ? 'inProgress' : currentSession.status}` })}
|
||||
</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-xs text-muted-foreground">{currentSession.session_id}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{currentSession.session_id}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Description */}
|
||||
|
||||
@@ -337,6 +337,30 @@
|
||||
"installFailed": "Failed to install ccw-litellm",
|
||||
"uninstallSuccess": "ccw-litellm uninstalled successfully",
|
||||
"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": {
|
||||
|
||||
@@ -337,6 +337,30 @@
|
||||
"installFailed": "ccw-litellm 安装失败",
|
||||
"uninstallSuccess": "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": {
|
||||
|
||||
@@ -17,6 +17,7 @@ Requires-Dist: tree-sitter-javascript>=0.25
|
||||
Requires-Dist: tree-sitter-typescript>=0.23
|
||||
Requires-Dist: pathspec>=0.11
|
||||
Requires-Dist: watchdog>=3.0
|
||||
Requires-Dist: ast-grep-py>=0.40.0
|
||||
Provides-Extra: semantic
|
||||
Requires-Dist: numpy>=1.24; 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: onnxruntime>=1.15; 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
|
||||
Requires-Dist: chardet>=5.0; extra == "encoding"
|
||||
Provides-Extra: clustering
|
||||
|
||||
@@ -17,6 +17,7 @@ src/codexlens/api/__init__.py
|
||||
src/codexlens/api/definition.py
|
||||
src/codexlens/api/file_context.py
|
||||
src/codexlens/api/hover.py
|
||||
src/codexlens/api/lsp_lifecycle.py
|
||||
src/codexlens/api/models.py
|
||||
src/codexlens/api/references.py
|
||||
src/codexlens/api/semantic.py
|
||||
@@ -34,6 +35,7 @@ src/codexlens/indexing/embedding.py
|
||||
src/codexlens/indexing/symbol_extractor.py
|
||||
src/codexlens/lsp/__init__.py
|
||||
src/codexlens/lsp/handlers.py
|
||||
src/codexlens/lsp/keepalive_bridge.py
|
||||
src/codexlens/lsp/lsp_bridge.py
|
||||
src/codexlens/lsp/lsp_graph_builder.py
|
||||
src/codexlens/lsp/providers.py
|
||||
@@ -44,14 +46,22 @@ src/codexlens/mcp/hooks.py
|
||||
src/codexlens/mcp/provider.py
|
||||
src/codexlens/mcp/schema.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/factory.py
|
||||
src/codexlens/parsers/tokenizer.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/binary_searcher.py
|
||||
src/codexlens/search/chain_search.py
|
||||
src/codexlens/search/enrichment.py
|
||||
src/codexlens/search/global_graph_expander.py
|
||||
src/codexlens/search/graph_expander.py
|
||||
src/codexlens/search/hybrid_search.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/litellm_embedder.py
|
||||
src/codexlens/semantic/rotational_embedder.py
|
||||
src/codexlens/semantic/splade_encoder.py
|
||||
src/codexlens/semantic/vector_store.py
|
||||
src/codexlens/semantic/reranker/__init__.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/path_mapper.py
|
||||
src/codexlens/storage/registry.py
|
||||
src/codexlens/storage/splade_index.py
|
||||
src/codexlens/storage/sqlite_store.py
|
||||
src/codexlens/storage/sqlite_utils.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_007_add_graph_neighbors.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/watcher/__init__.py
|
||||
src/codexlens/watcher/events.py
|
||||
@@ -118,12 +125,17 @@ src/codexlens/watcher/manager.py
|
||||
tests/test_ann_index.py
|
||||
tests/test_api_reranker.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_cli_hybrid_search.py
|
||||
tests/test_cli_output.py
|
||||
tests/test_clustering_strategies.py
|
||||
tests/test_code_extractor.py
|
||||
tests/test_config.py
|
||||
tests/test_config_cascade.py
|
||||
tests/test_config_staged_env_overrides.py
|
||||
tests/test_dual_fts.py
|
||||
tests/test_embedder.py
|
||||
tests/test_embedding_backend_availability.py
|
||||
@@ -132,20 +144,26 @@ tests/test_enrichment.py
|
||||
tests/test_entities.py
|
||||
tests/test_errors.py
|
||||
tests/test_file_cache.py
|
||||
tests/test_global_graph_expander.py
|
||||
tests/test_global_index.py
|
||||
tests/test_global_relationships.py
|
||||
tests/test_global_symbol_index.py
|
||||
tests/test_graph_expansion.py
|
||||
tests/test_hybrid_chunker.py
|
||||
tests/test_hybrid_search_e2e.py
|
||||
tests/test_hybrid_search_reranker_backend.py
|
||||
tests/test_hybrid_search_unit.py
|
||||
tests/test_incremental_indexing.py
|
||||
tests/test_litellm_reranker.py
|
||||
tests/test_lsp_graph_builder_depth.py
|
||||
tests/test_merkle_detection.py
|
||||
tests/test_parser_integration.py
|
||||
tests/test_parsers.py
|
||||
tests/test_path_mapper_windows_drive.py
|
||||
tests/test_performance_optimizations.py
|
||||
tests/test_pure_vector_search.py
|
||||
tests/test_query_parser.py
|
||||
tests/test_ranking.py
|
||||
tests/test_recursive_splitting.py
|
||||
tests/test_registry.py
|
||||
tests/test_reranker_backends.py
|
||||
@@ -160,7 +178,14 @@ tests/test_search_performance.py
|
||||
tests/test_semantic.py
|
||||
tests/test_semantic_search.py
|
||||
tests/test_sqlite_store.py
|
||||
tests/test_stage1_binary_search_uses_chunk_lines.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_concurrency.py
|
||||
tests/test_symbol_extractor.py
|
||||
|
||||
@@ -7,6 +7,7 @@ tree-sitter-javascript>=0.25
|
||||
tree-sitter-typescript>=0.23
|
||||
pathspec>=0.11
|
||||
watchdog>=3.0
|
||||
ast-grep-py>=0.40.0
|
||||
|
||||
[clustering]
|
||||
hdbscan>=0.8.1
|
||||
@@ -56,11 +57,3 @@ numpy>=1.24
|
||||
fastembed>=0.2
|
||||
hnswlib>=0.8.0
|
||||
onnxruntime-gpu>=1.15.0
|
||||
|
||||
[splade]
|
||||
transformers>=4.36
|
||||
optimum[onnxruntime]>=1.16
|
||||
|
||||
[splade-gpu]
|
||||
transformers>=4.36
|
||||
optimum[onnxruntime-gpu]>=1.16
|
||||
|
||||
Reference in New Issue
Block a user