gfathertech commited on
Commit
a194df6
Β·
verified Β·
1 Parent(s): eaecbd3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +133 -84
app.py CHANGED
@@ -1,119 +1,169 @@
1
- """Dynamic Modular ChatGPT-Style Sidebar UI for SOG-X Platform."""
 
 
 
 
2
  import os
3
- import tempfile
4
  import gradio as gr
5
  from client import BackendClient
6
 
 
7
  BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:7860")
8
  HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("API_KEY", "")
9
  USER_KEY = os.getenv("USER_KEY", "samuel_super_secret_key_1234567890_key")
10
 
11
  client = BackendClient(backend_url=BACKEND_URL, token=HF_TOKEN)
12
 
13
- # ── Parse OpenAPI Schema into Sidebar Modules ─────────────────────────────
14
- ok, schema = client.fetch_openapi_schema(user_api_key=USER_KEY)
15
- modules_map = {}
16
-
17
- if ok and "paths" in schema:
18
- for path, methods in schema["paths"].items():
19
- for method, details in methods.items():
20
- if method.lower() not in ["get", "post"]:
21
- continue
22
-
23
- # Module grouping by Tag or Path root
24
- tags = details.get("tags", ["General"])
25
- tag_name = tags[0].capitalize() if tags else "General"
26
-
27
- summary = details.get("summary") or f"{method.upper()} {path}"
28
- module_id = f"{tag_name}: {summary}"
29
-
30
- modules_map[module_id] = {
31
- "path": path,
32
- "method": method.upper(),
33
- "summary": summary,
34
- "description": details.get("description", "No description provided."),
35
- "parameters": details.get("parameters", []),
36
- "request_body": details.get("requestBody", {}),
37
- }
38
-
39
- module_names = list(modules_map.keys()) if modules_map else ["No Modules Discovered"]
40
-
41
- # ── Gradio ChatGPT-Style UI ───────────────────────────────────────────────
42
  custom_css = """
43
- .sidebar-container { background-color: #1e1e2e; padding: 15px; border-radius: 10px; }
44
- .main-content { padding: 20px; }
45
- .module-btn { text-align: left !important; justify-content: flex-start !important; margin-bottom: 5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  """
47
 
48
- with gr.Blocks(title="SOG-X Modular Workspace", theme=gr.themes.Soft(), css=custom_css) as demo:
49
- active_module_state = gr.State(value=module_names[0] if module_names else None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  with gr.Row():
52
  # ── Sidebar Navigation Column (ChatGPT Style) ─────────────────────
53
- with gr.Column(scale=1, min_width=280, elem_classes=["sidebar-container"]):
54
  gr.Markdown("## πŸ€– SOG-X Studio")
55
-
56
- with gr.Accordion("πŸ”‘ API Key & Config", open=False):
57
  user_key_input = gr.Textbox(
58
- label="Active User API Key",
59
  value=USER_KEY,
60
  type="password",
 
61
  interactive=True,
62
  )
63
- refresh_btn = gr.Button("πŸ”„ Sync Backend Modules")
64
 
65
  gr.Markdown("---")
66
  gr.Markdown("### πŸ“‚ Active Modules")
67
-
68
- # Sidebar Selector (Radio buttons designed as sidebar menu items)
69
  sidebar_menu = gr.Radio(
70
- choices=module_names,
71
- value=module_names[0] if module_names else None,
72
  label="Select Module",
73
  interactive=True,
 
74
  )
75
 
76
  # ── Main Content Workspace Column ─────────────────────────────────
77
  with gr.Column(scale=3, elem_classes=["main-content"]):
78
  module_title = gr.Markdown("## Select a module from the sidebar")
79
- module_desc = gr.Markdown("")
80
-
81
- # Form Inputs
82
- input_file = gr.File(label="Upload Source File (If required)", visible=True)
83
- input_text = gr.Textbox(
84
- label="Input Prompt / Data / Query",
85
- placeholder="Enter text, URL, or JSON payload...",
86
- lines=4,
87
- )
88
-
89
- submit_btn = gr.Button("πŸš€ Process Request", variant="primary")
90
-
 
 
 
 
 
91
  gr.Markdown("### πŸ“€ Output Response")
92
- output_display = gr.JSON(label="Response Data")
93
- output_file_download = gr.File(label="Output Download Artifact", visible=True)
94
-
95
- # ── Dynamic Module Switcher Handler ───────────────────────────────────
96
- def switch_module(selected_name):
97
- if not selected_name or selected_name not in modules_map:
98
- return "## No Module Selected", "Please select a valid backend route from the sidebar."
99
-
100
- mod = modules_map[selected_name]
101
- title_md = f"## [{mod['method']}] `{mod['path']}`"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  desc_md = f"**Summary:** {mod['summary']}\n\n{mod['description']}"
103
  return title_md, desc_md
104
 
105
  sidebar_menu.change(
106
- fn=switch_module,
107
- inputs=[sidebar_menu],
108
- outputs=[module_title, module_desc]
109
  )
110
 
111
- # ── Dynamic Processing Handler ────────────────────────────────────────
112
- def process_dynamic_action(selected_name, key_val, file_obj, text_val):
113
- if not selected_name or selected_name not in modules_map:
114
- return {"error": "Invalid module selected"}, None
115
 
116
- mod = modules_map[selected_name]
117
  path = mod["path"]
118
  method = mod["method"]
119
 
@@ -121,12 +171,12 @@ with gr.Blocks(title="SOG-X Modular Workspace", theme=gr.themes.Soft(), css=cust
121
  payload = {}
122
  files = {}
123
 
124
- # Handle File Uploads
125
  if file_obj is not None:
126
  file_path = file_obj.name if hasattr(file_obj, "name") else str(file_obj)
127
  files["file"] = open(file_path, "rb")
128
 
129
- # Handle Text Input / JSON payloads / Query Params
130
  if text_val and text_val.strip():
131
  cleaned_text = text_val.strip()
132
  if method == "GET":
@@ -143,27 +193,26 @@ with gr.Blocks(title="SOG-X Modular Workspace", theme=gr.themes.Soft(), css=cust
143
  params=params,
144
  payload=payload,
145
  files=files,
146
- user_api_key=key_val,
147
  )
148
 
 
149
  if success and isinstance(response, dict) and "file_path" in response:
150
  return response, response["file_path"]
151
 
152
  return response, None
153
 
154
  submit_btn.click(
155
- fn=process_dynamic_action,
156
- inputs=[sidebar_menu, user_key_input, input_file, input_text],
157
- outputs=[output_display, output_file_download]
158
  )
159
 
160
- demo.queue()
161
-
162
  if __name__ == "__main__":
163
  demo.launch(
164
  server_name="0.0.0.0",
165
  server_port=7860,
166
- ssr=False, # Explicitly disables the Node.js SSR proxy
167
  theme=gr.themes.Soft(),
168
  css=custom_css,
169
  share=False,
 
1
+ """
2
+ SOG-X Modular Studio - Dynamic ChatGPT-Style Interface
3
+ Auto-discovers backend routes via OpenAPI and generates functional UI components dynamically.
4
+ Compatible with Gradio 6.0+
5
+ """
6
  import os
 
7
  import gradio as gr
8
  from client import BackendClient
9
 
10
+ # ── Environment Configurations ────────────────────────────────────────────
11
  BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:7860")
12
  HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("API_KEY", "")
13
  USER_KEY = os.getenv("USER_KEY", "samuel_super_secret_key_1234567890_key")
14
 
15
  client = BackendClient(backend_url=BACKEND_URL, token=HF_TOKEN)
16
 
17
+ # ── Custom CSS for ChatGPT-style Layout ───────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  custom_css = """
19
+ .sidebar-container {
20
+ background-color: #1e1e2e;
21
+ padding: 16px;
22
+ border-radius: 12px;
23
+ height: 100%;
24
+ }
25
+ .main-content {
26
+ padding: 24px;
27
+ }
28
+ .module-radio label {
29
+ background-color: #2b2b3b !important;
30
+ border: 1px solid #3f3f56 !important;
31
+ border-radius: 8px !important;
32
+ margin-bottom: 6px !important;
33
+ padding: 10px 14px !important;
34
+ transition: all 0.2s ease;
35
+ }
36
+ .module-radio label:hover {
37
+ border-color: #6c63ff !important;
38
+ }
39
  """
40
 
41
+ # ── Helper to Load & Parse Backend Schema ─────────────────────────────────
42
+ def load_backend_modules(api_key: str):
43
+ """Fetch live OpenAPI spec and parse available backend routes into modules."""
44
+ ok, schema = client.fetch_openapi_schema(user_api_key=api_key)
45
+ modules_map = {}
46
+
47
+ if ok and isinstance(schema, dict) and "paths" in schema:
48
+ for path, methods in schema["paths"].items():
49
+ for method, details in methods.items():
50
+ if method.lower() not in ["get", "post"]:
51
+ continue
52
+
53
+ tags = details.get("tags", ["General"])
54
+ tag_name = tags[0].capitalize() if tags else "General"
55
+ summary = details.get("summary") or f"{method.upper()} {path}"
56
+ module_id = f"[{tag_name}] {summary}"
57
+
58
+ modules_map[module_id] = {
59
+ "path": path,
60
+ "method": method.upper(),
61
+ "summary": summary,
62
+ "description": details.get("description", "No description provided."),
63
+ "parameters": details.get("parameters", []),
64
+ "request_body": details.get("requestBody", {}),
65
+ }
66
+
67
+ module_names = list(modules_map.keys()) if modules_map else ["No Modules Discovered"]
68
+ return modules_map, module_names
69
+
70
+
71
+ # Load initial schema at startup
72
+ INITIAL_MAP, INITIAL_CHOICES = load_backend_modules(USER_KEY)
73
+
74
+ # ── Gradio Blocks Interface (Clean Gradio 6.0 Constructor) ────────────────
75
+ with gr.Blocks(title="SOG-X Modular Workspace") as demo:
76
+ # State Stores
77
+ schema_state = gr.State(value=INITIAL_MAP)
78
 
79
  with gr.Row():
80
  # ── Sidebar Navigation Column (ChatGPT Style) ─────────────────────
81
+ with gr.Column(scale=1, min_width=300, elem_classes=["sidebar-container"]):
82
  gr.Markdown("## πŸ€– SOG-X Studio")
83
+
84
+ with gr.Accordion("βš™οΈ Backend & Credentials", open=False):
85
  user_key_input = gr.Textbox(
86
+ label="Active API Key",
87
  value=USER_KEY,
88
  type="password",
89
+ placeholder="Enter your 32+ char key...",
90
  interactive=True,
91
  )
92
+ sync_btn = gr.Button("πŸ”„ Sync Backend Modules", size="sm")
93
 
94
  gr.Markdown("---")
95
  gr.Markdown("### πŸ“‚ Active Modules")
96
+
97
+ # Dynamic Module Navigation
98
  sidebar_menu = gr.Radio(
99
+ choices=INITIAL_CHOICES,
100
+ value=INITIAL_CHOICES[0] if INITIAL_CHOICES else None,
101
  label="Select Module",
102
  interactive=True,
103
+ elem_classes=["module-radio"],
104
  )
105
 
106
  # ── Main Content Workspace Column ─────────────────────────────────
107
  with gr.Column(scale=3, elem_classes=["main-content"]):
108
  module_title = gr.Markdown("## Select a module from the sidebar")
109
+ module_desc = gr.Markdown("Choose any active backend route from the left navigation panel.")
110
+
111
+ gr.Markdown("---")
112
+ gr.Markdown("### πŸ“₯ Input Data")
113
+
114
+ with gr.Row():
115
+ input_file = gr.File(label="Upload Source File (Optional)", interactive=True)
116
+ input_text = gr.Textbox(
117
+ label="Input Text / Query / JSON Payload",
118
+ placeholder="Type your prompt, URL, query string, or payload...",
119
+ lines=5,
120
+ interactive=True,
121
+ )
122
+
123
+ submit_btn = gr.Button("πŸš€ Process Request", variant="primary", size="lg")
124
+
125
+ gr.Markdown("---")
126
  gr.Markdown("### πŸ“€ Output Response")
127
+
128
+ output_display = gr.JSON(label="Response Payload")
129
+ output_file_download = gr.File(label="Generated File Download", interactive=False)
130
+
131
+ # ── Event Handlers ───────────────────────────────────────────────────
132
+
133
+ def refresh_schema_handler(active_key):
134
+ """Re-fetch openapi.json on user request."""
135
+ new_map, new_choices = load_backend_modules(active_key)
136
+ selected = new_choices[0] if new_choices else None
137
+ return new_map, gr.Radio(choices=new_choices, value=selected)
138
+
139
+ sync_btn.click(
140
+ fn=refresh_schema_handler,
141
+ inputs=[user_key_input],
142
+ outputs=[schema_state, sidebar_menu],
143
+ )
144
+
145
+ def switch_module_handler(selected_name, current_map):
146
+ """Update active workspace info when a sidebar module is selected."""
147
+ if not selected_name or selected_name not in current_map:
148
+ return "## No Module Selected", "Please select a valid module from the sidebar."
149
+
150
+ mod = current_map[selected_name]
151
+ title_md = f"## `{mod['method']}` {mod['path']}"
152
  desc_md = f"**Summary:** {mod['summary']}\n\n{mod['description']}"
153
  return title_md, desc_md
154
 
155
  sidebar_menu.change(
156
+ fn=switch_module_handler,
157
+ inputs=[sidebar_menu, schema_state],
158
+ outputs=[module_title, module_desc],
159
  )
160
 
161
+ def process_request_handler(selected_name, active_key, file_obj, text_val, current_map):
162
+ """Dynamically execute backend requests."""
163
+ if not selected_name or selected_name not in current_map:
164
+ return {"error": "Invalid or unselected module."}, None
165
 
166
+ mod = current_map[selected_name]
167
  path = mod["path"]
168
  method = mod["method"]
169
 
 
171
  payload = {}
172
  files = {}
173
 
174
+ # Parse uploaded files
175
  if file_obj is not None:
176
  file_path = file_obj.name if hasattr(file_obj, "name") else str(file_obj)
177
  files["file"] = open(file_path, "rb")
178
 
179
+ # Parse text input
180
  if text_val and text_val.strip():
181
  cleaned_text = text_val.strip()
182
  if method == "GET":
 
193
  params=params,
194
  payload=payload,
195
  files=files,
196
+ user_api_key=active_key,
197
  )
198
 
199
+ # Check for downloadable output path in JSON response
200
  if success and isinstance(response, dict) and "file_path" in response:
201
  return response, response["file_path"]
202
 
203
  return response, None
204
 
205
  submit_btn.click(
206
+ fn=process_request_handler,
207
+ inputs=[sidebar_menu, user_key_input, input_file, input_text, schema_state],
208
+ outputs=[output_display, output_file_download],
209
  )
210
 
211
+ # ── Launch Application (Gradio 6.0 Compliant) ─────────────────────────────
 
212
  if __name__ == "__main__":
213
  demo.launch(
214
  server_name="0.0.0.0",
215
  server_port=7860,
 
216
  theme=gr.themes.Soft(),
217
  css=custom_css,
218
  share=False,