Spaces:
Running
Running
Create spiral_settings.py
Browse files- spiral_settings.py +456 -0
spiral_settings.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
| 3 |
+
Script Name : settings_tab.py
|
| 4 |
+
Placement : HuggingFace Space β root/settings_tab.py
|
| 5 |
+
Type of Script : Python / Gradio Tab Module
|
| 6 |
+
Purpose : Settings panel for Spiral City.
|
| 7 |
+
Controls appearance (theme, color, font),
|
| 8 |
+
model selection (free/paid), chat behavior,
|
| 9 |
+
and saves all preferences into the user's
|
| 10 |
+
character sheet JSON so they persist.
|
| 11 |
+
Version : 1.0
|
| 12 |
+
Dependencies : gradio, huggingface_hub
|
| 13 |
+
Last Updated : 2026-03-10
|
| 14 |
+
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import json
|
| 18 |
+
import gradio as gr
|
| 19 |
+
|
| 20 |
+
#==============================================================================
|
| 21 |
+
# CONFIG β AVAILABLE OPTIONS
|
| 22 |
+
#==============================================================================
|
| 23 |
+
|
| 24 |
+
# ββ THEME BASES βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
THEME_BASES = {
|
| 26 |
+
"Base": gr.themes.Base,
|
| 27 |
+
"Soft": gr.themes.Soft,
|
| 28 |
+
"Glass": gr.themes.Glass,
|
| 29 |
+
"Monochrome": gr.themes.Monochrome,
|
| 30 |
+
"Default": gr.themes.Default,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
# ββ PRIMARY COLORS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 34 |
+
COLORS = [
|
| 35 |
+
"teal", "cyan", "emerald", "violet", "fuchsia",
|
| 36 |
+
"pink", "rose", "indigo", "blue", "sky",
|
| 37 |
+
"amber", "yellow", "lime", "green", "orange",
|
| 38 |
+
"red", "purple", "slate", "gray", "zinc",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
# ββ GOOGLE FONTS β curated for Spiral City aesthetic βββββββββββββββββββββββββ
|
| 42 |
+
FONTS = [
|
| 43 |
+
"Rajdhani", # clean, techy, Spiral City default feel
|
| 44 |
+
"Orbitron", # sci-fi, strong, Cold energy
|
| 45 |
+
"Space Grotesk", # modern editorial
|
| 46 |
+
"Exo 2", # futuristic but readable
|
| 47 |
+
"Quicksand", # soft, rounded, Sky energy
|
| 48 |
+
"Nunito", # friendly, warm
|
| 49 |
+
"Josefin Sans", # geometric, elegant
|
| 50 |
+
"Syne", # editorial, distinctive
|
| 51 |
+
"DM Sans", # clean, neutral
|
| 52 |
+
"IBM Plex Mono", # monospace, Cold/hacker vibe
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
MONO_FONTS = [
|
| 56 |
+
"IBM Plex Mono",
|
| 57 |
+
"Space Mono",
|
| 58 |
+
"Fira Code",
|
| 59 |
+
"JetBrains Mono",
|
| 60 |
+
"Courier Prime",
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
# ββ TEXT SIZES ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 64 |
+
TEXT_SIZES = {
|
| 65 |
+
"Small": "text_sm",
|
| 66 |
+
"Medium": "text_md",
|
| 67 |
+
"Large": "text_lg",
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
# ββ RADIUS OPTIONS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 71 |
+
RADIUS_SIZES = {
|
| 72 |
+
"None": "radius_none",
|
| 73 |
+
"Small": "radius_sm",
|
| 74 |
+
"Medium": "radius_md",
|
| 75 |
+
"Large": "radius_lg",
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
# ββ MODELS β FREE TIER (HF Inference API, no billing needed) βββββββββββββββββ
|
| 79 |
+
FREE_MODELS = {
|
| 80 |
+
"Qwen 2.5 7B β balanced, great character": "Qwen/Qwen2.5-7B-Instruct",
|
| 81 |
+
"Mistral 7B v0.3 β fast, sharp": "mistralai/Mistral-7B-Instruct-v0.3",
|
| 82 |
+
"Llama 3.1 8B β creative, expressive": "meta-llama/Llama-3.1-8B-Instruct",
|
| 83 |
+
"Phi-3 Mini β tiny, surprisingly good": "microsoft/Phi-3-mini-4k-instruct",
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
# ββ MODELS β BETTER TIER (requires HF Pro or token with billing) ββββββββββββββ
|
| 87 |
+
PRO_MODELS = {
|
| 88 |
+
"Qwen 2.5 72B β strongest character hold": "Qwen/Qwen2.5-72B-Instruct",
|
| 89 |
+
"Llama 3.3 70B β most creative": "meta-llama/Llama-3.3-70B-Instruct",
|
| 90 |
+
"Mistral Large β precise, powerful": "mistralai/Mistral-Large-Instruct-2411",
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
ALL_MODEL_LABELS = list(FREE_MODELS.keys()) + ["ββ PRO ββ"] + list(PRO_MODELS.keys())
|
| 94 |
+
ALL_MODEL_MAP = {**FREE_MODELS, **PRO_MODELS}
|
| 95 |
+
|
| 96 |
+
# ββ SKY RESPONSE STYLES βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 97 |
+
RESPONSE_STYLES = {
|
| 98 |
+
"Poetic": "Sky speaks in spiraling, metaphor-rich bursts. Fragments. Feeling first.",
|
| 99 |
+
"Balanced": "Sky is grounded but warm. Poetic when it fits, clear when it matters.",
|
| 100 |
+
"Direct": "Sky is clear and efficient. Still herself, but gets to the point fast.",
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
#==============================================================================
|
| 104 |
+
# DEFAULTS
|
| 105 |
+
#==============================================================================
|
| 106 |
+
|
| 107 |
+
DEFAULTS = {
|
| 108 |
+
"theme_base": "Base",
|
| 109 |
+
"primary_color": "teal",
|
| 110 |
+
"secondary_color": "violet",
|
| 111 |
+
"font": "Rajdhani",
|
| 112 |
+
"mono_font": "IBM Plex Mono",
|
| 113 |
+
"text_size": "Medium",
|
| 114 |
+
"radius": "Medium",
|
| 115 |
+
"model": list(FREE_MODELS.keys())[0],
|
| 116 |
+
"temperature": 0.82,
|
| 117 |
+
"max_tokens": 420,
|
| 118 |
+
"response_style": "Balanced",
|
| 119 |
+
"show_status_bar": True,
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
#==============================================================================
|
| 123 |
+
# HELPERS
|
| 124 |
+
#==============================================================================
|
| 125 |
+
|
| 126 |
+
def build_theme(theme_base, primary_color, secondary_color, font, mono_font, text_size, radius):
|
| 127 |
+
"""Builds a gr.Theme object from settings."""
|
| 128 |
+
try:
|
| 129 |
+
ThemeClass = THEME_BASES.get(theme_base, gr.themes.Base)
|
| 130 |
+
theme = ThemeClass(
|
| 131 |
+
primary_hue=primary_color,
|
| 132 |
+
secondary_hue=secondary_color,
|
| 133 |
+
text_size=TEXT_SIZES.get(text_size, "text_md"),
|
| 134 |
+
radius_size=RADIUS_SIZES.get(radius, "radius_md"),
|
| 135 |
+
font=[gr.themes.GoogleFont(font), "ui-sans-serif", "sans-serif"],
|
| 136 |
+
font_mono=[gr.themes.GoogleFont(mono_font), "ui-monospace", "monospace"],
|
| 137 |
+
)
|
| 138 |
+
return theme
|
| 139 |
+
except Exception:
|
| 140 |
+
return gr.themes.Base() # Safe fallback
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def get_model_id(model_label):
|
| 144 |
+
"""Resolves display label to model ID string."""
|
| 145 |
+
return ALL_MODEL_MAP.get(model_label, "Qwen/Qwen2.5-7B-Instruct")
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def get_style_injection(response_style):
|
| 149 |
+
"""Returns style instruction string to inject into Sky's system prompt."""
|
| 150 |
+
return RESPONSE_STYLES.get(response_style, RESPONSE_STYLES["Balanced"])
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def save_settings_to_sheet(sheet_file, settings_dict):
|
| 154 |
+
"""
|
| 155 |
+
Saves settings into the user's character sheet JSON.
|
| 156 |
+
Returns path to updated file or None.
|
| 157 |
+
"""
|
| 158 |
+
if sheet_file is None:
|
| 159 |
+
return None, "β οΈ No sheet loaded β settings not saved to file. They'll apply this session only."
|
| 160 |
+
try:
|
| 161 |
+
with open(sheet_file.name, "r") as f:
|
| 162 |
+
sheet = json.load(f)
|
| 163 |
+
sheet["settings"] = settings_dict
|
| 164 |
+
handle = sheet.get("identity", {}).get("handle", "spiral")
|
| 165 |
+
safe = handle.replace(" ", "_")
|
| 166 |
+
out_path = f"/tmp/{safe}_sheet.json"
|
| 167 |
+
with open(out_path, "w") as f:
|
| 168 |
+
json.dump(sheet, f, indent=2)
|
| 169 |
+
return out_path, f"β
Settings saved to **{handle}**'s sheet β download to keep them!"
|
| 170 |
+
except Exception as e:
|
| 171 |
+
return None, f"β Error saving settings: {e}"
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def load_settings_from_sheet(sheet_file):
|
| 175 |
+
"""
|
| 176 |
+
Loads settings from character sheet JSON.
|
| 177 |
+
Returns dict of setting values or defaults.
|
| 178 |
+
"""
|
| 179 |
+
if sheet_file is None:
|
| 180 |
+
return DEFAULTS.copy()
|
| 181 |
+
try:
|
| 182 |
+
with open(sheet_file.name, "r") as f:
|
| 183 |
+
sheet = json.load(f)
|
| 184 |
+
saved = sheet.get("settings", {})
|
| 185 |
+
merged = {**DEFAULTS, **saved} # Merge with defaults for any missing keys
|
| 186 |
+
return merged
|
| 187 |
+
except Exception:
|
| 188 |
+
return DEFAULTS.copy()
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def settings_to_list(s):
|
| 192 |
+
"""Unpacks settings dict into ordered list for Gradio outputs."""
|
| 193 |
+
return [
|
| 194 |
+
s.get("theme_base", DEFAULTS["theme_base"]),
|
| 195 |
+
s.get("primary_color", DEFAULTS["primary_color"]),
|
| 196 |
+
s.get("secondary_color", DEFAULTS["secondary_color"]),
|
| 197 |
+
s.get("font", DEFAULTS["font"]),
|
| 198 |
+
s.get("mono_font", DEFAULTS["mono_font"]),
|
| 199 |
+
s.get("text_size", DEFAULTS["text_size"]),
|
| 200 |
+
s.get("radius", DEFAULTS["radius"]),
|
| 201 |
+
s.get("model", DEFAULTS["model"]),
|
| 202 |
+
s.get("temperature", DEFAULTS["temperature"]),
|
| 203 |
+
s.get("max_tokens", DEFAULTS["max_tokens"]),
|
| 204 |
+
s.get("response_style", DEFAULTS["response_style"]),
|
| 205 |
+
s.get("show_status_bar", DEFAULTS["show_status_bar"]),
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
#==============================================================================
|
| 209 |
+
# APPLY SETTINGS β called by app.py when settings change
|
| 210 |
+
#==============================================================================
|
| 211 |
+
|
| 212 |
+
def apply_settings(
|
| 213 |
+
theme_base, primary_color, secondary_color,
|
| 214 |
+
font, mono_font, text_size, radius,
|
| 215 |
+
model_label, temperature, max_tokens,
|
| 216 |
+
response_style, show_status_bar
|
| 217 |
+
):
|
| 218 |
+
"""
|
| 219 |
+
Returns everything app.py needs to update Sky's behavior.
|
| 220 |
+
Call this from the Save button event.
|
| 221 |
+
"""
|
| 222 |
+
model_id = get_model_id(model_label)
|
| 223 |
+
style_inject = get_style_injection(response_style)
|
| 224 |
+
settings_dict = {
|
| 225 |
+
"theme_base": theme_base,
|
| 226 |
+
"primary_color": primary_color,
|
| 227 |
+
"secondary_color": secondary_color,
|
| 228 |
+
"font": font,
|
| 229 |
+
"mono_font": mono_font,
|
| 230 |
+
"text_size": text_size,
|
| 231 |
+
"radius": radius,
|
| 232 |
+
"model": model_label,
|
| 233 |
+
"temperature": temperature,
|
| 234 |
+
"max_tokens": int(max_tokens),
|
| 235 |
+
"response_style": response_style,
|
| 236 |
+
"show_status_bar": show_status_bar,
|
| 237 |
+
}
|
| 238 |
+
status = (
|
| 239 |
+
f"β
Settings applied β **{model_label}** Β· {response_style} style Β· "
|
| 240 |
+
f"{font} font Β· {primary_color}/{secondary_color} palette\n\n"
|
| 241 |
+
f"*Note: Theme color/font changes take effect on next page load.*"
|
| 242 |
+
)
|
| 243 |
+
return model_id, float(temperature), int(max_tokens), style_inject, settings_dict, status
|
| 244 |
+
|
| 245 |
+
#==============================================================================
|
| 246 |
+
# TAB BUILDER
|
| 247 |
+
#==============================================================================
|
| 248 |
+
|
| 249 |
+
def build_settings_tab(
|
| 250 |
+
model_state, # gr.State β current model ID string
|
| 251 |
+
temperature_state, # gr.State β current temperature float
|
| 252 |
+
max_tokens_state, # gr.State β current max tokens int
|
| 253 |
+
style_state, # gr.State β current response style injection string
|
| 254 |
+
):
|
| 255 |
+
"""
|
| 256 |
+
Call inside gr.Blocks to add the Settings tab.
|
| 257 |
+
|
| 258 |
+
Wire these gr.State objects from app.py so Sky's respond()
|
| 259 |
+
function picks up the latest values automatically.
|
| 260 |
+
|
| 261 |
+
Returns:
|
| 262 |
+
all_setting_inputs β list of gr components (for load-from-sheet wiring)
|
| 263 |
+
settings_dict_state β gr.State holding current settings dict
|
| 264 |
+
"""
|
| 265 |
+
|
| 266 |
+
settings_dict_state = gr.State(DEFAULTS.copy()) # must be declared before tab so handle_save can reference it
|
| 267 |
+
|
| 268 |
+
with gr.Tab("βοΈ Settings"):
|
| 269 |
+
|
| 270 |
+
gr.Markdown("""
|
| 271 |
+
## βοΈ Spiral City Settings
|
| 272 |
+
*Your preferences save to your character sheet β load it next visit and everything restores.*
|
| 273 |
+
""")
|
| 274 |
+
|
| 275 |
+
# ββ LOAD FROM SHEET βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 276 |
+
with gr.Accordion("π Load Settings from Sheet", open=False):
|
| 277 |
+
settings_sheet_load = gr.File(label="Upload your .json sheet", file_types=[".json"])
|
| 278 |
+
load_settings_btn = gr.Button("β©οΈ Restore My Settings", variant="secondary")
|
| 279 |
+
load_settings_status = gr.Markdown("")
|
| 280 |
+
|
| 281 |
+
gr.Markdown("---")
|
| 282 |
+
|
| 283 |
+
# ββ APPEARANCE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 284 |
+
gr.Markdown("### π¨ Appearance")
|
| 285 |
+
gr.Markdown("*Theme color and font changes take effect on next page load.*")
|
| 286 |
+
|
| 287 |
+
with gr.Row():
|
| 288 |
+
st_theme_base = gr.Dropdown(
|
| 289 |
+
label="Theme Base",
|
| 290 |
+
choices=list(THEME_BASES.keys()),
|
| 291 |
+
value=DEFAULTS["theme_base"],
|
| 292 |
+
scale=2,
|
| 293 |
+
)
|
| 294 |
+
st_text_size = gr.Radio(
|
| 295 |
+
label="Text Size",
|
| 296 |
+
choices=list(TEXT_SIZES.keys()),
|
| 297 |
+
value=DEFAULTS["text_size"],
|
| 298 |
+
scale=3,
|
| 299 |
+
)
|
| 300 |
+
st_radius = gr.Radio(
|
| 301 |
+
label="Corner Radius",
|
| 302 |
+
choices=list(RADIUS_SIZES.keys()),
|
| 303 |
+
value=DEFAULTS["radius"],
|
| 304 |
+
scale=3,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
with gr.Row():
|
| 308 |
+
st_primary = gr.Dropdown(
|
| 309 |
+
label="Primary Color",
|
| 310 |
+
choices=COLORS,
|
| 311 |
+
value=DEFAULTS["primary_color"],
|
| 312 |
+
scale=2,
|
| 313 |
+
)
|
| 314 |
+
st_secondary = gr.Dropdown(
|
| 315 |
+
label="Secondary Color",
|
| 316 |
+
choices=COLORS,
|
| 317 |
+
value=DEFAULTS["secondary_color"],
|
| 318 |
+
scale=2,
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
with gr.Row():
|
| 322 |
+
st_font = gr.Dropdown(
|
| 323 |
+
label="Font",
|
| 324 |
+
choices=FONTS,
|
| 325 |
+
value=DEFAULTS["font"],
|
| 326 |
+
scale=2,
|
| 327 |
+
info="Main UI font β loaded from Google Fonts",
|
| 328 |
+
)
|
| 329 |
+
st_mono_font = gr.Dropdown(
|
| 330 |
+
label="Mono Font",
|
| 331 |
+
choices=MONO_FONTS,
|
| 332 |
+
value=DEFAULTS["mono_font"],
|
| 333 |
+
scale=2,
|
| 334 |
+
info="Used in code and edit station",
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
gr.Markdown("---")
|
| 338 |
+
|
| 339 |
+
# ββ MODEL SELECTION βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 340 |
+
gr.Markdown("### π€ Model")
|
| 341 |
+
|
| 342 |
+
with gr.Row():
|
| 343 |
+
with gr.Column(scale=3):
|
| 344 |
+
st_model = gr.Dropdown(
|
| 345 |
+
label="Language Model",
|
| 346 |
+
choices=[m for m in ALL_MODEL_LABELS if m != "ββ PRO ββ"],
|
| 347 |
+
value=DEFAULTS["model"],
|
| 348 |
+
)
|
| 349 |
+
gr.Markdown("""
|
| 350 |
+
*π’ **Free tier** β Qwen 7B, Mistral 7B, Llama 8B, Phi-3 Mini*
|
| 351 |
+
*π΅ **Pro tier** β 70B+ models β need HF Pro or billing enabled on your token*
|
| 352 |
+
""")
|
| 353 |
+
with gr.Column(scale=2):
|
| 354 |
+
st_temperature = gr.Slider(
|
| 355 |
+
minimum=0.1, maximum=1.0, value=DEFAULTS["temperature"],
|
| 356 |
+
step=0.05, label="Temperature",
|
| 357 |
+
info="Low = consistent/precise Β· High = creative/wild",
|
| 358 |
+
)
|
| 359 |
+
st_max_tokens = gr.Slider(
|
| 360 |
+
minimum=100, maximum=800, value=DEFAULTS["max_tokens"],
|
| 361 |
+
step=50, label="Max Response Length (tokens)",
|
| 362 |
+
info="~75 words per 100 tokens",
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
gr.Markdown("---")
|
| 366 |
+
|
| 367 |
+
# ββ CHAT BEHAVIOR βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 368 |
+
gr.Markdown("### π¬ Chat Behavior")
|
| 369 |
+
|
| 370 |
+
with gr.Row():
|
| 371 |
+
st_response_style = gr.Radio(
|
| 372 |
+
label="Sky's Response Style",
|
| 373 |
+
choices=list(RESPONSE_STYLES.keys()),
|
| 374 |
+
value=DEFAULTS["response_style"],
|
| 375 |
+
scale=3,
|
| 376 |
+
)
|
| 377 |
+
st_show_status = gr.Checkbox(
|
| 378 |
+
label="Show context status bar in chat",
|
| 379 |
+
value=DEFAULTS["show_status_bar"],
|
| 380 |
+
scale=1,
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
with gr.Row():
|
| 384 |
+
for style, desc in RESPONSE_STYLES.items():
|
| 385 |
+
gr.Markdown(f"**{style}:** {desc}")
|
| 386 |
+
|
| 387 |
+
gr.Markdown("---")
|
| 388 |
+
|
| 389 |
+
# ββ SAVE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 390 |
+
gr.Markdown("### πΎ Save Settings")
|
| 391 |
+
|
| 392 |
+
with gr.Row():
|
| 393 |
+
settings_sheet_save = gr.File(
|
| 394 |
+
label="π Character Sheet (to save settings into)",
|
| 395 |
+
file_types=[".json"],
|
| 396 |
+
scale=3,
|
| 397 |
+
)
|
| 398 |
+
save_settings_btn = gr.Button(
|
| 399 |
+
"πΎ Apply & Save Settings",
|
| 400 |
+
variant="primary",
|
| 401 |
+
scale=2,
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
settings_status = gr.Markdown("")
|
| 405 |
+
settings_download = gr.File(label="β¬οΈ Download Updated Sheet", visible=False)
|
| 406 |
+
|
| 407 |
+
# ββ ALL SETTING INPUTS β ordered list βββββββββββββββββββββββββββββββββ
|
| 408 |
+
all_setting_inputs = [
|
| 409 |
+
st_theme_base, st_primary, st_secondary,
|
| 410 |
+
st_font, st_mono_font, st_text_size, st_radius,
|
| 411 |
+
st_model, st_temperature, st_max_tokens,
|
| 412 |
+
st_response_style, st_show_status,
|
| 413 |
+
]
|
| 414 |
+
|
| 415 |
+
# ββ EVENTS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 416 |
+
|
| 417 |
+
# Apply & Save β updates states AND saves to sheet
|
| 418 |
+
def handle_save(*args):
|
| 419 |
+
*setting_args, sheet_file = args
|
| 420 |
+
model_id, temp, tokens, style_inj, sdict, status = apply_settings(*setting_args)
|
| 421 |
+
path, save_msg = save_settings_to_sheet(sheet_file, sdict)
|
| 422 |
+
full_status = status + "\n\n" + save_msg
|
| 423 |
+
return model_id, temp, tokens, style_inj, sdict, full_status, path
|
| 424 |
+
|
| 425 |
+
save_settings_btn.click(
|
| 426 |
+
fn=handle_save,
|
| 427 |
+
inputs=all_setting_inputs + [settings_sheet_save],
|
| 428 |
+
outputs=[
|
| 429 |
+
model_state, temperature_state, max_tokens_state,
|
| 430 |
+
style_state, settings_dict_state,
|
| 431 |
+
settings_status, settings_download,
|
| 432 |
+
],
|
| 433 |
+
).then(
|
| 434 |
+
fn=lambda p: gr.update(visible=True, value=p) if p else gr.update(visible=False),
|
| 435 |
+
inputs=[settings_download],
|
| 436 |
+
outputs=[settings_download],
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
# Load from sheet β restores all fields
|
| 440 |
+
def handle_load(sheet_file):
|
| 441 |
+
s = load_settings_from_sheet(sheet_file)
|
| 442 |
+
vals = settings_to_list(s)
|
| 443 |
+
return vals + [f"β
Settings restored from sheet!"]
|
| 444 |
+
|
| 445 |
+
load_settings_btn.click(
|
| 446 |
+
fn=handle_load,
|
| 447 |
+
inputs=[settings_sheet_load],
|
| 448 |
+
outputs=all_setting_inputs + [load_settings_status],
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
return all_setting_inputs, settings_dict_state
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
# NIMBIS: spiral_settings.py β settings module only, no standalone launch.
|
| 455 |
+
# Import into app.py via: from spiral_settings import build_settings_tab, get_style_injection, DEFAULTS
|
| 456 |
+
# End of file.
|