| """ |
| Data Analyst Agent - Streamlit Version |
| Beautiful UI with Voice Input |
| """ |
|
|
| import streamlit as st |
| import asyncio |
| import os |
| import base64 |
| import tempfile |
| from fastapi_poe.client import get_bot_response |
| from fastapi_poe.types import ProtocolMessage, Attachment |
|
|
| |
| st.set_page_config( |
| page_title="π Data Analyst Agent", |
| page_icon="π", |
| layout="wide", |
| initial_sidebar_state="expanded" |
| ) |
|
|
| |
| st.markdown(""" |
| <style> |
| .main-header { |
| text-align: center; |
| padding: 1.5rem; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| border-radius: 16px; |
| margin-bottom: 2rem; |
| color: white; |
| } |
| .main-header h1 { |
| margin: 0; |
| font-size: 2.5rem; |
| } |
| .main-header p { |
| margin: 0.5rem 0 0 0; |
| opacity: 0.9; |
| } |
| .sidebar-card { |
| background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%); |
| border-radius: 12px; |
| padding: 1rem; |
| margin-bottom: 1rem; |
| } |
| .voice-card { |
| background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); |
| border-radius: 12px; |
| padding: 1rem; |
| margin-bottom: 1rem; |
| } |
| .stButton > button { |
| border-radius: 12px; |
| font-weight: 600; |
| } |
| .chat-message { |
| padding: 1rem; |
| border-radius: 12px; |
| margin-bottom: 1rem; |
| } |
| .user-message { |
| background: #e3f2fd; |
| border-left: 4px solid #2196f3; |
| } |
| .assistant-message { |
| background: #f5f5f5; |
| border-left: 4px solid #667eea; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| POE_API_KEY = os.environ.get("POE_API_KEY", "") |
|
|
| SYSTEM_PROMPT = """You are a powerful data analysis agent. You can: |
| |
| 1. **Analyze Data**: Read CSV, Excel (.xlsx, .xls) files and perform comprehensive analysis |
| 2. **Generate Visualizations**: Create charts using matplotlib, seaborn, plotly |
| 3. **Execute Python Code**: Run any Python code for data manipulation and analysis |
| 4. **Install Libraries**: Use `pip install` if a library is not available |
| 5. **Create Reports**: Generate Word (.docx) or PowerPoint (.pptx) documents with charts and summaries |
| |
| ## Guidelines |
| - When user uploads a file, first explore the data (shape, columns, dtypes, sample rows, missing values) |
| - Create meaningful visualizations based on the data types and relationships |
| - Always display charts inline so the user can see them |
| - For reports, save charts as images first, then embed them in Word/PowerPoint |
| - Be proactive: suggest insights and additional analyses the user might find valuable |
| - When creating downloadable files, provide the download link""" |
|
|
|
|
| async def transcribe_audio(audio_bytes: bytes) -> str: |
| """Transcribe audio using Whisper via Poe API.""" |
| if not audio_bytes: |
| return "" |
| try: |
| b64_data = base64.b64encode(audio_bytes).decode("utf-8") |
| data_url = f"data:audio/wav;base64,{b64_data}" |
| attachment = Attachment(url=data_url, name="voice.wav", content_type="audio/wav") |
| messages = [ProtocolMessage(role="user", content="Transcribe this audio accurately.", attachments=[attachment])] |
| text = "" |
| async for partial in get_bot_response(messages=messages, bot_name="Whisper-V3-Large-T", api_key=POE_API_KEY): |
| text += partial.text |
| return text.strip() |
| except Exception as e: |
| return f"Transcription error: {str(e)}" |
|
|
|
|
| async def call_analyst(message: str, file_bytes: bytes = None, filename: str = None, history: list = None): |
| """Call Claude-Code for analysis.""" |
| if not POE_API_KEY: |
| return "β **Error**: POE_API_KEY not set! Go to Settings β Secrets and add your Poe API key." |
|
|
| messages = [ProtocolMessage(role="system", content=SYSTEM_PROMPT)] |
|
|
| |
| if history: |
| for item in history: |
| if item["role"] == "user": |
| messages.append(ProtocolMessage(role="user", content=item["content"])) |
| elif item["role"] == "assistant": |
| messages.append(ProtocolMessage(role="assistant", content=item["content"])) |
|
|
| |
| attachments = [] |
| if file_bytes and filename: |
| if filename.endswith(".csv"): |
| ctype = "text/csv" |
| elif filename.endswith(".xlsx"): |
| ctype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" |
| else: |
| ctype = "application/octet-stream" |
|
|
| b64 = base64.b64encode(file_bytes).decode("utf-8") |
| attachments.append(Attachment(url=f"data:{ctype};base64,{b64}", name=filename, content_type=ctype)) |
|
|
| messages.append(ProtocolMessage(role="user", content=message, attachments=attachments)) |
|
|
| response = "" |
| try: |
| async for partial in get_bot_response(messages=messages, bot_name="Claude-Code", api_key=POE_API_KEY): |
| response += partial.text |
| except Exception as e: |
| response = f"β **API Error**: {str(e)}" |
|
|
| return response |
|
|
|
|
| def run_async(coro): |
| """Run async function.""" |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| try: |
| return loop.run_until_complete(coro) |
| finally: |
| loop.close() |
|
|
|
|
| |
| if "messages" not in st.session_state: |
| st.session_state.messages = [] |
| if "file_bytes" not in st.session_state: |
| st.session_state.file_bytes = None |
| if "filename" not in st.session_state: |
| st.session_state.filename = None |
|
|
| |
| st.markdown(""" |
| <div class="main-header"> |
| <h1>π Data Analyst Agent</h1> |
| <p>Upload your data β’ Ask questions β’ Get insights, charts & reports</p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| with st.sidebar: |
| st.markdown("### π Upload Your Data") |
| uploaded_file = st.file_uploader( |
| "Drop CSV or Excel file here", |
| type=["csv", "xlsx", "xls"], |
| help="Upload a CSV or Excel file to analyze" |
| ) |
|
|
| if uploaded_file: |
| st.session_state.file_bytes = uploaded_file.read() |
| st.session_state.filename = uploaded_file.name |
| st.success(f"β
Loaded: {uploaded_file.name}") |
| uploaded_file.seek(0) |
|
|
| st.markdown("---") |
|
|
| st.markdown("### π‘ Example Prompts") |
| st.markdown(""" |
| - Analyze this data and show key insights |
| - Create a bar chart of sales by category |
| - Show me a correlation heatmap |
| - Find the top 10 records by value |
| - Generate a PowerPoint summary |
| - Create a Word report with charts |
| """) |
|
|
| st.markdown("---") |
|
|
| st.markdown("### π€ Voice Input") |
| audio_file = st.file_uploader( |
| "Upload audio recording", |
| type=["wav", "mp3", "m4a", "ogg"], |
| help="Record audio on your device, then upload it here" |
| ) |
|
|
| if audio_file: |
| audio_bytes = audio_file.read() |
| st.audio(audio_bytes, format=f"audio/{audio_file.type.split('/')[-1]}") |
|
|
| if st.button("π― Transcribe Audio", use_container_width=True): |
| with st.spinner("Transcribing..."): |
| transcription = run_async(transcribe_audio(audio_bytes)) |
| if transcription: |
| st.session_state.voice_text = transcription |
| st.success("β
Transcribed!") |
| st.info(f"π {transcription}") |
|
|
| st.markdown("---") |
|
|
| if st.button("ποΈ Clear Chat History", use_container_width=True): |
| st.session_state.messages = [] |
| st.rerun() |
|
|
| |
| st.markdown("### π¬ Analysis Chat") |
|
|
| |
| for msg in st.session_state.messages: |
| if msg["role"] == "user": |
| st.markdown(f""" |
| <div class="chat-message user-message"> |
| <strong>π€ You:</strong><br>{msg["content"]} |
| </div> |
| """, unsafe_allow_html=True) |
| else: |
| st.markdown(f""" |
| <div class="chat-message assistant-message"> |
| <strong>π€ Agent:</strong><br>{msg["content"]} |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| col1, col2 = st.columns([5, 1]) |
|
|
| with col1: |
| |
| default_text = st.session_state.get("voice_text", "") |
| user_input = st.text_area( |
| "Your message", |
| value=default_text, |
| placeholder="Ask about your data, request charts, or generate reports...", |
| height=100, |
| label_visibility="collapsed" |
| ) |
| |
| if default_text and user_input == default_text: |
| st.session_state.voice_text = "" |
|
|
| with col2: |
| st.write("") |
| st.write("") |
| analyze_clicked = st.button("π Analyze", use_container_width=True, type="primary") |
|
|
| |
| if analyze_clicked and user_input.strip(): |
| |
| display_msg = user_input |
| if st.session_state.filename: |
| display_msg = f"π **{st.session_state.filename}**\n\n{user_input}" |
|
|
| st.session_state.messages.append({"role": "user", "content": display_msg}) |
|
|
| |
| with st.spinner("π Analyzing..."): |
| response = run_async(call_analyst( |
| user_input, |
| st.session_state.file_bytes, |
| st.session_state.filename, |
| st.session_state.messages[:-1] |
| )) |
|
|
| |
| st.session_state.messages.append({"role": "assistant", "content": response}) |
|
|
| |
| |
| |
|
|
| st.rerun() |
|
|
| |
| st.markdown("---") |
| st.markdown( |
| "<p style='text-align: center; color: #666;'>Powered by <a href='https://poe.com'>Poe API</a> β’ Claude-Code for intelligent analysis</p>", |
| unsafe_allow_html=True |
| ) |
|
|