Prat0 commited on
Commit
1fac056
·
verified ·
1 Parent(s): 95bd2c6

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +159 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,161 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ from agents import Agent, Runner
2
+ from composio_openai_agents import ComposioToolSet, App, Action
 
3
  import streamlit as st
4
+ from dotenv import load_dotenv
5
+ import asyncio
6
+ from datetime import datetime, timedelta
7
+ import pandas as pd
8
+ import plotly.express as px
9
+ from wordcloud import WordCloud
10
+ import matplotlib.pyplot as plt
11
+ from io import BytesIO
12
+ import base64
13
+ from pydantic import BaseModel
14
+ from typing import List, Optional
15
+ import os
16
+
17
+ load_dotenv()
18
+
19
+ max_date = datetime.now().strftime("%Y-%m-%d")
20
+ min_date = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
21
+
22
+ class SentimentDatum(BaseModel):
23
+ sentiment: str
24
+ count: int
25
+
26
+ class IncidentDatum(BaseModel):
27
+ source: str
28
+ incidents: int
29
+
30
+ class CompetitiveDatum(BaseModel):
31
+ company: str
32
+ mentions: int
33
+
34
+ class Citation(BaseModel):
35
+ source: str
36
+ text: str
37
+ url: str
38
+ sentiment: str
39
+
40
+ class DashboardOutput(BaseModel):
41
+ executive_summary: str
42
+ sentiment_data: List[SentimentDatum]
43
+ incident_data: List[IncidentDatum]
44
+ competitive_data: List[CompetitiveDatum]
45
+ wordcloud_text: str
46
+ citations: List[Citation]
47
+
48
+ openai_key = os.environ.get("OPENAI_API_KEY")
49
+ composio_key = os.environ.get("COMPOSIO_API_KEY")
50
+
51
+ async def run_analysis(company_name):
52
+ toolset = ComposioToolSet(api_key=composio_key)
53
+ tools = toolset.get_tools(actions=[Action.TWITTER_RECENT_SEARCH, Action.REDDIT_SEARCH_ACROSS_SUBREDDITS, Action.HACKERNEWS_SEARCH_POSTS, Action.HACKERNEWS_GET_ITEM_WITH_ID])
54
+ agent = Agent(
55
+ name="Social Media Analyst",
56
+ instructions=f"""
57
+ Your ONLY job is to deliver a crisp, actionable, clustered executive summary of new, high-utility, real-time technical incidents, security chatter, actionable customer/developer feedback for the company, and competitive intelligence.
58
+
59
+ - Do NOT cover general market or sector sentiment, financial/market positioning, or broad sentiment.
60
+ - Do NOT explain what you are not doing, do NOT ask for clarification, do NOT disclaim, and do NOT ask for permission or confirmation.
61
+ - Just deliver the report as specified, following the structure below.
62
+ - You must use Reddit, Twitter, and Hacker News to get the data. For each source, find and cluster at least 3 relevant, recent signals (if available) and include them in the summary and citations.
63
+ - The search query for twitter has to be something like this (Adjust the parameters according to the context): "{company_name} to:{max_date} from:{min_date}", don't pass any other fields. Just the query and max results.
64
+ - For Twitter: If no technical incidents, bugs, outages, or actionable feedback are found, instead summarize the general sentiment about {company_name} on Twitter, with clickable links to the original tweets.
65
+ - For hackernews, use the search query: "{company_name}", then use the get item with id tool to get the item.
66
+ - Don't give updates that the company already knows about, like product launches, new features, etc.
67
+ - For competitive intelligence, look for posts, tweets, or comments where users compare {company_name} to other competitors. Summarize the key points, advantages, disadvantages, and user sentiment in these comparisons. Include citations for each comparison.
68
+ - Provide more research-detailed information in both the executive summary and competitive intelligence sections. Go deeper into technical, user, and product details, and cite specific sources and findings.
69
+ Report structure:
70
+ 1. # Executive Summary: A crisp, actionable, clustered summary of all new, high-utility signals. No fluff, only what matters. Include detailed research and findings.
71
+ 2. # Competitive Intelligence: What users are saying when comparing {company_name} to competitors. Summarize key themes, pros/cons, and user sentiment. Include citations and research-level detail.
72
+ 3. # Exact Citations: For each cluster or item, provide clickable links to the original sources (tweets, posts, etc.).
73
+
74
+ Everything should be cited and referenced. Focus only on the tech side and actionable intelligence.
75
+ You will be given the name of the company.
76
+ If tool call fails, try again with different permutations of the query.
77
+ """,
78
+ tools=tools,
79
+ model='gpt-4.1',
80
+ )
81
+ res = await Runner.run(starting_agent=agent, input=f"What is the summary of {company_name}?")
82
+ return res.final_output
83
+
84
+ async def dashboard_result(result):
85
+ agent = Agent(
86
+ name="Dashboard Generator",
87
+ instructions=f"""
88
+ Your job is to generate a dashboard from the given result.
89
+ Competitors include direct competitors only.
90
+ """,
91
+ output_type=DashboardOutput,
92
+ )
93
+ res = await Runner.run(starting_agent=agent, input=result)
94
+ return res.final_output
95
+
96
+
97
+
98
+ name_from_url = st.query_params.get("name", "")
99
+ company_from_url = st.query_params.get("company", "")
100
+
101
+
102
+ if name_from_url:
103
+ st.title(f"{name_from_url}'s Market Pulse Agent ")
104
+ else:
105
+ st.title("Market Pulse Agent")
106
+
107
+ company_name = st.text_input("Company name:", value=company_from_url)
108
+
109
+ if st.button("Analyze"):
110
+ if company_name:
111
+ with st.spinner("Analyzing social media sentiment..."):
112
+ result = asyncio.run(run_analysis(company_name))
113
+ dashboard_result_response = asyncio.run(dashboard_result(str(result)))
114
+ st.subheader("Summary and Sentiment Analysis")
115
+ st.write(result)
116
+
117
+ st.markdown("---")
118
+ st.header("📊 Dashboard")
119
+ # Metrics Row
120
+ metric1, metric2, metric3 = st.columns(3)
121
+ with metric1:
122
+ st.metric("Total Mentions", sum([s.count for s in dashboard_result_response.sentiment_data]))
123
+ with metric2:
124
+ st.metric("Incidents", sum([i.incidents for i in dashboard_result_response.incident_data]))
125
+ with metric3:
126
+ st.metric("Competitors Compared", len(dashboard_result_response.competitive_data)-1)
127
+
128
+ # Main Grid
129
+ grid1, grid2 = st.columns([2, 1])
130
+ with grid1:
131
+ st.subheader(":green[Sentiment Breakdown]")
132
+ df_sentiment = pd.DataFrame([s.model_dump() for s in dashboard_result_response.sentiment_data])
133
+ fig = px.pie(df_sentiment, names='sentiment', values='count', color='sentiment',
134
+ color_discrete_map={"Positive": "#22c55e", "Negative": "#ef4444", "Neutral": "#a3a3a3"})
135
+ fig.update_traces(textinfo='percent+label', pull=[0.05, 0.05, 0.05])
136
+ st.plotly_chart(fig, use_container_width=True)
137
+ # Competitive Intelligence full width
138
+ st.subheader(":blue[Competitive Intelligence]")
139
+ df_comp = pd.DataFrame([c.model_dump() for c in dashboard_result_response.competitive_data])
140
+ fig3 = px.bar(df_comp, x='company', y='mentions', color='company', text='mentions',
141
+ color_discrete_sequence=px.colors.qualitative.Pastel)
142
+ fig3.update_layout(yaxis_title=None, xaxis_title=None, plot_bgcolor='#f8fafc')
143
+ st.plotly_chart(fig3, use_container_width=True)
144
+ with grid2:
145
+ if sum([i.incidents for i in dashboard_result_response.incident_data]) > 0:
146
+ st.subheader(":orange[Incident Frequency]")
147
+ df_incident = pd.DataFrame([i.model_dump() for i in dashboard_result_response.incident_data])
148
+ fig2 = px.bar(df_incident, x='source', y='incidents', color='source', text='incidents',
149
+ color_discrete_sequence=px.colors.qualitative.Set2)
150
+ fig2.update_layout(yaxis_title=None, xaxis_title=None, plot_bgcolor='#f8fafc')
151
+ st.plotly_chart(fig2, use_container_width=True)
152
 
153
+ st.markdown("<hr style='margin:2rem 0;' />", unsafe_allow_html=True)
154
+ st.subheader(":gray[Top Citations]")
155
+ df_cite = pd.DataFrame([c.model_dump() for c in dashboard_result_response.citations])
156
+ def make_clickable(url):
157
+ return f'<a href="{url}" target="_blank" style="color:#2563eb; text-decoration:underline;">link</a>'
158
+ df_cite['url'] = df_cite['url'].apply(make_clickable)
159
+ st.write(df_cite.to_html(escape=False, index=False, classes='tw-table tw-table-auto tw-bg-white tw-shadow-md tw-rounded-lg'), unsafe_allow_html=True)
160
+ else:
161
+ st.warning("Please enter a company name.")