Skip to content

Commit b770cec

Browse files
committed
feat: mobile GitHub project selector — connect repos from chat home
1 parent ff9d3f6 commit b770cec

File tree

1 file changed

+100
-6
lines changed

1 file changed

+100
-6
lines changed

components/chat-home.tsx

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { KnotBackground } from '@/components/knot-background'
77
import { ModeSelector } from '@/components/mode-selector'
88
import type { AgentMode } from '@/components/mode-selector'
99
import { PermissionsToggle } from '@/components/permissions-toggle'
10-
import { useRepo } from '@/context/repo-context'
10+
import { useRepo, type RepoInfo } from '@/context/repo-context'
1111
import { useLocal } from '@/context/local-context'
1212
import { useGateway } from '@/context/gateway-context'
1313
import { useGitHubAuth } from '@/context/github-auth-context'
1414
import { useEditor } from '@/context/editor-context'
1515
import { emit } from '@/lib/events'
1616
import { getRecentFolders } from '@/context/local-context'
1717
import { getAgentConfig } from '@/lib/agent-session'
18+
import { fetchRepoByName } from '@/lib/github-api'
1819

1920
const STATIC_SUGGESTIONS = [
2021
{
@@ -88,10 +89,11 @@ export const ChatHome = memo(function ChatHome({
8889
const [agentMode, setAgentMode] = useState<AgentMode>('ask')
8990
const [isFocused, setIsFocused] = useState(false)
9091
const inputRef = useRef<HTMLTextAreaElement>(null)
91-
const { repo } = useRepo()
92+
const { repo, setRepo } = useRepo()
9293
const local = useLocal()
9394
const { status } = useGateway()
9495
const { files: openFiles } = useEditor()
96+
const { token: ghToken } = useGitHubAuth()
9597

9698
const repoShort = useMemo(
9799
() => repo?.fullName?.split('/').pop() ?? local.rootPath?.split('/').pop() ?? null,
@@ -101,6 +103,45 @@ export const ChatHome = memo(function ChatHome({
101103
const branchName = local.gitInfo?.branch ?? null
102104

103105
const [isComposing, setIsComposing] = useState(false)
106+
const [showRepoInput, setShowRepoInput] = useState(false)
107+
const [repoInput, setRepoInput] = useState('')
108+
const [repoLoading, setRepoLoading] = useState(false)
109+
const [repoError, setRepoError] = useState<string | null>(null)
110+
const repoInputRef = useRef<HTMLInputElement>(null)
111+
112+
const isMobile = typeof window !== 'undefined' && window.innerWidth <= 768
113+
114+
const handleRepoConnect = useCallback(async () => {
115+
const val = repoInput.trim().replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '').replace(/\/$/, '')
116+
if (!val || !val.includes('/')) {
117+
setRepoError('Enter owner/repo (e.g. OpenKnots/code-editor)')
118+
return
119+
}
120+
setRepoLoading(true)
121+
setRepoError(null)
122+
try {
123+
const ghRepo = await fetchRepoByName(val)
124+
const info: RepoInfo = {
125+
owner: ghRepo.owner.login,
126+
repo: ghRepo.name,
127+
branch: ghRepo.default_branch,
128+
fullName: ghRepo.full_name,
129+
}
130+
setRepo(info)
131+
setShowRepoInput(false)
132+
setRepoInput('')
133+
} catch (err) {
134+
setRepoError(err instanceof Error ? err.message : 'Repository not found')
135+
} finally {
136+
setRepoLoading(false)
137+
}
138+
}, [repoInput, setRepo])
139+
140+
useEffect(() => {
141+
if (showRepoInput) {
142+
setTimeout(() => repoInputRef.current?.focus(), 100)
143+
}
144+
}, [showRepoInput])
104145

105146
useEffect(() => {
106147
const t = setTimeout(() => inputRef.current?.focus(), 100)
@@ -215,10 +256,63 @@ export const ChatHome = memo(function ChatHome({
215256
{repoShort ?? 'Select workspace'}
216257
<Icon icon="lucide:chevron-down" width={14} height={14} className="opacity-50" />
217258
</button>
218-
{/* Subtle tagline on mobile */}
219-
<p className="mt-2 text-[13px] text-[var(--text-disabled)] sm:hidden">
220-
Your AI-powered code companion
221-
</p>
259+
{/* Mobile project selector */}
260+
{isMobile && !hasWorkspace && !showRepoInput && (
261+
<button
262+
onClick={() => setShowRepoInput(true)}
263+
className="mt-3 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-dashed border-[var(--border)] text-[12px] text-[var(--text-disabled)] hover:text-[var(--text-secondary)] hover:border-[var(--text-disabled)] transition-all cursor-pointer"
264+
>
265+
<Icon icon="lucide:github" width={14} height={14} />
266+
Connect a repository
267+
</button>
268+
)}
269+
{isMobile && !hasWorkspace && showRepoInput && (
270+
<div className="mt-3 w-full max-w-[320px]">
271+
<div className="flex items-center gap-1.5">
272+
<div className="flex-1 relative">
273+
<Icon icon="lucide:github" width={14} height={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[var(--text-disabled)]" />
274+
<input
275+
ref={repoInputRef}
276+
type="text"
277+
value={repoInput}
278+
onChange={(e) => { setRepoInput(e.target.value); setRepoError(null) }}
279+
onKeyDown={(e) => { if (e.key === 'Enter') handleRepoConnect(); if (e.key === 'Escape') setShowRepoInput(false) }}
280+
placeholder="owner/repo"
281+
autoCapitalize="off"
282+
autoCorrect="off"
283+
spellCheck={false}
284+
className="w-full pl-8 pr-3 py-2 rounded-lg border border-[var(--border)] bg-[color-mix(in_srgb,var(--bg-elevated)_80%,transparent)] text-[13px] text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--brand)] transition-colors"
285+
/>
286+
</div>
287+
<button
288+
onClick={handleRepoConnect}
289+
disabled={repoLoading || !repoInput.trim()}
290+
className="shrink-0 px-3 py-2 rounded-lg text-[12px] font-medium transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default bg-[var(--brand)] text-[var(--brand-contrast,#fff)]"
291+
>
292+
{repoLoading ? '…' : 'Go'}
293+
</button>
294+
</div>
295+
{repoError && (
296+
<p className="mt-1.5 text-[11px] text-[var(--color-deletions)]">{repoError}</p>
297+
)}
298+
</div>
299+
)}
300+
{isMobile && hasWorkspace && (
301+
<button
302+
onClick={() => { setRepo(null); setShowRepoInput(true) }}
303+
className="mt-2 inline-flex items-center gap-1.5 text-[13px] text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors cursor-pointer"
304+
>
305+
<Icon icon="lucide:github" width={13} height={13} className="opacity-60" />
306+
{repoShort}
307+
<Icon icon="lucide:chevron-down" width={12} height={12} className="opacity-40" />
308+
</button>
309+
)}
310+
{/* Subtle tagline on mobile — only when no repo input shown */}
311+
{isMobile && hasWorkspace && (
312+
<p className="mt-1 text-[11px] text-[var(--text-disabled)]">
313+
{repo?.fullName ?? 'local project'}
314+
</p>
315+
)}
222316
</div>
223317

224318
{/* "Explore more" link — desktop only */}

0 commit comments

Comments
 (0)