1- """Review Radar — Streamlit Web UI v2(4 步引导交互)"""
1+ """AppPulse — Streamlit Web UI v2(4 步引导交互)"""
22
33import streamlit as st
44import plotly .graph_objects as go
@@ -53,37 +53,66 @@ def _load_cache(key: str) -> dict | None:
5353 return None
5454
5555# ── 页面配置 ──
56- st .set_page_config (page_title = "Review Radar " , page_icon = "📡 " , layout = "centered" )
56+ st .set_page_config (page_title = "AppPulse " , page_icon = "📊 " , layout = "centered" )
5757
58- # ── Notion 风格 CSS ──
58+ # ── AppPulse 品牌 CSS ──
5959st .markdown ("""
6060<style>
61- .stApp { background-color: #FFFFFF; color: #37352F; }
62- .main-title { font-size: 42px; font-weight: 700; color: #37352F; margin-bottom: 4px; letter-spacing: -0.5px; }
63- .sub-title { font-size: 18px; color: #787774; margin-bottom: 32px; font-weight: 400; }
64- .section-title { font-size: 24px; font-weight: 600; color: #37352F; margin-top: 36px; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #E8E8E8; }
65- .step-title { font-size: 20px; font-weight: 600; color: #37352F; margin-bottom: 12px; }
66- .step-desc { font-size: 15px; color: #787774; margin-bottom: 20px; }
67- .metric-card { padding: 24px 0; text-align: center; }
68- .metric-value { font-size: 36px; font-weight: 700; color: #37352F; line-height: 1.2; }
69- .metric-label { font-size: 14px; color: #787774; margin-top: 4px; }
70- .app-card { display: flex; align-items: center; gap: 16px; padding: 20px; border: 1px solid #E8E8E8; border-radius: 8px; margin: 16px 0; }
61+ .stApp { background-color: #FFFFFF; color: #1E1E2E; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif; }
62+ .main-title {
63+ font-size: 40px; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 2px;
64+ background: linear-gradient(135deg, #4F46E5, #7C3AED);
65+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
66+ }
67+ .sub-title { font-size: 16px; color: #6B7280; margin-bottom: 36px; font-weight: 400; letter-spacing: 0.3px; }
68+ .section-title { font-size: 22px; font-weight: 700; color: #1E1E2E; margin-top: 40px; margin-bottom: 18px; padding-bottom: 10px; border-bottom: 2px solid #E5E7EB; }
69+ .step-title { font-size: 20px; font-weight: 700; color: #1E1E2E; margin-bottom: 10px; }
70+ .step-desc { font-size: 14px; color: #6B7280; margin-bottom: 22px; line-height: 1.6; }
71+ .metric-card { padding: 20px 16px; text-align: center; background: #F8F7FF; border-radius: 12px; border: 1px solid #E5E7EB; }
72+ .metric-value { font-size: 32px; font-weight: 800; color: #4F46E5; line-height: 1.2; }
73+ .metric-label { font-size: 13px; color: #6B7280; margin-top: 6px; font-weight: 500; }
74+ .app-card {
75+ display: flex; align-items: center; gap: 16px; padding: 20px 24px;
76+ border: 1px solid #E5E7EB; border-radius: 12px; margin: 16px 0; background: #FFFFFF;
77+ box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.06);
78+ transition: box-shadow 0.2s ease;
79+ }
80+ .app-card:hover { box-shadow: 0 4px 12px rgba(79,70,229,0.08); }
7181 .app-icon { width: 64px; height: 64px; border-radius: 14px; }
7282 .app-info { flex: 1; }
73- .app-name { font-size: 20px; font-weight: 600 ; color: #37352F ; }
74- .app-category { font-size: 14px; color: #787774 ; }
83+ .app-name { font-size: 20px; font-weight: 700 ; color: #1E1E2E ; }
84+ .app-category { font-size: 14px; color: #6B7280 ; }
7585 .country-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 12px 0; }
76- .country-item { padding: 8px 12px; border-radius: 6px; font-size: 14px; }
77- .country-ok { background: #F0FFF0 ; color: #2E7D32 ; }
78- .country-no { background: #FFF0F0 ; color: #C62828 ; text-decoration: line-through; }
79- .phase-item { padding: 6px 0; font-size: 15px; color: #37352F ; }
80- .phase-done { color: #787774 ; }
86+ .country-item { padding: 8px 12px; font-size: 14px; }
87+ .country-ok { background: #F0FDF4 ; color: #166534; border: 1px solid #BBF7D0; border-radius: 8px ; }
88+ .country-no { background: #FEF2F2 ; color: #991B1B ; text-decoration: line-through; border: 1px solid #FECACA; border-radius: 8px ; }
89+ .phase-item { padding: 6px 0; font-size: 15px; color: #1E1E2E ; }
90+ .phase-done { color: #6B7280 ; }
8191 .phase-active { font-weight: 600; }
8292 #MainMenu {visibility: hidden;} footer {visibility: hidden;}
8393 header [data-testid="stHeader"] {visibility: visible !important;}
8494 button[kind="header"] {visibility: visible !important;}
85- .stButton > button { background-color: #37352F; color: white; border: none; padding: 12px 32px; font-size: 16px; border-radius: 4px; font-weight: 500; }
86- .stButton > button:hover { background-color: #555555; }
95+ .stButton > button {
96+ background: linear-gradient(135deg, #4F46E5, #7C3AED); color: white;
97+ border: none; padding: 10px 28px; font-size: 15px; border-radius: 8px; font-weight: 600;
98+ transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(79,70,229,0.3);
99+ }
100+ .stButton > button:hover {
101+ background: linear-gradient(135deg, #4338CA, #6D28D9);
102+ box-shadow: 0 4px 12px rgba(79,70,229,0.35); transform: translateY(-1px);
103+ }
104+ .stButton > button:active { transform: translateY(0); }
105+ section[data-testid="stSidebar"] { background-color: #FAFAFE; border-right: 1px solid #E5E7EB; }
106+ section[data-testid="stSidebar"] .stMarkdown h3 { font-size: 15px; font-weight: 700; color: #1E1E2E; letter-spacing: 0.02em; }
107+ hr { border: none; border-top: 1px solid #E5E7EB; margin: 24px 0; }
108+ .stTabs [data-baseweb="tab-list"] { gap: 8px; }
109+ .stTabs [data-baseweb="tab"] { border-radius: 8px 8px 0 0; font-weight: 600; font-size: 14px; }
110+ .stDownloadButton > button {
111+ background: #FFFFFF !important; color: #4F46E5 !important;
112+ border: 1px solid #4F46E5 !important; border-radius: 8px; font-weight: 600;
113+ }
114+ .stDownloadButton > button:hover { background: #F8F7FF !important; }
115+ .streamlit-expanderHeader { font-weight: 600; font-size: 14px; }
87116</style>
88117""" , unsafe_allow_html = True )
89118
@@ -242,20 +271,20 @@ def _load_cache(key: str) -> dict | None:
242271 st .caption ("暂无历史记录" )
243272
244273# ── 标题 ──
245- st .markdown ('<div class="main-title">Review Radar </div>' , unsafe_allow_html = True )
246- st .markdown ('<div class="sub-title">输入 App 名字,自动分析用户评论,生成洞察报告 </div>' , unsafe_allow_html = True )
274+ st .markdown ('<div class="main-title">AppPulse </div>' , unsafe_allow_html = True )
275+ st .markdown ('<div class="sub-title">感知每一条用户心声 </div>' , unsafe_allow_html = True )
247276
248277# ── 步骤指示器 ──
249278step = st .session_state .step
250279steps = ["搜索 App" , "选择市场与国家" , "高级选项" , "分析" ]
251280cols = st .columns (len (steps ))
252281for i , (col , label ) in enumerate (zip (cols , steps ), 1 ):
253282 if i < step :
254- col .markdown (f"<div style='text-align:center;color:#787774 ;'>✓ { label } </div>" , unsafe_allow_html = True )
283+ col .markdown (f"<div style='text-align:center;color:#10B981;font-weight:600;font-size:14px ;'>✓ { label } </div>" , unsafe_allow_html = True )
255284 elif i == step :
256- col .markdown (f"<div style='text-align:center;font-weight:600 ;color:#37352F ;'>● { label } </div>" , unsafe_allow_html = True )
285+ col .markdown (f"<div style='text-align:center;font-weight:700 ;color:#4F46E5;font-size:14px;padding:4px 0;border-bottom:2px solid #4F46E5 ;'>● { label } </div>" , unsafe_allow_html = True )
257286 else :
258- col .markdown (f"<div style='text-align:center;color:#CFCFCF ;'>○ { label } </div>" , unsafe_allow_html = True )
287+ col .markdown (f"<div style='text-align:center;color:#D1D5DB;font-size:14px ;'>○ { label } </div>" , unsafe_allow_html = True )
259288
260289st .markdown ("---" )
261290
@@ -265,7 +294,7 @@ def _load_cache(key: str) -> dict | None:
265294# ════════════════════════════════════════════════════════════════
266295def _render_sentiment_pie (sentiment : dict , title : str ):
267296 """情感分布饼图"""
268- colors_map = {"positive" : "#4285F4 " , "negative" : "#EA8600 " , "neutral" : "#9AA0A6 " }
297+ colors_map = {"positive" : "#4F46E5 " , "negative" : "#F59E0B " , "neutral" : "#D1D5DB " }
269298 labels_cn = {"positive" : "正面" , "negative" : "负面" , "neutral" : "中性" }
270299 fig = go .Figure (data = [go .Pie (
271300 labels = [labels_cn .get (k , k ) for k in sentiment .keys ()],
@@ -274,7 +303,8 @@ def _render_sentiment_pie(sentiment: dict, title: str):
274303 hole = 0.45 , textinfo = "label+percent" , textfont = dict (size = 14 ),
275304 )])
276305 fig .update_layout (showlegend = False , margin = dict (t = 20 , b = 20 , l = 20 , r = 20 ), height = 280 ,
277- paper_bgcolor = "rgba(0,0,0,0)" , plot_bgcolor = "rgba(0,0,0,0)" )
306+ paper_bgcolor = "rgba(0,0,0,0)" , plot_bgcolor = "rgba(0,0,0,0)" ,
307+ font = dict (family = "-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" , color = "#1E1E2E" ))
278308 st .plotly_chart (fig , use_container_width = True )
279309
280310
@@ -283,18 +313,19 @@ def _render_category_bar(categories: dict, title: str):
283313 sorted_cats = sorted (categories .items (), key = lambda x : x [1 ], reverse = True )
284314 fig2 = go .Figure (data = [go .Bar (
285315 x = [c [1 ] for c in sorted_cats ], y = [c [0 ] for c in sorted_cats ],
286- orientation = 'h' , marker_color = "#37352F " ,
316+ orientation = 'h' , marker_color = "#4F46E5 " ,
287317 )])
288318 fig2 .update_layout (margin = dict (t = 20 , b = 20 , l = 20 , r = 20 ), height = 280 ,
289319 paper_bgcolor = "rgba(0,0,0,0)" , plot_bgcolor = "rgba(0,0,0,0)" ,
290- xaxis = dict (showgrid = False ), yaxis = dict (showgrid = False , autorange = "reversed" ))
320+ xaxis = dict (showgrid = False ), yaxis = dict (showgrid = False , autorange = "reversed" ),
321+ font = dict (family = "-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" , color = "#1E1E2E" ))
291322 st .plotly_chart (fig2 , use_container_width = True )
292323
293324
294325def _render_rating_dist (rating_dist : dict , title : str ):
295326 """评分分布柱状图"""
296327 stars = sorted (rating_dist .keys (), key = lambda x : int (x ))
297- colors_rating = {1 : "#D93025 " , 2 : "#EA8600 " , 3 : "#F9AB00 " , 4 : "#5BB974 " , 5 : "#4285F4 " }
328+ colors_rating = {1 : "#EF4444 " , 2 : "#F59E0B " , 3 : "#FBBF24 " , 4 : "#34D399 " , 5 : "#4F46E5 " }
298329 fig3 = go .Figure (data = [go .Bar (
299330 x = [f"{ s } 星" for s in stars ],
300331 y = [rating_dist [s ] for s in stars ],
@@ -304,7 +335,8 @@ def _render_rating_dist(rating_dist: dict, title: str):
304335 )])
305336 fig3 .update_layout (margin = dict (t = 20 , b = 20 , l = 20 , r = 20 ), height = 280 ,
306337 paper_bgcolor = "rgba(0,0,0,0)" , plot_bgcolor = "rgba(0,0,0,0)" ,
307- xaxis = dict (showgrid = False ), yaxis = dict (showgrid = False ))
338+ xaxis = dict (showgrid = False ), yaxis = dict (showgrid = False ),
339+ font = dict (family = "-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" , color = "#1E1E2E" ))
308340 st .plotly_chart (fig3 , use_container_width = True )
309341
310342
@@ -332,14 +364,15 @@ def _version_sort_key(v):
332364 textposition = "top center" ,
333365 marker = dict (
334366 size = [max (8 , min (30 , vt [v ]["review_count" ])) for v in sorted_versions ],
335- color = "#37352F " ,
367+ color = "#4F46E5 " ,
336368 ),
337- line = dict (color = "#37352F " , width = 2 ),
369+ line = dict (color = "#4F46E5 " , width = 2 ),
338370 ))
339371 fig4 .update_layout (margin = dict (t = 20 , b = 20 , l = 20 , r = 20 ), height = 280 ,
340372 paper_bgcolor = "rgba(0,0,0,0)" , plot_bgcolor = "rgba(0,0,0,0)" ,
341373 xaxis = dict (showgrid = False , title = "版本" ),
342- yaxis = dict (showgrid = True , title = "平均评分" , range = [0.5 , 5.5 ]))
374+ yaxis = dict (showgrid = True , title = "平均评分" , range = [0.5 , 5.5 ]),
375+ font = dict (family = "-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" , color = "#1E1E2E" ))
343376 st .plotly_chart (fig4 , use_container_width = True )
344377
345378
@@ -373,21 +406,22 @@ def _render_time_trend(analyzed_reviews: list[dict], title: str):
373406 fig_time = go .Figure ()
374407 fig_time .add_trace (go .Scatter (
375408 x = sorted_weeks , y = [weekly [w ]["positive" ] for w in sorted_weeks ],
376- name = "正面" , mode = "lines" , line = dict (color = "#4285F4 " , width = 2 ), stackgroup = "one" ,
409+ name = "正面" , mode = "lines" , line = dict (color = "#4F46E5 " , width = 2 ), stackgroup = "one" ,
377410 ))
378411 fig_time .add_trace (go .Scatter (
379412 x = sorted_weeks , y = [weekly [w ]["neutral" ] for w in sorted_weeks ],
380- name = "中性" , mode = "lines" , line = dict (color = "#9AA0A6 " , width = 2 ), stackgroup = "one" ,
413+ name = "中性" , mode = "lines" , line = dict (color = "#D1D5DB " , width = 2 ), stackgroup = "one" ,
381414 ))
382415 fig_time .add_trace (go .Scatter (
383416 x = sorted_weeks , y = [weekly [w ]["negative" ] for w in sorted_weeks ],
384- name = "负面" , mode = "lines" , line = dict (color = "#EA8600 " , width = 2 ), stackgroup = "one" ,
417+ name = "负面" , mode = "lines" , line = dict (color = "#F59E0B " , width = 2 ), stackgroup = "one" ,
385418 ))
386419 fig_time .update_layout (
387420 margin = dict (t = 20 , b = 20 , l = 20 , r = 20 ), height = 280 ,
388421 paper_bgcolor = "rgba(0,0,0,0)" , plot_bgcolor = "rgba(0,0,0,0)" ,
389422 xaxis = dict (showgrid = False ), yaxis = dict (showgrid = True , title = "评论数" ),
390423 legend = dict (orientation = "h" , yanchor = "bottom" , y = 1.02 , xanchor = "right" , x = 1 ),
424+ font = dict (family = "-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" , color = "#1E1E2E" ),
391425 )
392426 st .plotly_chart (fig_time , use_container_width = True )
393427
@@ -627,7 +661,7 @@ def _show_results():
627661
628662 st .markdown (
629663 f'{ plat_label } { stars } { sent_emoji } '
630- f'<span style="color:#787774 ;font-size:12px;">v{ html_mod .escape (str (version ))} | { html_mod .escape (date )} | { html_mod .escape (category )} </span>\n \n '
664+ f'<span style="color:#6B7280 ;font-size:12px;">v{ html_mod .escape (str (version ))} | { html_mod .escape (date )} | { html_mod .escape (category )} </span>\n \n '
631665 f'> { html_mod .escape (content )} ' ,
632666 unsafe_allow_html = True ,
633667 )
@@ -751,7 +785,7 @@ def _show_results():
751785 <div class="app-info">
752786 <div class="app-name">{ html_mod .escape (name )} </div>
753787 <div class="app-category">{ html_mod .escape (category )} </div>
754- <div style="font-size:13px;color:#787774 ;margin-top:4px;">
788+ <div style="font-size:13px;color:#6B7280 ;margin-top:4px;">
755789 { "✅ App Store" if ios_result else "❌ App Store" }
756790 |
757791 { "✅ Google Play" if gplay_result else "❌ Google Play" }
0 commit comments