Finish off the links between frontend and backend #10

Merged
dylan merged 24 commits from feat/add-frontend-pages into main 2026-03-18 20:30:19 +00:00
2 changed files with 64 additions and 25 deletions
Showing only changes of commit e054997bb1 - Show all commits

View File

@@ -11,6 +11,15 @@ type CulturalStatsProps = {
const CulturalStats = ({ data }: CulturalStatsProps) => {
const identity = data.identity_markers;
const stance = data.stance_markers;
const inGroupWords = identity?.in_group_usage ?? 0;
const outGroupWords = identity?.out_group_usage ?? 0;
const totalGroupWords = inGroupWords + outGroupWords;
const inGroupWordRate = typeof identity?.in_group_ratio === "number"
? identity.in_group_ratio * 100
: null;
const outGroupWordRate = typeof identity?.out_group_ratio === "number"
? identity.out_group_ratio * 100
: null;
const rawEntities = data.avg_emotion_per_entity?.entity_emotion_avg ?? {};
const entities = Object.entries(rawEntities)
.sort((a, b) => (b[1].post_count - a[1].post_count))
@@ -25,77 +34,107 @@ const CulturalStats = ({ data }: CulturalStatsProps) => {
entries.sort((a, b) => b[1] - a[1]);
const dominant = entries[0] ?? ["emotion_unknown", 0];
const dominantLabel = dominant[0].replace("emotion_", "");
return `${dominantLabel} (${dominant[1].toFixed(3)})`;
return `${dominantLabel} (${(dominant[1] * 100).toFixed(1)}%)`;
};
return (
<div style={styles.page}>
<div style={{ ...styles.container, ...styles.grid }}>
<div style={{ ...styles.card, gridColumn: "span 12" }}>
<h2 style={styles.sectionTitle}>Community Framing Overview</h2>
<p style={styles.sectionSubtitle}>Simple view of how often people use "us" words vs "them" words, and the tone around that language.</p>
</div>
<Card
label="In-Group Usage"
value={identity?.in_group_usage?.toLocaleString() ?? "—"}
sublabel="we/us/our references"
label="In-Group Words"
value={inGroupWords.toLocaleString()}
sublabel="Times we/us/our appears"
style={{ gridColumn: "span 3" }}
/>
<Card
label="Out-Group Usage"
value={identity?.out_group_usage?.toLocaleString() ?? "—"}
sublabel="they/them/their references"
label="Out-Group Words"
value={outGroupWords.toLocaleString()}
sublabel="Times they/them/their appears"
style={{ gridColumn: "span 3" }}
/>
<Card
label="In-Group Posts"
value={identity?.in_group_posts?.toLocaleString() ?? "—"}
sublabel="Posts with stronger in-group language"
sublabel='Posts leaning toward "us" language'
style={{ gridColumn: "span 3" }}
/>
<Card
label="Out-Group Posts"
value={identity?.out_group_posts?.toLocaleString() ?? "—"}
sublabel="Posts with stronger out-group language"
sublabel='Posts leaning toward "them" language'
style={{ gridColumn: "span 3" }}
/>
<Card
label="Hedge Markers"
label="Balanced Posts"
value={identity?.tie_posts?.toLocaleString() ?? "—"}
sublabel="Posts with equal us/them signals"
style={{ gridColumn: "span 3" }}
/>
<Card
label="Total Group Words"
value={totalGroupWords.toLocaleString()}
sublabel="In-group + out-group words"
style={{ gridColumn: "span 3" }}
/>
<Card
label="In-Group Share"
value={inGroupWordRate === null ? "—" : `${inGroupWordRate.toFixed(2)}%`}
sublabel="Share of all words"
style={{ gridColumn: "span 3" }}
/>
<Card
label="Out-Group Share"
value={outGroupWordRate === null ? "—" : `${outGroupWordRate.toFixed(2)}%`}
sublabel="Share of all words"
style={{ gridColumn: "span 3" }}
/>
<Card
label="Hedging Words"
value={stance?.hedge_total?.toLocaleString() ?? "—"}
sublabel={typeof stance?.hedge_per_1k_tokens === "number" ? `${stance.hedge_per_1k_tokens.toFixed(3)} per 1k tokens` : "Marker frequency"}
sublabel={typeof stance?.hedge_per_1k_tokens === "number" ? `${stance.hedge_per_1k_tokens.toFixed(1)} per 1k words` : "Word frequency"}
style={{ gridColumn: "span 3" }}
/>
<Card
label="Certainty Markers"
label="Certainty Words"
value={stance?.certainty_total?.toLocaleString() ?? "—"}
sublabel={typeof stance?.certainty_per_1k_tokens === "number" ? `${stance.certainty_per_1k_tokens.toFixed(3)} per 1k tokens` : "Marker frequency"}
sublabel={typeof stance?.certainty_per_1k_tokens === "number" ? `${stance.certainty_per_1k_tokens.toFixed(1)} per 1k words` : "Word frequency"}
style={{ gridColumn: "span 3" }}
/>
<Card
label="Deontic Markers"
label="Need/Should Words"
value={stance?.deontic_total?.toLocaleString() ?? "—"}
sublabel={typeof stance?.deontic_per_1k_tokens === "number" ? `${stance.deontic_per_1k_tokens.toFixed(3)} per 1k tokens` : "Marker frequency"}
sublabel={typeof stance?.deontic_per_1k_tokens === "number" ? `${stance.deontic_per_1k_tokens.toFixed(1)} per 1k words` : "Word frequency"}
style={{ gridColumn: "span 3" }}
/>
<Card
label="Permission Markers"
label="Permission Words"
value={stance?.permission_total?.toLocaleString() ?? "—"}
sublabel={typeof stance?.permission_per_1k_tokens === "number" ? `${stance.permission_per_1k_tokens.toFixed(3)} per 1k tokens` : "Marker frequency"}
sublabel={typeof stance?.permission_per_1k_tokens === "number" ? `${stance.permission_per_1k_tokens.toFixed(1)} per 1k words` : "Word frequency"}
style={{ gridColumn: "span 3" }}
/>
<div style={{ ...styles.card, gridColumn: "span 6" }}>
<h2 style={styles.sectionTitle}>In-Group Emotion Profile</h2>
<p style={styles.sectionSubtitle}>Dominant average emotion where in-group framing is stronger.</p>
<h2 style={styles.sectionTitle}>Mood in "Us" Posts</h2>
<p style={styles.sectionSubtitle}>Most likely emotion when in-group wording is stronger.</p>
<div style={styles.topUserName}>{topEmotion(identity?.in_group_emotion_avg)}</div>
</div>
<div style={{ ...styles.card, gridColumn: "span 6" }}>
<h2 style={styles.sectionTitle}>Out-Group Emotion Profile</h2>
<p style={styles.sectionSubtitle}>Dominant average emotion where out-group framing is stronger.</p>
<h2 style={styles.sectionTitle}>Mood in "Them" Posts</h2>
<p style={styles.sectionSubtitle}>Most likely emotion when out-group wording is stronger.</p>
<div style={styles.topUserName}>{topEmotion(identity?.out_group_emotion_avg)}</div>
</div>
<div style={{ ...styles.card, gridColumn: "span 12" }}>
<h2 style={styles.sectionTitle}>Entity Emotion Averages</h2>
<p style={styles.sectionSubtitle}>Most frequent entities and their dominant average emotion signature.</p>
<h2 style={styles.sectionTitle}>Entity Mood Snapshot</h2>
<p style={styles.sectionSubtitle}>Most mentioned entities and the mood that appears most with each.</p>
{!entities.length ? (
<div style={styles.topUserMeta}>No entity-level cultural data available.</div>
) : (
@@ -104,7 +143,7 @@ const CulturalStats = ({ data }: CulturalStatsProps) => {
<div key={entity} style={styles.topUserItem}>
<div style={styles.topUserName}>{entity}</div>
<div style={styles.topUserMeta}>
{aggregate.post_count.toLocaleString()} posts Dominant emotion: {topEmotion(aggregate.emotion_avg)}
{aggregate.post_count.toLocaleString()} posts Likely mood: {topEmotion(aggregate.emotion_avg)}
</div>
</div>
))}

View File

@@ -192,7 +192,7 @@ const AutoScrapePage = () => {
Select sources and scrape settings, then queue processing automatically.
</p>
<p style={{ ...styles.subtleBodyText, marginTop: 6, color: "#9a6700" }}>
Warning: Scraping more than 250 posts from any single site can take hours.
Warning: Scraping more than 250 posts from any single site can take hours due to rate limits.
</p>
</div>
<button