Skip to content

Commit 3b695b5

Browse files
committed
feat: phase 4 -- wire DirectoryPicker to analyze API + DashboardHome flow (OPE-114)
AddRepoForm.tsx: - On submit, calls POST /repos/analyze for GitHub URLs - If large repo (suggestion: 'large_repo'), calls onAnalyzed callback - If small repo or non-GitHub, proceeds directly to clone+index - Button shows 'Analyzing...' state with pulse icon during API call - Graceful fallback: if analyze fails, falls through to direct add DashboardHome.tsx: - New state: analyzeResult, pendingGitUrl/Branch, showDirectoryPicker - handleAnalyzed: opens DirectoryPicker with analyze result - handleDirectoryConfirm: calls addAndIndex with include_paths - addAndIndex sends IndexConfig body when include_paths provided - DirectoryPicker wired with TIER_FUNCTION_LIMITS.free as budget limit Flow: URL -> Analyze -> Picker (large) -> Clone -> Index (subset) or: URL -> Analyze -> Clone -> Index (small, no picker) Build passes. 194 + 295 + 320 lines across 3 files.
1 parent a7c4550 commit 3b695b5

2 files changed

Lines changed: 109 additions & 11 deletions

File tree

frontend/src/components/AddRepoForm.tsx

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useState } from 'react'
22
import { motion, AnimatePresence } from 'framer-motion'
3-
import { Package, Plus, X, Loader2 } from 'lucide-react'
3+
import { Package, Plus, X, Loader2, Search } from 'lucide-react'
44
import { Button } from '@/components/ui/button'
55
import { Input } from '@/components/ui/input'
66
import { Label } from '@/components/ui/label'
7+
import { API_URL } from '@/config/api'
8+
import type { AnalyzeResult } from '@/types'
79

810
// Discriminated union: if isOpen is provided, onOpenChange is required
911
type UncontrolledProps = {
@@ -18,21 +20,50 @@ type ControlledProps = {
1820

1921
type AddRepoFormProps = {
2022
onAdd: (gitUrl: string, branch: string) => Promise<void>
23+
onAnalyzed?: (result: AnalyzeResult, gitUrl: string, branch: string) => void
2124
loading: boolean
2225
} & (UncontrolledProps | ControlledProps)
2326

24-
export function AddRepoForm({ onAdd, loading, isOpen, onOpenChange }: AddRepoFormProps) {
27+
export function AddRepoForm({ onAdd, onAnalyzed, loading, isOpen, onOpenChange }: AddRepoFormProps) {
2528
const [gitUrl, setGitUrl] = useState('')
2629
const [branch, setBranch] = useState('main')
30+
const [analyzing, setAnalyzing] = useState(false)
2731
const [internalOpen, setInternalOpen] = useState(false)
2832

2933
const isControlled = isOpen !== undefined
3034
const showForm = isControlled ? isOpen : internalOpen
3135
const setShowForm = isControlled ? onOpenChange : setInternalOpen
36+
const isBusy = loading || analyzing
3237

3338
const handleSubmit = async (e: React.FormEvent) => {
3439
e.preventDefault()
3540
if (!gitUrl) return
41+
42+
// If onAnalyzed provided and URL is GitHub, analyze first
43+
if (onAnalyzed && gitUrl.includes('github.com')) {
44+
try {
45+
setAnalyzing(true)
46+
const resp = await fetch(`${API_URL}/repos/analyze`, {
47+
method: 'POST',
48+
headers: { 'Content-Type': 'application/json' },
49+
body: JSON.stringify({ github_url: gitUrl }),
50+
})
51+
if (resp.ok) {
52+
const result: AnalyzeResult = await resp.json()
53+
if (result.suggestion === 'large_repo') {
54+
onAnalyzed(result, gitUrl, branch)
55+
setShowForm(false)
56+
return
57+
}
58+
}
59+
// Small repo or non-GitHub: proceed directly
60+
} catch {
61+
// Analyze failed -- fall through to direct add
62+
} finally {
63+
setAnalyzing(false)
64+
}
65+
}
66+
3667
await onAdd(gitUrl, branch)
3768
setGitUrl('')
3869
setBranch('main')
@@ -83,7 +114,7 @@ export function AddRepoForm({ onAdd, loading, isOpen, onOpenChange }: AddRepoFor
83114
<button
84115
onClick={() => setShowForm(false)}
85116
className="w-8 h-8 flex items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
86-
disabled={loading}
117+
disabled={isBusy}
87118
>
88119
<X className="w-4 h-4" />
89120
</button>
@@ -100,7 +131,7 @@ export function AddRepoForm({ onAdd, loading, isOpen, onOpenChange }: AddRepoFor
100131
onChange={(e) => setGitUrl(e.target.value)}
101132
placeholder="https://github.com/username/repo"
102133
required
103-
disabled={loading}
134+
disabled={isBusy}
104135
autoFocus
105136
/>
106137
</div>
@@ -114,7 +145,7 @@ export function AddRepoForm({ onAdd, loading, isOpen, onOpenChange }: AddRepoFor
114145
onChange={(e) => setBranch(e.target.value)}
115146
placeholder="main"
116147
required
117-
disabled={loading}
148+
disabled={isBusy}
118149
/>
119150
<p className="text-xs text-muted-foreground">Repository will be cloned and automatically indexed</p>
120151
</div>
@@ -125,17 +156,22 @@ export function AddRepoForm({ onAdd, loading, isOpen, onOpenChange }: AddRepoFor
125156
type="button"
126157
variant="outline"
127158
onClick={() => { setShowForm(false); setGitUrl(''); setBranch('main') }}
128-
disabled={loading}
159+
disabled={isBusy}
129160
className="flex-1"
130161
>
131162
Cancel
132163
</Button>
133164
<Button
134165
type="submit"
135-
disabled={loading}
166+
disabled={isBusy}
136167
className="flex-1 bg-primary hover:bg-primary/90 text-primary-foreground"
137168
>
138-
{loading ? (
169+
{analyzing ? (
170+
<>
171+
<Search className="w-4 h-4 animate-pulse" />
172+
Analyzing...
173+
</>
174+
) : loading ? (
139175
<>
140176
<Loader2 className="w-4 h-4 animate-spin" />
141177
Adding...

frontend/src/components/dashboard/DashboardHome.tsx

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import { extractErrorMessage, isUpgradeError } from '../../lib/api-errors'
1212
import { RepoListView } from './RepoListView'
1313
import { RepoDetailView } from './RepoDetailView'
1414
import { AddRepoForm } from '../AddRepoForm'
15+
import { DirectoryPicker } from '../DirectoryPicker'
1516
import { GitHubRepoSelector } from '../GitHubRepoSelector'
1617
import { IndexingProgressModal } from '../IndexingProgressModal'
1718
import { UpgradeLimitModal } from '../UpgradeLimitModal'
19+
import { TIER_FUNCTION_LIMITS } from '../../config/api'
1820
import type { GitHubRepo } from '../../hooks/useGitHubRepos'
19-
import type { RepoTab } from '../../types'
21+
import type { AnalyzeResult, RepoTab } from '../../types'
2022

2123
export function DashboardHome() {
2224
const { session } = useAuth()
@@ -34,6 +36,12 @@ export function DashboardHome() {
3436
const [indexingRepoName, setIndexingRepoName] = useState('')
3537
const [showIndexingModal, setShowIndexingModal] = useState(false)
3638

39+
// directory picker (monorepo subset selection)
40+
const [analyzeResult, setAnalyzeResult] = useState<AnalyzeResult | null>(null)
41+
const [pendingGitUrl, setPendingGitUrl] = useState('')
42+
const [pendingBranch, setPendingBranch] = useState('main')
43+
const [showDirectoryPicker, setShowDirectoryPicker] = useState(false)
44+
3745
// upgrade modal
3846
const [upgradeModal, setUpgradeModal] = useState<{ show: boolean; message: string; repoName?: string }>({
3947
show: false, message: '',
@@ -54,7 +62,9 @@ export function DashboardHome() {
5462
}
5563

5664
// shared helper: add a repo and start indexing
57-
const addAndIndex = async (name: string, gitUrl: string, branch: string): Promise<string | null> => {
65+
const addAndIndex = async (
66+
name: string, gitUrl: string, branch: string, includePaths?: string[],
67+
): Promise<string | null> => {
5868
const response = await fetch(`${API_URL}/repos`, {
5969
method: 'POST',
6070
headers: { Authorization: `Bearer ${session?.access_token}`, 'Content-Type': 'application/json' },
@@ -70,9 +80,17 @@ export function DashboardHome() {
7080
const data = await response.json()
7181
if (!data.repo_id) throw new Error('Missing repo_id in response')
7282

83+
const indexBody = includePaths
84+
? { include_paths: includePaths, incremental: false }
85+
: undefined
86+
7387
const indexResponse = await fetch(`${API_URL}/repos/${data.repo_id}/index/async`, {
7488
method: 'POST',
75-
headers: { Authorization: `Bearer ${session?.access_token}` },
89+
headers: {
90+
Authorization: `Bearer ${session?.access_token}`,
91+
...(indexBody ? { 'Content-Type': 'application/json' } : {}),
92+
},
93+
...(indexBody ? { body: JSON.stringify(indexBody) } : {}),
7694
})
7795

7896
if (!indexResponse.ok) {
@@ -160,6 +178,38 @@ export function DashboardHome() {
160178
}
161179
}
162180

181+
// When AddRepoForm detects a large repo, show the directory picker
182+
const handleAnalyzed = (result: AnalyzeResult, gitUrl: string, branch: string) => {
183+
setAnalyzeResult(result)
184+
setPendingGitUrl(gitUrl)
185+
setPendingBranch(branch)
186+
setShowDirectoryPicker(true)
187+
}
188+
189+
// User selected directories in the picker -- clone and index with subset
190+
const handleDirectoryConfirm = async (selectedPaths: string[]) => {
191+
if (!analyzeResult) return
192+
try {
193+
setLoading(true)
194+
const name = analyzeResult.repo
195+
const repoId = await addAndIndex(name, pendingGitUrl, pendingBranch, selectedPaths)
196+
setShowDirectoryPicker(false)
197+
setAnalyzeResult(null)
198+
if (repoId) {
199+
setIndexingRepoId(repoId)
200+
setIndexingRepoName(name)
201+
setShowIndexingModal(true)
202+
}
203+
refreshRepos()
204+
} catch (error) {
205+
toast.error('Failed to add repository', {
206+
description: error instanceof Error ? error.message : 'Please try again',
207+
})
208+
} finally {
209+
setLoading(false)
210+
}
211+
}
212+
163213
const selectedRepoData = repos.find((r) => r.id === selectedRepo)
164214
const isRepoView = selectedRepo && selectedRepoData
165215

@@ -193,11 +243,23 @@ export function DashboardHome() {
193243

194244
<AddRepoForm
195245
onAdd={handleAddRepo}
246+
onAnalyzed={handleAnalyzed}
196247
loading={loading}
197248
isOpen={showAddForm}
198249
onOpenChange={setShowAddForm}
199250
/>
200251

252+
{analyzeResult && (
253+
<DirectoryPicker
254+
isOpen={showDirectoryPicker}
255+
onClose={() => { setShowDirectoryPicker(false); setAnalyzeResult(null) }}
256+
repoInfo={analyzeResult}
257+
onConfirm={handleDirectoryConfirm}
258+
loading={loading}
259+
functionLimit={TIER_FUNCTION_LIMITS.free}
260+
/>
261+
)}
262+
201263
<IndexingProgressModal
202264
repoId={indexingRepoId}
203265
repoName={indexingRepoName}

0 commit comments

Comments
 (0)