Skip to content

Commit c8e9db5

Browse files
committed
Simpified architecture
1 parent e3ad906 commit c8e9db5

10 files changed

Lines changed: 537 additions & 279 deletions

File tree

claude-agent-github-wiki/.env.example

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ ANTHROPIC_API_KEY=your_anthropic_api_key
1111
# Get these from your Supabase project settings
1212
# https://app.supabase.com/project/_/settings/api
1313
SUPABASE_URL=your_supabase_project_url
14-
SUPABASE_PRIVATE_KEY=your_supabase_private_key # For backend use only - more secure than anon key
14+
SUPABASE_PRIVATE_KEY=your_supabase_service_role_jwt # Backend only - JWT token (starts with eyJ...) from "service_role" in API settings, NOT the database secret!
1515

16-
# Public Supabase keys for frontend (if needed)
17-
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
18-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
16+
# Public Supabase keys for frontend (safe to expose)
17+
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url # Same as SUPABASE_URL
18+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key # Found under "anon public" in API settings

claude-agent-github-wiki/app/api/chat/route.ts

Lines changed: 0 additions & 96 deletions
This file was deleted.

claude-agent-github-wiki/app/chat/[runId]/page.tsx

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
"use client";
22

3-
import { useState, useRef, useEffect } from "react";
4-
import { useSearchParams } from "next/navigation";
3+
import { AiMessage } from "@/components/chat/ai-message";
4+
import { ToolCard } from "@/components/chat/tool-card";
5+
import { UserMessage } from "@/components/chat/user-message";
6+
import { Badge } from "@/components/ui/badge";
57
import { Button } from "@/components/ui/button";
68
import { Input } from "@/components/ui/input";
79
import { ScrollArea } from "@/components/ui/scroll-area";
8-
import { Badge } from "@/components/ui/badge";
9-
import { Send, StopCircle, Github, MessageSquare } from "lucide-react";
10-
import { UserMessage } from "@/components/chat/user-message";
11-
import { AiMessage } from "@/components/chat/ai-message";
12-
import { ToolCard } from "@/components/chat/tool-card";
13-
import {
14-
useRealtimeStream,
15-
useTaskTrigger,
16-
useRealtimeRun,
17-
} from "@trigger.dev/react-hooks";
10+
import { supabase } from "@/lib/supabase";
1811
import { agentStream } from "@/trigger/agent-stream";
1912
import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
20-
import type { repoChatSession } from "@/trigger/repo-chat-session";
13+
import { RealtimeChannel } from "@supabase/supabase-js";
14+
import { useRealtimeStream } from "@trigger.dev/react-hooks";
15+
import { Github, MessageSquare, Send, StopCircle } from "lucide-react";
16+
import { useSearchParams } from "next/navigation";
17+
import { useEffect, useRef, useState } from "react";
2118

2219
type Message = {
2320
id: string;
@@ -69,6 +66,7 @@ export default function ChatPage({ params }: { params: { runId: string } }) {
6966
const [isRunning, setIsRunning] = useState(false);
7067
const [error, setError] = useState<string>("");
7168
const scrollRef = useRef<HTMLDivElement>(null);
69+
const channelRef = useRef<RealtimeChannel | null>(null);
7270

7371
// Subscribe to realtime stream from Trigger.dev (main data pipeline)
7472
const { parts, error: streamError } = useRealtimeStream(
@@ -93,20 +91,51 @@ export default function ChatPage({ params }: { params: { runId: string } }) {
9391
// Combine with user messages
9492
const allMessages = [...messages, ...streamMessages];
9593

96-
// Log initial session details
94+
// Initialize Supabase broadcast channel
9795
useEffect(() => {
98-
console.log("🚀 Chat page loaded with:", {
99-
chatRunId,
100-
sessionId,
101-
repoName: repoNameFromUrl,
102-
hasAccessToken: !!accessToken,
96+
if (!sessionId) {
97+
console.log("[Chat] No sessionId, skipping channel setup");
98+
return;
99+
}
100+
101+
console.log("[Chat] Setting up Supabase channel for sessionId:", sessionId);
102+
103+
// Create channel for broadcasting questions
104+
// NOTE: Clean up console.logs after debugging
105+
const channel = supabase.channel(`session:${sessionId}`, {
106+
config: {
107+
broadcast: {
108+
self: false,
109+
ack: false, // Match backend configuration
110+
},
111+
},
112+
});
113+
114+
// Subscribe to channel (no need to listen, just for sending)
115+
channel.subscribe((status) => {
116+
console.log(`[Chat] Channel subscription status: ${status}`, {
117+
sessionId,
118+
channelName: `session:${sessionId}`,
119+
});
120+
121+
if (status === "SUBSCRIBED") {
122+
console.log("[Chat] ✅ Successfully subscribed to channel");
123+
channelRef.current = channel;
124+
} else if (status === "CHANNEL_ERROR") {
125+
console.error("[Chat] ❌ Channel subscription error");
126+
setError("Failed to connect to chat channel");
127+
}
103128
});
104-
}, []);
105129

106-
// Note: Frontend doesn't need to subscribe to Supabase directly
107-
// All communication happens through:
108-
// - Questions: Frontend → /api/chat → Supabase → Backend task
109-
// - Responses: Backend task → Trigger.dev Stream → useRealtimeStream hook
130+
// Cleanup on unmount
131+
return () => {
132+
console.log("[Chat] Cleaning up channel subscription");
133+
if (channelRef.current) {
134+
supabase.removeChannel(channelRef.current);
135+
channelRef.current = null;
136+
}
137+
};
138+
}, [sessionId]);
110139

111140
useEffect(() => {
112141
if (scrollRef.current) {
@@ -115,7 +144,15 @@ export default function ChatPage({ params }: { params: { runId: string } }) {
115144
}, [messages]);
116145

117146
const handleSend = async () => {
118-
if (!input.trim() || isRunning || !sessionId) return;
147+
if (!input.trim() || isRunning || !sessionId || !channelRef.current) {
148+
console.log("[Chat] Cannot send:", {
149+
hasInput: !!input.trim(),
150+
isRunning,
151+
hasSessionId: !!sessionId,
152+
hasChannel: !!channelRef.current,
153+
});
154+
return;
155+
}
119156

120157
const userMessage: Message = {
121158
id: Date.now().toString(),
@@ -130,27 +167,29 @@ export default function ChatPage({ params }: { params: { runId: string } }) {
130167
setIsRunning(true);
131168

132169
try {
133-
console.log("📤 Sending question to API:", { sessionId, question });
170+
console.log("[Chat] 📤 Broadcasting question:", { sessionId, question });
134171

135-
// Send question via API (which uses Supabase)
136-
const response = await fetch("/api/chat", {
137-
method: "POST",
138-
headers: { "Content-Type": "application/json" },
139-
body: JSON.stringify({ sessionId, question }),
172+
// Send question directly via Supabase broadcast
173+
// NOTE: Clean up console.logs after debugging
174+
const result = await channelRef.current.send({
175+
type: "broadcast",
176+
event: "question",
177+
payload: {
178+
question,
179+
timestamp: new Date().toISOString(),
180+
messageId: userMessage.id,
181+
},
140182
});
141183

142-
if (!response.ok) {
143-
const errorData = await response.json();
144-
console.error("❌ Failed to send question:", errorData);
145-
throw new Error(errorData.error || "Failed to send message");
184+
if (result === "ok") {
185+
console.log("[Chat] ✅ Question broadcast successfully");
186+
// The response will come through the Trigger.dev stream
187+
} else {
188+
console.error("[Chat] ❌ Failed to broadcast question:", result);
189+
throw new Error("Failed to send message");
146190
}
147-
148-
const { messageId } = await response.json();
149-
console.log("✅ Question sent with messageId:", messageId);
150-
151-
// The response will come through the Trigger.dev stream
152-
// No need to update state here
153191
} catch (err: any) {
192+
console.error("[Chat] Error sending message:", err);
154193
setError(err.message || "Failed to send message");
155194
setIsRunning(false);
156195
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createClient } from "@supabase/supabase-js";
2+
3+
// Simple browser-safe Supabase client for broadcasts
4+
// NOTE: Clean up console.logs after debugging
5+
export const supabase = createClient(
6+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
7+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
8+
{
9+
realtime: {
10+
params: {
11+
eventsPerSecond: 10,
12+
},
13+
},
14+
}
15+
);
16+
17+
console.log("[supabase.ts] Client initialized with URL:", process.env.NEXT_PUBLIC_SUPABASE_URL);

0 commit comments

Comments
 (0)