Skip to content

Commit db8adf5

Browse files
wip
1 parent 9f9ce5f commit db8adf5

8 files changed

Lines changed: 198 additions & 6 deletions

File tree

packages/web/src/app/askgh/[owner]/[repo]/api.ts renamed to packages/web/src/app/[domain]/askgh/[owner]/[repo]/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const getRepoInfo = async (repoId: number): Promise<RepoInfo | ServiceErr
2727
id: repo.id,
2828
name: repo.name,
2929
displayName: repo.displayName,
30+
imageUrl: repo.imageUrl,
3031
isIndexed: repo.indexedAt !== null,
3132
};
3233
})
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use client';
2+
3+
import Image from 'next/image';
4+
import { SearchModeSelector } from "@/app/[domain]/components/searchModeSelector";
5+
import { Separator } from "@/components/ui/separator";
6+
import { ChatBox } from "@/features/chat/components/chatBox";
7+
import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar";
8+
import { NotConfiguredErrorBanner } from "@/features/chat/components/notConfiguredErrorBanner";
9+
import { LanguageModelInfo, RepoSearchScope } from "@/features/chat/types";
10+
import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread";
11+
import { getRepoImageSrc } from '@/lib/utils';
12+
import { useMemo, useState } from "react";
13+
14+
interface LandingPageProps {
15+
languageModels: LanguageModelInfo[];
16+
repoName: string;
17+
repoDisplayName?: string;
18+
imageUrl?: string | null;
19+
repoId: number;
20+
}
21+
22+
export const LandingPage = ({
23+
languageModels,
24+
repoName,
25+
repoDisplayName,
26+
imageUrl,
27+
repoId,
28+
}: LandingPageProps) => {
29+
const { createNewChatThread, isLoading } = useCreateNewChatThread();
30+
const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false);
31+
const isChatBoxDisabled = languageModels.length === 0;
32+
33+
const selectedSearchScopes = useMemo(() => [
34+
{
35+
type: 'repo',
36+
name: repoDisplayName ?? repoName,
37+
value: repoName,
38+
codeHostType: 'github' as const,
39+
} satisfies RepoSearchScope,
40+
], [repoDisplayName, repoName]);
41+
42+
const imageSrc = imageUrl ? getRepoImageSrc(imageUrl, repoId) : undefined;
43+
const displayName = repoDisplayName ?? repoName;
44+
45+
return (
46+
<div className="min-h-screen flex flex-col justify-between p-4">
47+
{/* Center Section - Repository Info */}
48+
<div className="flex-1 flex items-center justify-center">
49+
<div className="flex items-center gap-4">
50+
{imageSrc && (
51+
<Image
52+
src={imageSrc}
53+
alt={`${displayName} avatar`}
54+
width={32}
55+
height={32}
56+
className="rounded-lg"
57+
unoptimized={imageSrc.startsWith('/api/')}
58+
/>
59+
)}
60+
<h1 className="text-2xl font-bold">{displayName}</h1>
61+
</div>
62+
</div>
63+
64+
{/* Bottom Section - ChatBox */}
65+
<div className="flex justify-center pb-8">
66+
<div className="w-full max-w-[800px]">
67+
<div className="border rounded-md w-full shadow-sm">
68+
<ChatBox
69+
onSubmit={(children) => {
70+
createNewChatThread(children, selectedSearchScopes);
71+
}}
72+
className="min-h-[50px]"
73+
isRedirecting={isLoading}
74+
languageModels={languageModels}
75+
selectedSearchScopes={selectedSearchScopes}
76+
searchContexts={[]}
77+
onContextSelectorOpenChanged={setIsContextSelectorOpen}
78+
isDisabled={isChatBoxDisabled}
79+
/>
80+
<Separator />
81+
<div className="relative">
82+
<div className="w-full flex flex-row items-center bg-accent rounded-b-md px-2">
83+
<ChatBoxToolbar
84+
languageModels={languageModels}
85+
repos={[]}
86+
searchContexts={[]}
87+
selectedSearchScopes={selectedSearchScopes}
88+
onSelectedSearchScopesChange={() => {}}
89+
isContextSelectorOpen={isContextSelectorOpen}
90+
onContextSelectorOpenChanged={setIsContextSelectorOpen}
91+
/>
92+
<SearchModeSelector
93+
searchMode="agentic"
94+
className="ml-auto"
95+
/>
96+
</div>
97+
</div>
98+
</div>
99+
100+
{isChatBoxDisabled && (
101+
<NotConfiguredErrorBanner className="mt-4" />
102+
)}
103+
</div>
104+
</div>
105+
</div>
106+
)
107+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use client';
2+
3+
import { ServiceError } from "@/lib/serviceError";
4+
import { unwrapServiceError } from "@/lib/utils";
5+
import { useQuery } from "@tanstack/react-query";
6+
import { Loader2 } from "lucide-react";
7+
import { RepoInfo } from "../types";
8+
9+
const REINDEX_INTERVAL_MS = 2000;
10+
11+
interface Props {
12+
initialRepoInfo: RepoInfo;
13+
children: React.ReactNode;
14+
}
15+
16+
export function RepoIndexedGuard({ initialRepoInfo, children }: Props) {
17+
const { data: repoInfo, isError } = useQuery({
18+
queryKey: ['repo-status', initialRepoInfo.id],
19+
queryFn: () => unwrapServiceError(getRepoStatus(initialRepoInfo.id)),
20+
initialData: initialRepoInfo,
21+
refetchInterval: (query) => {
22+
const repo = query.state.data;
23+
24+
// If repo has been indexed before (indexedAt is not null), stop polling
25+
if (repo?.isIndexed) {
26+
return false;
27+
}
28+
29+
return REINDEX_INTERVAL_MS;
30+
},
31+
});
32+
33+
if (isError) {
34+
// todo
35+
return null;
36+
}
37+
38+
if (!repoInfo.isIndexed) {
39+
// Loading spinner only for first-time indexing (indexedAt is null)
40+
return (
41+
<div className="flex flex-col items-center justify-center min-h-[400px] p-4">
42+
<Loader2 className="w-12 h-12 animate-spin text-primary mb-4" />
43+
<h2 className="text-2xl font-semibold mb-2">
44+
Indexing in progress...
45+
</h2>
46+
<p className="text-muted-foreground text-center">
47+
This may take a few minutes. The page will update automatically.
48+
</p>
49+
</div>
50+
);
51+
}
52+
53+
return (
54+
children
55+
);
56+
}
57+
58+
const getRepoStatus = async (repoId: number): Promise<RepoInfo | ServiceError> => {
59+
const result = await fetch(
60+
`/api/repo-status/${repoId}`,
61+
{
62+
method: 'GET',
63+
headers: {
64+
'Content-Type': 'application/json',
65+
},
66+
}
67+
).then(response => response.json());
68+
return result as RepoInfo | ServiceError;
69+
}

packages/web/src/app/askgh/[owner]/[repo]/components/repoStatusDisplay.tsx renamed to packages/web/src/app/[domain]/askgh/[owner]/[repo]/components/repoStatusDisplay.tsx

File renamed without changes.

packages/web/src/app/askgh/[owner]/[repo]/page.tsx renamed to packages/web/src/app/[domain]/askgh/[owner]/[repo]/page.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { addGithubRepo } from "@/features/workerApi/actions";
2-
import { RepoStatusDisplay } from "./components/repoStatusDisplay";
32
import { isServiceError, unwrapServiceError } from "@/lib/utils";
43
import { ServiceErrorException } from "@/lib/serviceError";
54
import { prisma } from "@/prisma";
65
import { SINGLE_TENANT_ORG_ID } from "@/lib/constants";
76
import { getRepoInfo } from "./api";
7+
import { CustomSlateEditor } from "@/features/chat/customSlateEditor";
8+
import { RepoIndexedGuard } from "./components/repoIndexedGuard";
9+
import { LandingPage } from "./components/landingPage";
10+
import { getConfiguredLanguageModelsInfo } from "@/features/chat/actions";
811

912
interface PageProps {
1013
params: Promise<{ owner: string; repo: string }>;
@@ -41,6 +44,19 @@ export default async function GitHubRepoPage(props: PageProps) {
4144
})();
4245

4346
const repoInfo = await unwrapServiceError(getRepoInfo(repoId));
44-
45-
return <RepoStatusDisplay initialRepoInfo={repoInfo} />;
47+
const languageModels = await unwrapServiceError(getConfiguredLanguageModelsInfo());
48+
49+
return (
50+
<RepoIndexedGuard initialRepoInfo={repoInfo}>
51+
<CustomSlateEditor>
52+
<LandingPage
53+
languageModels={languageModels}
54+
repoName={repoInfo.name}
55+
repoDisplayName={repoInfo.displayName ?? undefined}
56+
imageUrl={repoInfo.imageUrl ?? undefined}
57+
repoId={repoInfo.id}
58+
/>
59+
</CustomSlateEditor>
60+
</RepoIndexedGuard>
61+
)
4662
}

packages/web/src/app/askgh/[owner]/[repo]/types.ts renamed to packages/web/src/app/[domain]/askgh/[owner]/[repo]/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ export type RepoInfo = {
44
id: number;
55
name: string;
66
displayName: string | null;
7+
imageUrl: string | null;
78
isIndexed: boolean;
89
};

packages/web/src/app/[domain]/chat/components/landingPageChatBox.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ export const LandingPageChatBox = ({
2929

3030
return (
3131
<div className="w-full max-w-[800px] mt-4">
32-
33-
3432
<div className="border rounded-md w-full shadow-sm">
3533
<ChatBox
3634
onSubmit={(children) => {

packages/web/src/app/api/repo-status/[repoId]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getRepoInfo } from "@/app/askgh/[owner]/[repo]/api";
1+
import { getRepoInfo } from "@/app/[domain]/askgh/[owner]/[repo]/api";
22
import { serviceErrorResponse } from "@/lib/serviceError";
33
import { isServiceError } from "@/lib/utils";
44
import { NextRequest } from "next/server";

0 commit comments

Comments
 (0)