@@ -21,35 +21,8 @@ import { motion } from 'framer-motion';
2121import { FaArrowLeft , FaBitbucket , FaBook , FaBookOpen , FaComments , FaDownload , FaExclamationTriangle , FaFileExport , FaFolder , FaGithub , FaGitlab , FaHome , FaProjectDiagram , FaSearch , FaSync , FaTimes } from 'react-icons/fa' ;
2222import DependencyGraph from '@/components/DependencyGraph' ;
2323import DiagramDetailPanel from '@/components/DiagramDetailPanel' ;
24- // Define the WikiSection and WikiStructure types directly in this file
25- // since the imported types don't have the sections and rootSections properties
26- interface WikiSection {
27- id : string ;
28- title : string ;
29- pages : string [ ] ;
30- subsections ?: string [ ] ;
31- }
32-
33- interface WikiPage {
34- id : string ;
35- title : string ;
36- content : string ;
37- filePaths : string [ ] ;
38- importance : 'high' | 'medium' | 'low' ;
39- relatedPages : string [ ] ;
40- parentId ?: string ;
41- isSection ?: boolean ;
42- children ?: string [ ] ;
43- }
44-
45- interface WikiStructure {
46- id : string ;
47- title : string ;
48- description : string ;
49- pages : WikiPage [ ] ;
50- sections : WikiSection [ ] ;
51- rootSections : string [ ] ;
52- }
24+ import { WikiSection , WikiPage , WikiStructure } from '@/types/wiki' ;
25+ import { addTokensToRequestBody } from '@/utils/addTokens' ;
5326
5427// Add CSS styles for wiki with Japanese aesthetic
5528const wikiStyles = `
@@ -205,50 +178,6 @@ const getCacheKey = (owner: string, repo: string, repoType: string, language: st
205178
206179
207180
208- // Helper function to add tokens and other parameters to request body
209- const addTokensToRequestBody = (
210- // eslint-disable-next-line @typescript-eslint/no-explicit-any
211- requestBody : Record < string , any > ,
212- token : string ,
213- repoType : string ,
214- provider : string = '' ,
215- model : string = '' ,
216- isCustomModel : boolean = false ,
217- customModel : string = '' ,
218- language : string = 'en' ,
219- excludedDirs ?: string ,
220- excludedFiles ?: string ,
221- includedDirs ?: string ,
222- includedFiles ?: string
223- ) : void => {
224- if ( token !== '' ) {
225- requestBody . token = token ;
226- }
227-
228- // Add provider-based model selection parameters
229- requestBody . provider = provider ;
230- requestBody . model = model ;
231- if ( isCustomModel && customModel ) {
232- requestBody . custom_model = customModel ;
233- }
234-
235- requestBody . language = language ;
236-
237- // Add file filter parameters if provided
238- if ( excludedDirs ) {
239- requestBody . excluded_dirs = excludedDirs ;
240- }
241- if ( excludedFiles ) {
242- requestBody . excluded_files = excludedFiles ;
243- }
244- if ( includedDirs ) {
245- requestBody . included_dirs = includedDirs ;
246- }
247- if ( includedFiles ) {
248- requestBody . included_files = includedFiles ;
249- }
250-
251- } ;
252181
253182const createGithubHeaders = ( githubToken : string ) : HeadersInit => {
254183 const headers : HeadersInit = {
@@ -296,8 +225,14 @@ export default function RepoWikiPage() {
296225 const owner = params . owner as string ;
297226 const repo = params . repo as string ;
298227
299- // Extract tokens from search params
300- const token = searchParams . get ( 'token' ) || '' ;
228+ // Read token from sessionStorage (secure) with URL param fallback (legacy)
229+ const token = ( ( ) => {
230+ if ( typeof window !== 'undefined' ) {
231+ const stored = sessionStorage . getItem ( 'bcw_access_token' ) ;
232+ if ( stored ) return stored ;
233+ }
234+ return searchParams . get ( 'token' ) || '' ;
235+ } ) ( ) ;
301236 const localPath = searchParams . get ( 'local_path' ) ? decodeURIComponent ( searchParams . get ( 'local_path' ) || '' ) : undefined ;
302237 const repoUrl = searchParams . get ( 'repo_url' ) ? decodeURIComponent ( searchParams . get ( 'repo_url' ) || '' ) : undefined ;
303238 const providerParam = searchParams . get ( 'provider' ) || '' ;
@@ -345,13 +280,15 @@ export default function RepoWikiPage() {
345280 const [ currentPageId , setCurrentPageId ] = useState < string | undefined > ( ) ;
346281 const [ generatedPages , setGeneratedPages ] = useState < Record < string , WikiPage > > ( { } ) ;
347282 const [ pagesInProgress , setPagesInProgress ] = useState ( new Set < string > ( ) ) ;
283+ const [ generationPhase , setGenerationPhase ] = useState < 'idle' | 'fetching' | 'planning' | 'generating' | 'done' > ( 'idle' ) ;
348284 const [ isExporting , setIsExporting ] = useState ( false ) ;
349285 const [ exportError , setExportError ] = useState < string | null > ( null ) ;
350286 const [ originalMarkdown , setOriginalMarkdown ] = useState < Record < string , string > > ( { } ) ;
351287 const [ requestInProgress , setRequestInProgress ] = useState ( false ) ;
352288 const [ currentToken , setCurrentToken ] = useState ( token ) ; // Track current effective token
353289 const [ effectiveRepoInfo , setEffectiveRepoInfo ] = useState ( repoInfo ) ; // Track effective repo info with cached data
354290 const [ embeddingError , setEmbeddingError ] = useState ( false ) ;
291+ const [ generatedAt , setGeneratedAt ] = useState < string | null > ( null ) ;
355292
356293 // Model selection state variables
357294 const [ selectedProviderState , setSelectedProviderState ] = useState ( providerParam ) ;
@@ -873,6 +810,7 @@ Remember:
873810
874811 try {
875812 setStructureRequestInProgress ( true ) ;
813+ setGenerationPhase ( 'planning' ) ;
876814 setLoadingMessage ( messages . loading ?. determiningStructure || 'Determining wiki structure...' ) ;
877815
878816 // Get repository URL
@@ -1267,10 +1205,11 @@ IMPORTANT:
12671205 const initialInProgress = new Set ( pages . map ( p => p . id ) ) ;
12681206 setPagesInProgress ( initialInProgress ) ;
12691207
1208+ setGenerationPhase ( 'generating' ) ;
12701209 console . log ( `Starting generation for ${ pages . length } pages with controlled concurrency` ) ;
12711210
12721211 // Maximum concurrent requests
1273- const MAX_CONCURRENT = 1 ;
1212+ const MAX_CONCURRENT = 3 ;
12741213
12751214 // Create a queue of pages
12761215 const queue = [ ...pages ] ;
@@ -1295,6 +1234,7 @@ IMPORTANT:
12951234 // Check if all work is done (queue empty and no active requests)
12961235 if ( queue . length === 0 && activeRequests === 0 ) {
12971236 console . log ( "All page generation tasks completed." ) ;
1237+ setGenerationPhase ( 'done' ) ;
12981238 setIsLoading ( false ) ;
12991239 setLoadingMessage ( undefined ) ;
13001240 } else {
@@ -1361,6 +1301,7 @@ IMPORTANT:
13611301
13621302 // Update loading state
13631303 setIsLoading ( true ) ;
1304+ setGenerationPhase ( 'fetching' ) ;
13641305 setLoadingMessage ( messages . loading ?. fetchingStructure || 'Fetching repository structure...' ) ;
13651306
13661307 let fileTreeData = '' ;
@@ -2019,6 +1960,9 @@ IMPORTANT:
20191960 setWikiStructure ( cachedStructure ) ;
20201961 setGeneratedPages ( cachedData . generated_pages ) ;
20211962 setCurrentPageId ( cachedStructure . pages . length > 0 ? cachedStructure . pages [ 0 ] . id : undefined ) ;
1963+ if ( cachedData . generated_at ) {
1964+ setGeneratedAt ( cachedData . generated_at ) ;
1965+ }
20221966 setIsLoading ( false ) ;
20231967 setEmbeddingError ( false ) ;
20241968 setLoadingMessage ( undefined ) ;
@@ -2081,7 +2025,8 @@ IMPORTANT:
20812025 wiki_structure : structureToCache ,
20822026 generated_pages : generatedPages ,
20832027 provider : selectedProviderState ,
2084- model : selectedModelState
2028+ model : selectedModelState ,
2029+ generated_at : new Date ( ) . toISOString ( ) ,
20852030 } ;
20862031 const response = await fetch ( `/api/wiki_cache` , {
20872032 method : 'POST' ,
@@ -2283,17 +2228,40 @@ IMPORTANT:
22832228
22842229 { /* Floating progress card */ }
22852230 < div className = "fixed bottom-8 left-1/2 -translate-x-1/2 z-30 bg-card border border-border rounded-xl elevation-3 p-4 max-w-md w-full" >
2231+ { /* Phase steps indicator */ }
2232+ < div className = "flex items-center justify-between mb-4 px-2" >
2233+ { ( [
2234+ { key : 'fetching' , label : 'Fetch' } ,
2235+ { key : 'planning' , label : 'Plan' } ,
2236+ { key : 'generating' , label : 'Generate' } ,
2237+ ] as const ) . map ( ( step , i ) => {
2238+ const phases = [ 'idle' , 'fetching' , 'planning' , 'generating' , 'done' ] as const ;
2239+ const currentIdx = phases . indexOf ( generationPhase ) ;
2240+ const stepIdx = phases . indexOf ( step . key ) ;
2241+ const isActive = generationPhase === step . key ;
2242+ const isDone = currentIdx > stepIdx ;
2243+ return (
2244+ < React . Fragment key = { step . key } >
2245+ { i > 0 && < div className = { `flex-1 h-px mx-2 ${ isDone ? 'bg-primary' : 'bg-border' } ` } /> }
2246+ < div className = "flex flex-col items-center gap-1" >
2247+ < div className = { `w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold transition-colors ${ isDone ? 'bg-primary text-primary-foreground' : isActive ? 'bg-primary/20 text-primary ring-2 ring-primary' : 'bg-muted text-muted-foreground' } ` } >
2248+ { isDone ? '✓' : i + 1 }
2249+ </ div >
2250+ < span className = { `text-[10px] font-medium ${ isActive ? 'text-foreground' : 'text-muted-foreground' } ` } > { step . label } </ span >
2251+ </ div >
2252+ </ React . Fragment >
2253+ ) ;
2254+ } ) }
2255+ </ div >
2256+
22862257 < p className = "text-sm font-medium text-foreground text-center mb-1" >
22872258 { loadingMessage || messages . common ?. loading || 'Loading...' }
22882259 </ p >
2289- < p className = "text-xs text-muted-foreground text-center mb-3" >
2290- { isExporting ? ( messages . loading ?. preparingDownload || 'Preparing download...' ) : 'Analyzing repository structure and content...' }
2291- </ p >
22922260
22932261 { /* Progress bar for page generation */ }
22942262 { wikiStructure && (
22952263 < >
2296- < div className = "flex justify-between text-xs text-muted-foreground mb-2" >
2264+ < div className = "flex justify-between text-xs text-muted-foreground mb-2 mt-3 " >
22972265 < span > Progress</ span >
22982266 < span > { Math . round ( 100 * ( wikiStructure . pages . length - pagesInProgress . size ) / wikiStructure . pages . length ) } %</ span >
22992267 </ div >
@@ -2365,10 +2333,24 @@ IMPORTANT:
23652333 < h3 className = "font-semibold text-foreground truncate" title = { wikiStructure . title } > { wikiStructure . title } </ h3 >
23662334 < p className = "text-xs text-muted-foreground mt-1 line-clamp-2" > { wikiStructure . description } </ p >
23672335
2368- < div className = "flex items-center gap-2 mt-3" >
2336+ < div className = "flex items-center gap-2 mt-3 flex-wrap " >
23692337 < div className = "inline-flex items-center rounded-full border border-transparent px-2 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-border bg-secondary text-secondary-foreground hover:bg-secondary/80" >
23702338 { isComprehensiveView ? 'Comprehensive' : 'Concise' }
23712339 </ div >
2340+ { generatedAt && (
2341+ < div className = "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] text-muted-foreground border border-border" title = { `Generated ${ new Date ( generatedAt ) . toLocaleString ( ) } ` } >
2342+ { ( ( ) => {
2343+ const diff = Date . now ( ) - new Date ( generatedAt ) . getTime ( ) ;
2344+ const mins = Math . floor ( diff / 60000 ) ;
2345+ if ( mins < 1 ) return 'Just now' ;
2346+ if ( mins < 60 ) return `${ mins } m ago` ;
2347+ const hrs = Math . floor ( mins / 60 ) ;
2348+ if ( hrs < 24 ) return `${ hrs } h ago` ;
2349+ const days = Math . floor ( hrs / 24 ) ;
2350+ return `${ days } d ago` ;
2351+ } ) ( ) }
2352+ </ div >
2353+ ) }
23722354 < button
23732355 onClick = { ( ) => setIsModelSelectionModalOpen ( true ) }
23742356 disabled = { isLoading }
@@ -2441,12 +2423,34 @@ IMPORTANT:
24412423 ) }
24422424 </ div >
24432425
2444- < div className = "prose prose-zinc dark:prose-invert max-w-none" >
2445- < Markdown
2446- content = { generatedPages [ currentPageId ] . content }
2447- onDiagramNodeClick = { handleDiagramNodeClick }
2448- />
2449- </ div >
2426+ { generatedPages [ currentPageId ] . content . startsWith ( 'Error generating content:' ) ? (
2427+ < div className = "flex flex-col items-center justify-center py-12 text-center" >
2428+ < div className = "inline-flex items-center justify-center p-3 bg-destructive/10 rounded-full mb-4" >
2429+ < FaExclamationTriangle className = "text-xl text-destructive" />
2430+ </ div >
2431+ < p className = "text-sm text-muted-foreground mb-4" > { generatedPages [ currentPageId ] . content } </ p >
2432+ < button
2433+ onClick = { ( ) => {
2434+ const page = wikiStructure ?. pages . find ( p => p . id === currentPageId ) ;
2435+ if ( page ) {
2436+ setPagesInProgress ( prev => new Set ( prev ) . add ( currentPageId ) ) ;
2437+ generatePageContent ( page , owner , repo ) ;
2438+ }
2439+ } }
2440+ className = "inline-flex items-center gap-2 rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium hover:bg-primary/90 transition-colors"
2441+ >
2442+ < FaSync className = "h-3 w-3" />
2443+ Retry
2444+ </ button >
2445+ </ div >
2446+ ) : (
2447+ < div className = "prose prose-zinc dark:prose-invert max-w-none" >
2448+ < Markdown
2449+ content = { generatedPages [ currentPageId ] . content }
2450+ onDiagramNodeClick = { handleDiagramNodeClick }
2451+ />
2452+ </ div >
2453+ ) }
24502454
24512455 { generatedPages [ currentPageId ] . relatedPages . length > 0 && (
24522456 < div className = "mt-12 pt-6 border-t border-border" >
0 commit comments