diff --git a/src/app/(dashboard)/dashboard/combos/page.js b/src/app/(dashboard)/dashboard/combos/page.js
index e5b5b36..bf21475 100644
--- a/src/app/(dashboard)/dashboard/combos/page.js
+++ b/src/app/(dashboard)/dashboard/combos/page.js
@@ -390,6 +390,10 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, kindF
}
};
+ const handleDeselectModel = (model) => {
+ setModels(models.filter((m) => m !== model.value));
+ };
+
const handleRemoveModel = (index) => {
setModels(models.filter((_, i) => i !== index));
};
@@ -502,10 +506,13 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, kindF
isOpen={showModelSelect}
onClose={() => setShowModelSelect(false)}
onSelect={handleAddModel}
+ onDeselect={handleDeselectModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Add Model to Combo"
kindFilter={kindFilter}
+ addedModelValues={models}
+ closeOnSelect={false}
/>
>
);
diff --git a/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js b/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js
index e4f6aad..45c1546 100644
--- a/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js
+++ b/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js
@@ -126,6 +126,14 @@ export default function ComboDetailPage() {
await saveCombo({ models: next });
};
+ const handleDeselectModel = async (model) => {
+ const value = model?.value || model;
+ if (!value || !providers.includes(value)) return;
+ const next = providers.filter((p) => p !== value);
+ setProviders(next);
+ await saveCombo({ models: next });
+ };
+
const handleRemoveProvider = async (idx) => {
const next = providers.filter((_, i) => i !== idx);
setProviders(next);
@@ -389,10 +397,13 @@ export default function ComboDetailPage() {
isOpen={showPicker}
onClose={() => setShowPicker(false)}
onSelect={handleAddModel}
+ onDeselect={handleDeselectModel}
activeProviders={connections}
modelAliases={modelAliases}
title={`Add ${kindLabel} Model`}
kindFilter={combo.kind}
+ addedModelValues={providers}
+ closeOnSelect={false}
/>
);
diff --git a/src/shared/components/ModelSelectModal.js b/src/shared/components/ModelSelectModal.js
index 051b30d..3d364b8 100644
--- a/src/shared/components/ModelSelectModal.js
+++ b/src/shared/components/ModelSelectModal.js
@@ -21,11 +21,14 @@ export default function ModelSelectModal({
isOpen,
onClose,
onSelect,
+ onDeselect,
selectedModel,
activeProviders = [],
title = "Select Model",
modelAliases = {},
kindFilter = null,
+ addedModelValues = [],
+ closeOnSelect = true,
}) {
// Filter activeProviders by serviceKinds when kindFilter set (e.g. "webSearch", "webFetch")
const filteredActiveProviders = useMemo(() => {
@@ -342,9 +345,19 @@ export default function ModelSelectModal({
}, [groupedModels, searchQuery]);
const handleSelect = (model) => {
- onSelect(model);
- onClose();
- setSearchQuery("");
+ const value = model?.value || model?.name || model;
+ const isAdded = addedModelValues.includes(value);
+
+ if (isAdded && onDeselect) {
+ onDeselect(model);
+ } else {
+ onSelect(model);
+ }
+
+ if (closeOnSelect) {
+ onClose();
+ setSearchQuery("");
+ }
};
return (
@@ -392,13 +405,18 @@ export default function ModelSelectModal({
key={combo.id}
onClick={() => handleSelect({ id: combo.name, name: combo.name, value: combo.name })}
className={`
- px-2 py-1 rounded-xl text-xs font-medium transition-all border hover:cursor-pointer
+ px-2 py-1 rounded-xl text-xs font-medium transition-all border hover:cursor-pointer flex items-center gap-1
${isSelected
? "bg-primary text-white border-primary"
- : "bg-surface border-border text-text-main hover:border-primary/50 hover:bg-primary/5"
+ : addedModelValues.includes(combo.name)
+ ? "bg-green-500/10 border-green-500/30 text-green-700 dark:text-green-400 hover:border-green-500/50"
+ : "bg-surface border-border text-text-main hover:border-primary/50 hover:bg-primary/5"
}
`}
>
+ {addedModelValues.includes(combo.name) && (
+ check_circle
+ )}
{combo.name}
);
@@ -439,21 +457,30 @@ export default function ModelSelectModal({
? "border-dashed border-border text-text-muted hover:border-primary/50 hover:text-primary bg-surface italic"
: isSelected
? "bg-primary text-white border-primary"
- : "bg-surface border-border text-text-main hover:border-primary/50 hover:bg-primary/5"
+ : addedModelValues.includes(model.value)
+ ? "bg-green-500/10 border-green-500/30 text-green-700 dark:text-green-400 hover:border-green-500/50"
+ : "bg-surface border-border text-text-main hover:border-primary/50 hover:bg-primary/5"
}
`}
>
- {isPlaceholder ? (
-
- edit
- {model.name}
-
- ) : model.isCustom ? (
-
- {model.name}
- custom
-
- ) : model.name}
+
+ {addedModelValues.includes(model.value) && !isPlaceholder && (
+ check_circle
+ )}
+ {isPlaceholder ? (
+ <>
+ edit
+ {model.name}
+ >
+ ) : model.isCustom ? (
+ <>
+ {model.name}
+ custom
+ >
+ ) : (
+ model.name
+ )}
+
);
})}
@@ -478,6 +505,7 @@ ModelSelectModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
+ onDeselect: PropTypes.func,
selectedModel: PropTypes.string,
activeProviders: PropTypes.arrayOf(
PropTypes.shape({
@@ -487,5 +515,7 @@ ModelSelectModal.propTypes = {
title: PropTypes.string,
modelAliases: PropTypes.object,
kindFilter: PropTypes.string,
+ addedModelValues: PropTypes.arrayOf(PropTypes.string),
+ closeOnSelect: PropTypes.bool,
};