tcmmichaelb139's picture
added loading when space is starting
26574d0
import { useState } from "react";
import Dropdown from "./Dropdown";
import NumberInput from "./NumberInput";
import InferencePopup from "./InferencePopup";
import { setModelLayers } from "../utils/modelCookies";
import { useAPI } from "../hooks/useAPI";
import { devLog, devError } from "../utils/devLogger";
const Options = ({
models,
selectedModel1,
selectedModel2,
setSelectedModel1,
setSelectedModel2,
numLayers,
setNumLayers,
layerRecipe,
embeddingLambdas,
linearLambdas,
setModels,
mergedName,
setMergedName,
isSpaceLoading,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [mergeStatus, setMergeStatus] = useState("");
const [isInferenceOpen, setIsInferenceOpen] = useState(false);
const { mergeModels, checkTaskStatus } = useAPI();
const handleMerge = async () => {
if (
!selectedModel1 ||
!selectedModel2 ||
layerRecipe.length === 0 ||
!mergedName.trim()
) {
setMergeStatus(
"Error: Please select models, configure recipe, and provide a merged model name"
);
return;
}
setIsLoading(true);
setMergeStatus("Merging models...");
try {
// Convert layer indices from 1-based (frontend) to 0-based (backend)
const backendLayerRecipe = layerRecipe.map((layer) =>
layer.map((block) => [
block[0] - 1, // Convert sourceLayer from 1-based to 0-based
block[1], // modelIndex stays the same
block[2], // percentage stays the same
])
);
const mergeData = {
model1_name: selectedModel1,
model2_name: selectedModel2,
layer_recipe: backendLayerRecipe,
embedding_lambdas: embeddingLambdas,
linear_lambdas: linearLambdas,
merged_name: mergedName,
};
devLog("Starting merge with data:", mergeData);
const taskId = await mergeModels(mergeData);
devLog("Got merge task ID:", taskId);
if (taskId) {
checkTaskStatus(
taskId,
(taskResult) => {
devLog("Merge result:", taskResult);
if (taskResult.response) {
setMergeStatus("Merge successful!");
const newModelName = taskResult.response || mergedName;
setModels((prev) => [...prev, newModelName]);
setModelLayers(newModelName, numLayers);
} else {
setMergeStatus(
`Merge failed: ${taskResult.error || "Unknown error"}`
);
}
setIsLoading(false);
},
(error) => {
devError("Merge task failed:", error);
setMergeStatus(`Merge failed: ${error}`);
setIsLoading(false);
}
);
}
} catch (error) {
devError("Merge error:", error);
setMergeStatus(`Error: ${error.message}`);
setIsLoading(false);
}
};
return (
<div>
<div className="p-6 border-2 border-primary-200 rounded-2xl bg-background shadow-xl fixed inset-y-4 left-4 w-[25rem] overflow-y-auto">
<div className="flex items-center space-x-2 mb-6">
<div className="w-8 h-8 bg-gradient-to-br from-primary-500 to-accent-500 rounded-lg flex items-center justify-center text-background">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M14 17H5" />
<path d="M19 7h-9" />
<circle cx="17" cy="17" r="3" />
<circle cx="7" cy="7" r="3" />
</svg>
</div>
<h2 className="text-xl font-bold text-foreground">
Evolution Transformer Options
</h2>
</div>
<div className="space-y-6">
<Dropdown
label="Model 1"
selectedValue={selectedModel1}
onSelect={setSelectedModel1}
options={models}
placeholder="Select base model..."
loading={isSpaceLoading || models.length === 0}
loadingMessage={
isSpaceLoading ? "Space is starting up..." : "Loading models..."
}
emptyMessage="No models available"
showSearch={true}
searchPlaceholder="Search models..."
/>
<Dropdown
label="Model 2"
selectedValue={selectedModel2}
onSelect={setSelectedModel2}
options={models}
placeholder="Select target model..."
loading={isSpaceLoading || models.length === 0}
loadingMessage={
isSpaceLoading ? "Space is starting up..." : "Loading models..."
}
emptyMessage="No models available"
showSearch={true}
searchPlaceholder="Search models..."
/>
<NumberInput
label="Number of Layers"
value={numLayers}
onChange={setNumLayers}
min={1}
max={48}
className="w-full"
/>
<div>
<label className="block text-sm font-medium text-foreground mb-1">
Merged Model Name
</label>
<input
type="text"
value={mergedName}
onChange={(e) => setMergedName(e.target.value)}
placeholder="Enter merged model name..."
className="w-full px-3 py-2 border-2 border-secondary-200 rounded-lg focus:border-primary-500 focus:ring-2 focus:ring-primary-200 text-foreground placeholder-secondary-400 transition-all duration-200"
/>
</div>
<div className="p-4 rounded-xl border-2 border-secondary-200 bg-secondary-50">
<div className="flex items-center space-x-2">
<div
className={`w-2 h-2 rounded-full ${
selectedModel1 && selectedModel2
? "bg-green-500"
: "bg-red-500"
}`}
></div>
<span>
Selected:{" "}
{selectedModel1 && selectedModel2
? "Ready to merge"
: "Incomplete"}
</span>
</div>
</div>
<div className="flex space-x-3">
<div className="flex-1 p-4 rounded-xl border-2 border-accent-200 bg-accent-50">
<div className="flex items-center space-x-2 mb-3">
<div className="w-6 h-6 bg-gradient-to-br from-accent-500 to-secondary-500 rounded-lg flex items-center justify-center text-background">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m8 6 4-4 4 4" />
<path d="M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22" />
<path d="m20 22-5-5" />
</svg>
</div>
<h3 className="text-sm font-semibold text-foreground">
Merge Models
</h3>
</div>
<button
onClick={handleMerge}
disabled={
isSpaceLoading ||
isLoading ||
!selectedModel1 ||
!selectedModel2 ||
layerRecipe.length === 0 ||
!mergedName.trim()
}
className="w-full py-2.5 px-3 bg-gradient-to-r from-accent-500 to-primary-500 text-background font-medium rounded-lg hover:from-accent-600 hover:to-primary-600 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 text-sm"
>
{isSpaceLoading ? (
<>
<div className="animate-spin w-3.5 h-3.5 border-2 border-background border-t-transparent rounded-full"></div>
<span>Starting up...</span>
</>
) : isLoading ? (
<>
<div className="animate-spin w-3.5 h-3.5 border-2 border-background border-t-transparent rounded-full"></div>
<span>Merging...</span>
</>
) : (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m8 6 4-4 4 4" />
<path d="M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22" />
<path d="m20 22-5-5" />
</svg>
<span>Merge</span>
</>
)}
</button>
</div>
<div className="flex-1 p-4 rounded-xl border-2 border-primary-200 bg-primary-50">
<div className="flex items-center space-x-2 mb-3">
<div className="w-6 h-6 bg-gradient-to-br from-secondary-500 to-primary-500 rounded-lg flex items-center justify-center text-background">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
</div>
<h3 className="text-sm font-semibold text-foreground">
Test Inference
</h3>
</div>
<button
onClick={() => setIsInferenceOpen(true)}
className="w-full py-2.5 px-3 bg-gradient-to-r from-secondary-500 to-primary-500 text-background font-medium rounded-lg hover:from-secondary-600 hover:to-primary-600 transition-all duration-200 flex items-center justify-center space-x-2 text-sm"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
<span>Inference</span>
</button>
</div>
</div>
{mergeStatus && (
<div
className={`p-2 rounded-lg text-sm font-medium ${
mergeStatus.includes("successful")
? "bg-green-100 text-green-800"
: mergeStatus.includes("Error") ||
mergeStatus.includes("failed")
? "bg-red-100 text-red-800"
: "bg-blue-100 text-blue-800"
}`}
>
{mergeStatus}
</div>
)}
</div>
</div>
<InferencePopup
isOpen={isInferenceOpen}
onClose={() => setIsInferenceOpen(false)}
models={models}
/>
</div>
);
};
export default Options;