+ )
+}
+
+interface ConversationItemContentProps {
+ content: RealtimeConversationItemContent
+ doScrollIntoView: boolean
+}
+
+const ConversationItemContent = ({
+ content,
+}: ConversationItemContentProps): ReactNode => {
+ if (!["text", "input_text", "input_audio"].includes(content.type)) {
+ // NOTE: we find content.type="audio" coming in here in logging though it is not in the types!
+ if ((content.type as string) !== "audio") {
+ log.warn(
+ `Unexpected type for RealtimeConversationItemContent '${content.type}'. Will not be rendered: %o`,
+ content
+ )
+ }
+ return null
+ }
+
+ return (
+
+ {conversation !== undefined
+ ? "Conversation data not yet available. Start a session and talk and they should appear."
+ : "Conversations not available in this SDK"}
+
+ )}
+
+
)
}
diff --git a/apps/browser-example/src/components/simpleConversation.ts b/apps/browser-example/src/components/simpleConversation.ts
new file mode 100644
index 0000000..120f66f
--- /dev/null
+++ b/apps/browser-example/src/components/simpleConversation.ts
@@ -0,0 +1,43 @@
+import {
+ RealtimeConversationItem,
+ RealtimeConversationItemContent,
+} from "@tsorta/browser/openai"
+
+/** Removes the possibility of `undefined` in @see RealtimeConversationItem.role. */
+export type DefinedRole = NonNullable
+
+/** Removes the possibility of `undefined` in @see RealtimeConversationItem.type. */
+type DefinedRealtimeConversationItemType = NonNullable<
+ RealtimeConversationItem["type"]
+>
+
+/**
+ * A simplified form of @see RealtimeConversationItem for rendering.
+ */
+export type RealtimeConversationItemSimple = Pick<
+ RealtimeConversationItem,
+ "id"
+> & {
+ role: DefinedRole
+ type: DefinedRealtimeConversationItemType
+ content: RealtimeConversationItemContent[]
+}
+
+/**
+ * Simplifies the @see RealtimeConversationItem for rendering purposes. Mostly removes the possibility of `undefined` values in places where they are unlikely (impossible) at render time.
+ */
+export function simplifyItem(
+ item: RealtimeConversationItem
+): RealtimeConversationItemSimple {
+ if (!item.role) {
+ throw new Error("Role missing in conversation item")
+ }
+ const role: DefinedRole = item.role
+ const id = item.id || "id-missing"
+ const type = item.type as DefinedRealtimeConversationItemType
+ // NOTE: There may be no contents on the initial creation while the model is still replying.
+ const content: RealtimeConversationItemContent[] = (item.content ||
+ []) as RealtimeConversationItemContent[]
+
+ return { id, type, role, content }
+}
diff --git a/apps/browser-example/src/pages/WebRTCExample.tsx b/apps/browser-example/src/pages/WebRTCExample.tsx
index 435f1a8..b427100 100644
--- a/apps/browser-example/src/pages/WebRTCExample.tsx
+++ b/apps/browser-example/src/pages/WebRTCExample.tsx
@@ -5,6 +5,7 @@ import {
} from "../components/RealtimeSessionView"
import { RealtimeClient } from "@tsorta/browser/WebRTC"
import { PageProps } from "./props"
+import { RealtimeConversationItem } from "@tsorta/browser/openai"
export function WebRTCExample({
apiKey,
@@ -15,6 +16,9 @@ export function WebRTCExample({
const [client, setClient] = useState(undefined)
const [events, setEvents] = useState([])
+ const [conversation, setConversation] = useState(
+ []
+ )
const startSession = useCallback(
async function startSession({
@@ -48,10 +52,13 @@ export function WebRTCExample({
setClient(client)
client.addEventListener("serverEvent", (event) => {
- console.debug("serverEvent event:", event)
setEvents((events) => [...events, event.event])
})
+ client.addEventListener("conversationChanged", (event) => {
+ setConversation(event.conversation)
+ })
+
await client.start()
onSessionStatusChanged("recording")
@@ -79,11 +86,13 @@ export function WebRTCExample({
by Scott Willeke.
+
)