AIvoices/frontend-nextjs/app/components/Realtime/contexts/TranscriptContext.tsx
2025-04-08 14:05:27 +01:00

122 lines
No EOL
3.7 KiB
TypeScript

"use client";
import React, { createContext, useContext, useState, FC, PropsWithChildren } from "react";
import { v4 as uuidv4 } from "uuid";
import { TranscriptItem } from "@/app/components/Realtime/types";
type TranscriptContextValue = {
transcriptItems: TranscriptItem[];
addTranscriptMessage: (itemId: string, role: "user" | "assistant", text: string, hidden?: boolean) => void;
updateTranscriptMessage: (itemId: string, text: string, isDelta: boolean) => void;
addTranscriptBreadcrumb: (title: string, data?: Record<string, any>) => void;
toggleTranscriptItemExpand: (itemId: string) => void;
updateTranscriptItemStatus: (itemId: string, newStatus: "IN_PROGRESS" | "DONE") => void;
};
const TranscriptContext = createContext<TranscriptContextValue | undefined>(undefined);
export const TranscriptProvider: FC<PropsWithChildren> = ({ children }) => {
const [transcriptItems, setTranscriptItems] = useState<TranscriptItem[]>([]);
function newTimestampPretty(): string {
return new Date().toLocaleTimeString([], {
hour12: true,
hour: "numeric",
minute: "2-digit",
second: "2-digit",
});
}
const addTranscriptMessage: TranscriptContextValue["addTranscriptMessage"] = (itemId, role, text = "", isHidden = false) => {
setTranscriptItems((prev) => {
if (prev.some((log) => log.itemId === itemId && log.type === "MESSAGE")) {
console.warn(`[addTranscriptMessage] skipping; message already exists for itemId=${itemId}, role=${role}, text=${text}`);
return prev;
}
const newItem: TranscriptItem = {
itemId,
type: "MESSAGE",
role,
title: text,
expanded: false,
timestamp: newTimestampPretty(),
createdAtMs: Date.now(),
status: "IN_PROGRESS",
isHidden,
};
return [...prev, newItem];
});
};
const updateTranscriptMessage: TranscriptContextValue["updateTranscriptMessage"] = (itemId, newText, append = false) => {
setTranscriptItems((prev) =>
prev.map((item) => {
if (item.itemId === itemId && item.type === "MESSAGE") {
return {
...item,
title: append ? (item.title ?? "") + newText : newText,
};
}
return item;
})
);
};
const addTranscriptBreadcrumb: TranscriptContextValue["addTranscriptBreadcrumb"] = (title, data) => {
setTranscriptItems((prev) => [
...prev,
{
itemId: `breadcrumb-${uuidv4()}`,
type: "BREADCRUMB",
title,
data,
expanded: false,
timestamp: newTimestampPretty(),
createdAtMs: Date.now(),
status: "DONE",
isHidden: false,
},
]);
};
const toggleTranscriptItemExpand: TranscriptContextValue["toggleTranscriptItemExpand"] = (itemId) => {
setTranscriptItems((prev) =>
prev.map((log) =>
log.itemId === itemId ? { ...log, expanded: !log.expanded } : log
)
);
};
const updateTranscriptItemStatus: TranscriptContextValue["updateTranscriptItemStatus"] = (itemId, newStatus) => {
setTranscriptItems((prev) =>
prev.map((item) =>
item.itemId === itemId ? { ...item, status: newStatus } : item
)
);
};
return (
<TranscriptContext.Provider
value={{
transcriptItems,
addTranscriptMessage,
updateTranscriptMessage,
addTranscriptBreadcrumb,
toggleTranscriptItemExpand,
updateTranscriptItemStatus,
}}
>
{children}
</TranscriptContext.Provider>
);
};
export function useTranscript() {
const context = useContext(TranscriptContext);
if (!context) {
throw new Error("useTranscript must be used within a TranscriptProvider");
}
return context;
}