Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,379 +1,399 @@
|
|
1 |
import os
|
2 |
import json
|
3 |
import gradio as gr
|
|
|
4 |
from datetime import datetime
|
5 |
from dotenv import load_dotenv
|
6 |
from openai import OpenAI
|
|
|
7 |
|
8 |
-
#
|
9 |
-
|
|
|
|
|
|
|
|
|
10 |
|
11 |
class ExplorationPathGenerator:
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
def explore(
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
|
291 |
def create_interface() -> gr.Blocks:
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
|
|
|
|
|
|
|
|
368 |
|
369 |
if __name__ == "__main__":
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
|
|
|
1 |
import os
|
2 |
import json
|
3 |
import gradio as gr
|
4 |
+
import logging
|
5 |
from datetime import datetime
|
6 |
from dotenv import load_dotenv
|
7 |
from openai import OpenAI
|
8 |
+
from typing import Optional, Dict, Any, Tuple
|
9 |
|
10 |
+
# Configure logging
|
11 |
+
logging.basicConfig(
|
12 |
+
level=logging.INFO,
|
13 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
14 |
+
)
|
15 |
+
logger = logging.getLogger(__name__)
|
16 |
|
17 |
class ExplorationPathGenerator:
|
18 |
+
def __init__(self, api_key: str):
|
19 |
+
self.client = OpenAI(
|
20 |
+
api_key=api_key,
|
21 |
+
base_url="https://api.groq.com/openai/v1"
|
22 |
+
)
|
23 |
+
|
24 |
+
def generate_exploration_path(
|
25 |
+
self,
|
26 |
+
query: str,
|
27 |
+
selected_path: Optional[list] = None,
|
28 |
+
exploration_parameters: Optional[dict] = None
|
29 |
+
) -> Dict[str, Any]:
|
30 |
+
try:
|
31 |
+
if selected_path is None:
|
32 |
+
selected_path = []
|
33 |
+
if exploration_parameters is None:
|
34 |
+
exploration_parameters = {}
|
35 |
+
|
36 |
+
system_prompt = """You are an expert art historian AI that helps users explore art history topics by generating interconnected exploration paths. Generate a JSON response with nodes representing concepts, artworks, or historical events, and connections showing their relationships."""
|
37 |
+
|
38 |
+
user_prompt = f"""Query: {query}
|
39 |
+
Selected Path: {json.dumps(selected_path)}
|
40 |
+
Parameters: {json.dumps(exploration_parameters)}
|
41 |
+
Generate an exploration path that includes:
|
42 |
+
- Multiple interconnected nodes
|
43 |
+
- Clear relationships between nodes
|
44 |
+
- Depth-based organization
|
45 |
+
- Relevant historical context
|
46 |
+
Response must be valid JSON with this structure:
|
47 |
+
{{
|
48 |
+
"nodes": [
|
49 |
+
{{
|
50 |
+
"id": "unique_string",
|
51 |
+
"title": "node_title",
|
52 |
+
"description": "detailed_description",
|
53 |
+
"depth": number,
|
54 |
+
"connections": [
|
55 |
+
{{
|
56 |
+
"target_id": "connected_node_id",
|
57 |
+
"relevance_score": float
|
58 |
+
}}
|
59 |
+
]
|
60 |
+
}}
|
61 |
+
]
|
62 |
+
}}"""
|
63 |
+
|
64 |
+
response = self.client.chat.completions.create(
|
65 |
+
model="mixtral-8x7b-32768",
|
66 |
+
messages=[
|
67 |
+
{"role": "system", "content": system_prompt},
|
68 |
+
{"role": "user", "content": user_prompt}
|
69 |
+
],
|
70 |
+
temperature=0.7,
|
71 |
+
max_tokens=4000
|
72 |
+
)
|
73 |
+
|
74 |
+
result = json.loads(response.choices[0].message.content)
|
75 |
+
return result
|
76 |
+
|
77 |
+
except Exception as e:
|
78 |
+
logger.error(f"Error generating exploration path: {e}")
|
79 |
+
return {"error": str(e)}
|
80 |
+
|
81 |
+
@staticmethod
|
82 |
+
def create_interactive_graph(nodes: list) -> str:
|
83 |
+
"""Create an interactive graph visualization using D3.js"""
|
84 |
+
nodes_data = [{
|
85 |
+
'id': node['id'],
|
86 |
+
'title': node['title'],
|
87 |
+
'description': node['description'],
|
88 |
+
'depth': node['depth']
|
89 |
+
} for node in nodes]
|
90 |
+
|
91 |
+
links_data = [{
|
92 |
+
'source': node['id'],
|
93 |
+
'target': conn['target_id'],
|
94 |
+
'value': conn.get('relevance_score', 1)
|
95 |
+
} for node in nodes for conn in node.get('connections', [])]
|
96 |
+
|
97 |
+
html_content = f"""
|
98 |
+
<!DOCTYPE html>
|
99 |
+
<html>
|
100 |
+
<head>
|
101 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
102 |
+
<style>
|
103 |
+
#graph-container {{
|
104 |
+
width: 100%;
|
105 |
+
height: 600px;
|
106 |
+
border: 1px solid #ddd;
|
107 |
+
border-radius: 4px;
|
108 |
+
}}
|
109 |
+
.node {{
|
110 |
+
cursor: pointer;
|
111 |
+
}}
|
112 |
+
.node text {{
|
113 |
+
font-size: 12px;
|
114 |
+
font-family: Arial, sans-serif;
|
115 |
+
}}
|
116 |
+
.link {{
|
117 |
+
stroke: #999;
|
118 |
+
stroke-opacity: 0.6;
|
119 |
+
}}
|
120 |
+
.tooltip {{
|
121 |
+
position: absolute;
|
122 |
+
padding: 8px;
|
123 |
+
background: rgba(0, 0, 0, 0.8);
|
124 |
+
color: white;
|
125 |
+
border-radius: 4px;
|
126 |
+
font-size: 12px;
|
127 |
+
pointer-events: none;
|
128 |
+
}}
|
129 |
+
</style>
|
130 |
+
</head>
|
131 |
+
<body>
|
132 |
+
<div id="graph-container"></div>
|
133 |
+
<script>
|
134 |
+
// Data
|
135 |
+
const data = {{
|
136 |
+
nodes: {json.dumps(nodes_data)},
|
137 |
+
links: {json.dumps(links_data)}
|
138 |
+
}};
|
139 |
+
|
140 |
+
// Set up the SVG container
|
141 |
+
const container = document.getElementById('graph-container');
|
142 |
+
const width = container.clientWidth;
|
143 |
+
const height = container.clientHeight;
|
144 |
+
const svg = d3.select("#graph-container")
|
145 |
+
.append("svg")
|
146 |
+
.attr("width", width)
|
147 |
+
.attr("height", height);
|
148 |
+
|
149 |
+
// Add zoom capabilities
|
150 |
+
const g = svg.append("g");
|
151 |
+
const zoom = d3.zoom()
|
152 |
+
.scaleExtent([0.1, 4])
|
153 |
+
.on("zoom", (event) => g.attr("transform", event.transform));
|
154 |
+
svg.call(zoom);
|
155 |
+
|
156 |
+
// Create a force simulation
|
157 |
+
const simulation = d3.forceSimulation(data.nodes)
|
158 |
+
.force("link", d3.forceLink(data.links).id(d => d.id))
|
159 |
+
.force("charge", d3.forceManyBody().strength(-400))
|
160 |
+
.force("center", d3.forceCenter(width / 2, height / 2));
|
161 |
+
|
162 |
+
// Create the links
|
163 |
+
const link = g.append("g")
|
164 |
+
.selectAll("line")
|
165 |
+
.data(data.links)
|
166 |
+
.join("line")
|
167 |
+
.attr("stroke", "#999")
|
168 |
+
.attr("stroke-width", 1);
|
169 |
+
|
170 |
+
// Create the nodes
|
171 |
+
const node = g.append("g")
|
172 |
+
.selectAll("g")
|
173 |
+
.data(data.nodes)
|
174 |
+
.join("g")
|
175 |
+
.call(d3.drag()
|
176 |
+
.on("start", dragstarted)
|
177 |
+
.on("drag", dragged)
|
178 |
+
.on("end", dragended));
|
179 |
+
|
180 |
+
// Add circles to nodes
|
181 |
+
node.append("circle")
|
182 |
+
.attr("r", 20)
|
183 |
+
.attr("fill", d => ['#FF9999', '#99FF99', '#9999FF'][d.depth % 3]);
|
184 |
+
|
185 |
+
// Add labels to nodes
|
186 |
+
node.append("text")
|
187 |
+
.text(d => d.title)
|
188 |
+
.attr("x", 25)
|
189 |
+
.attr("y", 5);
|
190 |
+
|
191 |
+
// Add tooltip
|
192 |
+
const tooltip = d3.select("body").append("div")
|
193 |
+
.attr("class", "tooltip")
|
194 |
+
.style("opacity", 0);
|
195 |
+
|
196 |
+
// Add hover effects
|
197 |
+
node.on("mouseover", function(event, d) {{
|
198 |
+
tooltip.transition()
|
199 |
+
.duration(200)
|
200 |
+
.style("opacity", .9);
|
201 |
+
tooltip.html(`<strong>${{d.title}}</strong><br/>${{d.description}}`)
|
202 |
+
.style("left", (event.pageX + 10) + "px")
|
203 |
+
.style("top", (event.pageY - 10) + "px");
|
204 |
+
}})
|
205 |
+
.on("mouseout", function() {{
|
206 |
+
tooltip.transition()
|
207 |
+
.duration(500)
|
208 |
+
.style("opacity", 0);
|
209 |
+
}});
|
210 |
+
|
211 |
+
// Add click handler
|
212 |
+
node.on("click", function(event, d) {{
|
213 |
+
if (window.gradio) {{
|
214 |
+
window.gradio.dispatch("select", d);
|
215 |
+
}}
|
216 |
+
}});
|
217 |
+
|
218 |
+
// Update positions on each tick
|
219 |
+
simulation.on("tick", () => {{
|
220 |
+
link
|
221 |
+
.attr("x1", d => d.source.x)
|
222 |
+
.attr("y1", d => d.source.y)
|
223 |
+
.attr("x2", d => d.target.x)
|
224 |
+
.attr("y2", d => d.target.y);
|
225 |
+
node.attr("transform", d => `translate(${{d.x}},${{d.y}})`);
|
226 |
+
}});
|
227 |
+
|
228 |
+
// Drag functions
|
229 |
+
function dragstarted(event, d) {{
|
230 |
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
231 |
+
d.fx = d.x;
|
232 |
+
d.fy = d.y;
|
233 |
+
}}
|
234 |
+
|
235 |
+
function dragged(event, d) {{
|
236 |
+
d.fx = event.x;
|
237 |
+
d.fy = event.y;
|
238 |
+
}}
|
239 |
+
|
240 |
+
function dragended(event, d) {{
|
241 |
+
if (!event.active) simulation.alphaTarget(0);
|
242 |
+
d.fx = null;
|
243 |
+
d.fy = null;
|
244 |
+
}}
|
245 |
+
</script>
|
246 |
+
</body>
|
247 |
+
</html>
|
248 |
+
"""
|
249 |
+
return html_content
|
250 |
+
|
251 |
+
@gr.cache_examples
|
252 |
+
def explore(
|
253 |
+
query: str,
|
254 |
+
path_history: str = "[]",
|
255 |
+
parameters: str = "{}",
|
256 |
+
depth: int = 5,
|
257 |
+
domain: str = ""
|
258 |
+
) -> Tuple[str, str, str]:
|
259 |
+
"""Generate exploration path and visualization"""
|
260 |
+
try:
|
261 |
+
# Initialize generator
|
262 |
+
api_key = os.getenv("GROQ_API_KEY")
|
263 |
+
if not api_key:
|
264 |
+
raise ValueError("GROQ_API_KEY not found in environment variables")
|
265 |
+
|
266 |
+
generator = ExplorationPathGenerator(api_key=api_key)
|
267 |
+
|
268 |
+
# Parse inputs
|
269 |
+
try:
|
270 |
+
selected_path = json.loads(path_history)
|
271 |
+
exploration_parameters = json.loads(parameters)
|
272 |
+
except json.JSONDecodeError as e:
|
273 |
+
raise ValueError(f"Invalid JSON input: {str(e)}")
|
274 |
+
|
275 |
+
# Add domain and depth to parameters
|
276 |
+
exploration_parameters.update({
|
277 |
+
"domain": domain,
|
278 |
+
"depth": depth
|
279 |
+
})
|
280 |
+
|
281 |
+
# Generate result
|
282 |
+
result = generator.generate_exploration_path(
|
283 |
+
query=query,
|
284 |
+
selected_path=selected_path,
|
285 |
+
exploration_parameters=exploration_parameters
|
286 |
+
)
|
287 |
+
|
288 |
+
# Create visualization
|
289 |
+
graph_html = ExplorationPathGenerator.create_interactive_graph(result.get('nodes', []))
|
290 |
+
|
291 |
+
# Initial summary
|
292 |
+
summary = "Click on nodes in the graph to see detailed information"
|
293 |
+
|
294 |
+
return json.dumps(result), graph_html, summary
|
295 |
+
|
296 |
+
except Exception as e:
|
297 |
+
logger.error(f"Error in explore function: {e}")
|
298 |
+
error_response = {
|
299 |
+
"error": str(e),
|
300 |
+
"status": "failed",
|
301 |
+
"timestamp": datetime.now().isoformat(),
|
302 |
+
"query": query
|
303 |
+
}
|
304 |
+
return json.dumps(error_response), "<div>Error generating visualization</div>", f"Error: {str(e)}"
|
305 |
|
306 |
def create_interface() -> gr.Blocks:
|
307 |
+
"""Create and configure the Gradio interface"""
|
308 |
+
with gr.Blocks(
|
309 |
+
title="Art History Exploration Path Generator",
|
310 |
+
theme=gr.themes.Soft(),
|
311 |
+
css="#graph-visualization {min-height: 600px;}"
|
312 |
+
) as interface:
|
313 |
+
gr.Markdown("""
|
314 |
+
# Art History Exploration Path Generator
|
315 |
+
Generate interactive exploration paths through art history topics.
|
316 |
+
Drag nodes to rearrange, zoom with mouse wheel, and click nodes for details.
|
317 |
+
""")
|
318 |
+
|
319 |
+
with gr.Row():
|
320 |
+
with gr.Column(scale=1):
|
321 |
+
query_input = gr.Textbox(
|
322 |
+
label="Exploration Query",
|
323 |
+
placeholder="Enter your art history exploration query...",
|
324 |
+
lines=2
|
325 |
+
)
|
326 |
+
|
327 |
+
path_history = gr.Textbox(
|
328 |
+
label="Path History (JSON)",
|
329 |
+
placeholder="[]",
|
330 |
+
lines=3,
|
331 |
+
value="[]"
|
332 |
+
)
|
333 |
+
|
334 |
+
parameters = gr.Textbox(
|
335 |
+
label="Additional Parameters (JSON)",
|
336 |
+
placeholder="{}",
|
337 |
+
lines=3,
|
338 |
+
value="{}"
|
339 |
+
)
|
340 |
+
|
341 |
+
depth = gr.Slider(
|
342 |
+
label="Exploration Depth",
|
343 |
+
minimum=1,
|
344 |
+
maximum=10,
|
345 |
+
value=5,
|
346 |
+
step=1
|
347 |
+
)
|
348 |
+
|
349 |
+
domain = gr.Textbox(
|
350 |
+
label="Domain Context",
|
351 |
+
placeholder="Optional: Specify art history period or movement",
|
352 |
+
lines=1
|
353 |
+
)
|
354 |
+
|
355 |
+
generate_btn = gr.Button("Generate Exploration Path", variant="primary")
|
356 |
+
|
357 |
+
with gr.Column(scale=2):
|
358 |
+
with gr.Accordion("Exploration Result", open=False):
|
359 |
+
text_output = gr.JSON(label="Raw Result")
|
360 |
+
graph_output = gr.HTML(
|
361 |
+
label="Interactive Exploration Graph",
|
362 |
+
value="<div>Generate a path to see the visualization</div>",
|
363 |
+
elem_id="graph-visualization"
|
364 |
+
)
|
365 |
+
node_summary = gr.Textbox(
|
366 |
+
label="Node Details",
|
367 |
+
lines=5,
|
368 |
+
placeholder="Click on nodes to see details"
|
369 |
+
)
|
370 |
+
|
371 |
+
generate_btn.click(
|
372 |
+
fn=explore,
|
373 |
+
inputs=[query_input, path_history, parameters, depth, domain],
|
374 |
+
outputs=[text_output, graph_output, node_summary]
|
375 |
+
)
|
376 |
+
|
377 |
+
gr.Examples(
|
378 |
+
examples=[
|
379 |
+
["Explore the evolution of Renaissance painting techniques", "[]", "{}", 5, "Renaissance"],
|
380 |
+
["Investigate the influence of Japanese art on Impressionism", "[]", "{}", 7, "Impressionism"],
|
381 |
+
["Analyze the development of Cubism through Picasso's work", "[]", "{}", 6, "Cubism"]
|
382 |
+
],
|
383 |
+
inputs=[query_input, path_history, parameters, depth, domain]
|
384 |
+
)
|
385 |
+
|
386 |
+
return interface
|
387 |
|
388 |
if __name__ == "__main__":
|
389 |
+
try:
|
390 |
+
logger.info(f"===== Application Startup at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====")
|
391 |
+
load_dotenv()
|
392 |
+
demo = create_interface()
|
393 |
+
demo.launch(
|
394 |
+
server_name="0.0.0.0",
|
395 |
+
server_port=7860,
|
396 |
+
share=True
|
397 |
+
)
|
398 |
+
except Exception as e:
|
399 |
+
logger.error(f"Failed to launch interface: {e}")
|