From 820f35b54e6850b652dd9a6db623fb97f842a50d Mon Sep 17 00:00:00 2001 From: Jon Taylor Date: Fri, 7 Jun 2024 14:38:58 +0200 Subject: [PATCH] added stats aggregator --- .eslintrc.cjs | 38 ++++++--- package.json | 3 +- src/App.tsx | 8 +- src/actions.ts | 14 +++- src/components/Session/agent.tsx | 7 +- src/components/Session/index.tsx | 29 ++++++- src/components/Session/status.tsx | 3 +- src/components/Stats/index.tsx | 47 +++++++++++ src/components/Stats/styles.module.css | 21 +++++ src/components/button.tsx | 2 + src/css/components.css | 20 +++++ src/main.tsx | 5 +- src/types/stats_aggregator.d.ts | 28 +++++++ src/utils/daily.js | 0 src/utils/stats_aggregator.ts | 52 ++++++++++++ yarn.lock | 108 ++----------------------- 16 files changed, 257 insertions(+), 128 deletions(-) create mode 100644 src/components/Stats/index.tsx create mode 100644 src/components/Stats/styles.module.css create mode 100644 src/types/stats_aggregator.d.ts delete mode 100644 src/utils/daily.js create mode 100644 src/utils/stats_aggregator.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d6c9537..ec19bee 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -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 }, ], }, -} +}; diff --git a/package.json b/package.json index 339205e..91cbf52 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "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", @@ -34,4 +35,4 @@ "vite": "^5.2.0", "vite-plugin-webfont-dl": "^3.9.4" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 8678a59..7b96156 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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" @@ -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("idle"); diff --git a/src/actions.ts b/src/actions.ts index cbb7640..9486ab2 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -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) => { @@ -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; }; diff --git a/src/components/Session/agent.tsx b/src/components/Session/agent.tsx index cc7c6a7..47fc514 100644 --- a/src/components/Session/agent.tsx +++ b/src/components/Session/agent.tsx @@ -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" }); @@ -12,7 +13,7 @@ export const Agent: React.FC = () => {
- User status + User status Agent status
diff --git a/src/components/Session/index.tsx b/src/components/Session/index.tsx index 4428a0f..61b8fd8 100644 --- a/src/components/Session/index.tsx +++ b/src/components/Session/index.tsx @@ -1,12 +1,15 @@ 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 { @@ -14,12 +17,15 @@ interface SessionProps { openMic?: boolean; } +const stats_aggregator: StatsAggregator = new StatsAggregator(); + export const Session: React.FC = ({ onLeave, openMic = false, }) => { const daily = useDaily(); const [showDevices, setShowDevices] = useState(false); + const [showStats, setShowStats] = useState(false); const modalRef = useRef(null); const [talkState, setTalkState] = useState<"user" | "assistant" | "open">( openMic ? "open" : "assistant" @@ -27,6 +33,14 @@ export const Session: React.FC = ({ 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 @@ -60,6 +74,8 @@ export const Session: React.FC = ({ + {showStats && } +
@@ -68,6 +84,13 @@ export const Session: React.FC = ({