eldarski commited on
Commit
80f4262
Β·
1 Parent(s): c32653f

🎨 Deploy Gradio Themer Demo - Dual Track Hackathon Submission (Track 1: MCP Server + Track 2: Custom Component)

Browse files
Files changed (7) hide show
  1. README.md +214 -7
  2. app.py +113 -0
  3. css.css +157 -0
  4. mcp_tools.py +728 -0
  5. page.py +568 -0
  6. requirements.txt +8 -0
  7. user_themes.json +321 -0
README.md CHANGED
@@ -1,12 +1,219 @@
1
  ---
2
- title: Gradio Themer Demo
3
- emoji: ⚑
4
- colorFrom: pink
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 5.33.1
8
  app_file: app.py
9
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: 🎨 Gradio Themer - Demo & MCP Server
3
+ emoji: 🎨
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.31.0
8
  app_file: app.py
9
+ pinned: true
10
+ license: mit
11
+ short_description: User-configurable themes for Gradio applications with MCP server integration
12
+ models:
13
+ - MasterControlAIML/DeepSeek-R1-Qwen2.5-1.5b-SFT-R1-JSON-Unstructured-To-Structured
14
+ tags:
15
+ - mcp-server-track
16
+ - custom-component-track
17
+ - Agents-MCP-Hackathon
18
+ - gradio
19
+ - theming
20
+ - agent-ui
21
  ---
22
 
23
+ # 🎨 Gradio Themer - Demo & MCP Server
24
+
25
+ **πŸ† Dual Track Hackathon Submission**: Custom Component + MCP Server
26
+
27
+ ## πŸš€ Features
28
+
29
+ - **Dynamic Theme System**: User-configurable themes via JSON
30
+ - **AI-Powered Conversion**: Convert CSS/descriptions to theme JSON using HuggingFace models
31
+ - **MCP Server Integration**: 4 practical tools for theme development
32
+ - **Production Ready**: Built and tested package
33
+
34
+ ## πŸ€– MCP Tools (Production-Focused)
35
+
36
+ ### 1. `generate_theme`
37
+
38
+ **Purpose**: Create complete theme JSON with intelligent color harmonies
39
+ **Parameters**:
40
+
41
+ - `theme_name`: Name of the theme
42
+ - `primary_color`: Main color (hex format, default: "#3b82f6")
43
+ - `theme_style`: "light" or "dark" (default: "light")
44
+ - `color_harmony`: "complementary", "triadic", "analogous", "monochromatic" (default: "complementary")
45
+
46
+ **Output**: Complete theme JSON with harmonious colors and accessibility considerations
47
+
48
+ ### 2. `convert_css_to_theme`
49
+
50
+ **Purpose**: Convert existing CSS or natural language descriptions to theme JSON
51
+ **Parameters**:
52
+
53
+ - `css_input`: CSS code or style description
54
+ - `theme_name`: Name for the converted theme (default: "converted_theme")
55
+
56
+ **Output**: Theme JSON extracted from CSS using AI and pattern matching
57
+
58
+ ### 3. `apply_theme_preview`
59
+
60
+ **Purpose**: Generate CSS code for immediate theme preview in any Gradio app
61
+ **Parameters**:
62
+
63
+ - `theme_json`: Theme JSON configuration
64
+
65
+ **Output**: Ready-to-use CSS code for instant theme application
66
+
67
+ ### 4. `export_theme_package`
68
+
69
+ **Purpose**: Export complete theme package with JSON, CSS, and usage instructions
70
+ **Parameters**:
71
+
72
+ - `theme_json`: Theme JSON configuration
73
+ - `package_name`: Name for the package files (default: "my_theme")
74
+
75
+ **Output**: Complete package with files, documentation, and sample code
76
+
77
+ ## 🎯 Why These Tools?
78
+
79
+ **❌ Removed**: Package management tools (not useful for theme development)
80
+ **βœ… Added**: Practical workflow tools that developers actually need:
81
+
82
+ 1. **Smart Theme Generation**: Uses color theory for harmonious palettes
83
+ 2. **CSS Migration**: Convert existing designs to Gradio themes
84
+ 3. **Instant Preview**: Get CSS for immediate testing
85
+ 4. **Complete Export**: Ready-to-use packages with documentation
86
+
87
+ ## πŸ”§ Usage Examples
88
+
89
+ ### Generate Theme with Color Harmony
90
+
91
+ ```python
92
+ # Create complementary color scheme from blue primary
93
+ generate_theme("Ocean Theme", "#0ea5e9", "light", "complementary")
94
+ ```
95
+
96
+ ### Convert Existing CSS
97
+
98
+ ```python
99
+ # Convert CSS variables to theme JSON
100
+ convert_css_to_theme("""
101
+ :root {
102
+ --primary: #3b82f6;
103
+ --background: #ffffff;
104
+ --text: #1e293b;
105
+ }
106
+ """, "Converted Blue Theme")
107
+ ```
108
+
109
+ ### Get Preview CSS
110
+
111
+ ```python
112
+ # Generate CSS for immediate application
113
+ apply_theme_preview('{"ocean_theme": {"colors": {...}}}')
114
+ ```
115
+
116
+ ### Export Complete Package
117
+
118
+ ```python
119
+ # Create ready-to-use theme package
120
+ export_theme_package('{"my_theme": {...}}', "awesome_theme")
121
+ ```
122
+
123
+ ## πŸ† Production Benefits
124
+
125
+ 1. **Workflow Efficiency**: Tools cover the complete theme development cycle
126
+ 2. **Color Theory**: Automatic generation of harmonious color schemes
127
+ 3. **Accessibility**: Built-in contrast considerations
128
+ 4. **Migration Support**: Easy conversion from existing CSS
129
+ 5. **Instant Testing**: Preview CSS for immediate application
130
+ 6. **Complete Packages**: Everything needed for distribution
131
+
132
+ ## πŸ›  Setup & Installation
133
+
134
+ ### For HuggingFace Spaces:
135
+
136
+ This Space is ready to deploy! Just upload all files to your HF Space.
137
+
138
+ ### For Local Development:
139
+
140
+ ```bash
141
+ pip install gradio>=5.31.0 gradio-themer>=0.1.0 requests transformers torch
142
+ python app.py
143
+ ```
144
+
145
+ ### For MCP Client Integration:
146
+
147
+ Configure your MCP client (Claude Desktop, Cursor, etc.) to connect to this Space:
148
+
149
+ ```json
150
+ {
151
+ "mcpServers": {
152
+ "gradio-themer": {
153
+ "command": "python",
154
+ "args": ["-m", "gradio_mcp"],
155
+ "env": {
156
+ "GRADIO_MCP_URL": "https://your-space-url.hf.space"
157
+ }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ ## πŸ“‹ Hackathon Submission
164
+
165
+ **Track 1 (MCP Server)**:
166
+
167
+ - Tag: `mcp-server-track`
168
+ - 4 production-focused MCP tools
169
+ - Complete theme development workflow
170
+
171
+ **Track 2 (Custom Component)**:
172
+
173
+ - Tag: `custom-component-track`
174
+ - Published package: `gradio_themer`
175
+ - Interactive Gradio component
176
+
177
+ ## 🎨 Demo Features
178
+
179
+ - **Random Theme Button**: Apply any of 13+ available themes
180
+ - **CSS to JSON Converter**: AI-powered conversion using HuggingFace models
181
+ - **Theme Showcase**: Live preview of different theme styles
182
+ - **MCP Integration**: Embedded MCP server functionality
183
+
184
+ ## πŸ“¦ Package Information
185
+
186
+ - **Package**: `gradio_themer-0.1.0`
187
+ - **Class**: `GradioThemer`
188
+ - **Import**: `from gradio_themer import GradioThemer`
189
+ - **Status**: Production ready
190
+
191
+ ## πŸ”— Links
192
+
193
+ - **🎨 This Demo Space**: `https://huggingface.co/spaces/eldarski/gradio-themer-demo`
194
+ - **πŸ“¦ PyPI Package**: `https://pypi.org/project/gradio-themer/`
195
+ - **πŸ“š GitHub Repository**: `[Repository URL]`
196
+ - **🎬 MCP Demo Video**: `[Video URL - Coming Soon]`
197
+ - **🎬 Component Demo Video**: `[Video URL - Coming Soon]`
198
+
199
+ ---
200
+
201
+ **Built for HuggingFace Gradio Hackathon 2024**
202
+
203
+ ## πŸ“Ί Demo Videos
204
+
205
+ ### πŸ€– Track 1: MCP Server Demo (`mcp-server-track`)
206
+
207
+ **MCP Server in Action with Cursor AI**: [Video Demo Link - Coming Soon]
208
+
209
+ - Shows the MCP server being used with Cursor AI for automated theme generation
210
+ - Demonstrates all 4 MCP tools in a real development workflow
211
+ - Live theme creation and application via AI agent
212
+
213
+ ### 🎨 Track 2: Custom Component Demo (`custom-component-track`)
214
+
215
+ **Component Usage Demo**: [Video Demo Link - Coming Soon]
216
+
217
+ - Interactive demonstration of the GradioThemer custom component
218
+ - Shows theme switching and configuration in various Gradio applications
219
+ - Demonstrates the published package in action
app.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio Themer - MCP Server Entry Point
3
+ Clean entry point that exposes only the 4 intended MCP tools.
4
+ """
5
+
6
+ import os
7
+ import gradio as gr
8
+
9
+ # CRITICAL: Enable MCP server mode (as per GRADIO_MCP_HF_SPACES_GUIDE.md)
10
+ os.environ["GRADIO_MCP_SERVER"] = "True"
11
+
12
+ # Import the 4 MCP tools - these will be exposed by the MCP server
13
+ from mcp_tools import (
14
+ setup_package,
15
+ generate_theme,
16
+ convert_css_to_theme,
17
+ generate_app_code,
18
+ )
19
+
20
+ if __name__ == "__main__":
21
+ try:
22
+ import page
23
+
24
+ # Add hidden MCP endpoints to the existing demo
25
+ with page.demo:
26
+ # Add hidden MCP tool endpoints (invisible to users, visible to MCP)
27
+
28
+ # Hidden setup_package endpoint
29
+ setup_btn = gr.Button("Setup Package", visible=False)
30
+ setup_output = gr.JSON(visible=False)
31
+ setup_btn.click(
32
+ fn=setup_package, outputs=setup_output, api_name="setup_package"
33
+ )
34
+
35
+ # Hidden generate_theme endpoint
36
+ theme_name_input = gr.Textbox(visible=False)
37
+ primary_color_input = gr.Textbox(visible=False, value="#3b82f6")
38
+ theme_style_input = gr.Textbox(visible=False, value="light")
39
+ accent_color_input = gr.Textbox(visible=False, value="")
40
+ generate_theme_btn = gr.Button("Generate Theme", visible=False)
41
+ generate_theme_output = gr.JSON(visible=False)
42
+ generate_theme_btn.click(
43
+ fn=generate_theme,
44
+ inputs=[
45
+ theme_name_input,
46
+ primary_color_input,
47
+ theme_style_input,
48
+ accent_color_input,
49
+ ],
50
+ outputs=generate_theme_output,
51
+ api_name="generate_theme",
52
+ )
53
+
54
+ # Hidden convert_css_to_theme endpoint
55
+ css_input = gr.Textbox(visible=False)
56
+ convert_theme_name_input = gr.Textbox(
57
+ visible=False, value="converted_theme"
58
+ )
59
+ user_token_input = gr.Textbox(visible=False, value="")
60
+ model_choice_input = gr.Textbox(visible=False, value="qwen")
61
+ convert_css_btn = gr.Button("Convert CSS", visible=False)
62
+ convert_css_output = gr.Textbox(visible=False)
63
+ convert_css_btn.click(
64
+ fn=convert_css_to_theme,
65
+ inputs=[
66
+ css_input,
67
+ convert_theme_name_input,
68
+ user_token_input,
69
+ model_choice_input,
70
+ ],
71
+ outputs=convert_css_output,
72
+ api_name="convert_css_to_theme",
73
+ )
74
+
75
+ # Hidden generate_app_code endpoint
76
+ app_theme_names_input = gr.Textbox(
77
+ visible=False, value="ocean_breeze,sunset_orange"
78
+ )
79
+ app_title_input = gr.Textbox(visible=False, value="My Themed App")
80
+ include_components_input = gr.Textbox(
81
+ visible=False, value="button,textbox,slider"
82
+ )
83
+ generate_app_btn = gr.Button("Generate App", visible=False)
84
+ generate_app_output = gr.Textbox(visible=False)
85
+ generate_app_btn.click(
86
+ fn=generate_app_code,
87
+ inputs=[
88
+ app_theme_names_input,
89
+ app_title_input,
90
+ include_components_input,
91
+ ],
92
+ outputs=generate_app_output,
93
+ api_name="generate_app_code",
94
+ )
95
+
96
+ # Launch the demo with MCP server enabled
97
+ page.demo.launch(
98
+ mcp_server=True, # CRITICAL: Enable MCP server functionality
99
+ server_name="0.0.0.0",
100
+ server_port=7860,
101
+ share=False,
102
+ debug=True,
103
+ allowed_paths=["./"],
104
+ )
105
+
106
+ except ImportError as e:
107
+ print(f"❌ Error importing demo interface: {e}")
108
+ print("Make sure page.py exists and contains the 'demo' variable")
109
+ exit(1)
110
+ except AttributeError as e:
111
+ print(f"❌ Error accessing demo object: {e}")
112
+ print("Make sure page.py contains a 'demo' variable")
113
+ exit(1)
css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
mcp_tools.py ADDED
@@ -0,0 +1,728 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import random
4
+ import colorsys
5
+ import re
6
+ import subprocess
7
+ import sys
8
+ from typing import Dict, Any, Optional, Tuple
9
+ from pathlib import Path
10
+ import requests
11
+
12
+ # CRITICAL: Enable MCP server mode
13
+ os.environ["GRADIO_MCP_SERVER"] = "True"
14
+
15
+ # Model API configuration for CSS to theme conversion
16
+ AVAILABLE_MODELS = {
17
+ "qwen": {
18
+ "hf_model": "Qwen/Qwen2.5-Coder-7B",
19
+ "nebius_model": "Qwen/Qwen2.5-Coder-7B",
20
+ "name": "Qwen2.5-Coder-7B",
21
+ },
22
+ "llama": {
23
+ "hf_model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
24
+ "nebius_model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
25
+ "name": "Meta-Llama-3.1-8B-Instruct",
26
+ },
27
+ }
28
+
29
+ # Nebius Studio API configuration
30
+ NEBIUS_API_URL = "https://api.studio.nebius.ai/v1/chat/completions"
31
+
32
+
33
+ def setup_package() -> Dict[str, Any]:
34
+ """
35
+ Install and verify the gradio-themer package is available and working.
36
+
37
+ Returns:
38
+ Dict[str, Any]: Status information about package installation
39
+ """
40
+ try:
41
+ # Test import to verify package is available
42
+ from gradio_themer import GradioThemer
43
+
44
+ return {
45
+ "status": "success",
46
+ "message": "βœ… gradio-themer package is installed and working",
47
+ "package_info": {
48
+ "name": "gradio_themer",
49
+ "class": "GradioThemer",
50
+ "version": "0.1.0",
51
+ },
52
+ "usage_example": """
53
+ import gradio as gr
54
+ from gradio_themer import GradioThemer
55
+
56
+ with gr.Blocks() as demo:
57
+ themer = GradioThemer(label="Theme Selector")
58
+ themer.change(fn=handle_theme_change, inputs=[themer])
59
+ """,
60
+ }
61
+ except ImportError as e:
62
+ return {
63
+ "status": "error",
64
+ "message": f"❌ gradio-themer package not found: {str(e)}",
65
+ "solution": "Install with: pip install gradio-themer",
66
+ }
67
+ except Exception as e:
68
+ return {"status": "error", "message": f"❌ Error testing package: {str(e)}"}
69
+
70
+
71
+ def generate_theme(
72
+ theme_name: str,
73
+ primary_color: str = "#3b82f6",
74
+ theme_style: str = "light",
75
+ accent_color: Optional[str] = None,
76
+ ) -> Dict[str, Any]:
77
+ """
78
+ Generate a complete theme JSON configuration with intelligent color harmonies.
79
+
80
+ Args:
81
+ theme_name (str): Name for the new theme
82
+ primary_color (str): Primary color in hex format (e.g., "#3b82f6")
83
+ theme_style (str): Theme style - "light", "dark", or "auto"
84
+ accent_color (Optional[str]): Optional accent color, auto-generated if not provided
85
+
86
+ Returns:
87
+ Dict[str, Any]: Complete theme configuration ready for use
88
+ """
89
+ try:
90
+ # Generate intelligent color palette based on primary color
91
+ colors = _generate_color_palette(primary_color, theme_style, accent_color)
92
+
93
+ # Create theme configuration
94
+ theme_config = {
95
+ theme_name.lower().replace(" ", "_"): {
96
+ "name": theme_name,
97
+ "colors": colors,
98
+ "background": colors["base-200"],
99
+ "style": theme_style,
100
+ "generated": True,
101
+ "font": {
102
+ "family": "Inter",
103
+ "type": "google_font",
104
+ "name": "Inter",
105
+ },
106
+ }
107
+ }
108
+
109
+ return {
110
+ "status": "success",
111
+ "message": f"βœ… Generated theme '{theme_name}' with {theme_style} style",
112
+ "theme_config": theme_config,
113
+ "usage_instructions": f"""
114
+ 1. Save this JSON to your user_themes.json file
115
+ 2. Use in your Gradio app:
116
+ themer = GradioThemer(
117
+ value={{"currentTheme": "{theme_name.lower().replace(' ', '_')}"}},
118
+ theme_config_path="user_themes.json"
119
+ )
120
+ """,
121
+ "color_info": {
122
+ "primary": colors["primary"],
123
+ "background": colors["base-200"],
124
+ "text": colors["base-content"],
125
+ "accent": colors["accent"],
126
+ },
127
+ }
128
+
129
+ except Exception as e:
130
+ return {
131
+ "status": "error",
132
+ "message": f"❌ Error generating theme: {str(e)}",
133
+ "suggestion": "Check that primary_color is in valid hex format (e.g., '#3b82f6')",
134
+ }
135
+
136
+
137
+ def convert_css_to_theme(
138
+ css_input: str,
139
+ theme_name: str = "converted_theme",
140
+ user_token: str = "",
141
+ model_choice: str = "qwen",
142
+ ) -> str:
143
+ """
144
+ Convert CSS styles or style descriptions into standardized theme JSON format using HF hosted LLM.
145
+
146
+ Args:
147
+ css_input (str): CSS code or natural language style description
148
+ theme_name (str): Name for the converted theme
149
+ user_token (str): Optional Nebius API token for better performance
150
+ model_choice (str): AI model to use ("qwen" or "llama")
151
+
152
+ Returns:
153
+ str: JSON string of converted theme configuration
154
+ """
155
+ if not css_input.strip():
156
+ return json.dumps(
157
+ {
158
+ "status": "error",
159
+ "message": "Please provide CSS code or describe your desired style.",
160
+ },
161
+ indent=2,
162
+ )
163
+
164
+ try:
165
+ # Create the prompt with schema definition
166
+ SCHEMA = """{
167
+ "themes": {
168
+ "generated_theme": {
169
+ "name": "Generated Theme",
170
+ "colors": {
171
+ "base-100": "#ffffff",
172
+ "base-200": "#f8fafc",
173
+ "base-300": "#e2e8f0",
174
+ "base-content": "#1e293b",
175
+ "primary": "#3b82f6",
176
+ "primary-content": "#ffffff",
177
+ "secondary": "#64748b",
178
+ "secondary-content": "#ffffff",
179
+ "accent": "#f59e0b",
180
+ "accent-content": "#000000",
181
+ "neutral": "#374151",
182
+ "neutral-content": "#ffffff",
183
+ "error": "#ef4444",
184
+ "error-content": "#ffffff"
185
+ },
186
+ "background": "#f1f5f9",
187
+ "font": {
188
+ "family": "Inter",
189
+ "type": "google_font",
190
+ "name": "Inter"
191
+ }
192
+ }
193
+ }
194
+ }"""
195
+
196
+ ALPACA_PROMPT = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
197
+
198
+ ### Instruction:
199
+ Convert the provided CSS code or style description into a JSON theme configuration that follows the exact schema structure. Extract colors from CSS variables, class names, or generate appropriate colors based on the description. Return ONLY valid JSON that matches the schema format.
200
+
201
+ Expected JSON Schema:
202
+ {schema}
203
+
204
+ ### Input:
205
+ {input_text}
206
+
207
+ ### Response:
208
+ """
209
+
210
+ prompt = ALPACA_PROMPT.format(schema=SCHEMA, input_text=css_input)
211
+
212
+ # Use the AI API to convert CSS to theme
213
+ result = _query_ai_api(prompt, user_token, model_choice)
214
+
215
+ # Process the AI response
216
+ generated_text = ""
217
+
218
+ if user_token and user_token.strip():
219
+ # Handle Nebius API response (OpenAI format)
220
+ if isinstance(result, dict):
221
+ if "error" in result or "detail" in result:
222
+ error_msg = result.get(
223
+ "error", result.get("detail", "Unknown error")
224
+ )
225
+ if (
226
+ "authentication" in str(error_msg).lower()
227
+ or "unauthorized" in str(error_msg).lower()
228
+ ):
229
+ return json.dumps(
230
+ {
231
+ "status": "error",
232
+ "message": "❌ Invalid Nebius API token provided. Please check your Nebius API key.",
233
+ },
234
+ indent=2,
235
+ )
236
+ else:
237
+ return json.dumps(
238
+ {
239
+ "status": "error",
240
+ "message": f"❌ Nebius API error: {error_msg}",
241
+ },
242
+ indent=2,
243
+ )
244
+ elif "choices" in result and len(result["choices"]) > 0:
245
+ generated_text = (
246
+ result["choices"][0].get("message", {}).get("content", "")
247
+ )
248
+ else:
249
+ return json.dumps(
250
+ {
251
+ "status": "error",
252
+ "message": f"❌ Unexpected Nebius API response format",
253
+ },
254
+ indent=2,
255
+ )
256
+ else:
257
+ # Handle HuggingFace Zero API response
258
+ if isinstance(result, list) and len(result) > 0:
259
+ generated_text = result[0].get("generated_text", "")
260
+ elif isinstance(result, dict):
261
+ if "error" in result:
262
+ if "loading" in result["error"].lower():
263
+ return json.dumps(
264
+ {
265
+ "status": "error",
266
+ "message": f"πŸ”„ Model is still loading on HuggingFace servers. Please try again in a few moments.",
267
+ },
268
+ indent=2,
269
+ )
270
+ else:
271
+ return json.dumps(
272
+ {
273
+ "status": "error",
274
+ "message": f"❌ HuggingFace API error: {result['error']}",
275
+ },
276
+ indent=2,
277
+ )
278
+ generated_text = result.get("generated_text", "")
279
+
280
+ if not generated_text:
281
+ return json.dumps(
282
+ {
283
+ "status": "error",
284
+ "message": "❌ No response generated. Please try again or rephrase your request.",
285
+ },
286
+ indent=2,
287
+ )
288
+
289
+ # Clean up and extract JSON from response
290
+ json_part = _extract_json_from_response(generated_text)
291
+
292
+ if json_part.startswith("❌"):
293
+ return json.dumps({"status": "error", "message": json_part}, indent=2)
294
+
295
+ # Try to parse and validate the JSON
296
+ try:
297
+ parsed_json = json.loads(json_part)
298
+
299
+ # Ensure proper structure
300
+ if "themes" in parsed_json:
301
+ for theme_key, theme_data in parsed_json["themes"].items():
302
+ if "background" not in theme_data:
303
+ theme_data["background"] = theme_data.get("colors", {}).get(
304
+ "base-100", "#ffffff"
305
+ )
306
+ if "font" not in theme_data:
307
+ theme_data["font"] = {
308
+ "family": "Inter",
309
+ "type": "google_font",
310
+ "name": "Inter",
311
+ }
312
+
313
+ return json.dumps(parsed_json, indent=2)
314
+
315
+ except json.JSONDecodeError as e:
316
+ return json.dumps(
317
+ {
318
+ "status": "error",
319
+ "message": f"❌ AI model generated invalid JSON. Please try rephrasing your request.\n\nError: {str(e)}",
320
+ },
321
+ indent=2,
322
+ )
323
+
324
+ except Exception as e:
325
+ return json.dumps(
326
+ {
327
+ "status": "error",
328
+ "message": f"❌ Error converting CSS to theme: {str(e)}",
329
+ },
330
+ indent=2,
331
+ )
332
+
333
+
334
+ def generate_app_code(
335
+ theme_names: str = "ocean_breeze,sunset_orange",
336
+ app_title: str = "My Themed App",
337
+ include_components: str = "button,textbox,slider",
338
+ ) -> str:
339
+ """
340
+ Generate complete Gradio application code with integrated theming system.
341
+
342
+ Args:
343
+ theme_names (str): Comma-separated list of theme names to include
344
+ app_title (str): Title for the generated application
345
+ include_components (str): Comma-separated list of components to include
346
+
347
+ Returns:
348
+ str: Complete Python code for a themed Gradio application
349
+ """
350
+ try:
351
+ # Parse input parameters
352
+ themes = [t.strip() for t in theme_names.split(",") if t.strip()]
353
+ components = [
354
+ c.strip().lower() for c in include_components.split(",") if c.strip()
355
+ ]
356
+
357
+ # Generate the complete app code
358
+ app_code = f'''"""
359
+ {app_title}
360
+ A themed Gradio application generated with gradio-themer
361
+ """
362
+
363
+ import gradio as gr
364
+ from gradio_themer import GradioThemer
365
+ import json
366
+
367
+ def handle_theme_change(theme_data):
368
+ """Handle theme changes from the GradioThemer component"""
369
+ print(f"Theme changed to: {{theme_data.get('currentTheme', 'default')}}")
370
+ return theme_data
371
+
372
+ # Custom CSS for enhanced styling
373
+ custom_css = """
374
+ .theme-container {{
375
+ max-width: 1200px;
376
+ margin: 0 auto;
377
+ padding: 2rem;
378
+ }}
379
+
380
+ .section {{
381
+ margin-bottom: 2rem;
382
+ padding: 1.5rem;
383
+ border-radius: 8px;
384
+ border: 1px solid #e2e8f0;
385
+ background: #fafafa;
386
+ }}
387
+ """
388
+
389
+ # Build the {app_title}
390
+ with gr.Blocks(css=custom_css, title="{app_title}") as demo:
391
+
392
+ # Header
393
+ with gr.Column(elem_classes="theme-container"):
394
+ gr.Markdown("# {app_title}")
395
+ gr.Markdown("**Powered by gradio-themer** - Dynamic theme system")
396
+
397
+ # Theme Controller
398
+ themer = GradioThemer(
399
+ value={{
400
+ "currentTheme": "{themes[0] if themes else 'ocean_breeze'}",
401
+ "type": "custom",
402
+ "removeBorders": True
403
+ }},
404
+ label="🎨 Theme Selector",
405
+ theme_config_path="user_themes.json"
406
+ )
407
+
408
+ # Main content sections
409
+ with gr.Column(elem_classes="theme-container"):
410
+ '''
411
+
412
+ # Add components based on user selection
413
+ if "button" in components:
414
+ app_code += """
415
+ # Button demonstration
416
+ with gr.Column(elem_classes="section"):
417
+ gr.Markdown("### Button Components")
418
+ with gr.Row():
419
+ btn_primary = gr.Button("Primary Action", variant="primary")
420
+ btn_secondary = gr.Button("Secondary Action", variant="secondary")
421
+ btn_stop = gr.Button("Stop Action", variant="stop")
422
+ """
423
+
424
+ if "textbox" in components:
425
+ app_code += """
426
+ # Text input demonstration
427
+ with gr.Column(elem_classes="section"):
428
+ gr.Markdown("### Text Input Components")
429
+ text_input = gr.Textbox(
430
+ label="Enter your text",
431
+ placeholder="Type something here...",
432
+ lines=3
433
+ )
434
+ text_output = gr.Textbox(label="Output", interactive=False)
435
+ """
436
+
437
+ if "slider" in components:
438
+ app_code += """
439
+ # Slider demonstration
440
+ with gr.Column(elem_classes="section"):
441
+ gr.Markdown("### Slider Components")
442
+ slider_value = gr.Slider(
443
+ minimum=0,
444
+ maximum=100,
445
+ value=50,
446
+ label="Adjust Value"
447
+ )
448
+ slider_output = gr.Number(label="Current Value", value=50)
449
+ """
450
+
451
+ if "dropdown" in components:
452
+ app_code += """
453
+ # Dropdown demonstration
454
+ with gr.Column(elem_classes="section"):
455
+ gr.Markdown("### Selection Components")
456
+ dropdown = gr.Dropdown(
457
+ choices=["Option 1", "Option 2", "Option 3"],
458
+ label="Choose an option",
459
+ value="Option 1"
460
+ )
461
+ radio = gr.Radio(
462
+ choices=["Choice A", "Choice B", "Choice C"],
463
+ label="Select one",
464
+ value="Choice A"
465
+ )
466
+ """
467
+
468
+ # Add event handlers
469
+ app_code += """
470
+ # Event handlers
471
+ themer.change(fn=handle_theme_change, inputs=[themer])
472
+
473
+ """
474
+
475
+ # Add simple interactions if textbox is included
476
+ if "textbox" in components:
477
+ app_code += """ # Simple text processing
478
+ def process_text(text):
479
+ return f"Processed: {text.upper()}"
480
+
481
+ text_input.change(fn=process_text, inputs=[text_input], outputs=[text_output])
482
+ """
483
+
484
+ # Add slider interaction if slider is included
485
+ if "slider" in components:
486
+ app_code += """ # Slider value update
487
+ slider_value.change(fn=lambda x: x, inputs=[slider_value], outputs=[slider_output])
488
+ """
489
+
490
+ # Launch configuration
491
+ app_code += """
492
+ # Launch the application
493
+ if __name__ == "__main__":
494
+ demo.launch(
495
+ server_name="0.0.0.0",
496
+ server_port=7860,
497
+ share=False,
498
+ debug=True
499
+ )
500
+ """
501
+
502
+ return app_code
503
+
504
+ except Exception as e:
505
+ return f"""# Error generating app code
506
+ # {str(e)}
507
+
508
+ import gradio as gr
509
+
510
+ with gr.Blocks(title="Error") as demo:
511
+ gr.Markdown("❌ Failed to generate app code. Please check your parameters.")
512
+
513
+ if __name__ == "__main__":
514
+ demo.launch()
515
+ """
516
+
517
+
518
+ # Helper functions for theme generation and API calls
519
+
520
+
521
+ def _generate_color_palette(
522
+ primary_color: str, style: str, accent_color: Optional[str] = None
523
+ ) -> Dict[str, str]:
524
+ """Generate a complete color palette based on primary color and style"""
525
+ try:
526
+ # Convert primary color to RGB for calculations
527
+ rgb = _hex_to_rgb(primary_color)
528
+ hsl = _rgb_to_hsl(rgb)
529
+
530
+ # Generate base colors based on style
531
+ if style == "dark":
532
+ base_100 = "#1a1a1a"
533
+ base_200 = "#2d2d2d"
534
+ base_300 = "#404040"
535
+ base_content = "#ffffff"
536
+ else: # light style
537
+ base_100 = "#ffffff"
538
+ base_200 = "#f8fafc"
539
+ base_300 = "#e2e8f0"
540
+ base_content = "#1e293b"
541
+
542
+ # Generate accent color if not provided
543
+ if not accent_color:
544
+ accent_color = _generate_complementary_color(primary_color, 0.1)
545
+
546
+ # Generate secondary color (triadic)
547
+ secondary_color = _generate_triadic_color(primary_color)
548
+
549
+ # Create complete color palette
550
+ colors = {
551
+ "base-100": base_100,
552
+ "base-200": base_200,
553
+ "base-300": base_300,
554
+ "base-content": base_content,
555
+ "primary": primary_color,
556
+ "primary-content": "#ffffff" if style == "light" else "#000000",
557
+ "secondary": secondary_color,
558
+ "secondary-content": "#ffffff",
559
+ "accent": accent_color,
560
+ "accent-content": "#ffffff",
561
+ "neutral": "#374151",
562
+ "neutral-content": "#ffffff",
563
+ "error": "#ef4444",
564
+ "error-content": "#ffffff",
565
+ }
566
+
567
+ return colors
568
+
569
+ except Exception:
570
+ # Fallback to default colors
571
+ return {
572
+ "base-100": "#ffffff",
573
+ "base-200": "#f8fafc",
574
+ "base-300": "#e2e8f0",
575
+ "base-content": "#1e293b",
576
+ "primary": primary_color,
577
+ "primary-content": "#ffffff",
578
+ "secondary": "#64748b",
579
+ "secondary-content": "#ffffff",
580
+ "accent": "#f59e0b",
581
+ "accent-content": "#000000",
582
+ "neutral": "#374151",
583
+ "neutral-content": "#ffffff",
584
+ "error": "#ef4444",
585
+ "error-content": "#ffffff",
586
+ }
587
+
588
+
589
+ def _query_ai_api(prompt: str, user_token: str = "", model_choice: str = "qwen"):
590
+ """Query AI API - Use Nebius if token provided, otherwise HF Zero inference"""
591
+ model_config = AVAILABLE_MODELS.get(model_choice, AVAILABLE_MODELS["qwen"])
592
+
593
+ if user_token and user_token.strip():
594
+ # Use Nebius Studio API with provided token
595
+ headers = {
596
+ "Authorization": f"Bearer {user_token.strip()}",
597
+ "Content-Type": "application/json",
598
+ }
599
+
600
+ payload = {
601
+ "model": model_config["nebius_model"],
602
+ "messages": [{"role": "user", "content": prompt}],
603
+ "max_tokens": 1000,
604
+ "temperature": 0.3,
605
+ "top_p": 0.9,
606
+ }
607
+
608
+ response = requests.post(NEBIUS_API_URL, headers=headers, json=payload)
609
+ return response.json()
610
+
611
+ else:
612
+ # Use HuggingFace Zero Inference API (no token required)
613
+ hf_inference_url = (
614
+ f"https://api-inference.huggingface.co/models/{model_config['hf_model']}"
615
+ )
616
+
617
+ headers = {"Content-Type": "application/json"}
618
+
619
+ payload = {
620
+ "inputs": prompt,
621
+ "parameters": {
622
+ "max_new_tokens": 1000,
623
+ "temperature": 0.3,
624
+ "top_p": 0.9,
625
+ "do_sample": True,
626
+ "return_full_text": False,
627
+ },
628
+ }
629
+
630
+ response = requests.post(hf_inference_url, headers=headers, json=payload)
631
+ return response.json()
632
+
633
+
634
+ def _extract_json_from_response(generated_text: str) -> str:
635
+ """Extract and clean JSON from AI response"""
636
+ json_part = generated_text.strip()
637
+
638
+ # Handle the case where AI returns explanation + JSON
639
+ if "Here is the JSON theme configuration" in json_part:
640
+ json_start = json_part.find("{")
641
+ if json_start != -1:
642
+ json_part = json_part[json_start:]
643
+
644
+ # Remove code block markers
645
+ if json_part.startswith("```json"):
646
+ json_part = json_part[7:]
647
+ elif json_part.startswith("```"):
648
+ json_part = json_part[3:]
649
+ if json_part.endswith("```"):
650
+ json_part = json_part[:-3]
651
+ json_part = json_part.strip()
652
+
653
+ # Find the first { and last } to extract clean JSON
654
+ start_idx = json_part.find("{")
655
+ if start_idx == -1:
656
+ return f"❌ No valid JSON found in response.\n\n**Raw response:**\n{generated_text}"
657
+
658
+ # Find the matching closing brace
659
+ brace_count = 0
660
+ end_idx = -1
661
+ for i in range(start_idx, len(json_part)):
662
+ if json_part[i] == "{":
663
+ brace_count += 1
664
+ elif json_part[i] == "}":
665
+ brace_count -= 1
666
+ if brace_count == 0:
667
+ end_idx = i + 1
668
+ break
669
+
670
+ if end_idx == -1:
671
+ return f"❌ Incomplete JSON found.\n\n**Raw response:**\n{generated_text}"
672
+
673
+ return json_part[start_idx:end_idx]
674
+
675
+
676
+ def _hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
677
+ """Convert hex color to RGB tuple"""
678
+ hex_color = hex_color.lstrip("#")
679
+ return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
680
+
681
+
682
+ def _rgb_to_hsl(rgb: Tuple[int, int, int]) -> Tuple[float, float, float]:
683
+ """Convert RGB to HSL"""
684
+ r, g, b = [x / 255.0 for x in rgb]
685
+ return colorsys.rgb_to_hls(r, g, b)
686
+
687
+
688
+ def _hsl_to_rgb(hsl: Tuple[float, float, float]) -> Tuple[int, int, int]:
689
+ """Convert HSL to RGB"""
690
+ h, l, s = hsl
691
+ r, g, b = colorsys.hls_to_rgb(h, l, s)
692
+ return tuple(int(x * 255) for x in (r, g, b))
693
+
694
+
695
+ def _rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
696
+ """Convert RGB tuple to hex color"""
697
+ return f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
698
+
699
+
700
+ def _generate_complementary_color(base_color: str, lightness_adjust: float = 0) -> str:
701
+ """Generate complementary color"""
702
+ try:
703
+ rgb = _hex_to_rgb(base_color)
704
+ h, l, s = _rgb_to_hsl(rgb)
705
+
706
+ # Shift hue by 180 degrees for complementary
707
+ comp_h = (h + 0.5) % 1.0
708
+ comp_l = max(0, min(1, l + lightness_adjust))
709
+
710
+ comp_rgb = _hsl_to_rgb((comp_h, comp_l, s))
711
+ return _rgb_to_hex(comp_rgb)
712
+ except:
713
+ return "#f59e0b" # Fallback color
714
+
715
+
716
+ def _generate_triadic_color(base_color: str) -> str:
717
+ """Generate triadic color (120 degrees hue shift)"""
718
+ try:
719
+ rgb = _hex_to_rgb(base_color)
720
+ h, l, s = _rgb_to_hsl(rgb)
721
+
722
+ # Shift hue by 120 degrees for triadic
723
+ triadic_h = (h + 0.33) % 1.0
724
+
725
+ triadic_rgb = _hsl_to_rgb((triadic_h, l, s))
726
+ return _rgb_to_hex(triadic_rgb)
727
+ except:
728
+ return "#64748b" # Fallback color
page.py ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio.themes.utils import fonts
3
+ import json
4
+ import random
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+ import requests
9
+ import os
10
+
11
+ # CRITICAL: Enable MCP server mode (as per GRADIO_MCP_HF_SPACES_GUIDE.md)
12
+ os.environ["GRADIO_MCP_SERVER"] = "True"
13
+
14
+ # Import MCP tools from mcp_tools.py to make them available for the MCP server
15
+ try:
16
+ from mcp_tools import (
17
+ setup_package,
18
+ generate_theme,
19
+ convert_css_to_theme,
20
+ generate_app_code,
21
+ )
22
+
23
+ print("βœ… MCP tools imported successfully")
24
+ except ImportError as e:
25
+ print(f"⚠️ Could not import MCP tools: {e}")
26
+
27
+ # New imports for HuggingFace Inference API
28
+ try:
29
+ import requests
30
+
31
+ HF_REQUESTS_AVAILABLE = True
32
+ print("βœ… HuggingFace Inference API available")
33
+ except ImportError:
34
+ HF_REQUESTS_AVAILABLE = False
35
+ print("⚠️ requests library not available")
36
+
37
+ try:
38
+ from gradio_themer import GradioThemer
39
+
40
+ THEMER_AVAILABLE = True
41
+ print("βœ… Using renamed GradioThemer package (gradio_themer-0.1.0)")
42
+ except ImportError:
43
+ print("⚠️ GradioThemer not available, themes will not work")
44
+ THEMER_AVAILABLE = False
45
+
46
+ # Model API configuration
47
+ # Available models for both HF Zero and Nebius inference
48
+ AVAILABLE_MODELS = {
49
+ "qwen": {
50
+ "hf_model": "Qwen/Qwen2.5-Coder-7B",
51
+ "nebius_model": "Qwen/Qwen2.5-Coder-7B",
52
+ "name": "Qwen2.5-Coder-7B",
53
+ },
54
+ "llama": {
55
+ "hf_model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
56
+ "nebius_model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
57
+ "name": "Meta-Llama-3.1-8B-Instruct",
58
+ },
59
+ }
60
+
61
+ # Nebius Studio API configuration
62
+ NEBIUS_API_URL = "https://api.studio.nebius.ai/v1/chat/completions"
63
+
64
+
65
+ def query_ai_api(prompt, user_token=None, model_choice="qwen"):
66
+ """Query AI API - Use Nebius if token provided, otherwise HF Zero inference"""
67
+
68
+ model_config = AVAILABLE_MODELS.get(model_choice, AVAILABLE_MODELS["qwen"])
69
+
70
+ if user_token and user_token.strip():
71
+ # Use Nebius Studio API with provided token
72
+ headers = {
73
+ "Authorization": f"Bearer {user_token.strip()}",
74
+ "Content-Type": "application/json",
75
+ }
76
+
77
+ payload = {
78
+ "model": model_config["nebius_model"],
79
+ "messages": [{"role": "user", "content": prompt}],
80
+ "max_tokens": 1000,
81
+ "temperature": 0.3,
82
+ "top_p": 0.9,
83
+ }
84
+
85
+ response = requests.post(NEBIUS_API_URL, headers=headers, json=payload)
86
+ return response.json()
87
+
88
+ else:
89
+ # Use HuggingFace Zero Inference API (no token required)
90
+ hf_inference_url = (
91
+ f"https://api-inference.huggingface.co/models/{model_config['hf_model']}"
92
+ )
93
+
94
+ headers = {"Content-Type": "application/json"}
95
+
96
+ payload = {
97
+ "inputs": prompt,
98
+ "parameters": {
99
+ "max_new_tokens": 1000,
100
+ "temperature": 0.3,
101
+ "top_p": 0.9,
102
+ "do_sample": True,
103
+ "return_full_text": False,
104
+ },
105
+ }
106
+
107
+ response = requests.post(hf_inference_url, headers=headers, json=payload)
108
+ return response.json()
109
+
110
+
111
+ def convert_css_to_theme_json_ai(input_text, user_token="", model_choice="qwen"):
112
+ # Internal UI function - not for MCP discovery
113
+ if not input_text.strip():
114
+ return "Please provide CSS code or describe your desired style."
115
+
116
+ if not HF_REQUESTS_AVAILABLE:
117
+ return "❌ requests library is required for API calls. Please install with: pip install requests"
118
+
119
+ try:
120
+ # Create the prompt with schema definition
121
+ SCHEMA = """{
122
+ "themes": {
123
+ "generated_theme": {
124
+ "name": "Generated Theme",
125
+ "colors": {
126
+ "base-100": "#ffffff",
127
+ "base-200": "#f8fafc",
128
+ "base-300": "#e2e8f0",
129
+ "base-content": "#1e293b",
130
+ "primary": "#3b82f6",
131
+ "primary-content": "#ffffff",
132
+ "secondary": "#64748b",
133
+ "secondary-content": "#ffffff",
134
+ "accent": "#f59e0b",
135
+ "accent-content": "#000000",
136
+ "neutral": "#374151",
137
+ "neutral-content": "#ffffff",
138
+ "error": "#ef4444",
139
+ "error-content": "#ffffff"
140
+ },
141
+ "background": "#f1f5f9",
142
+ "font": {
143
+ "family": "Inter",
144
+ "type": "google_font",
145
+ "name": "Inter"
146
+ }
147
+ }
148
+ }
149
+ }"""
150
+
151
+ ALPACA_PROMPT = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
152
+
153
+ ### Instruction:
154
+ Convert the provided CSS code or style description into a JSON theme configuration that follows the exact schema structure. Extract colors from CSS variables, class names, or generate appropriate colors based on the description. Return ONLY valid JSON that matches the schema format.
155
+
156
+ Expected JSON Schema:
157
+ {schema}
158
+
159
+ ### Input:
160
+ {input_text}
161
+
162
+ ### Response:
163
+ """
164
+
165
+ prompt = ALPACA_PROMPT.format(schema=SCHEMA, input_text=input_text)
166
+
167
+ model_name = AVAILABLE_MODELS[model_choice]["name"]
168
+ api_type = "Nebius" if user_token and user_token.strip() else "HuggingFace Zero"
169
+ print(f"πŸ€– Generating theme with {model_name} via {api_type}...")
170
+
171
+ try:
172
+ result = query_ai_api(prompt, user_token, model_choice)
173
+
174
+ # Handle different response formats
175
+ generated_text = ""
176
+
177
+ if user_token and user_token.strip():
178
+ # Handle Nebius API response (OpenAI format)
179
+ if isinstance(result, dict):
180
+ if "error" in result or "detail" in result:
181
+ error_msg = result.get(
182
+ "error", result.get("detail", "Unknown error")
183
+ )
184
+ if (
185
+ "authentication" in str(error_msg).lower()
186
+ or "unauthorized" in str(error_msg).lower()
187
+ or "invalid authentication token" in str(error_msg).lower()
188
+ ):
189
+ return "❌ Invalid Nebius API token provided. Please check your Nebius API key and try again."
190
+ else:
191
+ return f"❌ Nebius API error: {error_msg}"
192
+ elif "choices" in result and len(result["choices"]) > 0:
193
+ generated_text = (
194
+ result["choices"][0].get("message", {}).get("content", "")
195
+ )
196
+ else:
197
+ return "❌ Unexpected Nebius API response format"
198
+ else:
199
+ return "❌ Unexpected Nebius API response format"
200
+ else:
201
+ # Handle HuggingFace API response
202
+ if isinstance(result, list) and len(result) > 0:
203
+ generated_text = result[0].get("generated_text", "")
204
+ elif isinstance(result, dict):
205
+ if "error" in result:
206
+ if "loading" in str(result["error"]).lower():
207
+ return "⏳ Model is loading on HuggingFace. Please try again in a few moments."
208
+ return f"❌ HuggingFace API error: {result['error']}"
209
+ else:
210
+ generated_text = result.get("generated_text", "")
211
+
212
+ if not generated_text.strip():
213
+ return "❌ No response received from AI model. Please try again."
214
+
215
+ # Try to extract JSON from the response
216
+ try:
217
+ # Look for JSON in the response
218
+ start_idx = generated_text.find("{")
219
+ if start_idx == -1:
220
+ return f"❌ No valid JSON found in response:\n\n{generated_text}"
221
+
222
+ # Find the matching closing brace
223
+ brace_count = 0
224
+ end_idx = -1
225
+ for i in range(start_idx, len(generated_text)):
226
+ if generated_text[i] == "{":
227
+ brace_count += 1
228
+ elif generated_text[i] == "}":
229
+ brace_count -= 1
230
+ if brace_count == 0:
231
+ end_idx = i + 1
232
+ break
233
+
234
+ if end_idx == -1:
235
+ return f"❌ Incomplete JSON in response:\n\n{generated_text}"
236
+
237
+ json_str = generated_text[start_idx:end_idx]
238
+
239
+ # Validate JSON
240
+ parsed_json = json.loads(json_str)
241
+
242
+ # Pretty format the JSON
243
+ formatted_json = json.dumps(parsed_json, indent=2)
244
+
245
+ return formatted_json
246
+
247
+ except json.JSONDecodeError as e:
248
+ return f"❌ Invalid JSON generated:\n\nJSON Error: {str(e)}\n\nRaw Response:\n{generated_text}"
249
+
250
+ except requests.exceptions.RequestException as e:
251
+ return f"❌ Network error: {str(e)}"
252
+ except Exception as e:
253
+ return f"❌ Unexpected error during AI processing: {str(e)}"
254
+
255
+ except Exception as e:
256
+ return f"❌ Error: {str(e)}"
257
+
258
+
259
+ def apply_random_theme():
260
+ # Internal UI function - not for MCP discovery
261
+ # Load the actual theme data to get all available themes
262
+ themes_file = Path(__file__).parent / "user_themes.json"
263
+
264
+ try:
265
+ with open(themes_file, "r", encoding="utf-8") as f:
266
+ theme_data = json.load(f)
267
+
268
+ if not theme_data.get("themes"):
269
+ return (
270
+ {"currentTheme": "default", "type": "builtin"},
271
+ "❌ No themes found in user_themes.json",
272
+ )
273
+
274
+ # Get all available theme keys
275
+ theme_keys = list(theme_data["themes"].keys())
276
+ if not theme_keys:
277
+ return (
278
+ {"currentTheme": "default", "type": "builtin"},
279
+ "❌ No themes available",
280
+ )
281
+
282
+ # Select a random theme
283
+ random_theme_key = random.choice(theme_keys)
284
+ random_theme = theme_data["themes"][random_theme_key]
285
+
286
+ print(f"🎲 Randomly selected theme: {random_theme_key}")
287
+
288
+ # Update the component state
289
+ new_state = {
290
+ "currentTheme": random_theme_key,
291
+ "type": "custom",
292
+ "themeConfig": random_theme,
293
+ "font": random_theme.get("font", {"family": "Inter", "weights": ["400"]}),
294
+ "removeBorders": True,
295
+ }
296
+
297
+ return (
298
+ new_state,
299
+ f"βœ… Applied random theme: {random_theme.get('name', random_theme_key)}",
300
+ )
301
+
302
+ except FileNotFoundError:
303
+ return (
304
+ {"currentTheme": "default", "type": "builtin"},
305
+ "❌ Theme file not found: user_themes.json",
306
+ )
307
+ except json.JSONDecodeError as e:
308
+ return (
309
+ {"currentTheme": "default", "type": "builtin"},
310
+ f"❌ Invalid JSON in theme file: {str(e)}",
311
+ )
312
+ except Exception as e:
313
+ return (
314
+ {"currentTheme": "default", "type": "builtin"},
315
+ f"❌ Error loading theme: {str(e)}",
316
+ )
317
+
318
+
319
+ # def start_mcp_server():
320
+ # """Start the MCP server in background"""
321
+ # try:
322
+ # mcp_process = subprocess.Popen(
323
+ # [sys.executable, "mcp_server.py"],
324
+ # cwd=Path(__file__).parent,
325
+ # stdout=subprocess.PIPE,
326
+ # stderr=subprocess.PIPE,
327
+ # )
328
+ # return "βœ… MCP Server started successfully! Use stdio transport to connect."
329
+ # except Exception as e:
330
+ # return f"❌ Failed to start MCP server: {str(e)}"
331
+
332
+
333
+ # Custom CSS for minimal design (no @import allowed in Gradio)
334
+ custom_css = """
335
+ .header-container {
336
+ text-align: center;
337
+ padding: 1rem 1rem;
338
+ border-bottom: 1px solid #e5e7eb;
339
+ margin-bottom: 1rem;
340
+ }
341
+
342
+ .main-sections {
343
+ max-width: 800px;
344
+ margin: 0 auto;
345
+ padding: 0 1rem 0rem 1rem;
346
+ }
347
+
348
+ .section {
349
+ margin-bottom: 0.75rem;
350
+ padding: 1rem;
351
+ border: 1px solid #e5e7eb;
352
+ border-radius: 8px;
353
+ background: #fafafa;
354
+ }
355
+
356
+ .mcp-info {
357
+ background: #f0f9ff;
358
+ border-color: #0ea5e9;
359
+ }
360
+
361
+ /* Override Gradio's default spacing and remove nested borders */
362
+ .gradio-container .block {
363
+ margin: 0.5rem 0 !important;
364
+ border: none !important;
365
+ box-shadow: none !important;
366
+ background: transparent !important;
367
+ }
368
+
369
+ .gradio-container .gr-column {
370
+ gap: 0.5rem !important;
371
+ }
372
+
373
+ /* Remove nested styling from textbox containers */
374
+ .section .gradio-container .gr-textbox,
375
+ .section .gradio-container .gr-code {
376
+ border: 1px solid #d1d5db !important;
377
+ border-radius: 6px !important;
378
+ background: white !important;
379
+ }
380
+
381
+ /* Ensure code output is selectable and copy button works */
382
+ .gr-code .language-json,
383
+ .gr-code pre,
384
+ .gr-code code {
385
+ user-select: text !important;
386
+ -webkit-user-select: text !important;
387
+ -moz-user-select: text !important;
388
+ -ms-user-select: text !important;
389
+ }
390
+
391
+ /* Make sure copy button is visible and functional */
392
+ .gr-code .copy-button,
393
+ .gr-code button[aria-label*="copy"],
394
+ .gr-code button[title*="copy"] {
395
+ opacity: 1 !important;
396
+ visibility: visible !important;
397
+ pointer-events: auto !important;
398
+ }
399
+
400
+ /* Add margin to bottom footer/button area */
401
+ .gradio-container footer,
402
+ .gradio-container .footer,
403
+ .gradio-container > div:last-child,
404
+ footer {
405
+ margin-bottom: 2rem !important;
406
+ padding-bottom: 2rem !important;
407
+ }
408
+
409
+ /* Add bottom margin to the entire Gradio container */
410
+ .gradio-container {
411
+ margin-bottom: 3rem !important;
412
+ padding-bottom: 2rem !important;
413
+ }
414
+ """
415
+
416
+ # Build the demo interface with Gradio's built-in font system
417
+ with gr.Blocks(css=custom_css, title="Gradio Themer - Demo & MCP Server") as demo:
418
+
419
+ # Header
420
+ with gr.Column(elem_classes="header-container"):
421
+ gr.Markdown("# 🎨 Gradio Themer")
422
+ gr.Markdown(
423
+ "**Demo & MCP Server** - Dynamic theme system for Gradio applications"
424
+ )
425
+
426
+ # Add the working GradioThemer component
427
+ if THEMER_AVAILABLE:
428
+ themer = GradioThemer(
429
+ value={
430
+ "currentTheme": "corporate",
431
+ "type": "builtin",
432
+ "font": {"family": "Poppins", "weights": ["400", "500", "600", "700"]},
433
+ "removeBorders": True,
434
+ "themeInput": "",
435
+ "themeConfig": None,
436
+ "generatedCSS": "",
437
+ },
438
+ visible=False,
439
+ label="Theme Controller (for debugging)",
440
+ scale=1,
441
+ )
442
+ else:
443
+ themer = gr.HTML(visible=False) # Dummy component if themer not available
444
+
445
+ # Main content
446
+ with gr.Column(elem_classes="main-sections"):
447
+
448
+ # Top section - Random theme
449
+ with gr.Column(elem_classes="section"):
450
+ gr.Markdown("### 🎲 Random Theme")
451
+
452
+ random_btn = gr.Button("Apply Random Theme", variant="primary", size="lg")
453
+ theme_status = gr.Textbox(
454
+ label="Theme Status",
455
+ placeholder="Click button to apply random theme",
456
+ interactive=False,
457
+ )
458
+
459
+ # Demo components to show theming
460
+ with gr.Column(elem_classes="section"):
461
+ gr.Markdown("### 🎨 Live Theme Preview")
462
+
463
+ with gr.Row():
464
+ gr.Button("Primary Button", variant="primary")
465
+ gr.Button("Secondary Button", variant="secondary")
466
+ gr.Button("Stop Button", variant="stop")
467
+
468
+ with gr.Row():
469
+ gr.Textbox("Sample text input", label="Text Input")
470
+ gr.Slider(0, 100, value=50, label="Slider")
471
+
472
+ with gr.Row():
473
+ gr.Dropdown(["Option 1", "Option 2", "Option 3"], label="Dropdown")
474
+ gr.Radio(["Choice A", "Choice B"], label="Radio")
475
+
476
+ # Middle section - CSS converter
477
+ with gr.Column(elem_classes="section"):
478
+ gr.Markdown(
479
+ """### πŸ”„ AI-Powered CSS to Theme Converter
480
+ πŸ€– Convert CSS code or describe your style to generate JSON themes using AI models"""
481
+ )
482
+ with gr.Row():
483
+ model_selector = gr.Dropdown(
484
+ label="AI Model",
485
+ choices=[
486
+ ("Qwen2.5-Coder-7B", "qwen"),
487
+ ("Meta-Llama-3.1-8B-Instruct", "llama"),
488
+ ],
489
+ value="qwen",
490
+ info="Choose AI model for theme generation",
491
+ scale=1,
492
+ )
493
+ token_input = gr.Textbox(
494
+ label="Nebius API Token (Optional)",
495
+ placeholder="Leave empty for HF Zero inference, or provide Nebius token for better performance",
496
+ type="password",
497
+ lines=1,
498
+ scale=2,
499
+ )
500
+
501
+ css_input = gr.Textbox(
502
+ label="Describe your style or paste CSS code here",
503
+ placeholder="Examples:\nβ€’ 'Dark purple theme with neon accents'\nβ€’ 'Corporate blue and white design'\nβ€’ CSS code:\n.my-theme {\n --primary: #3b82f6;\n --background: #f8fafc;\n}",
504
+ lines=8,
505
+ max_lines=15,
506
+ )
507
+
508
+ convert_btn = gr.Button("Generate JSON", variant="primary", size="lg")
509
+
510
+ json_output = gr.Code(
511
+ label="Generated Theme JSON", language="json", lines=20
512
+ )
513
+
514
+ # Bottom section - MCP Server info
515
+ with gr.Column(elem_classes="section mcp-info"):
516
+ gr.Markdown("### πŸ€– MCP Server Integration")
517
+ gr.Markdown(
518
+ """
519
+ This Space functions as both a **Demo** and **MCP Server** for AI agents.
520
+
521
+ **Available MCP Tools:**
522
+
523
+ 1. **`setup_package`** - Install and verify gradio-themer package
524
+ 2. **`generate_theme`** - Create theme JSON configuration
525
+ 3. **`convert_css_to_theme`** - Convert CSS to standardized JSON format (uses HF LLM)
526
+ 4. **`generate_app_code`** - Generate complete Gradio app with theming
527
+
528
+ **For AI Agents:**
529
+ - Connect to this Space as an MCP server
530
+ - Use tools to help users create themed Gradio applications
531
+ - Automate theme generation and application setup
532
+
533
+ **For Developers:**
534
+ - Install: `pip install gradio-themer`
535
+ - Use the component in your Gradio apps
536
+ - Create custom themes with JSON configuration
537
+
538
+ **MCP Endpoint:** `/gradio_api/mcp/sse`
539
+ """
540
+ )
541
+
542
+ # Event handlers
543
+ random_btn.click(
544
+ fn=apply_random_theme,
545
+ outputs=[themer, theme_status],
546
+ show_api=False, # Hide from API docs but keep UI functional
547
+ )
548
+
549
+ convert_btn.click(
550
+ fn=convert_css_to_theme_json_ai,
551
+ inputs=[css_input, token_input, model_selector],
552
+ outputs=[json_output],
553
+ show_api=False, # Hide from API docs but keep UI functional
554
+ )
555
+
556
+
557
+ # Launch the demo
558
+ if __name__ == "__main__":
559
+ # Configure for Google Fonts support and MCP server (as per GRADIO_MCP_HF_SPACES_GUIDE.md)
560
+ demo.launch(
561
+ mcp_server=True, # CRITICAL: Enable MCP server functionality
562
+ server_name="0.0.0.0",
563
+ server_port=7860,
564
+ share=False,
565
+ debug=True,
566
+ # Allow external resources like Google Fonts
567
+ allowed_paths=["./"],
568
+ )
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Requirements for MCP Server - HF Spaces Deployment
2
+ gradio>=5.31.0
3
+ gradio-themer>=0.1.0
4
+ requests>=2.25.0
5
+ transformers>=4.30.0
6
+ torch>=2.0.0
7
+ accelerate>=0.20.0
8
+ sentencepiece>=0.1.95
user_themes.json ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "themes": {
3
+ "dark_emerald": {
4
+ "name": "Dark Emerald",
5
+ "colors": {
6
+ "base-100": "#064e3b",
7
+ "base-200": "#065f46",
8
+ "base-300": "#047857",
9
+ "base-content": "#d1fae5",
10
+ "primary": "#10b981",
11
+ "primary-content": "#ffffff",
12
+ "secondary": "#6b7280",
13
+ "secondary-content": "#ffffff",
14
+ "accent": "#34d399",
15
+ "accent-content": "#000000",
16
+ "neutral": "#374151",
17
+ "neutral-content": "#ffffff",
18
+ "error": "#ef4444",
19
+ "error-content": "#ffffff"
20
+ },
21
+ "background": "#022c22",
22
+ "font": {
23
+ "family": "Helvetica",
24
+ "type": "system_font",
25
+ "name": "Helvetica"
26
+ }
27
+ },
28
+ "ocean_breeze": {
29
+ "name": "Ocean Breeze",
30
+ "colors": {
31
+ "base-100": "#f0f9ff",
32
+ "base-200": "#e0f2fe",
33
+ "base-300": "#bae6fd",
34
+ "base-content": "#0c4a6e",
35
+ "primary": "#0ea5e9",
36
+ "primary-content": "#ffffff",
37
+ "secondary": "#64748b",
38
+ "secondary-content": "#ffffff",
39
+ "accent": "#06b6d4",
40
+ "accent-content": "#ffffff",
41
+ "neutral": "#374151",
42
+ "neutral-content": "#ffffff",
43
+ "error": "#ef4444",
44
+ "error-content": "#ffffff"
45
+ },
46
+ "background": "#bae6fd",
47
+ "font": {
48
+ "family": "Helvetica",
49
+ "type": "system_font",
50
+ "name": "Helvetica"
51
+ }
52
+ },
53
+ "sunset_orange": {
54
+ "name": "Sunset Orange",
55
+ "colors": {
56
+ "base-100": "#fff7ed",
57
+ "base-200": "#ffedd5",
58
+ "base-300": "#fed7aa",
59
+ "base-content": "#9a3412",
60
+ "primary": "#ea580c",
61
+ "primary-content": "#ffffff",
62
+ "secondary": "#64748b",
63
+ "secondary-content": "#ffffff",
64
+ "accent": "#f59e0b",
65
+ "accent-content": "#ffffff",
66
+ "neutral": "#374151",
67
+ "neutral-content": "#ffffff",
68
+ "error": "#ef4444",
69
+ "error-content": "#ffffff"
70
+ },
71
+ "background": "#fed7aa",
72
+ "font": {
73
+ "family": "Verdana",
74
+ "type": "system_font",
75
+ "name": "Verdana"
76
+ }
77
+ },
78
+ "forest_green": {
79
+ "name": "Forest Green",
80
+ "colors": {
81
+ "base-100": "#f0fdf4",
82
+ "base-200": "#dcfce7",
83
+ "base-300": "#bbf7d0",
84
+ "base-content": "#14532d",
85
+ "primary": "#16a34a",
86
+ "primary-content": "#ffffff",
87
+ "secondary": "#64748b",
88
+ "secondary-content": "#ffffff",
89
+ "accent": "#22c55e",
90
+ "accent-content": "#ffffff",
91
+ "neutral": "#374151",
92
+ "neutral-content": "#ffffff",
93
+ "error": "#ef4444",
94
+ "error-content": "#ffffff"
95
+ },
96
+ "background": "#bbf7d0",
97
+ "font": {
98
+ "family": "Times New Roman",
99
+ "type": "system_font",
100
+ "name": "Times New Roman"
101
+ }
102
+ },
103
+ "royal_purple": {
104
+ "name": "Royal Purple",
105
+ "colors": {
106
+ "base-100": "#faf5ff",
107
+ "base-200": "#f3e8ff",
108
+ "base-300": "#e9d5ff",
109
+ "base-content": "#581c87",
110
+ "primary": "#9333ea",
111
+ "primary-content": "#ffffff",
112
+ "secondary": "#64748b",
113
+ "secondary-content": "#ffffff",
114
+ "accent": "#a855f7",
115
+ "accent-content": "#ffffff",
116
+ "neutral": "#374151",
117
+ "neutral-content": "#ffffff",
118
+ "error": "#ef4444",
119
+ "error-content": "#ffffff"
120
+ },
121
+ "background": "#e9d5ff",
122
+ "font": {
123
+ "family": "Tahoma",
124
+ "type": "system_font",
125
+ "name": "Tahoma"
126
+ }
127
+ },
128
+ "midnight_blue": {
129
+ "name": "Midnight Blue",
130
+ "colors": {
131
+ "base-100": "#1e293b",
132
+ "base-200": "#334155",
133
+ "base-300": "#475569",
134
+ "base-content": "#f1f5f9",
135
+ "primary": "#3b82f6",
136
+ "primary-content": "#ffffff",
137
+ "secondary": "#64748b",
138
+ "secondary-content": "#ffffff",
139
+ "accent": "#06b6d4",
140
+ "accent-content": "#ffffff",
141
+ "neutral": "#374151",
142
+ "neutral-content": "#ffffff",
143
+ "error": "#ef4444",
144
+ "error-content": "#ffffff"
145
+ },
146
+ "background": "#0f172a",
147
+ "font": {
148
+ "family": "Courier New",
149
+ "type": "system_font",
150
+ "name": "Courier New"
151
+ }
152
+ },
153
+ "warm_beige": {
154
+ "name": "Warm Beige",
155
+ "colors": {
156
+ "base-100": "#fef7f0",
157
+ "base-200": "#fde8d7",
158
+ "base-300": "#fbd5ae",
159
+ "base-content": "#92400e",
160
+ "primary": "#d97706",
161
+ "primary-content": "#ffffff",
162
+ "secondary": "#64748b",
163
+ "secondary-content": "#ffffff",
164
+ "accent": "#f59e0b",
165
+ "accent-content": "#ffffff",
166
+ "neutral": "#374151",
167
+ "neutral-content": "#ffffff",
168
+ "error": "#ef4444",
169
+ "error-content": "#ffffff"
170
+ },
171
+ "background": "#f7e6d3",
172
+ "font": {
173
+ "family": "Georgia",
174
+ "type": "system_font",
175
+ "name": "Georgia"
176
+ }
177
+ },
178
+ "dark_obsidian": {
179
+ "name": "Dark Obsidian",
180
+ "colors": {
181
+ "base-100": "#0f0f23",
182
+ "base-200": "#1a1a2e",
183
+ "base-300": "#16213e",
184
+ "base-content": "#e2e8f0",
185
+ "primary": "#8b5cf6",
186
+ "primary-content": "#ffffff",
187
+ "secondary": "#64748b",
188
+ "secondary-content": "#ffffff",
189
+ "accent": "#06b6d4",
190
+ "accent-content": "#ffffff",
191
+ "neutral": "#374151",
192
+ "neutral-content": "#ffffff",
193
+ "error": "#ef4444",
194
+ "error-content": "#ffffff"
195
+ },
196
+ "background": "#0a0a1a",
197
+ "font": {
198
+ "family": "Arial",
199
+ "type": "system_font",
200
+ "name": "Arial"
201
+ }
202
+ },
203
+ "dark_crimson": {
204
+ "name": "Dark Crimson",
205
+ "colors": {
206
+ "base-100": "#450a0a",
207
+ "base-200": "#7f1d1d",
208
+ "base-300": "#991b1b",
209
+ "base-content": "#fecaca",
210
+ "primary": "#dc2626",
211
+ "primary-content": "#ffffff",
212
+ "secondary": "#6b7280",
213
+ "secondary-content": "#ffffff",
214
+ "accent": "#f87171",
215
+ "accent-content": "#000000",
216
+ "neutral": "#374151",
217
+ "neutral-content": "#ffffff",
218
+ "error": "#ef4444",
219
+ "error-content": "#ffffff"
220
+ },
221
+ "background": "#1f0404",
222
+ "font": {
223
+ "family": "Verdana",
224
+ "type": "system_font",
225
+ "name": "Verdana"
226
+ }
227
+ },
228
+ "dark_slate": {
229
+ "name": "Dark Slate",
230
+ "colors": {
231
+ "base-100": "#1e293b",
232
+ "base-200": "#334155",
233
+ "base-300": "#475569",
234
+ "base-content": "#f1f5f9",
235
+ "primary": "#3b82f6",
236
+ "primary-content": "#ffffff",
237
+ "secondary": "#64748b",
238
+ "secondary-content": "#ffffff",
239
+ "accent": "#06b6d4",
240
+ "accent-content": "#ffffff",
241
+ "neutral": "#374151",
242
+ "neutral-content": "#ffffff",
243
+ "error": "#ef4444",
244
+ "error-content": "#ffffff"
245
+ },
246
+ "background": "#0f172a",
247
+ "font": {
248
+ "family": "Tahoma",
249
+ "type": "system_font",
250
+ "name": "Tahoma"
251
+ }
252
+ },
253
+ "dark_carbon": {
254
+ "name": "Generated Theme",
255
+ "colors": {
256
+ "base-100": "#FFF5DC",
257
+ "base-200": "#FEF3C7",
258
+ "base-300": "#FDE68A",
259
+ "base-content": "#1F2937",
260
+ "primary": "#4F46E5",
261
+ "primary-content": "#FFFFFF",
262
+ "secondary": "#8B5CF6",
263
+ "secondary-content": "#FFFFFF",
264
+ "accent": "#EAB308",
265
+ "accent-content": "#000000",
266
+ "neutral": "#374151",
267
+ "neutral-content": "#FFFFFF",
268
+ "error": "#EF4444",
269
+ "error-content": "#FFFFFF"
270
+ },
271
+ "background": "#F3E8FF"
272
+ },
273
+ "dark_violet": {
274
+ "name": "Dark Violet",
275
+ "colors": {
276
+ "base-100": "#2e1065",
277
+ "base-200": "#3730a3",
278
+ "base-300": "#4338ca",
279
+ "base-content": "#e0e7ff",
280
+ "primary": "#8b5cf6",
281
+ "primary-content": "#ffffff",
282
+ "secondary": "#6b7280",
283
+ "secondary-content": "#ffffff",
284
+ "accent": "#a78bfa",
285
+ "accent-content": "#000000",
286
+ "neutral": "#374151",
287
+ "neutral-content": "#ffffff",
288
+ "error": "#ef4444",
289
+ "error-content": "#ffffff"
290
+ },
291
+ "background": "#1e1b4b",
292
+ "font": {
293
+ "family": "Georgia",
294
+ "type": "system_font",
295
+ "name": "Georgia"
296
+ }
297
+ },
298
+ "hacker_matrix": {
299
+ "name": "Dark Theme",
300
+ "colors": {
301
+ "base-100": "#2d3748",
302
+ "base-200": "#1a1d23",
303
+ "base-300": "#0f172a",
304
+ "base-content": "#ffffff",
305
+ "primary": "#6c5ce7",
306
+ "primary-content": "#ffffff",
307
+ "secondary": "#4d5d7e",
308
+ "secondary-content": "#ffffff",
309
+ "accent": "#f7dc6f",
310
+ "accent-content": "#000000",
311
+ "neutral": "#3b3f4e",
312
+ "neutral-content": "#ffffff",
313
+ "error": "#f87171",
314
+ "error-content": "#ffffff"
315
+ },
316
+ "background": "#2c3e50"
317
+ }
318
+ },
319
+ "default_theme": "ocean_breeze",
320
+ "default_font": "Helvetica"
321
+ }