Skip to content

Commit

Permalink
added stats aggregator
Browse files Browse the repository at this point in the history
  • Loading branch information
jptaylor committed Jun 7, 2024
1 parent 628dc26 commit 820f35b
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 128 deletions.
38 changes: 29 additions & 9 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,37 @@ module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh", "simple-import-sort"],
rules: {
'react-refresh/only-export-components': [
'warn',
"simple-import-sort/imports": [
"error",
{
groups: [
// Packages `react` related packages come first.
["^react", "^@?\\w"],
// Internal packages.
["^(@|components)(/.*|$)"],
// Side effect imports.
["^\\u0000"],
// Parent imports. Put `..` last.
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
// Other relative imports. Put same-folder imports and `.` last.
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
// Style imports.
["^.+\\.?(css)$"],
],
},
],
"simple-import-sort/exports": "error",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
}
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"eslint-plugin-simple-import-sort": "^12.1.0",
"i": "^0.3.7",
"npm": "^10.8.0",
"postcss-custom-media": "^10.0.6",
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite-plugin-webfont-dl": "^3.9.4"
}
}
}
8 changes: 3 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useState } from "react";
import { useDaily } from "@daily-co/daily-react";

import { fetch_meeting_token, fetch_start_agent } from "./actions";
import { ArrowRight, Loader2 } from "lucide-react";

import { Alert } from "./components/alert";
import { Button } from "./components/button";
import { ArrowRight, Loader2 } from "lucide-react";
import { DeviceSelect } from "./components/DeviceSelect";
import Session from "./components/Session";
import { RoomInput } from "./components/RoomInput";
import Session from "./components/Session";
import { SettingList } from "./components/SettingList/SettingList";
import { fetch_meeting_token, fetch_start_agent } from "./actions";

type State =
| "idle"
Expand Down Expand Up @@ -39,7 +38,6 @@ const checkRoomUrl = (url: string | null): boolean =>
!!(url && /^(https?:\/\/[^.]+\.daily\.co\/[^/]+)$/.test(url));

export default function App() {
// Use Daily as our agent transport
const daily = useDaily();

const [state, setState] = useState<State>("idle");
Expand Down
14 changes: 11 additions & 3 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ export const fetch_meeting_token = async (roomUrl: string) => {
}),
}
);
return await req.json();

const data = await req.json();

if (!req.ok) {
return { error: true, detail: data.detail };
}
return data;
};

export const fetch_start_agent = async (roomUrl: string, serverUrl: string) => {
Expand All @@ -27,8 +33,10 @@ export const fetch_start_agent = async (roomUrl: string, serverUrl: string) => {
body: JSON.stringify({ room_url: roomUrl }),
});

const data = await req.json();

if (!req.ok) {
return { error: true, detail: req.statusText };
return { error: true, detail: data.detail };
}
return await req.json();
return data;
};
7 changes: 4 additions & 3 deletions src/components/Session/agent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from "react";
import { useParticipantIds } from "@daily-co/daily-react";

import Status from "./status";
import styles from "./styles.module.css";

import { useParticipantIds } from "@daily-co/daily-react";
import styles from "./styles.module.css";

export const Agent: React.FC = () => {
const participantIds = useParticipantIds({ filter: "remote" });
Expand All @@ -12,7 +13,7 @@ export const Agent: React.FC = () => {
<div className={styles.agent}>
<div className={styles.agentWindow}></div>
<footer className={styles.agentFooter}>
<Status>User status</Status>
<Status variant="connected">User status</Status>
<Status variant={status}>Agent status</Status>
</footer>
</div>
Expand Down
29 changes: 26 additions & 3 deletions src/components/Session/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
import React, { useEffect, useRef, useState } from "react";
import { LogOut, Settings } from "lucide-react";
import { DailyAudio, useAppMessage, useDaily } from "@daily-co/daily-react";
import { LineChart, LogOut, Settings } from "lucide-react";

import DeviceSelect from "../DeviceSelect";
import Agent from "./agent";
import StatsAggregator from "../../utils/stats_aggregator";
import { Button } from "../button";
import DeviceSelect from "../DeviceSelect";
import Stats from "../Stats";
import UserMicBubble from "../UserMicBubble";

import Agent from "./agent";

import styles from "./styles.module.css";

interface SessionProps {
onLeave: () => void;
openMic?: boolean;
}

const stats_aggregator: StatsAggregator = new StatsAggregator();

export const Session: React.FC<SessionProps> = ({
onLeave,
openMic = false,
}) => {
const daily = useDaily();
const [showDevices, setShowDevices] = useState(false);
const [showStats, setShowStats] = useState(false);
const modalRef = useRef<HTMLDialogElement>(null);
const [talkState, setTalkState] = useState<"user" | "assistant" | "open">(
openMic ? "open" : "assistant"
);

useAppMessage({
onAppMessage: (e) => {
// Aggregate metrics from pipecat
if (e.data?.type === "pipecat-metrics") {
e.data.metrics.ttfb.map((m: { name: string; time: number }) => {
stats_aggregator.addStat([m.name, "ttfb", m.time, Date.now()]);
});
return;
}

if (!daily || !e.data?.cue) return;

// Determine the UI state from the cue sent by the bot
Expand Down Expand Up @@ -60,6 +74,8 @@ export const Session: React.FC<SessionProps> = ({
<Button onClick={() => setShowDevices(false)}>Close</Button>
</dialog>

{showStats && <Stats statsAggregator={stats_aggregator} />}

<div className={styles.agentContainer}>
<Agent />
<UserMicBubble openMic={openMic} active={talkState !== "assistant"} />
Expand All @@ -68,6 +84,13 @@ export const Session: React.FC<SessionProps> = ({

<footer className={styles.footer}>
<div className={styles.controls}>
<Button
variant={showStats ? "light" : "ghost"}
size="icon"
onClick={() => setShowStats(!showStats)}
>
<LineChart />
</Button>
<Button
variant="ghost"
size="icon"
Expand Down
3 changes: 2 additions & 1 deletion src/components/Session/status.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { VariantProps, cva } from "class-variance-authority";
import { cva, VariantProps } from "class-variance-authority";

import styles from "./styles.module.css";

const statusVariants = cva(styles.statusIndicator, {
Expand Down
47 changes: 47 additions & 0 deletions src/components/Stats/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useEffect, useRef, useState } from "react";

import styles from "./styles.module.css";

interface StatsProps {
statsAggregator: StatsAggregator;
}

const Stats = React.memo(
({ statsAggregator }: StatsProps) => {
const [currentStats, setCurrentStats] = useState<TransformedStats>(
statsAggregator.transformStats()
);
const intervalRef = useRef(0);

useEffect(() => {
intervalRef.current = setInterval(() => {
setCurrentStats(statsAggregator.transformStats());
}, 1000);

return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [statsAggregator]);

useEffect(() => () => clearInterval(intervalRef.current), []); // Cleanup

return (
<div className={styles.container}>
{currentStats &&
Object.entries(currentStats).map(([service, stat]) => (
<div key={service} className={styles.serviceStat}>
<div className={styles.serviceName}>{service}</div>
<div className={styles.value}>
{stat.metric}: {stat.value.toFixed(2)}s
</div>
</div>
))}
</div>
);
},
() => true
);

export default Stats;
21 changes: 21 additions & 0 deletions src/components/Stats/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.container {
display: flex;
flex-flow: row;
gap: 1rem;
}

.serviceStat {
display: flex;
flex-flow: column wrap;
font-size: var(--font-size-sm);
}

.serviceName {
font-weight: 500;
}

.value {
font-family: var(--font-mono);
font-size: var(--font-size-xs);
text-transform: uppercase;
}
2 changes: 2 additions & 0 deletions src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const buttonVariants = cva("button", {
variant: {
primary: "button-primary",
ghost: "button-ghost",
outline: "button-outline",
light: "button-light",
},
size: {
base: "",
Expand Down
20 changes: 20 additions & 0 deletions src/css/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@
border-color: var(--color-gray-600);
}

.button-outline {
border: 1px solid var(--color-gray-950);
background-color: transparent;
color: var(--color-gray-950);
}
.button-outline:hover:not(:disabled) {
background-color: white;
border-color: var(--color-gray-600);
}

.button-light {
border: 1px solid var(--color-gray-200);
background-color: var(--color-gray-200);
color: var(--color-gray-950);
}
.button-light:hover:not(:disabled) {
background-color: var(--color-gray-300);
border-color: var(--color-gray-300);
}

.button-icon {
padding: 1rem;
}
Expand Down
5 changes: 3 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import Header from "./components/header.tsx";
import { DailyProvider } from "@daily-co/daily-react";

import Header from "./components/header.tsx";
import App from "./App.tsx";

import "./css/global.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
Expand Down
28 changes: 28 additions & 0 deletions src/types/stats_aggregator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
type Metric = "ttfb";

type Stat = [string, Metric, number, number];
type TransformedStats = {
[key: string]: {
metric: Metric;
value: number;
timeseries: number[] | null;
median: number | null;
high: number | null;
low: number | null;
};
};

interface IStatsAggregator {
stats: Stat[];

addStat(stat: Stat): void;
transformStats(): TransformedStats;
}

declare class StatsAggregator implements IStatsAggregator {
stats: Stat[];

constructor();
addStat(stat: Stat): void;
transformStats(): TransformedStats;
}
Empty file removed src/utils/daily.js
Empty file.
Loading

0 comments on commit 820f35b

Please sign in to comment.