import { useNavigate } from "../../lib/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { MessageSquareIcon, RefreshCwIcon } from "lucide-react"; import { toast } from "../../lib/toast"; import type { SessionListResponse } from "@opengeni/sdk"; import type { OpenGeniBrowserClient } from "@opengeni/sdk/browser"; import { useAppContext } from "../../app-context"; import { Button } from "../../../ui/button"; import { EmptyState } from "../../../ui/empty-state"; import { ListRow, ListRowSkeleton, RowList } from "../../../ui/list-row"; import { LogoTile } from "../../../ui/logo-tile"; import { Notice } from "../../../ui/notice"; import { SegmentedControl } from "../../../ui/segmented-control"; import { Toolbar, ToolbarGroup, ToolbarSearch } from "../../../ui/toolbar"; import { sessionDisplayTitle } from "../../lib/session-rename"; import { sessionStateLabel } from "../../lib/session-rail"; import { userErrorText } from "../../../ui/api-error"; /** * The Site page's Conversations tab. Host-owned, so it works even when the * generated Site is broken. */ export function SiteConversations(props: { workspaceId: string; siteId: string; title: string; canManageAccess?: boolean | undefined; }) { const { client } = useAppContext(); return ; } export function SiteConversationsPanel({ client, workspaceId, siteId, title, canManageAccess = false, }: { client: Pick< OpenGeniBrowserClient, "listSessionPage" | "listSiteSessionAccess" | "setSiteSessionAccess" >; workspaceId: string; siteId: string; title: string; canManageAccess?: boolean | undefined; }) { const [search, setSearch] = useState(""); const [archivedOnly, setArchivedOnly] = useState(false); const [page, setPage] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(false); const [adding, setAdding] = useState(false); const [allowed, setAllowed] = useState([]); const [changing, setChanging] = useState(null); const request = useRef(null); const load = useCallback( async (cursor?: string) => { request.current?.abort(); const controller = new AbortController(); request.current = controller; setBusy(true); setError(false); try { const next = await client.listSessionPage(workspaceId, { ...(!adding ? { siteAccessId: siteId } : {}), search, archivedOnly, limit: 30, ...(cursor !== undefined ? { cursor } : {}), signal: controller.signal, }); if (controller.signal.aborted) return; if (canManageAccess) { const access = await client.listSiteSessionAccess(workspaceId, siteId); if (controller.signal.aborted) return; setAllowed(access.sessionIds); } setPage((previous) => cursor && previous ? { ...next, sessions: [ ...new Map( [...previous.sessions, ...next.sessions].map((s) => [s.id, s]), ).values(), ], } : next, ); } catch { if (!controller.signal.aborted) { setError(true); setPage(null); } } finally { if (!controller.signal.aborted) setBusy(false); } }, [client, workspaceId, siteId, search, archivedOnly, adding, canManageAccess], ); useEffect(() => { setPage(null); void load(); return () => request.current?.abort(); }, [load]); const visibleSessions = page ? [...new Map([...page.pinned, ...page.sessions].map((s) => [s.id, s])).values()] : []; const sessions = adding ? visibleSessions.filter( (session) => session.tenancy?.ownedByCurrentUser && !session.metadata._opengeniSiteOrigin, ) : visibleSessions; const changeAccess = async (sessionId: string, allow: boolean) => { setChanging(sessionId); try { await client.setSiteSessionAccess(workspaceId, siteId, sessionId, allow); setAllowed((current) => allow ? [...new Set([...current, sessionId])] : current.filter((id) => id !== sessionId), ); toast.success( allow ? "Chat allowed in this Site" : "Chat access removed", !allow ? { action: { label: "Undo", onClick: () => void changeAccess(sessionId, true) }, } : undefined, ); await load(); } catch (failure) { toast.error("Couldn’t change chat access", { description: userErrorText(failure) }); } finally { setChanging(null); } }; const navigate = useNavigate(); return (

{adding ? `Choose a chat you own. ${title} will be able to read it and send messages as you. Approvals stay in the console.` : `Conversations available in ${title}, including ones you explicitly allowed.`}

{canManageAccess && ( )} aria-label="Conversation visibility" size="sm" options={[ { value: "active", label: "Active" }, { value: "archived", label: "Archived" }, ]} value={archivedOnly ? "archived" : "active"} onValueChange={(value) => setArchivedOnly(value === "archived")} /> {error ? ( Couldn’t load conversations. Try refreshing. ) : busy && !page ? ( ) : sessions.length === 0 ? ( ) : ( {sessions.map((session) => ( } name="Conversation" />} title={sessionDisplayTitle(session)} meta={[sessionStateLabel(session)]} control={ canManageAccess && (adding || allowed.includes(session.id)) ? ( ) : undefined } href={`/workspaces/${encodeURIComponent(workspaceId)}/sessions/${encodeURIComponent(session.id)}`} onOpen={(event) => { if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) return; event.preventDefault(); void navigate({ to: "/workspaces/$workspaceId/sessions/$sessionId", params: { workspaceId, sessionId: session.id }, }); }} /> ))} )} {page?.nextCursor && !error ? ( ) : null}
); }