+
+ {/* Trigger button */}
+
+
+ {/* Dropdown */}
+ {isOpen && (
+
+ {/* Search input */}
+
+
+ setSearchQuery(e.target.value)}
+ className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
+ />
+
+
+ {/* Options list */}
+
+ {Object.entries(groupedRequesters).map(
+ ([category, items]) => {
+ if (items.length === 0) return null;
+ return (
+
+
+ {categoryLabels[category]}
+
+ {items.map((r) => (
+
+ ))}
+
+ );
+ },
+ )}
+ {filteredRequesters.length === 0 && (
+
+ No results found.
+
+ )}
+
+
+ )}
+
{selectedRequester?.description && (
diff --git a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx
index c0899318..ddfc3a70 100644
--- a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx
+++ b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx
@@ -34,6 +34,7 @@ interface AddModelPopoverProps {
isOpen: boolean;
initialMode?: 'manual' | 'scan';
trigger?: React.ReactNode;
+ supportTypes?: string[];
onOpen: () => void;
onClose: () => void;
onAddModel: (
@@ -41,6 +42,7 @@ interface AddModelPopoverProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
+ contextLength?: number | null,
) => Promise;
onScanModels: (modelType?: ModelType) => Promise;
onAddScannedModels: (
@@ -63,6 +65,7 @@ export default function AddModelPopover({
isOpen,
initialMode = 'manual',
trigger,
+ supportTypes,
onOpen,
onClose,
onAddModel,
@@ -77,10 +80,26 @@ export default function AddModelPopover({
const { t } = useTranslation();
const prevIsOpenRef = useRef(false);
- const [tab, setTab] = useState('llm');
+ // Map manifest support_type values to UI tab values.
+ // Manifest uses 'text-embedding'; the UI tab uses 'embedding'.
+ const tabSupport: Record = {
+ llm: 'llm',
+ embedding: 'text-embedding',
+ rerank: 'rerank',
+ };
+ const allTabs: ModelType[] = ['llm', 'embedding', 'rerank'];
+ // When supportTypes is undefined (unknown requester), show all tabs for
+ // backward compatibility. Otherwise only show supported tabs.
+ const visibleTabs: ModelType[] = supportTypes
+ ? allTabs.filter((tabKey) => supportTypes.includes(tabSupport[tabKey]))
+ : allTabs;
+ const defaultTab: ModelType = visibleTabs[0] ?? 'llm';
+
+ const [tab, setTab] = useState(defaultTab);
const [mode, setMode] = useState<'manual' | 'scan'>('manual');
const [name, setName] = useState('');
const [abilities, setAbilities] = useState([]);
+ const [contextLength, setContextLength] = useState('');
const [extraArgs, setExtraArgs] = useState([]);
const [scanLoading, setScanLoading] = useState(false);
const [scannedModels, setScannedModels] = useState(
@@ -94,10 +113,11 @@ export default function AddModelPopover({
useEffect(() => {
const wasOpen = prevIsOpenRef.current;
if (isOpen && !wasOpen) {
- setTab('llm');
+ setTab(defaultTab);
setMode(initialMode);
setName('');
setAbilities([]);
+ setContextLength('');
setExtraArgs([]);
setScanLoading(false);
setScannedModels([]);
@@ -119,7 +139,11 @@ export default function AddModelPopover({
}, [tab, mode]);
const handleAdd = async () => {
- await onAddModel(tab, name, abilities, extraArgs);
+ const parsedContextLength =
+ tab === 'llm' && contextLength.trim()
+ ? Number(contextLength.trim())
+ : null;
+ await onAddModel(tab, name, abilities, extraArgs, parsedContextLength);
};
const handleTest = async () => {
@@ -130,32 +154,6 @@ export default function AddModelPopover({
setScanLoading(true);
try {
const result = await onScanModels(trigger ? undefined : tab);
-
- const debugData = (
- result.debug?.response as { data?: Record[] }
- )?.data;
- if (Array.isArray(debugData)) {
- const debugMap = new Map>();
- for (const item of debugData) {
- if (typeof item?.id === 'string') {
- debugMap.set(item.id, item);
- }
- }
- for (const model of result.models) {
- const debugItem = debugMap.get(model.id);
- if (!debugItem) continue;
- const features = debugItem.features as
- | Record
- | undefined;
- const tools = features?.tools as Record | undefined;
- if (tools?.function_calling === true) {
- const nextAbilities = new Set(model.abilities || []);
- nextAbilities.add('func_call');
- model.abilities = [...nextAbilities];
- }
- }
- }
-
setScannedModels(result.models);
setSelectedScannedModels({});
} finally {
@@ -279,20 +277,31 @@ export default function AddModelPopover({
className="flex flex-col min-h-0 flex-1"
>
- {!(trigger && initialMode === 'scan') && (
-
-
-
- {t('models.chat')}
-
-
-
- {t('models.embedding')}
-
-
-
- {t('models.rerank')}
-
+ {!(trigger && initialMode === 'scan') && visibleTabs.length > 1 && (
+
+ {visibleTabs.includes('llm') && (
+
+
+ {t('models.chat')}
+
+ )}
+ {visibleTabs.includes('embedding') && (
+
+
+ {t('models.embedding')}
+
+ )}
+ {visibleTabs.includes('rerank') && (
+
+
+ {t('models.rerank')}
+
+ )}
)}
@@ -344,6 +353,24 @@ export default function AddModelPopover({
)}
+ {tab === 'llm' && (
+
+
+ setContextLength(e.target.value)}
+ />
+
+ )}
+
Promise;
onTestModel: (
name: string,
@@ -92,6 +93,11 @@ export default function ModelItem({
const [editAbilities, setEditAbilities] = useState(
modelType === 'llm' ? (model as LLMModel).abilities || [] : [],
);
+ const [editContextLength, setEditContextLength] = useState(
+ modelType === 'llm' && (model as LLMModel).context_length
+ ? String((model as LLMModel).context_length)
+ : '',
+ );
const [editExtraArgs, setEditExtraArgs] = useState(
convertExtraArgsToArray(model.extra_args),
);
@@ -106,13 +112,27 @@ export default function ModelItem({
setEditAbilities(
modelType === 'llm' ? (model as LLMModel).abilities || [] : [],
);
+ setEditContextLength(
+ modelType === 'llm' && (model as LLMModel).context_length
+ ? String((model as LLMModel).context_length)
+ : '',
+ );
setEditExtraArgs(convertExtraArgsToArray(model.extra_args));
onResetTestResult();
}
}, [isEditOpen]);
const handleSave = async () => {
- await onUpdateModel(editName, editAbilities, editExtraArgs);
+ const parsedContextLength =
+ modelType === 'llm' && editContextLength.trim()
+ ? Number(editContextLength.trim())
+ : null;
+ await onUpdateModel(
+ editName,
+ editAbilities,
+ editExtraArgs,
+ parsedContextLength,
+ );
};
const handleTest = async () => {
@@ -287,6 +307,25 @@ export default function ModelItem({
)}
+ {modelType === 'llm' && (
+
+
+ setEditContextLength(e.target.value)}
+ />
+
+ )}
+
Promise;
onScanModels: (modelType?: ModelType) => Promise;
onAddScannedModels: (
@@ -74,6 +76,7 @@ interface ProviderCardProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
+ contextLength?: number | null,
) => Promise;
onOpenDeleteConfirm: (modelId: string) => void;
onCloseDeleteConfirm: () => void;
@@ -99,6 +102,7 @@ function maskApiKey(key: string): string {
export default function ProviderCard({
provider,
isLangBotModels = false,
+ supportTypes,
isExpanded,
isLoading,
models,
@@ -319,6 +323,7 @@ export default function ProviderCard({
addModelMode === 'manual'
}
initialMode="manual"
+ supportTypes={supportTypes}
trigger={