Skip to content

Commit e76dbf3

Browse files
Feature/clustering dashboard (#398)
* First draft of Insights dashboard * Add backend router + models for getting raw queries * Add backend router + models for getting raw queries * Add retrieve_topics endpoint + dummy classification endpoint * Let frontend read backend dummy data * Modify return type on /topics endpoint * Add first draft of front-end/ backend topic modelling * Switch 4 -> 4o * Add loading wheel + pagination * Remove styled-components for MUI, fix spacing * Remove styled-components from package.json * Add example sentence to whole topic_df upfront since it's a quick operation * Remove debug line * Check for token before call * Make fetchTopicsData conform to other functions * Calculate correct number of examples * refactor backend * Expand synthetic query generation script * Fix pd.cut() bucketing issue * Add logic for process all time periods, return unclustered queries + modifies models * endpoints work; using litellm proxy * removed utils; fixed mypy * Add core backend logic for pulling Redis results * Frontend pulls data from Redis + working pop up to generate new data * Add regenerate button to top of each page + headers * made calls async * Add timestamps for new generation on frontend reading from Redis * Render new topics when they are freshly generated * Remove dummy api call, make time updated refresh live * New styles * minor colors and fonts fix * from co-working * Add timestamp to topicmodel backend logic * Convert query_datetime to strings for unclustered queries * Hook up backend to frontend, fix types + return lists of dicts * fixes * add pagination * Fixed render when no content avaialble + pagination issues * Removing old files + tempData * Fix typing errors * Fix typing errors * Ensure dicts contains strings for MyPy * fixed after merge * Added AI summary * fixes from review --------- Co-authored-by: Sid Ravinutala <sid.ravi1@gmail.com>
1 parent a26a9a8 commit e76dbf3

File tree

21 files changed

+1012
-11
lines changed

21 files changed

+1012
-11
lines changed

admin_app/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

admin_app/src/app/dashboard/api.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,40 @@ const getOverviewPageData = async (period: Period, token: string) => {
2121
});
2222
};
2323

24+
const fetchTopicsData = async (period: Period, token: string) => {
25+
return fetch(`${NEXT_PUBLIC_BACKEND_URL}/dashboard/insights/${period}`, {
26+
method: "GET",
27+
headers: {
28+
"Content-Type": "application/json",
29+
Authorization: `Bearer ${token}`,
30+
},
31+
}).then((response) => {
32+
if (response.ok) {
33+
let resp = response.json();
34+
return resp;
35+
} else {
36+
throw new Error("Error fetching Topics data");
37+
}
38+
});
39+
};
40+
41+
const generateNewTopics = async (period: Period, token: string) => {
42+
return fetch(`${NEXT_PUBLIC_BACKEND_URL}/dashboard/insights/${period}/refresh`, {
43+
method: "GET",
44+
headers: {
45+
"Content-Type": "application/json",
46+
Authorization: `Bearer ${token}`,
47+
},
48+
}).then((response) => {
49+
if (response.ok) {
50+
let resp = response.json();
51+
return resp;
52+
} else {
53+
throw new Error("Error generating Topics data");
54+
}
55+
});
56+
};
57+
2458
const getPerformancePageData = async (period: Period, token: string) => {
2559
return fetch(`${NEXT_PUBLIC_BACKEND_URL}/dashboard/performance/${period}`, {
2660
method: "GET",
@@ -90,4 +124,6 @@ export {
90124
getPerformancePageData,
91125
getPerformanceDrawerData,
92126
getPerformanceDrawerAISummary,
127+
fetchTopicsData,
128+
generateNewTopics,
93129
};
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React from "react";
2+
import Grid from "@mui/material/Unstable_Grid2";
3+
import Topics from "./insights/Topics";
4+
import Queries from "./insights/Queries";
5+
import Box from "@mui/material/Box";
6+
import { useState } from "react";
7+
import { QueryData, Period, TopicModelingResponse } from "../types";
8+
import { generateNewTopics, fetchTopicsData } from "../api";
9+
import { useAuth } from "@/utils/auth";
10+
11+
interface InsightProps {
12+
timePeriod: Period;
13+
}
14+
15+
const Insight: React.FC<InsightProps> = ({ timePeriod }) => {
16+
const { token } = useAuth();
17+
const [selectedTopicId, setSelectedTopicId] = useState<number | null>(null);
18+
const [topicQueries, setTopicQueries] = useState<QueryData[]>([]);
19+
const [refreshTimestamp, setRefreshTimestamp] = useState<string>("");
20+
const [refreshing, setRefreshing] = useState<boolean>(false);
21+
const [aiSummary, setAiSummary] = useState<string>("");
22+
23+
const [dataFromBackend, setDataFromBackend] = useState<TopicModelingResponse>({
24+
data: [],
25+
refreshTimeStamp: "",
26+
unclustered_queries: [],
27+
});
28+
29+
const runRefresh = () => {
30+
setRefreshing(true);
31+
generateNewTopics(timePeriod, token!).then((_) => {
32+
const date = new Date();
33+
setRefreshTimestamp(date.toLocaleString());
34+
setRefreshing(false);
35+
});
36+
};
37+
38+
React.useEffect(() => {
39+
if (token) {
40+
fetchTopicsData(timePeriod, token).then((dataFromBackend) => {
41+
setDataFromBackend(dataFromBackend);
42+
if (dataFromBackend.data.length > 0) {
43+
setSelectedTopicId(dataFromBackend.data[0].topic_id);
44+
}
45+
});
46+
} else {
47+
console.log("No token found");
48+
}
49+
}, [token, refreshTimestamp, timePeriod]);
50+
51+
React.useEffect(() => {
52+
if (selectedTopicId !== null) {
53+
const filterQueries = dataFromBackend.data.find(
54+
(topic) => topic.topic_id === selectedTopicId,
55+
);
56+
57+
if (filterQueries) {
58+
setTopicQueries(filterQueries.topic_samples);
59+
setAiSummary(filterQueries.topic_summary);
60+
} else {
61+
setTopicQueries([]);
62+
setAiSummary("Not available.");
63+
}
64+
} else {
65+
setTopicQueries([]);
66+
setAiSummary("Not available.");
67+
}
68+
}, [dataFromBackend, selectedTopicId, refreshTimestamp, timePeriod]);
69+
70+
const topics = dataFromBackend.data.map(
71+
({ topic_id, topic_name, topic_popularity }) => ({
72+
topic_id,
73+
topic_name,
74+
topic_popularity,
75+
}),
76+
);
77+
78+
return (
79+
<Grid container sx={{ bgcolor: "grey.100", borderRadius: 2, mx: 0.5 }}>
80+
<Grid
81+
container
82+
md={12}
83+
columnSpacing={{ xs: 2 }}
84+
sx={{ bgcolor: "white", borderRadius: 2, mx: 0.5, mt: 2, height: 400 }}
85+
>
86+
<Grid
87+
md={3}
88+
sx={{ p: 2, borderRight: 1, borderColor: "grey.300", borderWidth: 2 }}
89+
>
90+
<Topics
91+
data={topics}
92+
selectedTopicId={selectedTopicId}
93+
onClick={setSelectedTopicId}
94+
topicsPerPage={4}
95+
/>
96+
</Grid>
97+
<Grid md={9} sx={{ p: 2 }}>
98+
<Queries
99+
data={topicQueries}
100+
onRefreshClick={runRefresh}
101+
aiSummary={aiSummary}
102+
lastRefreshed={dataFromBackend.refreshTimeStamp}
103+
refreshing={refreshing}
104+
/>
105+
</Grid>
106+
</Grid>
107+
<Grid
108+
md={12}
109+
height={400}
110+
sx={{
111+
bgcolor: "white",
112+
borderRadius: 2,
113+
mx: 0.5,
114+
mt: 2,
115+
justifyItems: "center",
116+
justifySelf: "stretch",
117+
}}
118+
>
119+
<Box
120+
textAlign="center"
121+
height="100%"
122+
sx={{
123+
display: "flex",
124+
alignItems: "center",
125+
justifyContent: "center",
126+
}}
127+
>
128+
-- Chart - Coming Soon! --
129+
</Box>
130+
</Grid>
131+
</Grid>
132+
);
133+
};
134+
135+
export default Insight;
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import React from "react";
2+
import { Box } from "@mui/material";
3+
import Table from "@mui/material/Table";
4+
import TableBody from "@mui/material/TableBody";
5+
import TableCell from "@mui/material/TableCell";
6+
import TableContainer from "@mui/material/TableContainer";
7+
import TableHead from "@mui/material/TableHead";
8+
import TableRow from "@mui/material/TableRow";
9+
import { grey, orange } from "@mui/material/colors";
10+
import Typography from "@mui/material/Typography";
11+
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
12+
import Button from "@mui/material/Button";
13+
import { QueryData } from "../../types";
14+
import CircularProgress from "@mui/material/CircularProgress";
15+
16+
interface QueriesProps {
17+
data: QueryData[];
18+
onRefreshClick: () => void;
19+
lastRefreshed: string;
20+
refreshing: boolean;
21+
aiSummary: string;
22+
}
23+
24+
interface AISummaryProps {
25+
aiSummary: string;
26+
}
27+
const AISummary: React.FC<AISummaryProps> = ({ aiSummary }) => {
28+
return (
29+
<Box
30+
sx={{
31+
display: "flex",
32+
flexGrow: 1,
33+
flexDirection: "column",
34+
maxheight: 400,
35+
border: 1,
36+
borderColor: "secondary.main",
37+
borderRadius: 2,
38+
background: "linear-gradient(to bottom, rgba(176,198,255,0.5), #ffffff)",
39+
p: 2,
40+
mb: 3,
41+
}}
42+
>
43+
<Box sx={{ display: "flex", flexDirection: "row", mb: 2 }}>
44+
<AutoAwesomeIcon sx={{ fontSize: 25, mr: 1, color: "#9eb2e5" }} />
45+
<Typography
46+
sx={{
47+
lineHeight: "24px",
48+
fontWeight: 600,
49+
fontColor: "black",
50+
textAlign: "center",
51+
}}
52+
>
53+
AI Overview
54+
</Typography>
55+
</Box>
56+
<Typography
57+
sx={{
58+
lineHeight: "15px",
59+
fontWeight: 300,
60+
fontSize: "small",
61+
fontColor: "black",
62+
textAlign: "left",
63+
}}
64+
>
65+
{aiSummary}
66+
</Typography>
67+
</Box>
68+
);
69+
};
70+
71+
const Queries: React.FC<QueriesProps> = ({
72+
data,
73+
onRefreshClick,
74+
lastRefreshed,
75+
refreshing,
76+
aiSummary,
77+
}) => {
78+
const formattedLastRefreshed =
79+
lastRefreshed.length > 0
80+
? Intl.DateTimeFormat("en-ZA", {
81+
dateStyle: "short",
82+
timeStyle: "short",
83+
}).format(new Date(lastRefreshed))
84+
: "Never";
85+
86+
return (
87+
<Box sx={{ display: "flex", flexDirection: "column" }}>
88+
<Box
89+
sx={{
90+
display: "flex",
91+
flexDirection: "row",
92+
justifyContent: "space-between",
93+
mb: 2,
94+
}}
95+
>
96+
<Box sx={{ fontSize: 22, fontWeight: 700 }}>Example Queries</Box>
97+
<Box sx={{ display: "flex", flexDirection: "row" }}>
98+
<Box
99+
sx={{
100+
display: "flex",
101+
mr: 2,
102+
fontSize: "small",
103+
alignItems: "center",
104+
color: grey[600],
105+
}}
106+
>
107+
Last run: {formattedLastRefreshed}
108+
</Box>
109+
<Button
110+
disabled={refreshing}
111+
variant="contained"
112+
sx={{
113+
bgcolor: orange[500],
114+
width: 180,
115+
"&:hover": {
116+
bgcolor: orange[700],
117+
},
118+
}}
119+
onClick={onRefreshClick}
120+
>
121+
{refreshing ? <CircularProgress size={24} /> : "Re-run Discovery"}
122+
</Button>
123+
</Box>
124+
</Box>
125+
<AISummary aiSummary={aiSummary} />
126+
<Box
127+
sx={{
128+
display: "flex",
129+
flexDirection: "column",
130+
overflow: "hidden",
131+
overflowY: "scroll",
132+
maxHeight: 200,
133+
}}
134+
>
135+
{data.length > 0 ? (
136+
<TableContainer sx={{ border: 1, borderColor: grey[300], borderRadius: 1 }}>
137+
<Table size="small">
138+
<TableHead>
139+
<TableRow
140+
sx={{ bgcolor: grey[100], position: "sticky", top: 0, zIndex: 1 }}
141+
>
142+
<TableCell sx={{ fontWeight: 800 }}>Timestamp</TableCell>
143+
<TableCell sx={{ fontWeight: 800 }}>User Question</TableCell>
144+
</TableRow>
145+
</TableHead>
146+
<TableBody>
147+
{data.map((row, index) => (
148+
<TableRow key={index}>
149+
<TableCell width="20%">
150+
{Intl.DateTimeFormat("en-ZA", {
151+
dateStyle: "short",
152+
timeStyle: "short",
153+
}).format(new Date(row.query_datetime_utc))}
154+
</TableCell>
155+
<TableCell>{row.query_text}</TableCell>
156+
</TableRow>
157+
))}
158+
</TableBody>
159+
</Table>
160+
</TableContainer>
161+
) : (
162+
<Box sx={{ fontSize: "small" }}>
163+
No queries found. Please re-run discovery
164+
</Box>
165+
)}
166+
</Box>
167+
</Box>
168+
);
169+
};
170+
171+
export default Queries;

0 commit comments

Comments
 (0)