- This instance was provisioned before chat was enabled. Use the{' '}
-
-
- Upgrade to Latest
- {' '}
- button above to activate real-time chat with your KiloClaw bot.
-
-
-
- );
- }
-
- return ;
-}
-
-// ─── Internal components ────────────────────────────────────────────────────
-
-function StreamChatUI({
- apiKey,
- userId,
- channelId,
-}: {
- apiKey: string;
- userId: string;
- channelId: string;
-}) {
- const trpc = useTRPC();
- const queryClient = useQueryClient();
- const { organizationId } = useClawContext();
-
- // Stable token provider that fetches a fresh short-lived token on every call.
- // stream-chat-react calls this when the current token expires (via `exp` claim).
- // Routes to the correct tRPC endpoint based on personal vs org context.
- const tokenProvider = useCallback(async () => {
- const opts = organizationId
- ? trpc.organizations.kiloclaw.getStreamChatCredentials.queryOptions(
- { organizationId },
- { staleTime: 0 }
- )
- : trpc.kiloclaw.getStreamChatCredentials.queryOptions(undefined, {
- staleTime: 0,
- });
- const creds = await queryClient.fetchQuery(opts);
- if (!creds?.userToken) {
- throw new Error('Failed to fetch Stream Chat credentials');
- }
- return creds.userToken;
- }, [queryClient, trpc, organizationId]);
-
- const client = useCreateChatClient({
- apiKey,
- tokenOrProvider: tokenProvider,
- userData: { id: userId },
- });
-
- const [channel, setChannel] = useState();
-
- useEffect(() => {
- if (!client) return;
- const ch = client.channel('messaging', channelId);
- let cancelled = false;
- void (async () => {
- await ch.watch({ presence: true });
- if (cancelled) return;
- // Disable file uploads client-side by stripping the capability before
- // Channel reads it. This hides the attachment button, disables drag-
- // and-drop, and makes paste-to-upload a no-op — all three paths in
- // stream-chat-react gate on channel.data.own_capabilities["upload-file"].
- if (ch.data?.own_capabilities) {
- ch.data.own_capabilities = ch.data.own_capabilities.filter(
- capability => capability !== 'upload-file'
- );
- }
- setChannel(ch);
- })();
- return () => {
- cancelled = true;
- void ch.stopWatching();
- };
- }, [client, channelId]);
-
- // channelId is "default-{sandboxId}", bot user is "bot-{sandboxId}"
- const sandboxId = channelId.replace(/^default-/, '');
- const botUserId = `bot-${sandboxId}`;
-
- if (!client || !channel) {
- return ;
- }
-
- return (
-
-