Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -5,6 +5,7 @@ from langchain_google_genai import ChatGoogleGenerativeAI
|
|
5 |
from langchain_core.messages import HumanMessage
|
6 |
import os
|
7 |
import random
|
|
|
8 |
|
9 |
# --- Configuration ---
|
10 |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
@@ -13,8 +14,6 @@ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
|
13 |
def fetch_market_chatter(niche_description: str) -> list[str]:
|
14 |
"""Simulates scraping Reddit, Hacker News, etc., for a given niche."""
|
15 |
print(f"Simulating scraping for: {niche_description}")
|
16 |
-
# In a real app, this would use PRAW, snscrape, etc.
|
17 |
-
# We'll generate realistic-sounding comments.
|
18 |
base_comments = [
|
19 |
"Ugh, another project management tool that charges per user. I'm a solo founder, this kills me.",
|
20 |
"I love the idea of Notion but it's just too slow and bloated now. I need something faster.",
|
@@ -32,46 +31,46 @@ def fetch_market_chatter(niche_description: str) -> list[str]:
|
|
32 |
def analyze_resonance(niche_description: str, comments: list[str]) -> dict:
|
33 |
"""Uses an LLM to perform topic modeling, pain point extraction, and sentiment analysis."""
|
34 |
if not GEMINI_API_KEY:
|
35 |
-
return {"error": "GEMINI_API_KEY not set."}
|
36 |
|
37 |
-
llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro-latest", google_api_key=GEMINI_API_KEY)
|
38 |
-
|
39 |
-
# Create a single, powerful prompt for the analysis
|
40 |
-
comments_formatted = "\n".join([f"- \"{c}\"" for c in comments])
|
41 |
-
prompt = f"""
|
42 |
-
You are a market research analyst with superhuman insight. Analyze the following raw market chatter (comments from Reddit, Hacker News, etc.) for a proposed product idea.
|
43 |
-
|
44 |
-
**Proposed Product Idea:** "{niche_description}"
|
45 |
-
|
46 |
-
**Raw Market Chatter:**
|
47 |
-
{comments_formatted}
|
48 |
-
|
49 |
-
**Your Task:**
|
50 |
-
Analyze the chatter and return a JSON object with the following structure. Do not include any text outside the JSON object.
|
51 |
-
{{
|
52 |
-
"pain_points": [
|
53 |
-
"A summary of the most common complaint or frustration.",
|
54 |
-
"A summary of the second most common complaint.",
|
55 |
-
"A summary of a third, more niche complaint."
|
56 |
-
],
|
57 |
-
"magic_words": [
|
58 |
-
"A positive, benefit-oriented word or phrase people use.",
|
59 |
-
"Another evocative word people use to describe their ideal solution.",
|
60 |
-
"A third powerful, emotional word."
|
61 |
-
],
|
62 |
-
"target_villain": "The name of a competitor or a type of product that people frequently complain about.",
|
63 |
-
"unserved_tribe": "A description of a specific user subgroup whose needs are not being met.",
|
64 |
-
"resonance_score": A number between 1 and 100 representing how well the proposed product idea fits the market chatter,
|
65 |
-
"resonance_justification": "A one-sentence explanation for the score you gave."
|
66 |
-
}}
|
67 |
-
"""
|
68 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
response = llm.invoke([HumanMessage(content=prompt)])
|
70 |
# Clean up the response to ensure it's valid JSON
|
71 |
json_str = response.content.strip().replace("```json", "").replace("```", "")
|
72 |
-
return json.loads(json_str)
|
73 |
except Exception as e:
|
74 |
-
|
|
|
75 |
|
76 |
# --- Main Orchestrator ---
|
77 |
def run_precog_analysis(niche_description: str):
|
@@ -85,19 +84,18 @@ def run_precog_analysis(niche_description: str):
|
|
85 |
# Stage 2 & 3
|
86 |
analysis_result = analyze_resonance(niche_description, comments)
|
87 |
if "error" in analysis_result:
|
88 |
-
|
|
|
89 |
return
|
90 |
|
91 |
# --- Prepare Outputs ---
|
92 |
-
|
93 |
-
# The main report
|
94 |
score = analysis_result.get('resonance_score', 0)
|
95 |
color = "green" if score > 65 else "orange" if score > 40 else "red"
|
96 |
report_md = f"""
|
97 |
<div style="text-align:center; border: 2px solid {color}; border-radius:10px; padding:20px;">
|
98 |
<h2 style="margin:0;">Market Resonance Score</h2>
|
99 |
<p style="font-size: 80px; font-weight:bold; margin:0; color:{color};">{score}/100</p>
|
100 |
-
<p style="margin:0; font-style:italic;">"{analysis_result.get('resonance_justification', '')}"</p>
|
101 |
</div>
|
102 |
|
103 |
### π₯ Top 3 Unspoken Pain Points
|
@@ -112,18 +110,17 @@ def run_precog_analysis(niche_description: str):
|
|
112 |
- `{analysis_result.get('magic_words', ['N/A'])[2]}`
|
113 |
"""
|
114 |
|
115 |
-
# The strategic insights
|
116 |
strategy_md = f"""
|
117 |
### π― Your Go-to-Market Strategy
|
118 |
|
119 |
**Your "Villain":**
|
120 |
-
Position your product as the direct antidote to **{analysis_result.get('target_villain', '
|
121 |
|
122 |
**Your Unserved "Tribe":**
|
123 |
-
Focus your initial launch on this niche group: **{analysis_result.get('unserved_tribe', '
|
124 |
"""
|
125 |
|
126 |
-
yield report_md, strategy_md, "Analysis Complete", gr.Button("Analyze", interactive=True)
|
127 |
|
128 |
# --- Gradio UI ---
|
129 |
with gr.Blocks(theme=gr.themes.Glass(), css=".gradio-container{max-width: 800px !important}") as demo:
|
@@ -144,7 +141,7 @@ with gr.Blocks(theme=gr.themes.Glass(), css=".gradio-container{max-width: 800px
|
|
144 |
gr.Markdown("## Strategy")
|
145 |
strategy_output = gr.Markdown()
|
146 |
|
147 |
-
status_output = gr.Markdown()
|
148 |
|
149 |
submit_btn.click(
|
150 |
fn=run_precog_analysis,
|
@@ -153,7 +150,7 @@ with gr.Blocks(theme=gr.themes.Glass(), css=".gradio-container{max-width: 800px
|
|
153 |
)
|
154 |
|
155 |
gr.Markdown("---")
|
156 |
-
gr.Markdown("### This is a demo of the Pre-Cog Engine. \n The Pro version provides access to live data streams, deeper analysis, and continuous market monitoring. \n **[π
|
157 |
|
158 |
if __name__ == "__main__":
|
159 |
demo.launch()
|
|
|
5 |
from langchain_core.messages import HumanMessage
|
6 |
import os
|
7 |
import random
|
8 |
+
import json # <<< THE FIX IS HERE
|
9 |
|
10 |
# --- Configuration ---
|
11 |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
|
|
14 |
def fetch_market_chatter(niche_description: str) -> list[str]:
|
15 |
"""Simulates scraping Reddit, Hacker News, etc., for a given niche."""
|
16 |
print(f"Simulating scraping for: {niche_description}")
|
|
|
|
|
17 |
base_comments = [
|
18 |
"Ugh, another project management tool that charges per user. I'm a solo founder, this kills me.",
|
19 |
"I love the idea of Notion but it's just too slow and bloated now. I need something faster.",
|
|
|
31 |
def analyze_resonance(niche_description: str, comments: list[str]) -> dict:
|
32 |
"""Uses an LLM to perform topic modeling, pain point extraction, and sentiment analysis."""
|
33 |
if not GEMINI_API_KEY:
|
34 |
+
return {"error": "GEMINI_API_KEY not set in Space secrets."}
|
35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
try:
|
37 |
+
llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro-latest", google_api_key=GEMINI_API_KEY)
|
38 |
+
|
39 |
+
comments_formatted = "\n".join([f"- \"{c}\"" for c in comments])
|
40 |
+
prompt = f"""
|
41 |
+
You are a market research analyst with superhuman insight. Analyze the following raw market chatter (comments from Reddit, Hacker News, etc.) for a proposed product idea.
|
42 |
+
|
43 |
+
**Proposed Product Idea:** "{niche_description}"
|
44 |
+
|
45 |
+
**Raw Market Chatter:**
|
46 |
+
{comments_formatted}
|
47 |
+
|
48 |
+
**Your Task:**
|
49 |
+
Analyze the chatter and return a valid JSON object with the following structure. Do not include any text, code block markers, or explanations outside the single JSON object.
|
50 |
+
{{
|
51 |
+
"pain_points": [
|
52 |
+
"A summary of the most common complaint or frustration.",
|
53 |
+
"A summary of the second most common complaint.",
|
54 |
+
"A summary of a third, more niche complaint."
|
55 |
+
],
|
56 |
+
"magic_words": [
|
57 |
+
"A positive, benefit-oriented word or phrase people use.",
|
58 |
+
"Another evocative word people use to describe their ideal solution.",
|
59 |
+
"A third powerful, emotional word."
|
60 |
+
],
|
61 |
+
"target_villain": "The name of a competitor or a type of product that people frequently complain about.",
|
62 |
+
"unserved_tribe": "A description of a specific user subgroup whose needs are not being met.",
|
63 |
+
"resonance_score": A number between 1 and 100 representing how well the proposed product idea fits the market chatter,
|
64 |
+
"resonance_justification": "A one-sentence explanation for the score you gave."
|
65 |
+
}}
|
66 |
+
"""
|
67 |
response = llm.invoke([HumanMessage(content=prompt)])
|
68 |
# Clean up the response to ensure it's valid JSON
|
69 |
json_str = response.content.strip().replace("```json", "").replace("```", "")
|
70 |
+
return json.loads(json_str) # This line now works because `json` is imported
|
71 |
except Exception as e:
|
72 |
+
# Catch JSON decoding errors specifically
|
73 |
+
return {"error": f"Failed to analyze resonance. The AI model may have returned an invalid format. Details: {e}"}
|
74 |
|
75 |
# --- Main Orchestrator ---
|
76 |
def run_precog_analysis(niche_description: str):
|
|
|
84 |
# Stage 2 & 3
|
85 |
analysis_result = analyze_resonance(niche_description, comments)
|
86 |
if "error" in analysis_result:
|
87 |
+
# Display the error in the main report area for visibility
|
88 |
+
yield f"## Analysis Error\n\n**Details:** {analysis_result['error']}", "", "Error", gr.Button("Analyze Again", interactive=True)
|
89 |
return
|
90 |
|
91 |
# --- Prepare Outputs ---
|
|
|
|
|
92 |
score = analysis_result.get('resonance_score', 0)
|
93 |
color = "green" if score > 65 else "orange" if score > 40 else "red"
|
94 |
report_md = f"""
|
95 |
<div style="text-align:center; border: 2px solid {color}; border-radius:10px; padding:20px;">
|
96 |
<h2 style="margin:0;">Market Resonance Score</h2>
|
97 |
<p style="font-size: 80px; font-weight:bold; margin:0; color:{color};">{score}/100</p>
|
98 |
+
<p style="margin:0; font-style:italic;">"{analysis_result.get('resonance_justification', 'No justification provided.')}"</p>
|
99 |
</div>
|
100 |
|
101 |
### π₯ Top 3 Unspoken Pain Points
|
|
|
110 |
- `{analysis_result.get('magic_words', ['N/A'])[2]}`
|
111 |
"""
|
112 |
|
|
|
113 |
strategy_md = f"""
|
114 |
### π― Your Go-to-Market Strategy
|
115 |
|
116 |
**Your "Villain":**
|
117 |
+
Position your product as the direct antidote to **{analysis_result.get('target_villain', 'existing complex tools')}**. Your marketing should say, "Tired of [Villain's problem]? We fixed it."
|
118 |
|
119 |
**Your Unserved "Tribe":**
|
120 |
+
Focus your initial launch on this niche group: **{analysis_result.get('unserved_tribe', 'no specific tribe identified')}**. They are desperately looking for a solution and will become your first evangelists.
|
121 |
"""
|
122 |
|
123 |
+
yield report_md, strategy_md, "Analysis Complete", gr.Button("Analyze Resonance", interactive=True)
|
124 |
|
125 |
# --- Gradio UI ---
|
126 |
with gr.Blocks(theme=gr.themes.Glass(), css=".gradio-container{max-width: 800px !important}") as demo:
|
|
|
141 |
gr.Markdown("## Strategy")
|
142 |
strategy_output = gr.Markdown()
|
143 |
|
144 |
+
status_output = gr.Markdown()
|
145 |
|
146 |
submit_btn.click(
|
147 |
fn=run_precog_analysis,
|
|
|
150 |
)
|
151 |
|
152 |
gr.Markdown("---")
|
153 |
+
gr.Markdown("### This is a demo of the Pre-Cog Engine. \n The Pro version provides access to live data streams, deeper analysis, and continuous market monitoring. \n **[π Inquire About Early Access on Gumroad](https://gumroad.com/)**")
|
154 |
|
155 |
if __name__ == "__main__":
|
156 |
demo.launch()
|