feat(ui): enhance model select modal UX and modal traffic lights (#1111)
* feat(model-select-modal): highlight added models and support bulk selection - Add addedModelValues prop to highlight already-added models with primary color - Sort models alphabetically per provider, with added models floated to top - Replace green highlight with primary brand color (orange #E56A4A) - Use check icon (10px) inline with model name instead of check_circle - Replace Done button with info bar explaining click-to-toggle behavior - Add ProviderIcon to provider group headers replacing colored dot - Import ProviderIcon, remove unused Button import * feat(cli-tools): wire addedModelValues, onDeselect, and auto-save to model select modals - Pass selectedModels as addedModelValues to ModelSelectModal in OpenCode and Copilot cards - Add onDeselect handler to remove model from list on second click - Set closeOnSelect=false to allow bulk model selection - Remove manual setModalOpen(false) from onSelect callbacks - Add saveModels() silent auto-save triggered on modal close (OpenCodeToolCard) - Use useRef to track latest selectedModels in closure-safe way * feat(modal): functional traffic light close button with hover icon and tooltip - Make red dot a clickable button that closes the modal - Show ✕ icon inside red dot on hover via group-hover opacity transition - Gray out yellow and green dots (cursor-not-allowed, no tooltip) - Increase dot size from w-3 h-3 to w-4 h-4 - Add Tooltip with brand-matched color #FF5F56 on red dot - Remove X close button from modal header * feat(tooltip): add color prop for themed tooltip backgrounds * feat(i18n): add translations for model select info bar and close tooltip - Add 'Click to add, click again to remove. Changes are saved automatically.' to all 32 locales - Add 'Close' translation to all 32 locales * fix(ui): address code review feedback on modal UX and auto-save - Modal: remove showCloseButton prop, use showTrafficLights for header condition, hide traffic lights on mobile (hidden md:flex), add mobile X button (md:hidden) with aria-label, add aria-label and title on traffic light close button - OpenCodeToolCard: validate activeModel membership before saving — fallback to models[0] or empty string; clear/reassign activeModel on deselect when removed model was the active one - CopilotToolCard: add useRef + selectedModelsRef, add saveModels() using /api/cli-tools/copilot-settings, wire auto-save on modal close - ModelSelectModal: fix JSX formatting — separate info bar closing div from Search comment onto its own line
This commit is contained in:
committed by
GitHub
parent
4098f91ac5
commit
1fd3132647
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components";
|
||||
import Image from "next/image";
|
||||
import BaseUrlSelect from "./BaseUrlSelect";
|
||||
@@ -19,6 +19,11 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
|
||||
const [showManualConfigModal, setShowManualConfigModal] = useState(false);
|
||||
const [selectedModels, setSelectedModels] = useState([]);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const selectedModelsRef = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedModelsRef.current = selectedModels;
|
||||
}, [selectedModels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (apiKeys?.length > 0 && !selectedApiKey) {
|
||||
@@ -58,6 +63,21 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
|
||||
}
|
||||
};
|
||||
|
||||
const saveModels = async (models) => {
|
||||
try {
|
||||
const keyToUse = (selectedApiKey && selectedApiKey.trim())
|
||||
? selectedApiKey
|
||||
: (!cloudEnabled ? "sk_9router" : selectedApiKey);
|
||||
await fetch("/api/cli-tools/copilot-settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, models }),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error saving models:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getConfigStatus = () => {
|
||||
if (!status) return null;
|
||||
if (!status.has9Router) return "not_configured";
|
||||
@@ -272,16 +292,23 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
|
||||
|
||||
<ModelSelectModal
|
||||
isOpen={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
saveModels(selectedModelsRef.current);
|
||||
}}
|
||||
onSelect={(model) => {
|
||||
if (!selectedModels.includes(model.value)) {
|
||||
setSelectedModels([...selectedModels, model.value]);
|
||||
}
|
||||
setModalOpen(false);
|
||||
}}
|
||||
onDeselect={(model) => {
|
||||
setSelectedModels(selectedModels.filter(m => m !== model.value));
|
||||
}}
|
||||
selectedModel={null}
|
||||
activeProviders={activeProviders}
|
||||
modelAliases={modelAliases}
|
||||
addedModelValues={selectedModels}
|
||||
closeOnSelect={false}
|
||||
title="Add Model for GitHub Copilot"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components";
|
||||
import Image from "next/image";
|
||||
import BaseUrlSelect from "./BaseUrlSelect";
|
||||
@@ -24,6 +24,11 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
|
||||
const [customBaseUrl, setCustomBaseUrl] = useState("");
|
||||
const [selectedModels, setSelectedModels] = useState([]);
|
||||
const [activeModel, setActiveModel] = useState("");
|
||||
const selectedModelsRef = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedModelsRef.current = selectedModels;
|
||||
}, [selectedModels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (apiKeys?.length > 0 && !selectedApiKey) {
|
||||
@@ -68,6 +73,28 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
|
||||
}
|
||||
};
|
||||
|
||||
const saveModels = async (models) => {
|
||||
try {
|
||||
const keyToUse = (selectedApiKey && selectedApiKey.trim())
|
||||
? selectedApiKey
|
||||
: (!cloudEnabled ? "sk_9router" : selectedApiKey);
|
||||
const validActiveModel = models.includes(activeModel) ? activeModel : (models[0] || "");
|
||||
await fetch("/api/cli-tools/opencode-settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
baseUrl: getEffectiveBaseUrl(),
|
||||
apiKey: keyToUse,
|
||||
models,
|
||||
activeModel: validActiveModel,
|
||||
subagentModel,
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error saving models:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getConfigStatus = () => {
|
||||
if (!status?.installed) return null;
|
||||
if (!status.config) return "not_configured";
|
||||
@@ -427,17 +454,28 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
|
||||
|
||||
<ModelSelectModal
|
||||
isOpen={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
saveModels(selectedModelsRef.current);
|
||||
}}
|
||||
onSelect={(model) => {
|
||||
if (!selectedModels.includes(model.value)) {
|
||||
setSelectedModels([...selectedModels, model.value]);
|
||||
if (!activeModel) setActiveModel(model.value);
|
||||
}
|
||||
setModalOpen(false);
|
||||
}}
|
||||
onDeselect={(model) => {
|
||||
const remaining = selectedModels.filter(m => m !== model.value);
|
||||
setSelectedModels(remaining);
|
||||
if (activeModel === model.value) {
|
||||
setActiveModel(remaining[0] || "");
|
||||
}
|
||||
}}
|
||||
selectedModel={null}
|
||||
activeProviders={activeProviders}
|
||||
modelAliases={modelAliases}
|
||||
addedModelValues={selectedModels}
|
||||
closeOnSelect={false}
|
||||
title="Add Model for OpenCode"
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user