Compare commits
3 Commits
f9bc9cf9c9
...
b6815c490a
| Author | SHA1 | Date | |
|---|---|---|---|
| b6815c490a | |||
| 29c90ddfff | |||
| 3fe08b9c67 |
@@ -82,7 +82,7 @@ const AppLayout = () => {
|
|||||||
<div style={{ ...styles.card, ...styles.headerBar }}>
|
<div style={{ ...styles.card, ...styles.headerBar }}>
|
||||||
<div style={styles.appHeaderBrandRow}>
|
<div style={styles.appHeaderBrandRow}>
|
||||||
<span style={styles.appTitle}>
|
<span style={styles.appTitle}>
|
||||||
Ethnograph View
|
CrossPost Analysis Engine
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -42,3 +42,24 @@ textarea:focus {
|
|||||||
box-shadow: 0 0 0 3px var(--focus-ring);
|
box-shadow: 0 0 0 3px var(--focus-ring);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes stats-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stats-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,7 +136,27 @@ const StatPage = () => {
|
|||||||
getStats();
|
getStats();
|
||||||
}, [datasetId])
|
}, [datasetId])
|
||||||
|
|
||||||
if (loading) return <p style={{...styles.page, minWidth: "100vh", minHeight: "100vh"}}>Loading insights…</p>;
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={styles.loadingPage}>
|
||||||
|
<div style={styles.loadingCard}>
|
||||||
|
<div style={styles.loadingHeader}>
|
||||||
|
<div style={styles.loadingSpinner} />
|
||||||
|
<div>
|
||||||
|
<h2 style={styles.loadingTitle}>Loading analytics</h2>
|
||||||
|
<p style={styles.loadingSubtitle}>Fetching summary, timeline, user, and content insights.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.loadingSkeleton}>
|
||||||
|
<div style={{ ...styles.loadingSkeletonLine, ...styles.loadingSkeletonLineLong }} />
|
||||||
|
<div style={{ ...styles.loadingSkeletonLine, ...styles.loadingSkeletonLineMed }} />
|
||||||
|
<div style={{ ...styles.loadingSkeletonLine, ...styles.loadingSkeletonLineShort }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (error) return <p style={{...styles.page}}>{error}</p>;
|
if (error) return <p style={{...styles.page}}>{error}</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,78 @@ import { palette } from "./palette";
|
|||||||
import type { StyleMap } from "./types";
|
import type { StyleMap } from "./types";
|
||||||
|
|
||||||
export const feedbackStyles: StyleMap = {
|
export const feedbackStyles: StyleMap = {
|
||||||
|
loadingPage: {
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100vh",
|
||||||
|
padding: 20,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingCard: {
|
||||||
|
width: "min(560px, 92vw)",
|
||||||
|
background: palette.surface,
|
||||||
|
border: `1px solid ${palette.borderDefault}`,
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: `0 1px 0 ${palette.shadowSubtle}`,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingHeader: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSpinner: {
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
borderRadius: "50%",
|
||||||
|
border: `2px solid ${palette.borderDefault}`,
|
||||||
|
borderTopColor: palette.brandGreen,
|
||||||
|
animation: "stats-spin 0.9s linear infinite",
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingTitle: {
|
||||||
|
margin: 0,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: palette.textPrimary,
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSubtitle: {
|
||||||
|
margin: "6px 0 0",
|
||||||
|
fontSize: 13,
|
||||||
|
color: palette.textSecondary,
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSkeleton: {
|
||||||
|
marginTop: 16,
|
||||||
|
display: "grid",
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSkeletonLine: {
|
||||||
|
height: 9,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: palette.canvas,
|
||||||
|
animation: "stats-pulse 1.25s ease-in-out infinite",
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSkeletonLineLong: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSkeletonLineMed: {
|
||||||
|
width: "78%",
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingSkeletonLineShort: {
|
||||||
|
width: "62%",
|
||||||
|
},
|
||||||
|
|
||||||
alertCardError: {
|
alertCardError: {
|
||||||
borderColor: palette.alertErrorBorder,
|
borderColor: palette.alertErrorBorder,
|
||||||
background: palette.alertErrorBg,
|
background: palette.alertErrorBg,
|
||||||
|
|||||||
@@ -55,16 +55,24 @@ type TimeAnalysisResponse = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Content Analysis
|
// Content Analysis
|
||||||
|
type Emotion = {
|
||||||
|
emotion_anger: number;
|
||||||
|
emotion_disgust: number;
|
||||||
|
emotion_fear: number;
|
||||||
|
emotion_joy: number;
|
||||||
|
emotion_sadness: number;
|
||||||
|
};
|
||||||
|
|
||||||
type NGram = {
|
type NGram = {
|
||||||
count: number;
|
count: number;
|
||||||
ngram: string;
|
ngram: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AverageEmotionByTopic = {
|
type AverageEmotionByTopic = Emotion & {
|
||||||
topic: string;
|
|
||||||
n: number;
|
n: number;
|
||||||
[emotion: string]: string | number;
|
topic: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
type ContentAnalysisResponse = {
|
type ContentAnalysisResponse = {
|
||||||
word_frequencies: FrequencyWord[];
|
word_frequencies: FrequencyWord[];
|
||||||
|
|||||||
@@ -96,10 +96,7 @@ class StatGen:
|
|||||||
"common_three_phrases": self.linguistic_analysis.ngrams(filtered_df, n=3),
|
"common_three_phrases": self.linguistic_analysis.ngrams(filtered_df, n=3),
|
||||||
"average_emotion_by_topic": self.emotional_analysis.avg_emotion_by_topic(
|
"average_emotion_by_topic": self.emotional_analysis.avg_emotion_by_topic(
|
||||||
filtered_df
|
filtered_df
|
||||||
),
|
)
|
||||||
"reply_time_by_emotion": self.temporal_analysis.avg_reply_time_per_emotion(
|
|
||||||
filtered_df
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_user_analysis(self, df: pd.DataFrame, filters: dict | None = None) -> dict:
|
def get_user_analysis(self, df: pd.DataFrame, filters: dict | None = None) -> dict:
|
||||||
|
|||||||
Reference in New Issue
Block a user