tcmmichaelb139 commited on
Commit
658125c
·
1 Parent(s): 80b5ee0

rough frontend + fix backend

Browse files
.gitignore CHANGED
@@ -6,6 +6,8 @@ dist/
6
  wheels/
7
  *.egg-info
8
 
 
 
9
  .env
10
  .venv
11
 
 
6
  wheels/
7
  *.egg-info
8
 
9
+ .pytest_cache/
10
+
11
  .env
12
  .venv
13
 
README.md CHANGED
@@ -6,3 +6,5 @@ colorTo: pink
6
  sdk: docker
7
  app_port: 7860
8
  ---
 
 
 
6
  sdk: docker
7
  app_port: 7860
8
  ---
9
+
10
+ Uses Huggingface Spaces for the backend, Upstart for the database, and Vercel for the frontend.
evolutiontransformer/worker.py CHANGED
@@ -12,6 +12,7 @@ from evolutiontransformer.redis import (
12
  get_session_models,
13
  save_model_recipe,
14
  get_model_recipe,
 
15
  )
16
 
17
 
@@ -256,16 +257,13 @@ def merge_models_task(
256
 
257
  @celery_app.task(name="tasks.get_all_models")
258
  def get_all_models_task(session_id: str) -> List[str]:
259
- global SESSION_MODELS
260
- return {
261
- "response": list((BASE_MODELS | SESSION_MODELS[session_id]).keys()),
262
- }
263
 
264
 
265
  @celery_app.task(name="tasks.clear_session_models")
266
  def clear_session_models_task(session_id: str) -> str:
267
- global SESSION_MODELS
268
- if session_id in SESSION_MODELS:
269
- del SESSION_MODELS[session_id]
270
- del SESSION_MODELS[session_id]
271
- return {"response": "SUCCESS"}
 
12
  get_session_models,
13
  save_model_recipe,
14
  get_model_recipe,
15
+ delete_session,
16
  )
17
 
18
 
 
257
 
258
  @celery_app.task(name="tasks.get_all_models")
259
  def get_all_models_task(session_id: str) -> List[str]:
260
+ base_models = BASE_MODELS_NAMES
261
+ session_models = get_session_models(session_id)
262
+ all_models = list(set(base_models + session_models))
263
+ return {"response": all_models}
264
 
265
 
266
  @celery_app.task(name="tasks.clear_session_models")
267
  def clear_session_models_task(session_id: str) -> str:
268
+ delete_session(session_id)
269
+ return {"response": ""}
 
 
 
frontend/src/App.css CHANGED
@@ -1,44 +1,39 @@
1
  @import "tailwindcss";
2
 
3
- #root {
4
- max-width: 1280px;
5
- margin: 0 auto;
6
- padding: 2rem;
7
- text-align: center;
 
 
8
  }
9
 
10
- .logo {
11
- height: 6em;
12
- padding: 1.5em;
13
- will-change: filter;
14
- transition: filter 300ms;
15
- }
16
- .logo:hover {
17
- filter: drop-shadow(0 0 2em #646cffaa);
18
- }
19
- .logo.react:hover {
20
- filter: drop-shadow(0 0 2em #61dafbaa);
21
- }
22
-
23
- @keyframes logo-spin {
24
- from {
25
- transform: rotate(0deg);
26
- }
27
- to {
28
- transform: rotate(360deg);
29
- }
30
  }
31
 
32
- @media (prefers-reduced-motion: no-preference) {
33
- a:nth-of-type(2) .logo {
34
- animation: logo-spin infinite 20s linear;
35
- }
 
 
 
 
36
  }
37
 
38
- .card {
39
- padding: 2em;
40
  }
41
 
42
- .read-the-docs {
43
- color: #888;
44
  }
 
1
  @import "tailwindcss";
2
 
3
+ input[type="range"] {
4
+ -webkit-appearance: none;
5
+ appearance: none;
6
+ height: 6px;
7
+ border-radius: 3px;
8
+ background: #e2e8f0;
9
+ outline: none;
10
  }
11
 
12
+ input[type="range"]::-webkit-slider-thumb {
13
+ -webkit-appearance: none;
14
+ appearance: none;
15
+ width: 18px;
16
+ height: 18px;
17
+ border-radius: 50%;
18
+ background: #0ea5e9;
19
+ cursor: pointer;
20
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
+ input[type="range"]::-moz-range-thumb {
24
+ width: 18px;
25
+ height: 18px;
26
+ border-radius: 50%;
27
+ background: #0ea5e9;
28
+ cursor: pointer;
29
+ border: none;
30
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
31
  }
32
 
33
+ input[type="range"].accent-primary-600::-webkit-slider-thumb {
34
+ background: #0284c7;
35
  }
36
 
37
+ input[type="range"].accent-primary-600::-moz-range-thumb {
38
+ background: #0284c7;
39
  }
frontend/src/App.jsx CHANGED
@@ -1,35 +1,132 @@
1
- import { useState } from 'react'
2
- import reactLogo from './assets/react.svg'
3
- import viteLogo from '/vite.svg'
4
- import './App.css'
 
 
 
5
 
6
  function App() {
7
- const [count, setCount] = useState(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  return (
10
- <>
11
- <div>
12
- <a href="https://vite.dev" target="_blank">
13
- <img src={viteLogo} className="logo" alt="Vite logo" />
14
- </a>
15
- <a href="https://react.dev" target="_blank">
16
- <img src={reactLogo} className="logo react" alt="React logo" />
17
- </a>
18
- </div>
19
- <h1>Vite + React</h1>
20
- <div className="card">
21
- <button onClick={() => setCount((count) => count + 1)}>
22
- count is {count}
23
- </button>
24
- <p>
25
- Edit <code>src/App.jsx</code> and save to test HMR
26
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  </div>
28
- <p className="read-the-docs">
29
- Click on the Vite and React logos to learn more
30
- </p>
31
- </>
32
- )
33
  }
34
 
35
- export default App
 
1
+ import { useState, useEffect } from "react";
2
+ import "./App.css";
3
+ import Header from "./components/Header";
4
+ import ModelSelection from "./components/ModelSelection";
5
+ import GlobalControls from "./components/GlobalControls";
6
+ import LayerRecipe from "./components/LayerRecipe";
7
+ import { useAPI } from "./hooks/useAPI";
8
 
9
  function App() {
10
+ const [models, setModels] = useState([]);
11
+ const [selectedModel1, setSelectedModel1] = useState("");
12
+ const [selectedModel2, setSelectedModel2] = useState("");
13
+ const [layerRecipe, setLayerRecipe] = useState([]);
14
+ const [embeddingLambdas, setEmbeddingLambdas] = useState([0.5, 0.5]);
15
+ const [linearLambdas, setLinearLambdas] = useState([0.5, 0.5]);
16
+ const [mergedName, setMergedName] = useState("merged");
17
+ const [isLoading, setIsLoading] = useState(false);
18
+ const [result, setResult] = useState(null);
19
+ const [numLayers, setNumLayers] = useState(12);
20
+
21
+ const { checkTaskStatus, fetchModels, mergeModels } = useAPI();
22
+
23
+ useEffect(() => {
24
+ const loadModels = async () => {
25
+ const taskId = await fetchModels();
26
+ if (taskId) {
27
+ setTimeout(
28
+ () =>
29
+ checkTaskStatus(taskId, (result) => {
30
+ if (result && Array.isArray(result)) {
31
+ setModels(result);
32
+ }
33
+ }),
34
+ 1000
35
+ );
36
+ }
37
+ };
38
+
39
+ loadModels();
40
+ }, [fetchModels, checkTaskStatus]);
41
+
42
+ const initializeLayerRecipe = () => {
43
+ const recipe = [];
44
+ for (let i = 0; i < numLayers; i++) {
45
+ recipe.push([[i, i, 0.5]]);
46
+ }
47
+ setLayerRecipe(recipe);
48
+ };
49
+
50
+ const updateLayerWeight = (layerIndex, weight) => {
51
+ const newRecipe = [...layerRecipe];
52
+ if (!newRecipe[layerIndex]) {
53
+ newRecipe[layerIndex] = [[layerIndex, layerIndex, weight]];
54
+ } else {
55
+ newRecipe[layerIndex][0][2] = weight;
56
+ }
57
+ setLayerRecipe(newRecipe);
58
+ };
59
+
60
+ const handleMerge = async () => {
61
+ if (!selectedModel1 || !selectedModel2 || layerRecipe.length === 0) {
62
+ alert("Please select both models and configure layer recipe");
63
+ return;
64
+ }
65
+
66
+ setIsLoading(true);
67
+ setResult(null);
68
+
69
+ const mergeData = {
70
+ model1_name: selectedModel1,
71
+ model2_name: selectedModel2,
72
+ layer_recipe: layerRecipe,
73
+ embedding_lambdas: embeddingLambdas,
74
+ linear_lambdas: linearLambdas,
75
+ merged_name: mergedName,
76
+ };
77
+
78
+ const taskId = await mergeModels(mergeData);
79
+ if (taskId) {
80
+ checkTaskStatus(taskId, (result) => {
81
+ setResult(result);
82
+ setIsLoading(false);
83
+ });
84
+ } else {
85
+ setIsLoading(false);
86
+ }
87
+ };
88
+
89
+ const isMergeDisabled =
90
+ isLoading || !selectedModel1 || !selectedModel2 || layerRecipe.length === 0;
91
 
92
  return (
93
+ <div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 p-6">
94
+ <div className="max-w-6xl mx-auto">
95
+ <Header />
96
+
97
+ <div className="grid lg:grid-cols-2 gap-8">
98
+ <ModelSelection
99
+ models={models}
100
+ selectedModel1={selectedModel1}
101
+ setSelectedModel1={setSelectedModel1}
102
+ selectedModel2={selectedModel2}
103
+ setSelectedModel2={setSelectedModel2}
104
+ numLayers={numLayers}
105
+ setNumLayers={setNumLayers}
106
+ mergedName={mergedName}
107
+ setMergedName={setMergedName}
108
+ onInitializeRecipe={initializeLayerRecipe}
109
+ />
110
+
111
+ <GlobalControls
112
+ embeddingLambdas={embeddingLambdas}
113
+ setEmbeddingLambdas={setEmbeddingLambdas}
114
+ linearLambdas={linearLambdas}
115
+ setLinearLambdas={setLinearLambdas}
116
+ onMerge={handleMerge}
117
+ isLoading={isLoading}
118
+ isDisabled={isMergeDisabled}
119
+ result={result}
120
+ />
121
+ </div>
122
+
123
+ <LayerRecipe
124
+ layerRecipe={layerRecipe}
125
+ onUpdateLayerWeight={updateLayerWeight}
126
+ />
127
  </div>
128
+ </div>
129
+ );
 
 
 
130
  }
131
 
132
+ export default App;
frontend/src/components/GlobalControls.jsx ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const GlobalControls = ({
2
+ embeddingLambdas,
3
+ setEmbeddingLambdas,
4
+ linearLambdas,
5
+ setLinearLambdas,
6
+ onMerge,
7
+ isLoading,
8
+ isDisabled,
9
+ result,
10
+ }) => {
11
+ return (
12
+ <div className="bg-white rounded-2xl shadow-lg p-8 border border-secondary-100">
13
+ <h2 className="text-2xl font-semibold text-secondary-800 mb-6">
14
+ Global Controls
15
+ </h2>
16
+
17
+ <div className="space-y-6">
18
+ <div>
19
+ <label className="block text-sm font-medium text-secondary-700 mb-2">
20
+ Embedding Weights
21
+ </label>
22
+ <div className="grid grid-cols-2 gap-4">
23
+ <div>
24
+ <span className="text-xs text-secondary-600">Model 1</span>
25
+ <input
26
+ type="range"
27
+ min="0"
28
+ max="1"
29
+ step="0.05"
30
+ value={embeddingLambdas[0]}
31
+ onChange={(e) =>
32
+ setEmbeddingLambdas([
33
+ parseFloat(e.target.value),
34
+ 1 - parseFloat(e.target.value),
35
+ ])
36
+ }
37
+ className="w-full"
38
+ />
39
+ <span className="text-sm text-secondary-600">
40
+ {embeddingLambdas[0].toFixed(2)}
41
+ </span>
42
+ </div>
43
+ <div>
44
+ <span className="text-xs text-secondary-600">Model 2</span>
45
+ <input
46
+ type="range"
47
+ min="0"
48
+ max="1"
49
+ step="0.05"
50
+ value={embeddingLambdas[1]}
51
+ onChange={(e) =>
52
+ setEmbeddingLambdas([
53
+ 1 - parseFloat(e.target.value),
54
+ parseFloat(e.target.value),
55
+ ])
56
+ }
57
+ className="w-full"
58
+ />
59
+ <span className="text-sm text-secondary-600">
60
+ {embeddingLambdas[1].toFixed(2)}
61
+ </span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <div>
67
+ <label className="block text-sm font-medium text-secondary-700 mb-2">
68
+ Linear Weights
69
+ </label>
70
+ <div className="grid grid-cols-2 gap-4">
71
+ <div>
72
+ <span className="text-xs text-secondary-600">Model 1</span>
73
+ <input
74
+ type="range"
75
+ min="0"
76
+ max="1"
77
+ step="0.05"
78
+ value={linearLambdas[0]}
79
+ onChange={(e) =>
80
+ setLinearLambdas([
81
+ parseFloat(e.target.value),
82
+ 1 - parseFloat(e.target.value),
83
+ ])
84
+ }
85
+ className="w-full"
86
+ />
87
+ <span className="text-sm text-secondary-600">
88
+ {linearLambdas[0].toFixed(2)}
89
+ </span>
90
+ </div>
91
+ <div>
92
+ <span className="text-xs text-secondary-600">Model 2</span>
93
+ <input
94
+ type="range"
95
+ min="0"
96
+ max="1"
97
+ step="0.05"
98
+ value={linearLambdas[1]}
99
+ onChange={(e) =>
100
+ setLinearLambdas([
101
+ 1 - parseFloat(e.target.value),
102
+ parseFloat(e.target.value),
103
+ ])
104
+ }
105
+ className="w-full"
106
+ />
107
+ <span className="text-sm text-secondary-600">
108
+ {linearLambdas[1].toFixed(2)}
109
+ </span>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <button
115
+ onClick={onMerge}
116
+ disabled={isDisabled}
117
+ className="w-full py-4 bg-gradient-to-r from-primary-600 to-accent-600 text-white font-semibold rounded-lg hover:from-primary-700 hover:to-accent-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
118
+ >
119
+ {isLoading ? "Merging Models..." : "Merge Models"}
120
+ </button>
121
+
122
+ {result && (
123
+ <div className="mt-4 p-4 bg-accent-50 border border-accent-200 rounded-lg">
124
+ <h3 className="font-medium text-accent-800 mb-2">
125
+ Merge Complete!
126
+ </h3>
127
+ <p className="text-sm text-accent-700">{JSON.stringify(result)}</p>
128
+ </div>
129
+ )}
130
+ </div>
131
+ </div>
132
+ );
133
+ };
134
+
135
+ export default GlobalControls;
frontend/src/components/Header.jsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const Header = () => {
2
+ return (
3
+ <header className="text-center mb-12">
4
+ <h1 className="text-5xl font-bold text-secondary-800 mb-4">
5
+ Evolution Transformer
6
+ </h1>
7
+ <p className="text-xl text-secondary-600 max-w-2xl mx-auto">
8
+ Combine and evolve transformer models by blending layers with precise
9
+ control
10
+ </p>
11
+ </header>
12
+ );
13
+ };
14
+
15
+ export default Header;
frontend/src/components/LayerRecipe.jsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const LayerRecipe = ({ layerRecipe, onUpdateLayerWeight }) => {
2
+ if (layerRecipe.length === 0) return null;
3
+
4
+ return (
5
+ <div className="mt-8 bg-white rounded-2xl shadow-lg p-8 border border-secondary-100">
6
+ <h2 className="text-2xl font-semibold text-secondary-800 mb-6">
7
+ Layer Recipe
8
+ </h2>
9
+
10
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
11
+ {layerRecipe.map((layer, index) => (
12
+ <div
13
+ key={index}
14
+ className="bg-secondary-50 rounded-lg p-4 border border-secondary-200"
15
+ >
16
+ <div className="text-sm font-medium text-secondary-700 mb-2">
17
+ Layer {index}
18
+ </div>
19
+ <div className="space-y-2">
20
+ <input
21
+ type="range"
22
+ min="0"
23
+ max="1"
24
+ step="0.05"
25
+ value={layer[0]?.[2] || 0.5}
26
+ onChange={(e) =>
27
+ onUpdateLayerWeight(index, parseFloat(e.target.value))
28
+ }
29
+ className="w-full accent-primary-600"
30
+ />
31
+ <div className="flex justify-between text-xs text-secondary-600">
32
+ <span>Model 1</span>
33
+ <span className="font-medium">
34
+ {(layer[0]?.[2] || 0.5).toFixed(2)}
35
+ </span>
36
+ <span>Model 2</span>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ ))}
41
+ </div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default LayerRecipe;
frontend/src/components/ModelSelection.jsx ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const ModelSelection = ({
2
+ models,
3
+ selectedModel1,
4
+ setSelectedModel1,
5
+ selectedModel2,
6
+ setSelectedModel2,
7
+ numLayers,
8
+ setNumLayers,
9
+ mergedName,
10
+ setMergedName,
11
+ onInitializeRecipe,
12
+ }) => {
13
+ return (
14
+ <div className="bg-white rounded-2xl shadow-lg p-8 border border-secondary-100">
15
+ <h2 className="text-2xl font-semibold text-secondary-800 mb-6">
16
+ Model Selection
17
+ </h2>
18
+
19
+ <div className="space-y-6">
20
+ <div>
21
+ <label className="block text-sm font-medium text-secondary-700 mb-2">
22
+ Base Model (Model 1)
23
+ </label>
24
+ <select
25
+ value={selectedModel1}
26
+ onChange={(e) => setSelectedModel1(e.target.value)}
27
+ className="w-full p-3 border border-secondary-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
28
+ >
29
+ <option value="">Select a model</option>
30
+ {models.map((model) => (
31
+ <option key={model} value={model}>
32
+ {model}
33
+ </option>
34
+ ))}
35
+ </select>
36
+ </div>
37
+
38
+ <div>
39
+ <label className="block text-sm font-medium text-secondary-700 mb-2">
40
+ Target Model (Model 2)
41
+ </label>
42
+ <select
43
+ value={selectedModel2}
44
+ onChange={(e) => setSelectedModel2(e.target.value)}
45
+ className="w-full p-3 border border-secondary-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
46
+ >
47
+ <option value="">Select a model</option>
48
+ {models.map((model) => (
49
+ <option key={model} value={model}>
50
+ {model}
51
+ </option>
52
+ ))}
53
+ </select>
54
+ </div>
55
+
56
+ <div>
57
+ <label className="block text-sm font-medium text-secondary-700 mb-2">
58
+ Number of Layers
59
+ </label>
60
+ <input
61
+ type="number"
62
+ value={numLayers}
63
+ onChange={(e) => setNumLayers(parseInt(e.target.value))}
64
+ className="w-full p-3 border border-secondary-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
65
+ min="1"
66
+ max="48"
67
+ />
68
+ <button
69
+ onClick={onInitializeRecipe}
70
+ className="mt-2 px-4 py-2 bg-secondary-600 text-white rounded-lg hover:bg-secondary-700 transition-colors"
71
+ >
72
+ Initialize Recipe
73
+ </button>
74
+ </div>
75
+
76
+ <div>
77
+ <label className="block text-sm font-medium text-secondary-700 mb-2">
78
+ Merged Model Name
79
+ </label>
80
+ <input
81
+ type="text"
82
+ value={mergedName}
83
+ onChange={(e) => setMergedName(e.target.value)}
84
+ className="w-full p-3 border border-secondary-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
85
+ placeholder="Enter merged model name"
86
+ />
87
+ </div>
88
+ </div>
89
+ </div>
90
+ );
91
+ };
92
+
93
+ export default ModelSelection;
frontend/src/hooks/useAPI.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from "react";
2
+
3
+ const API_BASE = "https://tcmmichaelb139-evolutiontransformer.hf.space";
4
+
5
+ export const useAPI = () => {
6
+ const checkTaskStatus = useCallback(async (taskId, callback) => {
7
+ try {
8
+ const response = await fetch(`${API_BASE}/tasks/${taskId}`);
9
+ const data = await response.json();
10
+
11
+ if (data.status === "SUCCESS") {
12
+ callback(data.result);
13
+ } else if (data.status === "PENDING") {
14
+ setTimeout(() => checkTaskStatus(taskId, callback), 1000);
15
+ }
16
+ } catch (error) {
17
+ console.error("Task check failed:", error);
18
+ }
19
+ }, []);
20
+
21
+ const fetchModels = useCallback(async () => {
22
+ try {
23
+ const response = await fetch(`${API_BASE}/list_models`, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ });
27
+ const data = await response.json();
28
+ return data.task_id;
29
+ } catch (error) {
30
+ console.error("Failed to fetch models:", error);
31
+ return null;
32
+ }
33
+ }, []);
34
+
35
+ const mergeModels = useCallback(async (mergeData) => {
36
+ try {
37
+ const response = await fetch(`${API_BASE}/merge`, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json" },
40
+ body: JSON.stringify(mergeData),
41
+ });
42
+ const data = await response.json();
43
+ return data.task_id;
44
+ } catch (error) {
45
+ console.error("Merge failed:", error);
46
+ return null;
47
+ }
48
+ }, []);
49
+
50
+ return {
51
+ checkTaskStatus,
52
+ fetchModels,
53
+ mergeModels,
54
+ };
55
+ };
frontend/src/index.css CHANGED
@@ -1,68 +1,19 @@
 
 
1
  :root {
2
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
 
3
  line-height: 1.5;
4
  font-weight: 400;
5
-
6
- color-scheme: light dark;
7
- color: rgba(255, 255, 255, 0.87);
8
- background-color: #242424;
9
-
10
  font-synthesis: none;
11
  text-rendering: optimizeLegibility;
12
  -webkit-font-smoothing: antialiased;
13
  -moz-osx-font-smoothing: grayscale;
14
  }
15
 
16
- a {
17
- font-weight: 500;
18
- color: #646cff;
19
- text-decoration: inherit;
20
- }
21
- a:hover {
22
- color: #535bf2;
23
- }
24
-
25
  body {
26
  margin: 0;
27
- display: flex;
28
- place-items: center;
29
- min-width: 320px;
30
  min-height: 100vh;
31
  }
32
-
33
- h1 {
34
- font-size: 3.2em;
35
- line-height: 1.1;
36
- }
37
-
38
- button {
39
- border-radius: 8px;
40
- border: 1px solid transparent;
41
- padding: 0.6em 1.2em;
42
- font-size: 1em;
43
- font-weight: 500;
44
- font-family: inherit;
45
- background-color: #1a1a1a;
46
- cursor: pointer;
47
- transition: border-color 0.25s;
48
- }
49
- button:hover {
50
- border-color: #646cff;
51
- }
52
- button:focus,
53
- button:focus-visible {
54
- outline: 4px auto -webkit-focus-ring-color;
55
- }
56
-
57
- @media (prefers-color-scheme: light) {
58
- :root {
59
- color: #213547;
60
- background-color: #ffffff;
61
- }
62
- a:hover {
63
- color: #747bff;
64
- }
65
- button {
66
- background-color: #f9f9f9;
67
- }
68
- }
 
1
+ @import "tailwindcss";
2
+
3
  :root {
4
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu,
5
+ Cantarell, sans-serif;
6
  line-height: 1.5;
7
  font-weight: 400;
8
+ color-scheme: light;
 
 
 
 
9
  font-synthesis: none;
10
  text-rendering: optimizeLegibility;
11
  -webkit-font-smoothing: antialiased;
12
  -moz-osx-font-smoothing: grayscale;
13
  }
14
 
 
 
 
 
 
 
 
 
 
15
  body {
16
  margin: 0;
17
+ padding: 0;
 
 
18
  min-height: 100vh;
19
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/tailwind.config.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4
+ theme: {
5
+ extend: {
6
+ colors: {
7
+ primary: {
8
+ 50: "#f0f9ff",
9
+ 100: "#e0f2fe",
10
+ 200: "#bae6fd",
11
+ 300: "#7dd3fc",
12
+ 400: "#38bdf8",
13
+ 500: "#0ea5e9",
14
+ 600: "#0284c7",
15
+ 700: "#0369a1",
16
+ 800: "#075985",
17
+ 900: "#0c4a6e",
18
+ },
19
+ secondary: {
20
+ 50: "#f8fafc",
21
+ 100: "#f1f5f9",
22
+ 200: "#e2e8f0",
23
+ 300: "#cbd5e1",
24
+ 400: "#94a3b8",
25
+ 500: "#64748b",
26
+ 600: "#475569",
27
+ 700: "#334155",
28
+ 800: "#1e293b",
29
+ 900: "#0f172a",
30
+ },
31
+ accent: {
32
+ 50: "#f0fdf4",
33
+ 100: "#dcfce7",
34
+ 200: "#bbf7d0",
35
+ 300: "#86efac",
36
+ 400: "#4ade80",
37
+ 500: "#22c55e",
38
+ 600: "#16a34a",
39
+ 700: "#15803d",
40
+ 800: "#166534",
41
+ 900: "#14532d",
42
+ },
43
+ },
44
+ },
45
+ },
46
+ plugins: [],
47
+ };
tests/test_hf_api.py CHANGED
@@ -279,6 +279,20 @@ def test_merge_two_children_then_merge(session):
279
  output_text = final_result["response"]
280
  answer = get_final_answer(output_text)
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  assert answer == 14
283
 
284
 
 
279
  output_text = final_result["response"]
280
  answer = get_final_answer(output_text)
281
 
282
+ number_of_models = session.post(f"{BASE_URL}/list_models")
283
+
284
+ assert number_of_models.status_code == 200
285
+ number_of_models_data = number_of_models.json()
286
+ assert "task_id" in number_of_models_data
287
+ number_of_models_task_id = number_of_models_data["task_id"]
288
+
289
+ number_of_models_result = await_task_completion(session, number_of_models_task_id)
290
+
291
+ assert "response" in number_of_models_result
292
+ models = number_of_models_result["response"]
293
+ print(models)
294
+ assert len(models) >= 5
295
+
296
  assert answer == 14
297
 
298