Spaces:
Sleeping
Sleeping
add housing and UI
Browse files- app.py +42 -2
- index.html +74 -27
- requirements.txt +3 -1
- static/script.js +76 -53
- static/style.css +147 -110
app.py
CHANGED
@@ -4,6 +4,7 @@ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
|
|
4 |
|
5 |
import logging
|
6 |
import io
|
|
|
7 |
from pathlib import Path
|
8 |
from fastapi import FastAPI, File, UploadFile, HTTPException
|
9 |
from fastapi.staticfiles import StaticFiles
|
@@ -49,16 +50,38 @@ def load_models():
|
|
49 |
cat_dog_model = None
|
50 |
logging.error(f"Error loading cat & dog model: {e}")
|
51 |
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
# --- FastAPI App Initialization ---
|
55 |
app = FastAPI()
|
56 |
-
tokenizer, sentiment_model, cat_dog_model = load_models()
|
57 |
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
58 |
|
59 |
class SentimentRequest(BaseModel):
|
60 |
text: str
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
# --- API Endpoints ---
|
63 |
@app.get("/")
|
64 |
async def read_root():
|
@@ -101,3 +124,20 @@ async def predict_catdog(file: UploadFile = File(...)):
|
|
101 |
except Exception as e:
|
102 |
logging.error(f"Cat/Dog prediction error: {e}")
|
103 |
raise HTTPException(status_code=500, detail="An error occurred during image classification.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
import logging
|
6 |
import io
|
7 |
+
import pickle
|
8 |
from pathlib import Path
|
9 |
from fastapi import FastAPI, File, UploadFile, HTTPException
|
10 |
from fastapi.staticfiles import StaticFiles
|
|
|
50 |
cat_dog_model = None
|
51 |
logging.error(f"Error loading cat & dog model: {e}")
|
52 |
|
53 |
+
try:
|
54 |
+
xgb_model_path = hf_hub_download(repo_id="muhalwan/california_housing_price_predictor", filename="xgb_model.pkl")
|
55 |
+
with open(xgb_model_path, "rb") as f:
|
56 |
+
housing_model = pickle.load(f)
|
57 |
+
scaler_path = hf_hub_download(repo_id="muhalwan/california_housing_price_predictor", filename="scaler.pkl")
|
58 |
+
with open(scaler_path, "rb") as f:
|
59 |
+
housing_scaler = pickle.load(f)
|
60 |
+
logging.info("Housing price model and scaler loaded successfully.")
|
61 |
+
except Exception as e:
|
62 |
+
housing_model, housing_scaler = None, None
|
63 |
+
logging.error(f"Error loading housing price model: {e}")
|
64 |
+
|
65 |
+
return tokenizer, sentiment_model, cat_dog_model, housing_model, housing_scaler
|
66 |
|
67 |
# --- FastAPI App Initialization ---
|
68 |
app = FastAPI()
|
69 |
+
tokenizer, sentiment_model, cat_dog_model, housing_model, housing_scaler = load_models()
|
70 |
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
71 |
|
72 |
class SentimentRequest(BaseModel):
|
73 |
text: str
|
74 |
|
75 |
+
class HousingRequest(BaseModel):
|
76 |
+
MedInc: float
|
77 |
+
HouseAge: float
|
78 |
+
AveRooms: float
|
79 |
+
AveBedrms: float
|
80 |
+
Population: float
|
81 |
+
AveOccup: float
|
82 |
+
Latitude: float
|
83 |
+
Longitude: float
|
84 |
+
|
85 |
# --- API Endpoints ---
|
86 |
@app.get("/")
|
87 |
async def read_root():
|
|
|
124 |
except Exception as e:
|
125 |
logging.error(f"Cat/Dog prediction error: {e}")
|
126 |
raise HTTPException(status_code=500, detail="An error occurred during image classification.")
|
127 |
+
|
128 |
+
@app.post("/predict/housing")
|
129 |
+
async def predict_housing(request: HousingRequest):
|
130 |
+
if not housing_model or not housing_scaler:
|
131 |
+
raise HTTPException(status_code=503, detail="Housing model is not available.")
|
132 |
+
try:
|
133 |
+
input_data = np.array([[
|
134 |
+
request.MedInc, request.HouseAge, request.AveRooms, request.AveBedrms,
|
135 |
+
request.Population, request.AveOccup, request.Latitude, request.Longitude
|
136 |
+
]])
|
137 |
+
data_scaled = housing_scaler.transform(input_data)
|
138 |
+
raw_prediction = housing_model.predict(data_scaled)[0]
|
139 |
+
final_prediction = raw_prediction * 100000
|
140 |
+
return {"prediction": f"${final_prediction:,.2f}"}
|
141 |
+
except Exception as e:
|
142 |
+
logging.error(f"Housing prediction error: {e}")
|
143 |
+
raise HTTPException(status_code=500, detail="An error occurred during housing price prediction.")
|
index.html
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
-
<title>ML
|
7 |
<link rel="stylesheet" href="/static/style.css">
|
8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
@@ -12,44 +12,91 @@
|
|
12 |
<body>
|
13 |
<div class="container">
|
14 |
<header>
|
15 |
-
<h1>ML
|
16 |
-
<p>
|
17 |
</header>
|
18 |
|
19 |
-
<
|
20 |
-
<button id="cat-dog-btn" class="model-btn">Cat & Dog Classifier</button>
|
21 |
<button id="sentiment-btn" class="model-btn active">Sentiment Analysis</button>
|
22 |
-
<button class="model-btn
|
23 |
-
|
|
|
24 |
|
25 |
-
<main
|
26 |
-
<!-- Sentiment Analysis
|
27 |
-
<div id="sentiment-
|
|
|
|
|
28 |
<form id="sentiment-form">
|
29 |
-
<
|
30 |
-
<
|
31 |
-
<label for="tweet-text">Tweet Text</label>
|
32 |
-
<textarea id="tweet-text" placeholder="Enter tweet here..."></textarea>
|
33 |
-
<button type="submit" class="predict-btn">Predict</button>
|
34 |
</form>
|
35 |
-
<div id="sentiment-result" class="result
|
36 |
</div>
|
37 |
|
38 |
-
<!-- Cat & Dog Classifier
|
39 |
-
<div id="
|
40 |
-
<
|
41 |
-
|
42 |
-
|
43 |
-
<
|
44 |
-
<
|
45 |
-
|
46 |
-
|
|
|
47 |
</form>
|
48 |
-
<div id="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
</div>
|
50 |
</main>
|
51 |
-
</div>
|
52 |
|
|
|
|
|
|
|
|
|
53 |
<script src="/static/script.js"></script>
|
54 |
</body>
|
55 |
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>ML Nexus</title>
|
7 |
<link rel="stylesheet" href="/static/style.css">
|
8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
12 |
<body>
|
13 |
<div class="container">
|
14 |
<header>
|
15 |
+
<h1>ML Nexus</h1>
|
16 |
+
<p>Your one-stop hub for ML model inference.</p>
|
17 |
</header>
|
18 |
|
19 |
+
<nav class="model-selector">
|
|
|
20 |
<button id="sentiment-btn" class="model-btn active">Sentiment Analysis</button>
|
21 |
+
<button id="catdog-btn" class="model-btn">Cat & Dog Classifier</button>
|
22 |
+
<button id="housing-btn" class="model-btn">House Price Predictor</button>
|
23 |
+
</nav>
|
24 |
|
25 |
+
<main>
|
26 |
+
<!-- Sentiment Analysis Section -->
|
27 |
+
<div id="sentiment-section" class="model-section active">
|
28 |
+
<h2>Sentiment Analysis <span class="beta-tag">Beta</span></h2>
|
29 |
+
<p>Enter text to classify its sentiment (Bearish or Bullish). This model is in beta, and its accuracy may vary.</p>
|
30 |
<form id="sentiment-form">
|
31 |
+
<textarea id="sentiment-text" name="text" rows="4" placeholder="Enter text here..."></textarea>
|
32 |
+
<button type="submit">Predict Sentiment</button>
|
|
|
|
|
|
|
33 |
</form>
|
34 |
+
<div id="sentiment-result" class="result"></div>
|
35 |
</div>
|
36 |
|
37 |
+
<!-- Cat & Dog Classifier Section -->
|
38 |
+
<div id="catdog-section" class="model-section">
|
39 |
+
<h2>Cat & Dog Classifier</h2>
|
40 |
+
<p>Upload an image to classify it as a cat or a dog.</p>
|
41 |
+
<form id="catdog-form">
|
42 |
+
<input type="file" id="catdog-file" name="file" accept="image/*">
|
43 |
+
<div class="image-preview-container">
|
44 |
+
<img id="image-preview" src="#" alt="Image Preview" class="image-preview"/>
|
45 |
+
</div>
|
46 |
+
<button type="submit">Classify Image</button>
|
47 |
</form>
|
48 |
+
<div id="catdog-result" class="result"></div>
|
49 |
+
</div>
|
50 |
+
|
51 |
+
<!-- California Housing Price Predictor Section -->
|
52 |
+
<div id="housing-section" class="model-section">
|
53 |
+
<h2>California House Price Predictor</h2>
|
54 |
+
<p>Enter the details below to predict the median house value.</p>
|
55 |
+
<form id="housing-form">
|
56 |
+
<div class="form-grid">
|
57 |
+
<div class="form-group">
|
58 |
+
<label for="MedInc">Median Income</label>
|
59 |
+
<input type="number" step="any" id="MedInc" name="MedInc" placeholder="e.g., 8.3252" required>
|
60 |
+
</div>
|
61 |
+
<div class="form-group">
|
62 |
+
<label for="HouseAge">House Age</label>
|
63 |
+
<input type="number" step="any" id="HouseAge" name="HouseAge" placeholder="e.g., 41.0" required>
|
64 |
+
</div>
|
65 |
+
<div class="form-group">
|
66 |
+
<label for="AveRooms">Average Rooms</label>
|
67 |
+
<input type="number" step="any" id="AveRooms" name="AveRooms" placeholder="e.g., 6.9841" required>
|
68 |
+
</div>
|
69 |
+
<div class="form-group">
|
70 |
+
<label for="AveBedrms">Average Bedrooms</label>
|
71 |
+
<input type="number" step="any" id="AveBedrms" name="AveBedrms" placeholder="e.g., 1.0238" required>
|
72 |
+
</div>
|
73 |
+
<div class="form-group">
|
74 |
+
<label for="Population">Population</label>
|
75 |
+
<input type="number" step="any" id="Population" name="Population" placeholder="e.g., 322.0" required>
|
76 |
+
</div>
|
77 |
+
<div class="form-group">
|
78 |
+
<label for="AveOccup">Average Occupancy</label>
|
79 |
+
<input type="number" step="any" id="AveOccup" name="AveOccup" placeholder="e.g., 2.5555" required>
|
80 |
+
</div>
|
81 |
+
<div class="form-group">
|
82 |
+
<label for="Latitude">Latitude</label>
|
83 |
+
<input type="number" step="any" id="Latitude" name="Latitude" placeholder="e.g., 37.88" required>
|
84 |
+
</div>
|
85 |
+
<div class="form-group">
|
86 |
+
<label for="Longitude">Longitude</label>
|
87 |
+
<input type="number" step="any" id="Longitude" name="Longitude" placeholder="e.g., -122.23" required>
|
88 |
+
</div>
|
89 |
+
</div>
|
90 |
+
<button type="submit">Predict Price</button>
|
91 |
+
</form>
|
92 |
+
<div id="housing-result" class="result"></div>
|
93 |
</div>
|
94 |
</main>
|
|
|
95 |
|
96 |
+
<footer>
|
97 |
+
<p>© 2025 ML Nexus. All rights reserved.</p>
|
98 |
+
</footer>
|
99 |
+
</div>
|
100 |
<script src="/static/script.js"></script>
|
101 |
</body>
|
102 |
</html>
|
requirements.txt
CHANGED
@@ -10,4 +10,6 @@ Pillow
|
|
10 |
types-pytz
|
11 |
fastapi
|
12 |
uvicorn[standard]
|
13 |
-
python-multipart
|
|
|
|
|
|
10 |
types-pytz
|
11 |
fastapi
|
12 |
uvicorn[standard]
|
13 |
+
python-multipart
|
14 |
+
scikit-learn
|
15 |
+
xgboost
|
static/script.js
CHANGED
@@ -1,47 +1,49 @@
|
|
1 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
2 |
const sentimentBtn = document.getElementById('sentiment-btn');
|
3 |
-
const
|
4 |
-
const
|
5 |
-
const catDogInterface = document.getElementById('cat-dog-interface');
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
const sentimentForm = document.getElementById('sentiment-form');
|
8 |
-
const
|
9 |
-
const
|
10 |
-
const imageUpload = document.getElementById('image-upload');
|
11 |
-
const imagePreview = document.getElementById('image-preview');
|
12 |
|
13 |
const sentimentResult = document.getElementById('sentiment-result');
|
14 |
-
const
|
|
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
22 |
}
|
23 |
|
24 |
-
sentimentBtn.addEventListener('click', () =>
|
25 |
-
|
|
|
26 |
|
27 |
-
|
28 |
-
const file = imageUpload.files[0];
|
29 |
-
if (file) {
|
30 |
-
const reader = new FileReader();
|
31 |
-
reader.onload = (e) => {
|
32 |
-
imagePreview.innerHTML = `<img src="${e.target.result}" alt="Image preview">`;
|
33 |
-
};
|
34 |
-
reader.readAsDataURL(file);
|
35 |
-
}
|
36 |
-
});
|
37 |
|
|
|
38 |
sentimentForm.addEventListener('submit', async (e) => {
|
39 |
e.preventDefault();
|
40 |
-
const text =
|
41 |
-
|
42 |
-
|
43 |
-
showResult(sentimentResult, 'Analyzing...', 'loading');
|
44 |
-
|
45 |
try {
|
46 |
const response = await fetch('/predict/sentiment', {
|
47 |
method: 'POST',
|
@@ -49,42 +51,63 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
49 |
body: JSON.stringify({ text })
|
50 |
});
|
51 |
const data = await response.json();
|
52 |
-
if (!response.ok) throw new Error(data.detail || '
|
53 |
-
|
54 |
-
showResult(sentimentResult, `Prediction: ${data.prediction}`, 'success');
|
55 |
} catch (error) {
|
56 |
-
|
57 |
}
|
58 |
});
|
59 |
|
60 |
-
|
|
|
61 |
e.preventDefault();
|
62 |
-
const
|
63 |
-
|
64 |
-
|
65 |
-
showResult(catDogResult, 'Classifying...', 'loading');
|
66 |
-
|
67 |
-
const formData = new FormData();
|
68 |
-
formData.append('file', file);
|
69 |
-
|
70 |
try {
|
71 |
const response = await fetch('/predict/catdog', {
|
72 |
method: 'POST',
|
73 |
body: formData
|
74 |
});
|
75 |
const data = await response.json();
|
76 |
-
if (!response.ok) throw new Error(data.detail || '
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
} catch (error) {
|
80 |
-
|
81 |
}
|
82 |
});
|
83 |
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
});
|
|
|
1 |
document.addEventListener('DOMContentLoaded', () => {
|
2 |
+
// Model selection buttons and sections
|
3 |
const sentimentBtn = document.getElementById('sentiment-btn');
|
4 |
+
const catdogBtn = document.getElementById('catdog-btn');
|
5 |
+
const housingBtn = document.getElementById('housing-btn');
|
|
|
6 |
|
7 |
+
const sentimentSection = document.getElementById('sentiment-section');
|
8 |
+
const catdogSection = document.getElementById('catdog-section');
|
9 |
+
const housingSection = document.getElementById('housing-section');
|
10 |
+
|
11 |
+
const sections = [sentimentSection, catdogSection, housingSection];
|
12 |
+
const buttons = [sentimentBtn, catdogBtn, housingBtn];
|
13 |
+
|
14 |
+
// Forms and result displays
|
15 |
const sentimentForm = document.getElementById('sentiment-form');
|
16 |
+
const catdogForm = document.getElementById('catdog-form');
|
17 |
+
const housingForm = document.getElementById('housing-form');
|
|
|
|
|
18 |
|
19 |
const sentimentResult = document.getElementById('sentiment-result');
|
20 |
+
const catdogResult = document.getElementById('catdog-result');
|
21 |
+
const housingResult = document.getElementById('housing-result');
|
22 |
|
23 |
+
const imagePreview = document.getElementById('image-preview');
|
24 |
+
const catdogFileInput = document.getElementById('catdog-file');
|
25 |
+
|
26 |
+
// --- Event Listeners for Model Switching ---
|
27 |
+
function switchTab(activeIndex) {
|
28 |
+
buttons.forEach((button, index) => {
|
29 |
+
button.classList.toggle('active', index === activeIndex);
|
30 |
+
});
|
31 |
+
sections.forEach((section, index) => {
|
32 |
+
section.classList.toggle('active', index === activeIndex);
|
33 |
+
});
|
34 |
}
|
35 |
|
36 |
+
sentimentBtn.addEventListener('click', () => switchTab(0));
|
37 |
+
catdogBtn.addEventListener('click', () => switchTab(1));
|
38 |
+
housingBtn.addEventListener('click', () => switchTab(2));
|
39 |
|
40 |
+
// --- Form Submission Handlers ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
+
// Sentiment Form
|
43 |
sentimentForm.addEventListener('submit', async (e) => {
|
44 |
e.preventDefault();
|
45 |
+
const text = document.getElementById('sentiment-text').value;
|
46 |
+
sentimentResult.textContent = 'Analyzing...';
|
|
|
|
|
|
|
47 |
try {
|
48 |
const response = await fetch('/predict/sentiment', {
|
49 |
method: 'POST',
|
|
|
51 |
body: JSON.stringify({ text })
|
52 |
});
|
53 |
const data = await response.json();
|
54 |
+
if (!response.ok) throw new Error(data.detail || 'An unknown error occurred.');
|
55 |
+
sentimentResult.textContent = `Prediction: ${data.prediction}`;
|
|
|
56 |
} catch (error) {
|
57 |
+
sentimentResult.textContent = `Error: ${error.message}`;
|
58 |
}
|
59 |
});
|
60 |
|
61 |
+
// Cat & Dog Form
|
62 |
+
catdogForm.addEventListener('submit', async (e) => {
|
63 |
e.preventDefault();
|
64 |
+
const formData = new FormData(catdogForm);
|
65 |
+
catdogResult.textContent = 'Classifying...';
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
try {
|
67 |
const response = await fetch('/predict/catdog', {
|
68 |
method: 'POST',
|
69 |
body: formData
|
70 |
});
|
71 |
const data = await response.json();
|
72 |
+
if (!response.ok) throw new Error(data.detail || 'An unknown error occurred.');
|
73 |
+
catdogResult.textContent = `Prediction: ${data.prediction}`;
|
74 |
+
} catch (error) {
|
75 |
+
catdogResult.textContent = `Error: ${error.message}`;
|
76 |
+
}
|
77 |
+
});
|
78 |
|
79 |
+
// Housing Form
|
80 |
+
housingForm.addEventListener('submit', async (e) => {
|
81 |
+
e.preventDefault();
|
82 |
+
const formData = new FormData(housingForm);
|
83 |
+
const data = Object.fromEntries(formData.entries());
|
84 |
+
housingResult.textContent = 'Predicting...';
|
85 |
+
try {
|
86 |
+
const response = await fetch('/predict/housing', {
|
87 |
+
method: 'POST',
|
88 |
+
headers: { 'Content-Type': 'application/json' },
|
89 |
+
body: JSON.stringify(data)
|
90 |
+
});
|
91 |
+
const result = await response.json();
|
92 |
+
if (!response.ok) throw new Error(result.detail || 'An unknown error occurred.');
|
93 |
+
housingResult.textContent = `Predicted Price: ${result.prediction}`;
|
94 |
} catch (error) {
|
95 |
+
housingResult.textContent = `Error: ${error.message}`;
|
96 |
}
|
97 |
});
|
98 |
|
99 |
+
// Image Preview Handler
|
100 |
+
catdogFileInput.addEventListener('change', () => {
|
101 |
+
const file = catdogFileInput.files[0];
|
102 |
+
if (file) {
|
103 |
+
const reader = new FileReader();
|
104 |
+
reader.onload = (e) => {
|
105 |
+
imagePreview.src = e.target.result;
|
106 |
+
imagePreview.style.display = 'block';
|
107 |
+
};
|
108 |
+
reader.readAsDataURL(file);
|
109 |
+
} else {
|
110 |
+
imagePreview.style.display = 'none';
|
111 |
+
}
|
112 |
+
});
|
113 |
});
|
static/style.css
CHANGED
@@ -1,194 +1,231 @@
|
|
|
|
1 |
body {
|
2 |
-
font-family:
|
3 |
-
background-color: #
|
4 |
-
color: #
|
5 |
margin: 0;
|
6 |
-
padding: 20px;
|
7 |
display: flex;
|
8 |
justify-content: center;
|
9 |
-
align-items:
|
10 |
min-height: 100vh;
|
|
|
11 |
}
|
12 |
|
13 |
.container {
|
|
|
|
|
|
|
|
|
14 |
width: 100%;
|
15 |
max-width: 800px;
|
16 |
-
background-color: #ffffff;
|
17 |
-
border-radius: 8px;
|
18 |
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
19 |
overflow: hidden;
|
20 |
}
|
21 |
|
22 |
header {
|
23 |
text-align: center;
|
24 |
-
padding:
|
25 |
-
border-bottom: 1px solid #
|
26 |
}
|
27 |
|
28 |
header h1 {
|
29 |
-
margin: 0
|
30 |
-
font-size:
|
31 |
font-weight: 700;
|
32 |
-
color: #
|
33 |
}
|
34 |
|
35 |
header p {
|
36 |
-
margin:
|
37 |
-
color: #
|
|
|
38 |
}
|
39 |
|
|
|
40 |
.model-selector {
|
41 |
display: flex;
|
42 |
justify-content: center;
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
}
|
47 |
|
48 |
.model-btn {
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
51 |
font-weight: 600;
|
52 |
-
|
53 |
-
border-radius: 20px;
|
54 |
-
background-color: #ffffff;
|
55 |
-
color: #555;
|
56 |
cursor: pointer;
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
.model-btn:hover {
|
61 |
-
background-color: #f1f3f4;
|
62 |
-
border-color: #c0c0c0;
|
63 |
}
|
64 |
|
65 |
.model-btn.active {
|
66 |
-
background-color: #
|
67 |
-
color: #
|
68 |
-
border-color: #1a73e8;
|
69 |
}
|
70 |
|
71 |
-
.model-btn.
|
72 |
-
background-color: #
|
73 |
-
color: #
|
74 |
-
cursor: not-allowed;
|
75 |
-
border-color: #e0e0e0;
|
76 |
}
|
77 |
|
|
|
78 |
main {
|
79 |
-
padding:
|
80 |
}
|
81 |
|
82 |
-
.model-
|
83 |
display: none;
|
|
|
84 |
}
|
85 |
|
86 |
-
.model-
|
87 |
display: block;
|
88 |
}
|
89 |
|
90 |
-
|
91 |
-
background-color: #ffffff;
|
92 |
-
border: 1px solid #e8e8e8;
|
93 |
-
border-radius: 8px;
|
94 |
-
padding: 24px;
|
95 |
-
}
|
96 |
-
|
97 |
-
form h2 {
|
98 |
margin-top: 0;
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
101 |
}
|
102 |
|
103 |
-
|
104 |
-
margin-
|
105 |
-
|
106 |
-
color: #
|
107 |
}
|
108 |
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
}
|
114 |
|
115 |
-
textarea, input[type="file"] {
|
116 |
width: 100%;
|
117 |
-
box-sizing: border-box;
|
118 |
-
}
|
119 |
-
|
120 |
-
textarea {
|
121 |
padding: 12px;
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
127 |
}
|
128 |
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
font-size: 16px;
|
133 |
-
font-weight: 600;
|
134 |
border: none;
|
135 |
-
|
136 |
-
|
137 |
-
|
|
|
138 |
cursor: pointer;
|
139 |
-
margin-top: 24px;
|
140 |
transition: background-color 0.2s;
|
|
|
141 |
}
|
142 |
|
143 |
-
|
144 |
-
background-color: #
|
145 |
}
|
146 |
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
border-radius: 6px;
|
151 |
text-align: center;
|
152 |
-
cursor: pointer;
|
153 |
-
display: block;
|
154 |
-
margin-bottom: 16px;
|
155 |
}
|
156 |
|
157 |
-
|
|
|
|
|
|
|
|
|
158 |
display: none;
|
159 |
}
|
160 |
|
161 |
-
|
162 |
-
|
163 |
-
|
|
|
|
|
164 |
}
|
165 |
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
|
|
170 |
}
|
171 |
|
172 |
-
.
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
background-color: #f7f9fc;
|
177 |
-
border: 1px solid #e8e8e8;
|
178 |
-
text-align: center;
|
179 |
-
font-size: 18px;
|
180 |
font-weight: 600;
|
181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
}
|
183 |
|
184 |
-
|
185 |
-
|
|
|
|
|
|
|
|
|
186 |
}
|
187 |
|
188 |
-
.
|
189 |
-
|
|
|
|
|
|
|
190 |
}
|
191 |
|
192 |
-
.
|
193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
}
|
|
|
1 |
+
/* --- General Layout & Dark Theme --- */
|
2 |
body {
|
3 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
4 |
+
background-color: #0b0d11;
|
5 |
+
color: #e6edf3;
|
6 |
margin: 0;
|
|
|
7 |
display: flex;
|
8 |
justify-content: center;
|
9 |
+
align-items: center;
|
10 |
min-height: 100vh;
|
11 |
+
padding: 20px;
|
12 |
}
|
13 |
|
14 |
.container {
|
15 |
+
background: #1c2128;
|
16 |
+
border-radius: 12px;
|
17 |
+
border: 1px solid #30363d;
|
18 |
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
19 |
width: 100%;
|
20 |
max-width: 800px;
|
|
|
|
|
|
|
21 |
overflow: hidden;
|
22 |
}
|
23 |
|
24 |
header {
|
25 |
text-align: center;
|
26 |
+
padding: 30px;
|
27 |
+
border-bottom: 1px solid #30363d;
|
28 |
}
|
29 |
|
30 |
header h1 {
|
31 |
+
margin: 0;
|
32 |
+
font-size: 2.5rem;
|
33 |
font-weight: 700;
|
34 |
+
color: #e6edf3;
|
35 |
}
|
36 |
|
37 |
header p {
|
38 |
+
margin-top: 8px;
|
39 |
+
color: #8b949e;
|
40 |
+
font-size: 1.1rem;
|
41 |
}
|
42 |
|
43 |
+
/* --- Model Selector Navigation --- */
|
44 |
.model-selector {
|
45 |
display: flex;
|
46 |
justify-content: center;
|
47 |
+
padding: 20px;
|
48 |
+
background-color: #161b22;
|
49 |
+
border-bottom: 1px solid #30363d;
|
50 |
}
|
51 |
|
52 |
.model-btn {
|
53 |
+
background-color: transparent;
|
54 |
+
border: none;
|
55 |
+
padding: 12px 24px;
|
56 |
+
margin: 0 10px;
|
57 |
+
font-size: 1rem;
|
58 |
font-weight: 600;
|
59 |
+
color: #8b949e;
|
|
|
|
|
|
|
60 |
cursor: pointer;
|
61 |
+
border-radius: 8px;
|
62 |
+
transition: background-color 0.2s, color 0.2s;
|
|
|
|
|
|
|
|
|
63 |
}
|
64 |
|
65 |
.model-btn.active {
|
66 |
+
background-color: #ffd866;
|
67 |
+
color: #000000;
|
|
|
68 |
}
|
69 |
|
70 |
+
.model-btn:not(.active):hover {
|
71 |
+
background-color: #30363d;
|
72 |
+
color: #e6edf3;
|
|
|
|
|
73 |
}
|
74 |
|
75 |
+
/* --- Main Content & Model Sections --- */
|
76 |
main {
|
77 |
+
padding: 30px;
|
78 |
}
|
79 |
|
80 |
+
.model-section {
|
81 |
display: none;
|
82 |
+
animation: fadeIn 0.5s;
|
83 |
}
|
84 |
|
85 |
+
.model-section.active {
|
86 |
display: block;
|
87 |
}
|
88 |
|
89 |
+
.model-section h2 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
margin-top: 0;
|
91 |
+
font-size: 1.8rem;
|
92 |
+
color: #e6edf3;
|
93 |
+
border-bottom: 2px solid #ffd866;
|
94 |
+
padding-bottom: 10px;
|
95 |
+
margin-bottom: 20px;
|
96 |
}
|
97 |
|
98 |
+
.model-section p {
|
99 |
+
margin-bottom: 20px;
|
100 |
+
line-height: 1.6;
|
101 |
+
color: #8b949e;
|
102 |
}
|
103 |
|
104 |
+
/* --- Forms --- */
|
105 |
+
form {
|
106 |
+
display: flex;
|
107 |
+
flex-direction: column;
|
108 |
}
|
109 |
|
110 |
+
textarea, input[type="file"], input[type="number"] {
|
111 |
width: 100%;
|
|
|
|
|
|
|
|
|
112 |
padding: 12px;
|
113 |
+
background-color: #0d1117;
|
114 |
+
color: #e6edf3;
|
115 |
+
border: 1px solid #30363d;
|
116 |
+
border-radius: 8px;
|
117 |
+
font-size: 1rem;
|
118 |
+
margin-bottom: 15px;
|
119 |
+
box-sizing: border-box;
|
120 |
}
|
121 |
|
122 |
+
button[type="submit"] {
|
123 |
+
background-color: #ffd866;
|
124 |
+
color: #000000;
|
|
|
|
|
125 |
border: none;
|
126 |
+
padding: 14px 20px;
|
127 |
+
font-size: 1.1rem;
|
128 |
+
font-weight: 600;
|
129 |
+
border-radius: 8px;
|
130 |
cursor: pointer;
|
|
|
131 |
transition: background-color 0.2s;
|
132 |
+
align-self: flex-start;
|
133 |
}
|
134 |
|
135 |
+
button[type="submit"]:hover {
|
136 |
+
background-color: #f0c340;
|
137 |
}
|
138 |
|
139 |
+
/* --- Specific Form Styling --- */
|
140 |
+
.image-preview-container {
|
141 |
+
margin-bottom: 15px;
|
|
|
142 |
text-align: center;
|
|
|
|
|
|
|
143 |
}
|
144 |
|
145 |
+
.image-preview {
|
146 |
+
max-width: 100%;
|
147 |
+
max-height: 200px;
|
148 |
+
border: 1px solid #30363d;
|
149 |
+
border-radius: 8px;
|
150 |
display: none;
|
151 |
}
|
152 |
|
153 |
+
.form-grid {
|
154 |
+
display: grid;
|
155 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
156 |
+
gap: 20px;
|
157 |
+
margin-bottom: 20px;
|
158 |
}
|
159 |
|
160 |
+
.form-group label {
|
161 |
+
display: block;
|
162 |
+
font-weight: 600;
|
163 |
+
margin-bottom: 8px;
|
164 |
+
color: #e6edf3;
|
165 |
}
|
166 |
|
167 |
+
.beta-tag {
|
168 |
+
background-color: #30363d;
|
169 |
+
color: #8b949e;
|
170 |
+
font-size: 0.8rem;
|
|
|
|
|
|
|
|
|
171 |
font-weight: 600;
|
172 |
+
padding: 4px 8px;
|
173 |
+
border-radius: 12px;
|
174 |
+
vertical-align: middle;
|
175 |
+
margin-left: 8px;
|
176 |
+
}
|
177 |
+
|
178 |
+
/* --- Result Display --- */
|
179 |
+
.result {
|
180 |
+
margin-top: 20px;
|
181 |
+
padding: 15px;
|
182 |
+
background-color: #161b22;
|
183 |
+
border-left: 4px solid #ffd866;
|
184 |
+
border-radius: 4px;
|
185 |
+
font-size: 1.1rem;
|
186 |
+
font-weight: 500;
|
187 |
+
min-height: 24px;
|
188 |
+
}
|
189 |
+
|
190 |
+
/* --- Footer --- */
|
191 |
+
footer {
|
192 |
+
text-align: center;
|
193 |
+
padding: 20px;
|
194 |
+
background-color: #161b22;
|
195 |
+
border-top: 1px solid #30363d;
|
196 |
+
font-size: 0.9rem;
|
197 |
+
color: #8b949e;
|
198 |
+
}
|
199 |
+
|
200 |
+
/* --- Animations --- */
|
201 |
+
@keyframes fadeIn {
|
202 |
+
from { opacity: 0; }
|
203 |
+
to { opacity: 1; }
|
204 |
}
|
205 |
|
206 |
+
/* --- Housing Predictor Form Grid --- */
|
207 |
+
.housing-predictor-form-grid {
|
208 |
+
display: grid;
|
209 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
210 |
+
gap: 20px;
|
211 |
+
margin-bottom: 20px;
|
212 |
}
|
213 |
|
214 |
+
.housing-predictor-form-group label {
|
215 |
+
display: block;
|
216 |
+
font-weight: 600;
|
217 |
+
margin-bottom: 8px;
|
218 |
+
color: #e6edf3;
|
219 |
}
|
220 |
|
221 |
+
.housing-predictor-form-group input[type="number"] {
|
222 |
+
width: 100%;
|
223 |
+
padding: 12px;
|
224 |
+
background-color: #0d1117;
|
225 |
+
color: #e6edf3;
|
226 |
+
border: 1px solid #30363d;
|
227 |
+
border-radius: 8px;
|
228 |
+
font-size: 1rem;
|
229 |
+
margin-bottom: 15px;
|
230 |
+
box-sizing: border-box;
|
231 |
}
|