import { OpenGeniApiError } from "@opengeni/sdk"; import { lazy, Suspense, useRef, type ComponentProps, type FormEvent } from "react"; import { useNewConversation, type NewConversationController, type UseNewConversationOptions, } from "../hooks/use-new-conversation"; import { useRealtimeVoiceModels } from "../hooks/use-realtime-voice-models"; import { useErrorMessage } from "../lib/error-message"; import { useHostTheme } from "../lib/host-theme"; import { cn } from "../lib/cn"; import type { NewSessionRealtimeControl } from "../realtime/realtime-control"; import { ChatComposer } from "./chat-composer"; import { ModelPolicyPicker } from "./model-policy-picker"; import { useComposerTranscription, type SessionConversationProps } from "./session-conversation"; import { SessionProxyScope, type SessionProxyBaseUrl } from "./session-proxy-scope"; import { ConversationRenderBoundary } from "./conversation-render-boundary"; import { useOpenGeniText, type OpenGeniText } from "../messages/runtime"; const LazyNewVoice = lazy(() => import("../realtime/embedded-new-voice")); export type NewConversationLabels = { title: string; placeholder: string; send: string; unavailable: string; retry: string; pending: string; finishingUploads: string; }; const DEFAULT_LABELS = (t: OpenGeniText): NewConversationLabels => ({ title: t("newConversation.howCanIHelp"), placeholder: t("newConversation.askAnything"), send: t("newConversation.send"), unavailable: t("newConversation.newChatsAreNotEnabled"), retry: t("newConversation.retry"), pending: t("newConversation.creationIsNotConfirmedRetry"), finishingUploads: t("newConversation.chatCreatedFinishOrRemove"), }); type Presentation = { composerProps?: SessionConversationProps["composerProps"] | undefined; modelPickerProps?: SessionConversationProps["modelPickerProps"] | undefined; voiceInput?: boolean | undefined; realtimeVoiceProps?: SessionConversationProps["realtimeVoiceProps"] | undefined; labels?: Partial | undefined; theme?: SessionConversationProps["theme"] | undefined; surface?: SessionConversationProps["surface"] | undefined; height?: SessionConversationProps["height"] | undefined; className?: string | undefined; }; export type NewConversationProps = UseNewConversationOptions & SessionProxyBaseUrl & Presentation; export type NewConversationViewProps = Presentation & { creation: NewConversationController }; /** Standalone stock new-chat composer. OpenGeniChat uses this same implementation. */ export function NewConversation({ baseUrl, headers, fetch, ...props }: NewConversationProps) { const resetKey = props.workspaceId ?? ""; if (baseUrl === undefined) return ( ); const { client, workspaceId, ...rest } = props; return ( ); } function NewConversationRuntime(props: NewConversationProps) { const creation = useNewConversation(props); return ; } /** Presentation for a new-chat controller kept above host layout changes. */ export function NewConversationView({ creation, labels: overrides, composerProps, modelPickerProps, voiceInput = true, realtimeVoiceProps, theme, surface, height = "100%", className, }: NewConversationViewProps) { const t = useOpenGeniText(); const labels = { ...DEFAULT_LABELS(t), ...overrides }; const { composer, config, catalog, files, uploadsEnabled, available, error, pending } = creation; const formatError = useErrorMessage(); const root = useRef(null); const hostTheme = useHostTheme(root, { theme, surface }); const transcription = useComposerTranscription( creation.client, creation.workspaceId, voiceInput && available ? config.voiceInput : null, ); const models = useRealtimeVoiceModels( creation.client, creation.workspaceId, creation.realtimeVoiceEnabled && available, ); const voice = models.length ? ( ["client"] } workspaceId={creation.workspaceId} codexConnected={realtimeVoiceProps?.codexConnected ?? false} modelMenu={realtimeVoiceProps?.modelMenu} disabled={ composer.sending || pending !== null || creation.finishingUploads || files.hasUnresolved } onStart={creation.startRealtime} /> ) : null; return (
{ event.preventDefault(); void composer.send(); }} ref={root} className={cn( "og-root mx-auto box-border flex w-full max-w-3xl flex-col gap-4 bg-og-bg p-3 text-og-base text-og-fg", className, )} style={{ ...hostTheme.style, height }} data-og-theme={hostTheme.attribute} data-og-host-theme="" data-og-new-chat-composer="" >
{labels.title ? (

{labels.title}

) : null} {composerProps?.actionsStart} {voice} ) : ( composerProps?.actionsStart ) } controlsStart={ composerProps?.controlsStart ?? (composer.policy ? ( { if (open) void catalog.refresh(); }} onModelChange={composer.setModel!} onEffortChange={composer.setReasoningEffort!} onLatencyModeChange={composer.setLatencyMode!} /> ) : undefined) } responsiveBasis={composerProps?.responsiveBasis ?? "container"} /> {!available ? (

{labels.unavailable}

) : error ? (

{formatError( error.cause, error.cause instanceof OpenGeniApiError && error.cause.code === "route_not_allowed" ? labels.unavailable : undefined, )}

) : null} {pending ? (

{labels.pending}

) : null} {creation.finishingUploads ? (

{labels.finishingUploads}

) : null}
); }