Merge branch 'main' into jose/ui

This commit is contained in:
Jose B
2023-08-22 13:50:05 -05:00
66 changed files with 1615 additions and 315 deletions

View File

@@ -79,7 +79,7 @@ This data is then returned from the `useWebRTC` hook and can be used in your com
To generate the TypeScript files from the openapi.json file, make sure the python server is running, then run:
```
openapi-generator-cli generate -i http://localhost:1250/openapi.json -g typescript-fetch -o app/api
yarn openapi
```
You may need to run `yarn global add @openapitools/openapi-generator-cli` first. You also need a Java runtime installed on your machine.

View File

@@ -0,0 +1,11 @@
"use client";
import { FiefAuthProvider } from "@fief/fief/nextjs/react";
export default function FiefWrapper({ children }) {
return (
<FiefAuthProvider currentUserPath="/api/current-user">
{children}
</FiefAuthProvider>
);
}

View File

@@ -0,0 +1,43 @@
"use client";
import {
useFiefIsAuthenticated,
useFiefUserinfo,
} from "@fief/fief/nextjs/react";
import Link from "next/link";
import Image from "next/image";
export default function UserInfo() {
const isAuthenticated = useFiefIsAuthenticated();
const userinfo = useFiefUserinfo();
return (
<header className="bg-black w-full border-b border-gray-700 flex justify-between items-center py-2 mb-3">
{/* Logo on the left */}
<Link href="/">
<Image
src="/reach.png"
width={16}
height={16}
className="h-6 w-auto ml-2"
alt="Reflector"
/>
</Link>
{/* Text link on the right */}
{!isAuthenticated && (
<span className="text-white hover:underline font-thin px-2">
<Link href="/login">Log in or create account</Link>
</span>
)}
{isAuthenticated && (
<span className="text-white font-thin px-2">
{userinfo?.email} (
<span className="hover:underline">
<Link href="/logout">Log out</Link>
</span>
)
</span>
)}
</header>
);
}

View File

@@ -9,6 +9,7 @@ models/PageGetTranscript.ts
models/RtcOffer.ts
models/TranscriptTopic.ts
models/UpdateTranscript.ts
models/UserInfo.ts
models/ValidationError.ts
models/index.ts
runtime.ts

View File

@@ -55,6 +55,10 @@ export interface V1TranscriptGetAudioRequest {
transcriptId: any;
}
export interface V1TranscriptGetAudioMp3Request {
transcriptId: any;
}
export interface V1TranscriptGetTopicsRequest {
transcriptId: any;
}
@@ -159,6 +163,14 @@ export class DefaultApi extends runtime.BaseAPI {
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
// oauth required
headerParameters["Authorization"] = await this.configuration.accessToken(
"OAuth2AuthorizationCodeBearer",
[],
);
}
const response = await this.request(
{
path: `/v1/transcripts/{transcript_id}`.replace(
@@ -212,6 +224,14 @@ export class DefaultApi extends runtime.BaseAPI {
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
// oauth required
headerParameters["Authorization"] = await this.configuration.accessToken(
"OAuth2AuthorizationCodeBearer",
[],
);
}
const response = await this.request(
{
path: `/v1/transcripts/{transcript_id}`.replace(
@@ -299,6 +319,61 @@ export class DefaultApi extends runtime.BaseAPI {
return await response.value();
}
/**
* Transcript Get Audio Mp3
*/
async v1TranscriptGetAudioMp3Raw(
requestParameters: V1TranscriptGetAudioMp3Request,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<any>> {
if (
requestParameters.transcriptId === null ||
requestParameters.transcriptId === undefined
) {
throw new runtime.RequiredError(
"transcriptId",
"Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptGetAudioMp3.",
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request(
{
path: `/v1/transcripts/{transcript_id}/audio/mp3`.replace(
`{${"transcript_id"}}`,
encodeURIComponent(String(requestParameters.transcriptId)),
),
method: "GET",
headers: headerParameters,
query: queryParameters,
},
initOverrides,
);
if (this.isJsonMime(response.headers.get("content-type"))) {
return new runtime.JSONApiResponse<any>(response);
} else {
return new runtime.TextApiResponse(response) as any;
}
}
/**
* Transcript Get Audio Mp3
*/
async v1TranscriptGetAudioMp3(
requestParameters: V1TranscriptGetAudioMp3Request,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<any> {
const response = await this.v1TranscriptGetAudioMp3Raw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Transcript Get Topics
*/
@@ -510,6 +585,14 @@ export class DefaultApi extends runtime.BaseAPI {
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
// oauth required
headerParameters["Authorization"] = await this.configuration.accessToken(
"OAuth2AuthorizationCodeBearer",
[],
);
}
const response = await this.request(
{
path: `/v1/transcripts/{transcript_id}`.replace(
@@ -566,6 +649,14 @@ export class DefaultApi extends runtime.BaseAPI {
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
// oauth required
headerParameters["Authorization"] = await this.configuration.accessToken(
"OAuth2AuthorizationCodeBearer",
[],
);
}
const response = await this.request(
{
path: `/v1/transcripts`,
@@ -615,6 +706,14 @@ export class DefaultApi extends runtime.BaseAPI {
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
// oauth required
headerParameters["Authorization"] = await this.configuration.accessToken(
"OAuth2AuthorizationCodeBearer",
[],
);
}
const response = await this.request(
{
path: `/v1/transcripts`,
@@ -643,4 +742,49 @@ export class DefaultApi extends runtime.BaseAPI {
);
return await response.value();
}
/**
* User Me
*/
async v1UserMeRaw(
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<any>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
// oauth required
headerParameters["Authorization"] = await this.configuration.accessToken(
"OAuth2AuthorizationCodeBearer",
[],
);
}
const response = await this.request(
{
path: `/v1/me`,
method: "GET",
headers: headerParameters,
query: queryParameters,
},
initOverrides,
);
if (this.isJsonMime(response.headers.get("content-type"))) {
return new runtime.JSONApiResponse<any>(response);
} else {
return new runtime.TextApiResponse(response) as any;
}
}
/**
* User Me
*/
async v1UserMe(
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<any> {
const response = await this.v1UserMeRaw(initOverrides);
return await response.value();
}
}

View File

@@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
/**
* FastAPI
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from "../runtime";
/**
*
* @export
* @interface UserInfo
*/
export interface UserInfo {
/**
*
* @type {any}
* @memberof UserInfo
*/
sub: any | null;
/**
*
* @type {any}
* @memberof UserInfo
*/
email: any | null;
/**
*
* @type {any}
* @memberof UserInfo
*/
emailVerified: any | null;
}
/**
* Check if a given object implements the UserInfo interface.
*/
export function instanceOfUserInfo(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "sub" in value;
isInstance = isInstance && "email" in value;
isInstance = isInstance && "emailVerified" in value;
return isInstance;
}
export function UserInfoFromJSON(json: any): UserInfo {
return UserInfoFromJSONTyped(json, false);
}
export function UserInfoFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): UserInfo {
if (json === undefined || json === null) {
return json;
}
return {
sub: json["sub"],
email: json["email"],
emailVerified: json["email_verified"],
};
}
export function UserInfoToJSON(value?: UserInfo | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
sub: value.sub,
email: value.email,
email_verified: value.emailVerified,
};
}

View File

@@ -8,4 +8,5 @@ export * from "./PageGetTranscript";
export * from "./RtcOffer";
export * from "./TranscriptTopic";
export * from "./UpdateTranscript";
export * from "./UserInfo";
export * from "./ValidationError";

View File

@@ -1,11 +1,12 @@
import "./styles/globals.scss";
import { Roboto } from "next/font/google";
import Head from "next/head";
import { Metadata } from "next";
import FiefWrapper from "./(auth)/fiefWrapper";
import UserInfo from "./(auth)/userInfo";
const roboto = Roboto({ subsets: ["latin"], weight: "400" });
export const metadata = {
export const metadata: Metadata = {
title: {
template: "%s Reflector",
default: "Reflector - AI-Powered Meeting Transcriptions by Monadical",
@@ -52,11 +53,22 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<Head>
<title>Test</title>
</Head>
<body className={roboto.className + " flex flex-col min-h-screen"}>
{children}
<FiefWrapper>
<div id="container">
<div className="flex flex-col items-center h-[100svh] bg-gradient-to-r from-[#8ec5fc30] to-[#e0c3fc42]">
<UserInfo />
<div className="h-[13svh] flex flex-col justify-center items-center">
<h1 className="text-5xl font-bold text-blue-500">Reflector</h1>
<p className="text-gray-500">
Capture The Signal, Not The Noise
</p>
</div>
{children}
</div>
</div>
</FiefWrapper>
</body>
</html>
);

50
www/app/lib/fief.ts Normal file
View File

@@ -0,0 +1,50 @@
"use client";
import { Fief, FiefUserInfo } from "@fief/fief";
import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs";
export const SESSION_COOKIE_NAME = "reflector-auth";
export const fiefClient = new Fief({
baseURL: process.env.FIEF_URL ?? "",
clientId: process.env.FIEF_CLIENT_ID ?? "",
clientSecret: process.env.FIEF_CLIENT_SECRET ?? "",
});
class MemoryUserInfoCache implements IUserInfoCache {
private storage: Record<string, any>;
constructor() {
this.storage = {};
}
async get(id: string): Promise<FiefUserInfo | null> {
const userinfo = this.storage[id];
if (userinfo) {
return userinfo;
}
return null;
}
async set(id: string, userinfo: FiefUserInfo): Promise<void> {
this.storage[id] = userinfo;
}
async remove(id: string): Promise<void> {
this.storage[id] = undefined;
}
async clear(): Promise<void> {
this.storage = {};
}
}
export const fiefAuth = new FiefAuth({
client: fiefClient,
sessionCookieName: SESSION_COOKIE_NAME,
redirectURI:
process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL ||
"http://localhost:3000/auth-callback",
logoutRedirectURI:
process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000",
userInfoCache: new MemoryUserInfoCache(),
});

18
www/app/lib/getApi.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Configuration } from "../api/runtime";
import { DefaultApi } from "../api/apis/DefaultApi";
import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react";
export default function getApi(): DefaultApi {
const accessTokenInfo = useFiefAccessTokenInfo();
const apiConfiguration = new Configuration({
basePath: process.env.NEXT_PUBLIC_API_URL,
accessToken: accessTokenInfo
? "Bearer " + accessTokenInfo.access_token
: undefined,
});
const api = new DefaultApi(apiConfiguration);
return api;
}

View File

@@ -1,15 +1,15 @@
export function getRandomNumber(min, max) {
export function getRandomNumber(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function SeededRand(seed) {
export function SeededRand(seed: number): number {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return seed / 2 ** 32;
}
export function Mulberry32(seed) {
export function Mulberry32(seed: number) {
return function () {
var t = (seed += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);

View File

@@ -1,4 +1,4 @@
export const formatTime = (seconds) => {
export const formatTime = (seconds: number): string => {
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let secs = Math.floor(seconds % 60);

21
www/app/lib/user.ts Normal file
View File

@@ -0,0 +1,21 @@
export async function getCurrentUser(): Promise<any> {
try {
const response = await fetch("/api/current-user");
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
// Ensure the data structure is as expected
if (data.userinfo && data.access_token_info) {
return data;
} else {
throw new Error("Unexpected data structure");
}
} catch (error) {
console.error("Error fetching the user data:", error);
throw error; // or you can return an appropriate fallback or error indicator
}
}

View File

@@ -1,4 +1,4 @@
import { redirect } from "next/navigation";
export default async function Index({ params }) {
export default async function Index() {
redirect("/transcripts/new");
}

View File

@@ -3,17 +3,29 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronRight,
faChevronDown,
faLinkSlash,
} from "@fortawesome/free-solid-svg-icons";
import { formatTime } from "../lib/time";
import ScrollToBottom from "./scrollToBottom";
import DisconnectedIndicator from "./disconnectedIndicator";
import LiveTrancription from "./liveTranscription";
import FinalSummary from "./finalSummary";
import { Topic, FinalSummary as FinalSummaryType } from "./webSocketTypes";
type DashboardProps = {
transcriptionText: string;
finalSummary: FinalSummaryType;
topics: Topic[];
disconnected: boolean;
};
export function Dashboard({
transcriptionText,
finalSummary,
topics,
disconnected,
}) {
const [openIndex, setOpenIndex] = useState(null);
const [autoscrollEnabled, setAutoscrollEnabled] = useState(true);
}: DashboardProps) {
const [openIndex, setOpenIndex] = useState<number | null>(null);
const [autoscrollEnabled, setAutoscrollEnabled] = useState<boolean>(true);
useEffect(() => {
if (autoscrollEnabled) scrollToBottom();
@@ -21,7 +33,10 @@ export function Dashboard({
const scrollToBottom = () => {
const topicsDiv = document.getElementById("topics-div");
topicsDiv.scrollTop = topicsDiv.scrollHeight;
if (!topicsDiv)
console.error("Could not find topics div to scroll to bottom");
else topicsDiv.scrollTop = topicsDiv.scrollHeight;
};
const handleScroll = (e) => {
@@ -34,18 +49,6 @@ export function Dashboard({
}
};
const formatTime = (seconds) => {
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let secs = Math.floor(seconds % 60);
let timeString = `${hours > 0 ? hours + ":" : ""}${minutes
.toString()
.padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
return timeString;
};
return (
<>
<div className="relative h-[60svh] w-3/4 flex flex-col">
@@ -57,16 +60,12 @@ export function Dashboard({
<div className="w-3/4 font-bold">Topic</div>
</div>
<div
className={`absolute right-5 w-10 h-10 ${
autoscrollEnabled ? "hidden" : "flex"
} ${
finalSummary ? "top-[49%]" : "bottom-1"
} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
onClick={scrollToBottom}
>
&#11015;
</div>
<ScrollToBottom
visible={!autoscrollEnabled}
hasFinalSummary={finalSummary ? true : false}
handleScrollBottom={scrollToBottom}
/>
<div
id="topics-div"
className="py-2 overflow-y-auto"
@@ -99,26 +98,12 @@ export function Dashboard({
)}
</div>
{finalSummary && (
<div className="min-h-[200px] overflow-y-auto mt-2 p-2 bg-white temp-transcription rounded">
<h2>Final Summary</h2>
<p>{finalSummary.summary}</p>
</div>
)}
{finalSummary.summary && <FinalSummary text={finalSummary.summary} />}
</div>
{disconnected && (
<div className="absolute top-0 left-0 w-full h-full bg-black opacity-50 flex justify-center items-center">
<div className="text-white text-2xl">
<FontAwesomeIcon icon={faLinkSlash} className="mr-2" />
Disconnected
</div>
</div>
)}
{disconnected && <DisconnectedIndicator />}
<footer className="h-[7svh] w-full bg-gray-800 text-white text-center py-4 text-2xl">
&nbsp;{transcriptionText}&nbsp;
</footer>
<LiveTrancription text={transcriptionText} />
</>
);
}

View File

@@ -0,0 +1,13 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faLinkSlash } from "@fortawesome/free-solid-svg-icons";
export default function DisconnectedIndicator() {
return (
<div className="absolute top-0 left-0 w-full h-full bg-black opacity-50 flex justify-center items-center">
<div className="text-white text-2xl">
<FontAwesomeIcon icon={faLinkSlash} className="mr-2" />
Disconnected
</div>
</div>
);
}

View File

@@ -0,0 +1,12 @@
type FinalSummaryProps = {
text: string;
};
export default function FinalSummary(props: FinalSummaryProps) {
return (
<div className="min-h-[200px] overflow-y-auto mt-2 p-2 bg-white temp-transcription rounded">
<h2>Final Summary</h2>
<p>{props.text}</p>
</div>
);
}

View File

@@ -0,0 +1,11 @@
type LiveTranscriptionProps = {
text: string;
};
export default function LiveTrancription(props: LiveTranscriptionProps) {
return (
<div className="h-[7svh] w-full bg-gray-800 text-white text-center py-4 text-2xl">
&nbsp;{props.text}&nbsp;
</div>
);
}

View File

@@ -7,10 +7,11 @@ import useTranscript from "../useTranscript";
import { useWebSockets } from "../useWebSockets";
import useAudioDevice from "../useAudioDevice";
import "../../styles/button.css";
import getApi from "../../lib/getApi";
const App = () => {
const [stream, setStream] = useState(null);
const [disconnected, setDisconnected] = useState(false);
const [stream, setStream] = useState<MediaStream | null>(null);
const [disconnected, setDisconnected] = useState<boolean>(false);
useEffect(() => {
if (process.env.NEXT_PUBLIC_ENV === "development") {
@@ -22,8 +23,9 @@ const App = () => {
}
}, []);
const api = getApi();
const transcript = useTranscript();
const webRTC = useWebRTC(stream, transcript.response?.id);
const webRTC = useWebRTC(stream, transcript.response?.id, api);
const webSockets = useWebSockets(transcript.response?.id);
const {
loading,
@@ -56,7 +58,6 @@ const App = () => {
transcriptionText={webSockets.transcriptText}
finalSummary={webSockets.finalSummary}
topics={webSockets.topics}
stream={stream}
disconnected={disconnected}
/>
</>

View File

@@ -8,7 +8,7 @@ import { faDownload } from "@fortawesome/free-solid-svg-icons";
import Dropdown from "react-dropdown";
import "react-dropdown/style.css";
import CustomRecordPlugin from "./CustomRecordPlugin";
import CustomRecordPlugin from "../lib/CustomRecordPlugin";
import { formatTime } from "../lib/time";
const AudioInputsDropdown = (props) => {

View File

@@ -0,0 +1,23 @@
type ScrollToBottomProps = {
visible: boolean;
hasFinalSummary: boolean;
handleScrollBottom: () => void;
};
export default function ScrollToBottom(props: ScrollToBottomProps) {
return (
<div
className={`absolute right-5 w-10 h-10 ${
props.visible ? "flex" : "hidden"
} ${
props.hasFinalSummary ? "top-[49%]" : "bottom-1"
} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
onClick={() => {
props.handleScrollBottom();
return false;
}}
>
&#11015;
</div>
);
}

View File

@@ -1,20 +1,26 @@
import { useEffect, useState } from "react";
import { DefaultApi } from "../api/apis/DefaultApi";
import { DefaultApi, V1TranscriptsCreateRequest } from "../api/apis/DefaultApi";
import { Configuration } from "../api/runtime";
import { GetTranscript } from "../api";
import getApi from "../lib/getApi";
const useTranscript = () => {
const [response, setResponse] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
type UseTranscript = {
response: GetTranscript | null;
loading: boolean;
error: string | null;
createTranscript: () => void;
};
const apiConfiguration = new Configuration({
basePath: process.env.NEXT_PUBLIC_API_URL,
});
const api = new DefaultApi(apiConfiguration);
const useTranscript = (): UseTranscript => {
const [response, setResponse] = useState<GetTranscript | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const api = getApi();
const createTranscript = () => {
setLoading(true);
const requestParameters = {
const requestParameters: V1TranscriptsCreateRequest = {
createTranscript: {
name: "Weekly All-Hands", // Hardcoded for now
},

View File

@@ -1,28 +1,28 @@
import { useEffect, useState } from "react";
import Peer from "simple-peer";
import { DefaultApi } from "../api/apis/DefaultApi";
import {
DefaultApi,
V1TranscriptRecordWebrtcRequest,
} from "../api/apis/DefaultApi";
import { Configuration } from "../api/runtime";
const useWebRTC = (stream, transcriptId) => {
const [data, setData] = useState({
peer: null,
});
const useWebRTC = (
stream: MediaStream | null,
transcriptId: string | null,
api: DefaultApi,
): Peer => {
const [peer, setPeer] = useState<Peer | null>(null);
useEffect(() => {
if (!stream || !transcriptId) {
return;
}
const apiConfiguration = new Configuration({
basePath: process.env.NEXT_PUBLIC_API_URL,
});
const api = new DefaultApi(apiConfiguration);
let p: Peer = new Peer({ initiator: true, stream: stream });
let peer = new Peer({ initiator: true, stream: stream });
peer.on("signal", (data) => {
p.on("signal", (data: any) => {
if ("sdp" in data) {
const requestParameters = {
const requestParameters: V1TranscriptRecordWebrtcRequest = {
transcriptId: transcriptId,
rtcOffer: {
sdp: data.sdp,
@@ -33,7 +33,7 @@ const useWebRTC = (stream, transcriptId) => {
api
.v1TranscriptRecordWebrtc(requestParameters)
.then((answer) => {
peer.signal(answer);
p.signal(answer);
})
.catch((err) => {
console.error("WebRTC signaling error:", err);
@@ -41,17 +41,17 @@ const useWebRTC = (stream, transcriptId) => {
}
});
peer.on("connect", () => {
p.on("connect", () => {
console.log("WebRTC connected");
setData((prevData) => ({ ...prevData, peer: peer }));
setPeer(p);
});
return () => {
peer.destroy();
p.destroy();
};
}, [stream, transcriptId]);
return data;
return peer;
};
export default useWebRTC;

View File

@@ -1,10 +1,20 @@
import { useEffect, useState } from "react";
import { Topic, FinalSummary, Status } from "./webSocketTypes";
export const useWebSockets = (transcriptId) => {
const [transcriptText, setTranscriptText] = useState("");
const [topics, setTopics] = useState([]);
const [finalSummary, setFinalSummary] = useState("");
const [status, setStatus] = useState("disconnected");
type UseWebSockets = {
transcriptText: string;
topics: Topic[];
finalSummary: FinalSummary;
status: Status;
};
export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
const [transcriptText, setTranscriptText] = useState<string>("");
const [topics, setTopics] = useState<Topic[]>([]);
const [finalSummary, setFinalSummary] = useState<FinalSummary>({
summary: "",
});
const [status, setStatus] = useState<Status>({ value: "disconnected" });
useEffect(() => {
if (!transcriptId) return;
@@ -40,7 +50,7 @@ export const useWebSockets = (transcriptId) => {
break;
case "STATUS":
setStatus(message.data.status);
setStatus(message.data);
break;
default:

View File

@@ -0,0 +1,19 @@
export type Topic = {
timestamp: number;
title: string;
transcript: string;
summary: string;
id: string;
};
export type Transcript = {
text: string;
};
export type FinalSummary = {
summary: string;
};
export type Status = {
value: string;
};

20
www/middleware.ts Normal file
View File

@@ -0,0 +1,20 @@
import type { NextRequest } from "next/server";
import { fiefAuth } from "./app/lib/fief";
const authMiddleware = fiefAuth.middleware([
{
matcher: "/private",
parameters: {},
},
{
matcher: "/castles/:path*",
parameters: {
permissions: ["castles:read"],
},
},
]);
export async function middleware(request: NextRequest) {
return authMiddleware(request);
}

View File

@@ -5,7 +5,7 @@ const nextConfig = {
module.exports = nextConfig;
// Sentry content below
// Injected content via Sentry wizard below
const { withSentryConfig } = require("@sentry/nextjs");

View File

@@ -11,10 +11,11 @@
"openapi": "openapi-generator-cli generate -i http://localhost:1250/openapi.json -g typescript-fetch -o app/api && yarn format"
},
"dependencies": {
"@fief/fief": "^0.13.5",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@sentry/nextjs": "^7.61.0",
"@sentry/nextjs": "^7.64.0",
"autoprefixer": "10.4.14",
"axios": "^1.4.0",
"fontawesome": "^5.6.3",

View File

@@ -0,0 +1,3 @@
import { fiefAuth } from "../../app/lib/fief";
export default fiefAuth.currentUser();

7
www/pages/forbidden.tsx Normal file
View File

@@ -0,0 +1,7 @@
import type { NextPage } from "next";
const Forbidden: NextPage = () => {
return <h2>Sorry, you are not authorized to access this page.</h2>;
};
export default Forbidden;

View File

@@ -14,6 +14,16 @@
dependencies:
regenerator-runtime "^0.14.0"
"@fief/fief@^0.13.5":
version "0.13.5"
resolved "https://registry.yarnpkg.com/@fief/fief/-/fief-0.13.5.tgz#01ac833ddff0b84f2e1737cc168721568b0e7a39"
integrity sha512-st4+/Rc1rw18B6RtqLmC6t9k0pnYLG7AqMe0JYjKYcamCNnABwl198ZJt6HShtaZKI5maHsh99UoCA3MqpbWsQ==
dependencies:
encoding "^0.1.13"
jose "^4.6.0"
node-fetch "^2.6.7"
path-to-regexp "^6.2.1"
"@fortawesome/fontawesome-common-types@6.4.0":
version "6.4.0"
resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz"
@@ -252,26 +262,26 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
"@sentry-internal/tracing@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.61.0.tgz#5a0dd4a9a0b41f2e22904430f3fe0216f36ee086"
integrity sha512-zTr+MXEG4SxNxif42LIgm2RQn+JRXL2NuGhRaKSD2i4lXKFqHVGlVdoWqY5UfqnnJPokiTWIj9ejR8I5HV8Ogw==
"@sentry-internal/tracing@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.64.0.tgz#3e110473b8edf805b799cc91d6ee592830237bb4"
integrity sha512-1XE8W6ki7hHyBvX9hfirnGkKDBKNq3bDJyXS86E0bYVDl94nvbRM9BD9DHsCFetqYkVm1yDGEK+6aUVs4CztoQ==
dependencies:
"@sentry/core" "7.61.0"
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry/core" "7.64.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
tslib "^2.4.1 || ^1.9.3"
"@sentry/browser@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.61.0.tgz#04f4122e444d8b5ffefed97af3cde2bc1c71bb80"
integrity sha512-IGEkJZRP16Oe5CkXkmhU3QdV5RugW6Vds16yJFFYsgp87NprWtRZgqzldFDYkINStfBHVdctj/Rh/ZrLf8QlkQ==
"@sentry/browser@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.64.0.tgz#76db08a5d32ffe7c5aa907f258e6c845ce7f10d7"
integrity sha512-lB2IWUkZavEDclxfLBp554dY10ZNIEvlDZUWWathW+Ws2wRb6PNLtuPUNu12R7Q7z0xpkOLrM1kRNN0OdldgKA==
dependencies:
"@sentry-internal/tracing" "7.61.0"
"@sentry/core" "7.61.0"
"@sentry/replay" "7.61.0"
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry-internal/tracing" "7.64.0"
"@sentry/core" "7.64.0"
"@sentry/replay" "7.64.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
tslib "^2.4.1 || ^1.9.3"
"@sentry/cli@^1.74.6":
@@ -286,88 +296,88 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
"@sentry/core@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.61.0.tgz#0de4f73055bd156c5c0cbac50bb814b272567188"
integrity sha512-zl0ZKRjIoYJQWYTd3K/U6zZfS4GDY9yGd2EH4vuYO4kfYtEp/nJ8A+tfAeDo0c9FGxZ0Q+5t5F4/SfwbgyyQzg==
"@sentry/core@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.64.0.tgz#9d61cdc29ba299dedbdcbe01cfadf94bd0b7df48"
integrity sha512-IzmEyl5sNG7NyEFiyFHEHC+sizsZp9MEw1+RJRLX6U5RITvcsEgcajSkHQFafaBPzRrcxZMdm47Cwhl212LXcw==
dependencies:
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
tslib "^2.4.1 || ^1.9.3"
"@sentry/integrations@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.61.0.tgz#49c97a59ceb0438bd5ec070415d95f8c6c708d5f"
integrity sha512-NEQ+CatBfUM1TmA4FOOyHfsMvSIwSg4pA55Lxiq9quDykzkEtrXFzUfFpZbTunz4cegG8hucPOqbzKFrDPfGjQ==
"@sentry/integrations@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.64.0.tgz#a392ddeebeec0c08ae5ca1f544c80ab15977fe10"
integrity sha512-6gbSGiruOifAmLtXw//Za19GWiL5qugDMEFxSvc5WrBWb+A8UK+foPn3K495OcivLS68AmqAQCUGb+6nlVowwA==
dependencies:
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
localforage "^1.8.1"
tslib "^2.4.1 || ^1.9.3"
"@sentry/nextjs@^7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.61.0.tgz#5a30faa6fb04d9148853edbb5c148dd522126097"
integrity sha512-zSEcAITqVmJpR4hhah1jUyCzm/hjlq9vjmO6BmTnQjr84OgOdeKJGWtRdktXId+9zzHdCOehs/JPtmO7y+yG6Q==
"@sentry/nextjs@^7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.64.0.tgz#5c0bd7ccc6637e0b925dec25ca247dcb8476663c"
integrity sha512-hKlIQpFugdRlWj0wcEG9I8JyVm/osdsE72zwMBGnmCw/jf7U63vjOjfxMe/gRuvllCf/AvoGHEkR5jPufcO+bw==
dependencies:
"@rollup/plugin-commonjs" "24.0.0"
"@sentry/core" "7.61.0"
"@sentry/integrations" "7.61.0"
"@sentry/node" "7.61.0"
"@sentry/react" "7.61.0"
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry/core" "7.64.0"
"@sentry/integrations" "7.64.0"
"@sentry/node" "7.64.0"
"@sentry/react" "7.64.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
"@sentry/webpack-plugin" "1.20.0"
chalk "3.0.0"
rollup "2.78.0"
stacktrace-parser "^0.1.10"
tslib "^2.4.1 || ^1.9.3"
"@sentry/node@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.61.0.tgz#1309330f2ad136af532ad2a03b2a312e885705de"
integrity sha512-oTCqD/h92uvbRCrtCdiAqN6Mfe3vF7ywVHZ8Nq3hHmJp6XadUT+fCBwNQ7rjMyqJAOYAnx/vp6iN9n8C5qcYZQ==
"@sentry/node@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.64.0.tgz#c6f7a67c1442324298f0525e7191bc18572ee1ce"
integrity sha512-wRi0uTnp1WSa83X2yLD49tV9QPzGh5e42IKdIDBiQ7lV9JhLILlyb34BZY1pq6p4dp35yDasDrP3C7ubn7wo6A==
dependencies:
"@sentry-internal/tracing" "7.61.0"
"@sentry/core" "7.61.0"
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry-internal/tracing" "7.64.0"
"@sentry/core" "7.64.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
cookie "^0.4.1"
https-proxy-agent "^5.0.0"
lru_map "^0.3.3"
tslib "^2.4.1 || ^1.9.3"
"@sentry/react@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.61.0.tgz#21dc8eb5168fdb45f994e62738313b50c710d6a4"
integrity sha512-17ZPDdzx3hzJSHsVFAiw4hUT701LUVIcm568q38sPlSUmnOmNmPeHx/xcQkuxMoVsw/xgf/82B/BKKnIP5/diA==
"@sentry/react@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.64.0.tgz#edee24ac232990204e0fb43dd83994642d4b0f54"
integrity sha512-wOyJUQi7OoT1q+F/fVVv1fzbyO4OYbTu6m1DliLOGQPGEHPBsgPc722smPIExd1/rAMK/FxOuNN5oNhubH8nhg==
dependencies:
"@sentry/browser" "7.61.0"
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry/browser" "7.64.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
hoist-non-react-statics "^3.3.2"
tslib "^2.4.1 || ^1.9.3"
"@sentry/replay@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.61.0.tgz#f816d6a2fc7511877efee2e328681d659433d147"
integrity sha512-1ugk0yZssOPkSg6uTVcysjxlBydycXiOgV0PCU7DsXCFOV1ua5YpyPZFReTz9iFTtwD0LwGFM1LW9wJeQ67Fzg==
"@sentry/replay@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.64.0.tgz#bdf09b0c4712f9dc6b24b3ebefa55a4ac76708e6"
integrity sha512-alaMCZDZhaAVmEyiUnszZnvfdbiZx5MmtMTGrlDd7tYq3K5OA9prdLqqlmfIJYBfYtXF3lD0iZFphOZQD+4CIw==
dependencies:
"@sentry/core" "7.61.0"
"@sentry/types" "7.61.0"
"@sentry/utils" "7.61.0"
"@sentry/core" "7.64.0"
"@sentry/types" "7.64.0"
"@sentry/utils" "7.64.0"
"@sentry/types@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.61.0.tgz#4243b5ef4658f6b0673bc4372c90e6ec920f78d8"
integrity sha512-/GLlIBNR35NKPE/SfWi9W10dK9hE8qTShzsuPVn5wAJxpT3Lb4+dkwmKCTLUYxdkmvRDEudkfOxgalsfQGTAWA==
"@sentry/types@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.64.0.tgz#21fc545ea05c3c8c4c3e518583eca1a8c5429506"
integrity sha512-LqjQprWXjUFRmzIlUjyA+KL+38elgIYmAeoDrdyNVh8MK5IC1W2Lh1Q87b4yOiZeMiIhIVNBd7Ecoh2rodGrGA==
"@sentry/utils@7.61.0":
version "7.61.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.61.0.tgz#16944afb2b851af045fb528c0c35b7dea3e1cd3b"
integrity sha512-jfj14d0XBFiCU0G6dZZ12SizATiF5Mt4stBGzkM5iS9nXFj8rh1oTT7/p+aZoYzP2JTF+sDzkNjWxyKZkcTo0Q==
"@sentry/utils@7.64.0":
version "7.64.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.64.0.tgz#6fe3ce9a56d3433ed32119f914907361a54cc184"
integrity sha512-HRlM1INzK66Gt+F4vCItiwGKAng4gqzCR4C5marsL3qv6SrKH98dQnCGYgXluSWaaa56h97FRQu7TxCk6jkSvQ==
dependencies:
"@sentry/types" "7.61.0"
"@sentry/types" "7.64.0"
tslib "^2.4.1 || ^1.9.3"
"@sentry/webpack-plugin@1.20.0":
@@ -868,6 +878,13 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
err-code@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz"
@@ -1108,6 +1125,13 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
@@ -1247,6 +1271,11 @@ jiti@^1.18.2:
resolved "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz"
integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==
jose@^4.6.0:
version "4.14.4"
resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.4.tgz#59e09204e2670c3164ee24cbfe7115c6f8bff9ca"
integrity sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -1529,6 +1558,11 @@ path-to-regexp@3.2.0:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f"
integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==
path-to-regexp@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
@@ -1786,7 +1820,7 @@ safe-buffer@^5.1.0, safe-buffer@~5.2.0:
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==