@@ -10,6 +10,9 @@ import { Send, StopCircle, Github, MessageSquare } from "lucide-react";
1010import { UserMessage } from "@/components/chat/user-message" ;
1111import { AiMessage } from "@/components/chat/ai-message" ;
1212import { ToolCard } from "@/components/chat/tool-card" ;
13+ import { useRealtimeStream } from "@trigger.dev/react-hooks" ;
14+ import { agentStream } from "@/trigger/chat-with-repo" ;
15+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk" ;
1316
1417type Message = {
1518 id : string ;
@@ -21,6 +24,31 @@ type Message = {
2124 timestamp : Date ;
2225} ;
2326
27+ function transformSDKMessage ( sdkMsg : SDKMessage , index : number ) : Message | null {
28+ if ( sdkMsg . type === 'assistant' ) {
29+ for ( const block of sdkMsg . message . content ) {
30+ if ( block . type === 'text' ) {
31+ return {
32+ id : `ai-${ index } ` ,
33+ type : 'ai' ,
34+ content : block . text ,
35+ timestamp : new Date ( ) ,
36+ } ;
37+ } else if ( block . type === 'tool_use' ) {
38+ return {
39+ id : `tool-${ index } ` ,
40+ type : 'tool' ,
41+ content : '' ,
42+ toolName : block . name ,
43+ toolInput : block . input ,
44+ timestamp : new Date ( ) ,
45+ } ;
46+ }
47+ }
48+ }
49+ return null ;
50+ }
51+
2452const mockMessages : Message [ ] = [
2553 {
2654 id : "1" ,
@@ -91,46 +119,80 @@ The codebase shows extensive server-side rendering capabilities and a robust bui
91119 } ,
92120] ;
93121
94- export default function ChatPage ( ) {
95- const searchParams = useSearchParams ( ) ;
96- const repo = searchParams . get ( "repo" ) || "" ;
97- const [ messages , setMessages ] = useState < Message [ ] > ( mockMessages ) ;
122+ export default function ChatPage ( { params } : { params : { runId : string } } ) {
123+ const cloneRunId = params . runId ;
124+ const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
98125 const [ input , setInput ] = useState ( "" ) ;
99126 const [ isRunning , setIsRunning ] = useState ( false ) ;
127+ const [ repoName , setRepoName ] = useState < string > ( "" ) ;
128+ const [ error , setError ] = useState < string > ( "" ) ;
129+ const [ chatRunId , setChatRunId ] = useState < string | null > ( null ) ;
130+ const [ accessToken , setAccessToken ] = useState < string | null > ( null ) ;
100131 const scrollRef = useRef < HTMLDivElement > ( null ) ;
101132
102- const repoName = repo . split ( "/" ) . slice ( - 2 ) . join ( "/" ) . replace ( ".git" , "" ) ;
133+ // Subscribe to realtime stream
134+ const { parts, error : streamError } = useRealtimeStream (
135+ agentStream ,
136+ chatRunId ?? '' ,
137+ {
138+ accessToken : accessToken ?? undefined ,
139+ enabled : ! ! chatRunId && ! ! accessToken ,
140+ timeoutInSeconds : 600 ,
141+ throttleInMs : 50 ,
142+ }
143+ ) ;
144+
145+ // Transform stream parts directly - NO useEffect
146+ const streamMessages = parts
147+ . map ( ( msg , idx ) => transformSDKMessage ( msg , idx ) )
148+ . filter ( ( msg ) : msg is Message => msg !== null ) ;
149+
150+ // Combine with user messages
151+ const allMessages = [ ...messages , ...streamMessages ] ;
103152
104153 useEffect ( ( ) => {
105154 if ( scrollRef . current ) {
106155 scrollRef . current . scrollTop = scrollRef . current . scrollHeight ;
107156 }
108157 } , [ messages ] ) ;
109158
110- const handleSend = ( ) => {
159+ const handleSend = async ( ) => {
111160 if ( ! input . trim ( ) || isRunning ) return ;
112161
113- const newMessage : Message = {
162+ const userMessage : Message = {
114163 id : Date . now ( ) . toString ( ) ,
115164 type : "user" ,
116165 content : input ,
117166 timestamp : new Date ( ) ,
118167 } ;
119168
120- setMessages ( ( prev ) => [ ...prev , newMessage ] ) ;
169+ setMessages ( ( prev ) => [ ...prev , userMessage ] ) ;
170+ const query = input ;
121171 setInput ( "" ) ;
122172 setIsRunning ( true ) ;
123173
124- setTimeout ( ( ) => {
125- const aiResponse : Message = {
126- id : ( Date . now ( ) + 1 ) . toString ( ) ,
127- type : "ai" ,
128- content : "This is a demo. In a real implementation, the AI would analyze the repository and provide insights based on the codebase." ,
129- timestamp : new Date ( ) ,
130- } ;
131- setMessages ( ( prev ) => [ ...prev , aiResponse ] ) ;
174+ try {
175+ const response = await fetch ( "/api/chat" , {
176+ method : "POST" ,
177+ headers : { "Content-Type" : "application/json" } ,
178+ body : JSON . stringify ( { cloneRunId, query } ) ,
179+ } ) ;
180+
181+ if ( ! response . ok ) {
182+ const errorData = await response . json ( ) ;
183+ throw new Error ( errorData . error || "Failed to send message" ) ;
184+ }
185+
186+ const { chatRunId, accessToken, repoName : repo } = await response . json ( ) ;
187+ if ( repo && ! repoName ) setRepoName ( repo ) ;
188+
189+ // Store for streaming
190+ setChatRunId ( chatRunId ) ;
191+ setAccessToken ( accessToken ) ;
192+ } catch ( err : any ) {
193+ setError ( err . message || "Failed to send message" ) ;
132194 setIsRunning ( false ) ;
133- } , 2000 ) ;
195+ }
134196 } ;
135197
136198 const handleAbort = ( ) => {
@@ -143,30 +205,23 @@ export default function ChatPage() {
143205 < Github className = "w-5 h-5" />
144206 < div className = "flex items-center gap-2 flex-1" >
145207 < span className = "font-semibold" > { repoName || "Repository" } </ span >
146- { repo && (
147- < Badge variant = "secondary" className = "font-normal" >
148- < a
149- href = { repo }
150- target = "_blank"
151- rel = "noopener noreferrer"
152- className = "hover:underline"
153- >
154- { repo }
155- </ a >
208+ { cloneRunId && (
209+ < Badge variant = "secondary" className = "font-normal text-xs" >
210+ Clone ID: { cloneRunId . substring ( 0 , 8 ) } ...
156211 </ Badge >
157212 ) }
158213 </ div >
159214 </ header >
160215
161216 < ScrollArea className = "flex-1 px-6" ref = { scrollRef } >
162217 < div className = "max-w-4xl mx-auto py-6 space-y-6" >
163- { messages . length === 0 ? (
218+ { allMessages . length === 0 ? (
164219 < div className = "text-center text-muted-foreground py-12" >
165220 < MessageSquare className = "w-12 h-12 mx-auto mb-4 opacity-50" />
166221 < p className = "text-lg" > Ask a question about this repository</ p >
167222 </ div >
168223 ) : (
169- messages . map ( ( message ) => {
224+ allMessages . map ( ( message ) => {
170225 if ( message . type === "user" ) {
171226 return < UserMessage key = { message . id } content = { message . content } /> ;
172227 } else if ( message . type === "ai" ) {
0 commit comments