-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathuseChatConversation.ts
More file actions
87 lines (75 loc) · 2.67 KB
/
Copy pathuseChatConversation.ts
File metadata and controls
87 lines (75 loc) · 2.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { firebaseAuth } from "./firebase";
import { ChatClient, type ChatMessage, type ChatClientStatus } from "./chatClient";
// One shared PerkOS-Chat WS connection per browser session — the same pattern
// the main PerkOS App uses (chatClient is plain TS; we wrap it in this hook).
// Auth is the user's Firebase ID token; message bodies live on the agent +
// stream over the socket (PerkOS-Chat NEVER persists content to Firestore, so
// the old Firestore-messages approach could never see agent replies).
let sharedClient: ChatClient | null = null;
function getClient(): ChatClient {
if (sharedClient) return sharedClient;
const url = process.env.NEXT_PUBLIC_CHAT_URL || "wss://chat.perkos.xyz/chat";
sharedClient = new ChatClient({
url,
getToken: async () => {
const user = firebaseAuth().currentUser;
return user ? user.getIdToken() : null;
},
});
sharedClient.start();
return sharedClient;
}
/**
* Subscribe to a conversation: loads the last history page, then streams live
* `chat_message` frames (user + agent). `send` posts a message over the socket;
* the agent's reply arrives as another live message.
*/
export function useChatConversation(convId: string | null | undefined) {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [status, setStatus] = useState<ChatClientStatus>("idle");
const seen = useRef<Set<string>>(new Set());
useEffect(() => {
if (!convId) {
setMessages([]);
seen.current = new Set();
return;
}
const client = getClient();
seen.current = new Set();
setMessages([]);
const offStatus = client.onStatus(setStatus);
const push = (m: ChatMessage) => {
if (seen.current.has(m.id)) return;
seen.current.add(m.id);
setMessages((cur) => [...cur, m].sort((a, b) => a.timestamp.localeCompare(b.timestamp)));
};
const offMsg = client.onMessage(convId, push);
// Load the most recent history page once connected. history() waits out a
// brief window if the socket is still authing.
let cancelled = false;
client
.history({ convId, limit: 50 })
.then((page) => {
if (cancelled) return;
for (const m of page.messages) push(m);
})
.catch(() => {
/* no history yet (fresh conv) — fine */
});
return () => {
cancelled = true;
offMsg();
offStatus();
};
}, [convId]);
const send = useCallback(
(text: string) => {
if (!convId || !text.trim()) return;
getClient().send({ convId, text: text.trim() });
},
[convId],
);
return { messages, status, send };
}