1import { useAtomValue } from "@effect/atom-react";
2import { usesChatGptSharing } from "@t3tools/shared/usageLimits";
3import { useState } from "react";
4import { environmentPresentations } from "../../state/presentation";
5import { OpenAI } from "../Icons";
6import { Button } from "../ui/button";
7import {
8 Dialog,
9 DialogPopup,
10 DialogHeader,
11 DialogTitle,
12 DialogDescription,
13 DialogFooter,
14} from "../ui/dialog";
15import { ChatGptUsageButton } from "./ChatGptUsageButton";
16
17const STORAGE_KEY = "t3:chatgpt-sharing-welcome:v1";
18function readAcknowledgedProfiles(): string[] {
19 try {
20 const value: unknown = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
21 return Array.isArray(value)
22 ? value.filter((key): key is string => typeof key === "string")
23 : [];
24 } catch {
25 return [];
26 }
27}
28
29/** Read the verified environment snapshot, never the browser callback acknowledgment. */
30export function ChatGptWelcomeCoordinator() {
31 const presentations = useAtomValue(environmentPresentations.presentationsAtom);
32 const [acknowledged, setAcknowledged] = useState(readAcknowledgedProfiles);
33 const profiles = [...presentations].flatMap(([environmentId, presentation]) =>
34 presentation.connection.phase !== "connected"
35 ? []
36 : (presentation.serverConfig?.providers ?? []).filter(usesChatGptSharing).map((provider) => ({
37 key: JSON.stringify([
38 environmentId,
39 provider.instanceId,
40 provider.auth.profileId ?? provider.auth.email ?? "default",
41 ]),
42 environmentLabel: presentation.entry.target.label,
43 providerName: provider.displayName ?? "Codex",
44 })),
45 );
46 const next = profiles.find((profile) => !acknowledged.includes(profile.key));
47 const dismiss = () => {
48 if (!next) return;
49 const updated = [...acknowledged, next.key];
50 setAcknowledged(updated);
51 try {
52 localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
53 } catch {
54 /* Session dismissal still works. */
55 }
56 };
57 return (
58 <Dialog
59 open={next !== undefined}
60 onOpenChange={(open) => {
61 if (!open) dismiss();
62 }}
63 >
64 <DialogPopup>
65 <DialogHeader>
66 <OpenAI className="mb-2 size-8" aria-hidden="true" />
67 <DialogTitle>Your ChatGPT plan is connected</DialogTitle>
68 <DialogDescription>
69 Eligible usage in The Knife uses your ChatGPT plan. Manage your shared usage and any
70 credit settings in ChatGPT.
71 </DialogDescription>
72 <p className="text-xs text-muted-foreground">
73 {next?.providerName} on {next?.environmentLabel}
74 </p>
75 </DialogHeader>
76 <DialogFooter>
77 <ChatGptUsageButton />
78 <Button onClick={dismiss}>Continue</Button>
79 </DialogFooter>
80 </DialogPopup>
81 </Dialog>
82 );
83}