kimhyunwoo's picture
Update app.py
734a3d4 verified
import gradio as gr
import time
import io
import base64
from PIL import Image, ImageDraw
# --- 1. 가상 백엔드 (Virtual Backend) ---
class AgentSystem:
def __init__(self):
self.file_system = {
"research_data.csv": {"type": "csv", "content": "Month,Sales,Growth\nJan,100,10%\nFeb,120,15%\nMar,150,20%\nApr,170,18%"},
"agent_architecture.png": {"type": "image", "content": "architecture_diagram"},
"meeting_notes.txt": {"type": "text", "content": "Meeting Notes:\n- Discussed multi-agent protocols.\n- Need to optimize latency.\n- Next sprint starts Monday."},
"demo_video.mp4": {"type": "video", "content": "dummy_video_path"}
}
def read_file(self, filename):
if filename not in self.file_system:
return {"type": "none", "content": ""}, "File not found."
return self.file_system[filename], f"File '{filename}' opened."
def generate_plot(self):
"""Matplotlib을 사용하여 실제 그래프 이미지를 생성"""
try:
import matplotlib.pyplot as plt
plt.switch_backend('Agg')
fig, ax = plt.subplots(figsize=(5, 3))
months = ['Jan', 'Feb', 'Mar', 'Apr']
sales = [100, 120, 150, 170]
ax.bar(months, sales, color='#007aff', alpha=0.7)
ax.set_title("Q1 Research Growth")
ax.grid(True, alpha=0.3)
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
buf.seek(0)
plt.close(fig)
return Image.open(buf)
except ImportError:
img = Image.new('RGB', (400, 300), color='white')
d = ImageDraw.Draw(img)
d.text((10,10), "Matplotlib not found", fill='black')
return img
def process_query(self, query, current_file):
query = query.lower()
logs = []
result_img = None
result_text = ""
logs.append(f"🧠 **Thought:** User wants to '{query}'...")
time.sleep(0.5)
if "plot" in query and current_file == "research_data.csv":
logs.append(f"🛠️ **Tool:** Executing `Python.matplotlib` on {current_file}...")
time.sleep(0.8)
result_img = self.generate_plot()
result_text = "I've generated a bar chart based on the CSV data."
elif "summarize" in query and current_file:
logs.append(f"🛠️ **Tool:** Reading content of {current_file}...")
time.sleep(0.5)
content = self.file_system[current_file]['content']
result_text = f"📝 **Summary:** The file contains {len(content)} characters. Key topic appears to be related to research/sales."
elif "open" in query:
result_text = "Please use the Finder to open files for now."
else:
result_text = "I am an Agent OS. I can **plot data**, **summarize text**, or help you navigate."
return logs, result_text, result_img
os_system = AgentSystem()
# --- 2. CSS (No Inline Styles) ---
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root { --bg-desktop: url('https://images.unsplash.com/photo-1621574539436-4b7044e3756c?q=80&w=2832&auto=format&fit=crop'); }
body { margin: 0; padding: 0; overflow: hidden !important; background: #000; }
.gradio-container { width: 100vw !important; height: 100vh !important; padding: 0 !important; margin: 0 !important; background: var(--bg-desktop) center/cover no-repeat fixed !important; display: block !important; }
footer { display: none !important; }
#menubar { position: absolute; top: 0; left: 0; width: 100%; height: 28px; background: rgba(255,255,255,0.8); backdrop-filter: blur(10px); display: flex; align-items: center; padding: 0 15px; z-index: 9000; font-family: 'Inter'; font-size: 12px; font-weight: 600; }
#desktop { position: absolute; top: 28px; bottom: 0; left: 0; right: 0; pointer-events: none; }
.os-window { position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 650px !important; height: 450px !important; background: rgba(255,255,255,0.95); border-radius: 10px; box-shadow: 0 25px 50px rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.4); display: flex; flex-direction: column; overflow: hidden; pointer-events: auto; z-index: 100; }
.window-header { height: 28px; background: #f0f0f0; border-bottom: 1px solid #ddd; display: flex; align-items: center; padding: 0 10px; gap: 6px; }
.btn-dot { width: 11px; height: 11px; border-radius: 50%; }
.red { background: #ff5f57; border: 1px solid #e0443e; }
.yellow { background: #ffbd2e; border: 1px solid #dea123; }
.green { background: #28c940; border: 1px solid #1aab29; }
.win-title { flex:1; text-align:center; font-size:12px; color:#666; font-weight:500; margin-right:50px;}
.window-content { flex: 1; padding: 0; overflow: hidden; display: flex; flex-direction: column; }
/* Specific styling for preview content area */
.preview-container { background: white; justify-content: center; align-items: center; }
.finder-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; padding: 20px; }
.finder-item { display: flex; flex-direction: column; align-items: center; cursor: pointer; padding: 10px; border-radius: 5px; transition: background 0.2s; }
.finder-item:hover { background: rgba(0,0,0,0.05); }
#copilot { position: absolute; bottom: 100px; right: 30px; width: 340px; height: 500px; background: rgba(255,255,255,0.85); backdrop-filter: blur(20px); border-radius: 12px; border: 1px solid rgba(255,255,255,0.5); box-shadow: 0 10px 40px rgba(0,0,0,0.15); display: flex; flex-direction: column; z-index: 5000; pointer-events: auto; }
.copilot-bg { background: transparent !important; }
.copilot-header-row { background: #f9f9f9; }
.copilot-input-row { padding: 10px; border-top: 1px solid #eee; background: white; border-radius: 0 0 12px 12px; }
#dock-wrap { position: absolute; bottom: 20px; width: 100%; display: flex; justify-content: center; pointer-events: none; z-index: 6000; }
#dock { background: rgba(255,255,255,0.3); backdrop-filter: blur(15px); padding: 8px 15px; border-radius: 16px; display: flex; gap: 12px; border: 1px solid rgba(255,255,255,0.2); pointer-events: auto; box-shadow: 0 5px 15px rgba(0,0,0,0.1); }
.dock-icon { font-size: 24px; width: 44px; height: 44px; background: rgba(255,255,255,0.8); border-radius: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.2s; border: none; }
.dock-icon:hover { transform: scale(1.1) translateY(-5px); }
"""
# --- 3. Logic Functions ---
def handle_file_click(file_name):
data, msg = os_system.read_file(file_name)
vis_finder = gr.update(visible=False)
vis_preview = gr.update(visible=True)
if data['type'] == 'image':
return vis_finder, vis_preview, gr.update(value=None, visible=False), gr.update(value=f"### {file_name}\n(Image Content Placeholder)", visible=True), file_name
elif data['type'] == 'csv':
return vis_finder, vis_preview, gr.update(value=None, visible=False), gr.update(value=f"### {file_name}\n```csv\n{data['content']}\n```", visible=True), file_name
else:
return vis_finder, vis_preview, gr.update(value=None, visible=False), gr.update(value=f"### {file_name}\n{data['content']}", visible=True), file_name
def agent_response(message, history, current_file):
if not message: return "", history
history.append({"role": "user", "content": message})
yield "", history
logs, result_text, result_img = os_system.process_query(message, current_file)
for log in logs:
history.append({"role": "assistant", "content": log})
yield "", history
time.sleep(0.2)
history.append({"role": "assistant", "content": result_text})
yield "", history
def agent_update_preview(message, current_file):
if "plot" in message.lower() and current_file == "research_data.csv":
img = os_system.generate_plot()
return gr.update(visible=True, value=img), gr.update(visible=False)
return gr.update(), gr.update()
def show_finder(): return gr.update(visible=True), gr.update(visible=False), ""
def close_all(): return gr.update(visible=False), gr.update(visible=False), ""
# --- 4. UI Builder (Cleaned & Ordered) ---
with gr.Blocks(css=CSS, theme=gr.themes.Base()) as demo:
current_file_state = gr.State("")
# Top Bar
with gr.Column(elem_id="menubar"):
gr.HTML(" &nbsp; <b>AgentOS</b> &nbsp;&nbsp; File &nbsp; Edit &nbsp; View &nbsp; Window &nbsp; Help")
# Desktop Area
with gr.Group(elem_id="desktop"):
# 1. Finder Window
with gr.Group(visible=False, elem_classes=["os-window"]) as win_finder:
with gr.Row(elem_classes=["window-header"]):
gr.HTML('<div class="btn-dot red"></div><div class="btn-dot yellow"></div><div class="btn-dot green"></div><div class="win-title">Finder</div>')
with gr.Column(elem_classes=["window-content"]):
gr.Markdown("### 📍 Desktop")
with gr.Row(elem_classes=["finder-grid"]):
finder_buttons = []
for fname, fmeta in os_system.file_system.items():
icon = "📊" if fmeta['type']=='csv' else "🖼️" if fmeta['type']=='image' else "📝"
btn = gr.Button(f"{icon}\n{fname}", elem_classes=["finder-item"])
finder_buttons.append((btn, fname))
# 2. Preview Window (Fixed: Moved styles to CSS class 'preview-container')
with gr.Group(visible=False, elem_classes=["os-window"]) as win_preview:
with gr.Row(elem_classes=["window-header"]):
gr.HTML('<div class="btn-dot red"></div><div class="btn-dot yellow"></div><div class="btn-dot green"></div><div class="win-title">Preview</div>')
with gr.Column(elem_classes=["window-content", "preview-container"]):
win_preview_img = gr.Image(visible=False, height=350, width=500, show_label=False, container=False)
win_preview_text = gr.Markdown(visible=True, value="### No Content")
# Copilot Chatbot
with gr.Group(elem_id="copilot"):
# Fixed: Moved styles to CSS class 'copilot-header-row'
with gr.Row(elem_classes=["window-header", "copilot-header-row"]):
gr.HTML('<div class="win-title" style="margin:0; text-align:left; padding-left:10px;">🤖 Agent Copilot</div>')
chatbot = gr.Chatbot(elem_id="chatbot", elem_classes=["copilot-bg"], height=400, type="messages")
# Fixed: Moved styles to CSS class 'copilot-input-row'
with gr.Row(elem_classes=["copilot-input-row"]):
msg_input = gr.Textbox(show_label=False, placeholder="Type 'plot graph'...", container=False, scale=4)
send_btn = gr.Button("⬆", scale=1, size="sm", variant="primary")
# Dock
with gr.Group(elem_id="dock-wrap"):
with gr.Row(elem_id="dock"):
btn_finder = gr.Button("📂", elem_classes=["dock-icon"])
btn_reset = gr.Button("🏠", elem_classes=["dock-icon"])
# --- 5. Wiring Events ---
# Finder Buttons
for btn, fname in finder_buttons:
btn.click(
fn=handle_file_click,
inputs=[gr.State(fname)],
outputs=[win_finder, win_preview, win_preview_img, win_preview_text, current_file_state]
)
# Chatbot
msg_input.submit(
agent_response, [msg_input, chatbot, current_file_state], [msg_input, chatbot]
).then(
agent_update_preview, [msg_input, current_file_state], [win_preview_img, win_preview_text]
)
send_btn.click(
agent_response, [msg_input, chatbot, current_file_state], [msg_input, chatbot]
).then(
agent_update_preview, [msg_input, current_file_state], [win_preview_img, win_preview_text]
)
# Dock
btn_finder.click(show_finder, outputs=[win_finder, win_preview, current_file_state])
btn_reset.click(close_all, outputs=[win_finder, win_preview, current_file_state])
if __name__ == "__main__":
demo.launch()