Skip to content

Commit e7fc86e

Browse files
shantanu patilclaude
authored andcommitted
feat: Phase 1 quick wins — shared types, security, UX, and naming
- Extract shared WikiSection/WikiPage/WikiStructure types to src/types/wiki.ts - Extract shared addTokensToRequestBody utility to src/utils/addTokens.ts - Store access tokens in sessionStorage instead of URL query params - Increase wiki generation concurrency from 1 to 3 - Add 3-step generation progress indicator (Fetch → Plan → Generate) - Add per-page retry button for failed page generations - Add generated_at timestamp metadata to cached wikis - Add skip-to-content link and aria labels for accessibility - Debounce repo input config lookup on landing page - Rename deepwiki references to bettercodewiki (docker, API, cache keys) - Add planning docs for AI agent diagram leverage and next improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fd48645 commit e7fc86e

17 files changed

Lines changed: 2526 additions & 272 deletions

.planning/ai-agent-diagram-leverage.md

Lines changed: 940 additions & 0 deletions
Large diffs are not rendered by default.

.planning/diagrams-as-thinking-tool.md

Lines changed: 856 additions & 0 deletions
Large diffs are not rendered by default.

.planning/next-improvements-analysis.md

Lines changed: 523 additions & 0 deletions
Large diffs are not rendered by default.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ fi\n\
8989
# Check for required environment variables\n\
9090
if [ -z "$OPENAI_API_KEY" ] || [ -z "$GOOGLE_API_KEY" ]; then\n\
9191
echo "Warning: OPENAI_API_KEY and/or GOOGLE_API_KEY environment variables are not set."\n\
92-
echo "These are required for DeepWiki to function properly."\n\
92+
echo "These are required for BetterCodeWiki to function properly."\n\
9393
echo "You can provide them via a mounted .env file or as environment variables when running the container."\n\
9494
fi\n\
9595
\n\

api/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ async def health_check():
559559
return {
560560
"status": "healthy",
561561
"timestamp": datetime.now().isoformat(),
562-
"service": "deepwiki-api"
562+
"service": "bettercodewiki-api"
563563
}
564564

565565
@app.get("/")

api/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
2-
name = "open-deepwiki-api"
2+
name = "bettercodewiki-api"
33
version = "1.0.0"
4-
description = "Backend API for DeepWiki, providing smart code analysis and AI-powered documentation generation."
4+
description = "Backend API for BetterCodeWiki, providing smart code analysis and AI-powered documentation generation."
55
license = {text = "MIT License"}
66

77
[tool.poetry]

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
services:
2-
deepwiki:
2+
bettercodewiki:
33
build:
44
context: .
55
dockerfile: Dockerfile

src/app/[owner]/[repo]/page.tsx

Lines changed: 92 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,8 @@ import { motion } from 'framer-motion';
2121
import { FaArrowLeft, FaBitbucket, FaBook, FaBookOpen, FaComments, FaDownload, FaExclamationTriangle, FaFileExport, FaFolder, FaGithub, FaGitlab, FaHome, FaProjectDiagram, FaSearch, FaSync, FaTimes } from 'react-icons/fa';
2222
import DependencyGraph from '@/components/DependencyGraph';
2323
import 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
5528
const 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

253182
const 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">

src/app/[owner]/[repo]/slides/page.tsx

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,9 @@ import { FaArrowLeft, FaSync, FaDownload, FaArrowRight, FaArrowUp, FaTimes } fro
77
import ThemeToggle from '@/components/theme-toggle';
88
import { useLanguage } from '@/contexts/LanguageContext';
99
import { RepoInfo } from '@/types/repoinfo';
10+
import { WikiPage, WikiStructure } from '@/types/wiki';
1011
import getRepoUrl from '@/utils/getRepoUrl';
11-
12-
// Helper function to add tokens and other parameters to request body
13-
const addTokensToRequestBody = (
14-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15-
requestBody: Record<string, any>,
16-
token: string,
17-
repoType: string,
18-
provider: string = '',
19-
model: string = '',
20-
isCustomModel: boolean = false,
21-
customModel: string = '',
22-
language: string = 'en',
23-
) => {
24-
if (token !== '') {
25-
requestBody.token = token;
26-
}
27-
28-
// Add provider-based model selection parameters
29-
requestBody.provider = provider;
30-
requestBody.model = model;
31-
if (isCustomModel && customModel) {
32-
requestBody.custom_model = customModel;
33-
}
34-
35-
requestBody.language = language;
36-
};
12+
import { addTokensToRequestBody } from '@/utils/addTokens';
3713

3814
interface Slide {
3915
id: string;
@@ -87,30 +63,6 @@ export default function SlidesPage() {
8763
const [exportError, setExportError] = useState<string | null>(null);
8864
const [isFullscreen, setIsFullscreen] = useState(false);
8965

90-
// Define a type for the wiki content
91-
interface WikiPage {
92-
id: string;
93-
title: string;
94-
content: string;
95-
importance: string;
96-
filePaths: string[];
97-
relatedPages: string[];
98-
}
99-
100-
interface WikiSection {
101-
id: string;
102-
title: string;
103-
pages: string[];
104-
subsections: string[];
105-
}
106-
107-
interface WikiStructure {
108-
description: string;
109-
pages: WikiPage[];
110-
sections: WikiSection[];
111-
rootSections: string[];
112-
}
113-
11466
interface WikiCacheData {
11567
wiki_structure: WikiStructure;
11668
generated_pages: Record<string, WikiPage>;

0 commit comments

Comments
 (0)