@@ -6,13 +6,86 @@ import { Header } from './components/Header';
66import { consensusApi } from './services/api' ;
77import type { JobStatusModel , LogEntryModel } from './types/api' ;
88
9+ // Helper function to validate UUID format
10+ const isValidUuid = ( str : string ) : boolean => {
11+ const uuidRegex = / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i;
12+ return uuidRegex . test ( str ) ;
13+ } ;
14+
15+ // Helper function to get runId from URL path (/answer/{runId})
16+ const getRunIdFromUrl = ( ) : string | null => {
17+ const pathMatch = window . location . pathname . match ( / \/ a n s w e r \/ ( [ 0 - 9 a - f - ] + ) $ / i) ;
18+ return pathMatch && isValidUuid ( pathMatch [ 1 ] ) ? pathMatch [ 1 ] : null ;
19+ } ;
20+
921function App ( ) {
1022 const [ jobStatus , setJobStatus ] = useState < JobStatusModel | null > ( null ) ;
1123 const [ logs , setLogs ] = useState < LogEntryModel [ ] > ( [ ] ) ;
1224 const [ html , setHtml ] = useState < string > ( '' ) ;
1325 const [ error , setError ] = useState < string > ( '' ) ;
1426 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
1527 const [ currentPrompt , setCurrentPrompt ] = useState < string > ( '' ) ;
28+ const [ isLoadingFromUrl , setIsLoadingFromUrl ] = useState ( false ) ;
29+
30+ // Load response from URL path on mount
31+ useEffect ( ( ) => {
32+ const runIdFromUrl = getRunIdFromUrl ( ) ;
33+ if ( ! runIdFromUrl ) {
34+ return ;
35+ }
36+
37+ const loadFromUrl = async ( ) => {
38+ setIsLoadingFromUrl ( true ) ;
39+ setError ( '' ) ;
40+
41+ try {
42+ // Fetch job status
43+ const status = await consensusApi . getJobStatus ( runIdFromUrl ) ;
44+ setJobStatus ( status ) ;
45+
46+ // Fetch logs
47+ const jobLogs = await consensusApi . getLogs ( runIdFromUrl ) ;
48+ setLogs ( jobLogs ) ;
49+
50+ // If job is finished, fetch HTML
51+ if ( status . status === 2 ) {
52+ const htmlResult = await consensusApi . getHtml ( runIdFromUrl ) ;
53+ setHtml ( htmlResult ) ;
54+ }
55+ } catch ( err ) {
56+ const errorMessage = err instanceof Error ? err . message : 'Failed to load response' ;
57+ setError ( errorMessage ) ;
58+ window . history . replaceState ( { } , '' , '/' ) ; // Update URL to home without adding history entry
59+ } finally {
60+ setIsLoadingFromUrl ( false ) ;
61+ }
62+ } ;
63+
64+ loadFromUrl ( ) ;
65+ } , [ ] ) ; // Run only on mount
66+
67+ // Handle browser back/forward navigation
68+ useEffect ( ( ) => {
69+ const handlePopState = ( ) => {
70+ const runIdFromUrl = getRunIdFromUrl ( ) ;
71+
72+ if ( ! runIdFromUrl ) {
73+ // User navigated back to home, reset the app
74+ setJobStatus ( null ) ;
75+ setLogs ( [ ] ) ;
76+ setHtml ( '' ) ;
77+ setError ( '' ) ;
78+ setIsSubmitting ( false ) ;
79+ setCurrentPrompt ( '' ) ;
80+ } else if ( ! jobStatus || jobStatus . runId !== runIdFromUrl ) {
81+ // User navigated to a different runId, reload
82+ window . location . reload ( ) ;
83+ }
84+ } ;
85+
86+ window . addEventListener ( 'popstate' , handlePopState ) ;
87+ return ( ) => window . removeEventListener ( 'popstate' , handlePopState ) ;
88+ } , [ jobStatus ] ) ;
1689
1790 // Poll for job status
1891 useEffect ( ( ) => {
@@ -25,10 +98,11 @@ function App() {
2598 const status = await consensusApi . getJobStatus ( jobStatus . runId ) ;
2699 setJobStatus ( status ) ;
27100
28- // If job is finished, fetch the HTML result
101+ // If job is finished, fetch the HTML result and update URL
29102 if ( status . status === 2 ) { // Finished
30103 const htmlResult = await consensusApi . getHtml ( status . runId ) ;
31104 setHtml ( htmlResult ) ;
105+ window . history . pushState ( { } , '' , `/answer/${ status . runId } ` ) ; // Update URL with runId
32106 }
33107 } catch ( err ) {
34108 console . error ( 'Error polling job status:' , err ) ;
@@ -67,6 +141,7 @@ function App() {
67141 try {
68142 const status = await consensusApi . startJob ( prompt ) ;
69143 setJobStatus ( status ) ;
144+ window . history . pushState ( { } , '' , `/answer/${ status . runId } ` ) ; // Update URL with new runId
70145
71146 // Immediately fetch logs after starting the job
72147 const initialLogs = await consensusApi . getLogs ( status . runId ) ;
@@ -85,6 +160,7 @@ function App() {
85160 setError ( '' ) ;
86161 setIsSubmitting ( false ) ;
87162 setCurrentPrompt ( '' ) ;
163+ window . history . pushState ( { } , '' , '/' ) ; // Navigate back to home
88164 } ;
89165
90166 const isJobRunning = ! ! ( jobStatus && jobStatus . status !== 2 ) ; // Not Finished
@@ -111,11 +187,11 @@ function App() {
111187
112188 { /* Error Alert */ }
113189 { error && (
114- < div className = "p-4 bg-red-50 border border-red-200 rounded-lg flex items-center justify-between" >
115- < span className = "text-red-800 " > { error } </ span >
116- < button
190+ < div className = "w-full max-w-[700px] mx-auto p-4 bg-gray-100 border border-gray-300 rounded-lg flex items-center justify-between" >
191+ < span className = "text-gray-700 " > { error } </ span >
192+ < button
117193 onClick = { ( ) => setError ( '' ) }
118- className = "text-red-600 hover:text-red-800 font-bold cursor-pointer font-sans"
194+ className = "text-gray-500 hover:text-gray-700 font-bold cursor-pointer font-sans"
119195 >
120196 ×
121197 </ button >
@@ -142,9 +218,12 @@ function App() {
142218 ) }
143219
144220 { /* Loading State */ }
145- { isSubmitting && (
146- < div className = "flex justify-center py-8" >
221+ { ( isSubmitting || isLoadingFromUrl ) && (
222+ < div className = "flex flex-col items-center justify-center py-8 gap-3 " >
147223 < div className = "animate-spin rounded-full h-12 w-12 border-b-2 border-primary" > </ div >
224+ { isLoadingFromUrl && (
225+ < p className = "text-sm text-gray-600" > Loading response...</ p >
226+ ) }
148227 </ div >
149228 ) }
150229
0 commit comments