Spaces:
Sleeping
Sleeping
import os | |
import openai | |
import gradio as gr | |
import json | |
import plotly.graph_objects as go | |
CONTEXTUAL_ZOOM_PROMPT = """ | |
You are an expert art historian specializing in interactive exploration. Analyze the query and generate contextually aware zoom configurations with explanations. | |
###Input### | |
User Query: {user_query} | |
Current Zoom States: { | |
"temporal": {"level": "", "selection": ""}, | |
"geographical": {"level": "", "selection": ""}, | |
"style": {"level": "", "selection": ""}, | |
"subject": {"level": "", "selection": ""} | |
} | |
###Output Format### | |
{ | |
"analysis": { | |
"query_focus": "main subject", | |
"historical_context": "brief explanation" | |
}, | |
"axis_configurations": { | |
"temporal": { | |
"component": "st.slider", | |
"current_zoom": { | |
"level": "century/decade/year", | |
"range": [start, end], | |
"explanation": "Why this time range is relevant" | |
}, | |
"available_zooms": { | |
"in": { | |
"range": [narrower_start, narrower_end], | |
"explanation": "What focusing here reveals" | |
}, | |
"out": { | |
"range": [broader_start, broader_end], | |
"explanation": "Broader historical context" | |
} | |
}, | |
"impacted_by": { | |
"geographical": "how location affects timeframe", | |
"style": "how style affects timeframe" | |
} | |
}, | |
"geographical": { | |
"component": "st.map", | |
"current_zoom": { | |
"level": "continent/country/city", | |
"locations": [ | |
{ | |
"name": "", | |
"lat": 0, | |
"lon": 0, | |
"relevance": "why this location matters" | |
} | |
] | |
}, | |
"available_zooms": { | |
"in": { | |
"locations": ["more specific locations"], | |
"explanation": "What focusing here reveals" | |
}, | |
"out": { | |
"locations": ["broader regions"], | |
"explanation": "Broader geographical context" | |
} | |
}, | |
"impacted_by": { | |
"temporal": "how time period affects locations", | |
"style": "how style affects locations" | |
} | |
}, | |
"style": { | |
"component": "st.multiselect" if current_zoom == "broad" else "st.selectbox", | |
"current_zoom": { | |
"level": "movement/sub_movement/specific", | |
"options": ["list of styles"], | |
"explanation": "Style context for this period/location" | |
} | |
} | |
}, | |
"streamlit_adaptations": { | |
"recommended_components": { | |
"component_name": "reason for recommendation based on zoom level", | |
"configuration": {} | |
} | |
} | |
} | |
###Example for "paint during napoleon war"### | |
{ | |
"temporal": { | |
"component": "st.slider", | |
"current_zoom": { | |
"level": "period", | |
"range": [1799, 1815], | |
"explanation": "Napoleon's reign as First Consul and Emperor"[1] | |
}, | |
"available_zooms": { | |
"in": { | |
"range": [1812, 1815], | |
"explanation": "Focus on final campaigns and artistic responses"[1] | |
} | |
} | |
}, | |
"geographical": { | |
"component": "st.map", | |
"current_zoom": { | |
"level": "continent", | |
"locations": [ | |
{ | |
"name": "France", | |
"relevance": "Center of Napoleonic art production"[2] | |
}, | |
{ | |
"name": "Spain", | |
"relevance": "Goya's war paintings perspective"[1] | |
} | |
] | |
} | |
} | |
} | |
###Requirements### | |
1. Explain zoom level changes and their historical significance | |
2. Adapt Streamlit components based on zoom level | |
3. Show relationships between different axes | |
4. Provide historical context for available selections | |
5. Consider how selections affect other axes | |
6. Include relevant historical explanations for each zoom level | |
Generate only the JSON response, maintaining strict JSON format.""" | |
class ArtExplorer: | |
def __init__(self): | |
self.client = openai.OpenAI( | |
base_url="https://api.groq.com/openai/v1", | |
api_key=os.environ.get("GROQ_API_KEY") | |
) | |
self.current_state = { | |
"zoom_level": 0, | |
"selections": {} | |
} | |
def create_map(self, locations): | |
"""Create a Plotly map figure from location data""" | |
if not locations: | |
locations = [{"name": "Paris", "lat": 48.8566, "lon": 2.3522}] | |
fig = go.Figure(go.Scattermapbox( | |
lat=[loc.get('lat') for loc in locations], | |
lon=[loc.get('lon') for loc in locations], | |
mode='markers', | |
marker=go.scattermapbox.Marker(size=10), | |
text=[loc.get('name') for loc in locations] | |
)) | |
fig.update_layout( | |
mapbox_style="open-street-map", | |
mapbox=dict( | |
center=dict(lat=48.8566, lon=2.3522), | |
zoom=4 | |
), | |
margin=dict(r=0, t=0, l=0, b=0) | |
) | |
return fig | |
def get_llm_response(self, query: str, zoom_context: dict = None) -> dict: | |
try: | |
current_zoom_states = { | |
"temporal": {"level": self.current_state["zoom_level"], "selection": ""}, | |
"geographical": {"level": self.current_state["zoom_level"], "selection": ""}, | |
"style": {"level": self.current_state["zoom_level"], "selection": ""}, | |
"subject": {"level": self.current_state["zoom_level"], "selection": ""} | |
} | |
if zoom_context: | |
for key, value in zoom_context.items(): | |
if key in current_zoom_states: | |
current_zoom_states[key]["selection"] = value | |
messages=[ | |
{"role": "system", "content": "You are an expert art historian specializing in interactive exploration."}, | |
{"role": "user", "content": CONTEXTUAL_ZOOM_PROMPT.format( | |
user_query=query, | |
current_zoom_states=json.dumps(current_zoom_states, indent=2) | |
)} | |
], | |
print(messages) | |
response = self.client.chat.completions.create( | |
model="mixtral-8x7b-32768", | |
messages=messages, | |
temperature=0.1, | |
max_tokens=2048 | |
) | |
print(response) | |
result = json.loads(response.choices[0].message.content) | |
return result | |
except Exception as e: | |
print(f"Error in LLM response: {str(e)}") | |
return self.get_default_response() | |
def get_default_response(self): | |
return { | |
"analysis": { | |
"query_focus": "Default focus", | |
"historical_context": "Default historical context" | |
}, | |
"axis_configurations": { | |
"temporal": { | |
"component": "st.slider", | |
"current_zoom": { | |
"level": "century", | |
"range": [1700, 2000], | |
"explanation": "Default time range" | |
}, | |
"available_zooms": { | |
"in": { | |
"range": [1800, 1900], | |
"explanation": "Zoom in to see more detail" | |
}, | |
"out": { | |
"range": [1500, 2024], | |
"explanation": "Broader historical context" | |
} | |
} | |
}, | |
"geographical": { | |
"current_zoom": { | |
"level": "continent", | |
"locations": [ | |
{ | |
"name": "Paris", | |
"lat": 48.8566, | |
"lon": 2.3522, | |
"relevance": "Default location" | |
} | |
] | |
} | |
}, | |
"style": { | |
"current_zoom": { | |
"level": "movement", | |
"options": ["Classical", "Modern"], | |
"explanation": "Default art movements" | |
} | |
} | |
} | |
} | |
def create_interface(self): | |
with gr.Blocks() as demo: | |
gr.Markdown("# Art History Explorer") | |
with gr.Row(): | |
query = gr.Textbox( | |
label="Enter your art history query", | |
placeholder="e.g., Napoleon wars, Renaissance Italy" | |
) | |
search_btn = gr.Button("Explore") | |
with gr.Row(): | |
# Temporal axis | |
with gr.Column(): | |
time_slider = gr.Slider( | |
minimum=1000, | |
maximum=2024, | |
label="Time Period", | |
interactive=True | |
) | |
time_explanation = gr.Markdown() | |
time_zoom = gr.Button("π Zoom Time Period") | |
# Geographical axis | |
with gr.Column(): | |
map_plot = gr.Plot(label="Geographic Location") | |
geo_explanation = gr.Markdown() | |
geo_zoom = gr.Button("π Zoom Geography") | |
with gr.Row(): | |
style_select = gr.Dropdown( | |
multiselect=True, | |
label="Artistic Styles" | |
) | |
style_explanation = gr.Markdown() | |
style_zoom = gr.Button("π Zoom Styles") | |
def initial_search(query): | |
config = self.get_llm_response(query) | |
# Access the correct nested structure | |
temporal_config = config["axis_configurations"]["temporal"]["current_zoom"] | |
geographical_config = config["axis_configurations"]["geographical"]["current_zoom"] | |
style_config = config["axis_configurations"]["style"]["current_zoom"] | |
# Create map figure | |
map_fig = self.create_map(geographical_config["locations"]) | |
return { | |
time_slider: temporal_config["range"], | |
map_plot: map_fig, | |
style_select: gr.Dropdown(choices=style_config["options"]), | |
time_explanation: temporal_config["explanation"], | |
geo_explanation: geographical_config.get("explanation", ""), | |
style_explanation: style_config["explanation"] | |
} | |
def zoom_axis(query, axis_name, current_value): | |
self.current_state["zoom_level"] += 1 | |
config = self.get_llm_response( | |
query, | |
zoom_context={axis_name: current_value} | |
) | |
axis_config = config["axis_configurations"][axis_name]["current_zoom"] | |
if axis_name == "temporal": | |
return { | |
time_slider: axis_config["range"], | |
time_explanation: axis_config["explanation"] | |
} | |
elif axis_name == "geographical": | |
map_fig = self.create_map(axis_config["locations"]) | |
return { | |
map_plot: map_fig, | |
geo_explanation: axis_config.get("explanation", "") | |
} | |
else: # style | |
return { | |
style_select: gr.Dropdown(choices=axis_config["options"]), | |
style_explanation: axis_config["explanation"] | |
} | |
# Connect event handlers | |
search_btn.click( | |
fn=initial_search, | |
inputs=[query], | |
outputs=[ | |
time_slider, | |
map_plot, | |
style_select, | |
time_explanation, | |
geo_explanation, | |
style_explanation | |
] | |
) | |
time_zoom.click( | |
fn=lambda q, v: zoom_axis(q, "temporal", v), | |
inputs=[query, time_slider], | |
outputs=[time_slider, time_explanation] | |
) | |
geo_zoom.click( | |
fn=lambda q, v: zoom_axis(q, "geographical", v), | |
inputs=[query, map_plot], | |
outputs=[map_plot, geo_explanation] | |
) | |
style_zoom.click( | |
fn=lambda q, v: zoom_axis(q, "style", v), | |
inputs=[query, style_select], | |
outputs=[style_select, style_explanation] | |
) | |
return demo | |
if __name__ == "__main__": | |
explorer = ArtExplorer() | |
demo = explorer.create_interface() | |
demo.launch() |