Skip to content

Commit afb5868

Browse files
author
pengyu
committed
finished notification
1 parent 8426879 commit afb5868

File tree

8 files changed

+258
-68
lines changed

8 files changed

+258
-68
lines changed

frontend/src/app/(main)/page.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { SignUpModal } from '@/components/sign-up-modal';
1313
import { useRouter } from 'next/navigation';
1414
import { logger } from '../log/logger';
1515
import { AuroraText } from '@/components/magicui/aurora-text';
16+
1617
export default function HomePage() {
1718
// States for AuthChoiceModal
1819
const [showAuthChoice, setShowAuthChoice] = useState(false);
@@ -22,9 +23,10 @@ export default function HomePage() {
2223

2324
const promptFormRef = useRef<PromptFormRef>(null);
2425
const { isAuthorized } = useAuthContext();
25-
const { createProjectFromPrompt, isLoading } = useContext(ProjectContext);
26+
const { createProjectFromPrompt, isLoading, setRecentlyCompletedProjectId } =
27+
useContext(ProjectContext);
2628

27-
const handleSubmit = async () => {
29+
const handleSubmit = async (): Promise<string> => {
2830
if (!promptFormRef.current) return;
2931

3032
const { message, isPublic, model } = promptFormRef.current.getPromptData();
@@ -34,12 +36,25 @@ export default function HomePage() {
3436
const chatId = await createProjectFromPrompt(message, isPublic, model);
3537

3638
promptFormRef.current.clearMessage();
37-
router.push(`/chat?id=${chatId}`);
39+
if (chatId) {
40+
setRecentlyCompletedProjectId(chatId);
41+
return chatId;
42+
}
3843
} catch (error) {
3944
logger.error('Error creating project:', error);
4045
}
4146
};
47+
// useEffect(() => {
48+
// if (!chatId) return;
49+
50+
// const interval = setInterval(() => {
51+
// pollChatProject(chatId).catch((error) => {
52+
// logger.error('Polling error in HomePage:', error);
53+
// });
54+
// }, 6000);
4255

56+
// return () => clearInterval(interval);
57+
// }, [chatId, pollChatProject]);
4358
return (
4459
<div className="min-h-screen pt-16 pb-24 px-6 flex flex-col items-center justify-center relative overflow-hidden">
4560
<div className="fixed inset-0 -z-20">
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { EventEnum } from '@/const/EventEnum';
2+
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
3+
4+
export const redirectChatPage = (
5+
chatId: string,
6+
setCurrentChatid: (id: string) => void,
7+
setChatId: (id: string) => void,
8+
router: AppRouterInstance
9+
) => {
10+
setCurrentChatid(chatId);
11+
setChatId(chatId);
12+
router.push(`/chat?id=${chatId}`);
13+
const event = new Event(EventEnum.CHAT);
14+
window.dispatchEvent(event);
15+
};

frontend/src/components/chat/code-engine/project-context.tsx

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface ProjectContextType {
3636
prompt: string,
3737
isPublic: boolean,
3838
model?: string
39-
) => Promise<boolean>;
39+
) => Promise<string | false>;
4040
forkProject: (projectId: string) => Promise<void>;
4141
setProjectPublicStatus: (
4242
projectId: string,
@@ -109,10 +109,25 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
109109
const [filePath, setFilePath] = useState<string | null>(null);
110110
const [isLoading, setIsLoading] = useState<boolean>(false);
111111
const editorRef = useRef<any>(null);
112-
const [recentlyCompletedProjectId, setRecentlyCompletedProjectId] = useState<
113-
string | null
114-
>(null);
112+
const [recentlyCompletedProjectIdRaw, setRecentlyCompletedProjectIdRaw] =
113+
useState<string | null>(() =>
114+
typeof window !== 'undefined'
115+
? localStorage.getItem('pendingChatId')
116+
: null
117+
);
118+
119+
// setter:更新 state + localStorage
120+
const setRecentlyCompletedProjectId = (id: string | null) => {
121+
if (id) {
122+
localStorage.setItem('pendingChatId', id);
123+
} else {
124+
localStorage.removeItem('pendingChatId');
125+
}
126+
setRecentlyCompletedProjectIdRaw(id);
127+
};
115128
const [chatId, setChatId] = useState<string | null>(null);
129+
const [pollTime, setPollTime] = useState(Date.now());
130+
const [isCreateButtonClicked, setIsCreateButtonClicked] = useState(false);
116131
interface ChatProjectCacheEntry {
117132
project: Project | null;
118133
timestamp: number;
@@ -149,24 +164,6 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
149164
pendingOperations.current.clear();
150165
};
151166
}, []);
152-
// Poll project data every 5 seconds
153-
useEffect(() => {
154-
if (!chatId) return;
155-
let stopped = false;
156-
157-
const interval = setInterval(async () => {
158-
const project = await pollChatProject(chatId);
159-
if (project?.projectPath) {
160-
setCurProject(project);
161-
clearInterval(interval);
162-
stopped = true;
163-
}
164-
}, 5000);
165-
166-
return () => {
167-
if (!stopped) clearInterval(interval);
168-
};
169-
}, [chatId]);
170167

171168
// Function to clean expired cache entries
172169
const cleanCache = useCallback(() => {
@@ -711,12 +708,12 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
711708
prompt: string,
712709
isPublic: boolean,
713710
model = 'gpt-4o-mini'
714-
): Promise<boolean> => {
711+
): Promise<string> => {
715712
if (!prompt.trim()) {
716713
if (isMounted.current) {
717714
toast.error('Please enter a project description');
718715
}
719-
return false;
716+
throw new Error('Invalid prompt');
720717
}
721718

722719
try {
@@ -741,21 +738,28 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
741738
},
742739
},
743740
});
744-
745-
return result.data.createProject.id;
741+
const createdChat = result.data?.createProject;
742+
if (createdChat?.id) {
743+
setChatId(createdChat.id);
744+
setIsCreateButtonClicked(true);
745+
localStorage.setItem('pendingChatId', createdChat.id);
746+
return createdChat.id;
747+
} else {
748+
throw new Error('Project creation failed: no chatId');
749+
}
746750
} catch (error) {
747751
logger.error('Error creating project:', error);
748752
if (isMounted.current) {
749753
toast.error('Failed to create project from prompt');
750754
}
751-
return false;
755+
throw error;
752756
} finally {
753757
if (isMounted.current) {
754758
setIsLoading(false);
755759
}
756760
}
757761
},
758-
[createProject]
762+
[createProject, setChatId]
759763
);
760764

761765
// New function to fork a project
@@ -920,6 +924,28 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
920924
]
921925
);
922926

927+
useEffect(() => {
928+
const interval = setInterval(() => {
929+
setPollTime(Date.now()); // 每6秒更新时间,触发下面的 useEffect
930+
}, 6000);
931+
932+
return () => clearInterval(interval);
933+
}, []);
934+
935+
// Poll project data every 5 seconds
936+
useEffect(() => {
937+
if (!chatId) return;
938+
939+
const fetch = async () => {
940+
try {
941+
await pollChatProject(chatId);
942+
} catch (error) {
943+
logger.error('Polling error:', error);
944+
}
945+
};
946+
947+
fetch();
948+
}, [pollTime, chatId, isCreateButtonClicked, pollChatProject]);
923949
const contextValue = useMemo(
924950
() => ({
925951
projects,
@@ -941,6 +967,8 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
941967
editorRef,
942968
chatId,
943969
setChatId,
970+
recentlyCompletedProjectId: recentlyCompletedProjectIdRaw,
971+
setRecentlyCompletedProjectId,
944972
}),
945973
[
946974
projects,
@@ -959,17 +987,12 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
959987
editorRef,
960988
chatId,
961989
setChatId,
990+
recentlyCompletedProjectIdRaw,
962991
]
963992
);
964993

965994
return (
966-
<ProjectContext.Provider
967-
value={{
968-
...contextValue,
969-
recentlyCompletedProjectId,
970-
setRecentlyCompletedProjectId,
971-
}}
972-
>
995+
<ProjectContext.Provider value={contextValue}>
973996
{children}
974997
</ProjectContext.Provider>
975998
);
Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,71 @@
1-
// GlobalToastListener.tsx
21
'use client';
3-
import { useContext, useEffect } from 'react';
4-
import { toast } from 'sonner'; // 或你使用的 toast 库
2+
import { useContext, useEffect, useRef } from 'react';
3+
import { toast } from 'sonner';
4+
import { useRouter } from 'next/navigation';
55
import { ProjectContext } from './chat/code-engine/project-context';
6+
import { logger } from '@/app/log/logger';
7+
import { ProjectReadyToast } from './project-ready-toast';
68

79
const GlobalToastListener = () => {
8-
const { recentlyCompletedProjectId, setRecentlyCompletedProjectId } =
9-
useContext(ProjectContext);
10+
const {
11+
recentlyCompletedProjectId,
12+
setRecentlyCompletedProjectId,
13+
pollChatProject,
14+
setChatId,
15+
} = useContext(ProjectContext);
16+
const router = useRouter();
17+
const intervalRef = useRef<NodeJS.Timeout | null>(null);
18+
19+
// optional: if you use this in your logic
20+
const setCurrentChatid = (id: string) => {}; // or import your actual setter
1021

1122
useEffect(() => {
12-
if (recentlyCompletedProjectId) {
13-
toast.success('Project is ready! 🎉');
23+
if (!recentlyCompletedProjectId) return;
24+
25+
const checkProjectReady = async () => {
26+
try {
27+
const project = await pollChatProject(recentlyCompletedProjectId);
28+
29+
if (project?.projectPath) {
30+
toast.custom(
31+
(t) => (
32+
<ProjectReadyToast
33+
chatId={recentlyCompletedProjectId}
34+
close={() => toast.dismiss(t)}
35+
router={router}
36+
setCurrentChatid={setCurrentChatid}
37+
setChatId={setChatId}
38+
/>
39+
),
40+
{
41+
duration: 30000,
42+
}
43+
);
44+
45+
setRecentlyCompletedProjectId(null);
46+
47+
if (intervalRef.current) {
48+
clearInterval(intervalRef.current);
49+
intervalRef.current = null;
50+
}
51+
} else {
52+
logger.debug('Project not ready yet, will retry...');
53+
}
54+
} catch (err) {
55+
logger.error('Error polling project status:', err);
56+
}
57+
};
58+
59+
intervalRef.current = setInterval(checkProjectReady, 5000);
1460

15-
// 可选:重置,避免重复 toast
16-
setRecentlyCompletedProjectId(null);
17-
}
61+
return () => {
62+
if (intervalRef.current) {
63+
clearInterval(intervalRef.current);
64+
}
65+
};
1866
}, [recentlyCompletedProjectId]);
1967

20-
return null; // 不渲染任何内容,只是监听
68+
return null;
2169
};
2270

2371
export default GlobalToastListener;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use client';
2+
import { motion } from 'framer-motion';
3+
import { X } from 'lucide-react';
4+
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
5+
import { redirectChatPage } from './chat-page-navigation';
6+
7+
interface ProjectReadyToastProps {
8+
chatId: string;
9+
close: () => void;
10+
router: AppRouterInstance;
11+
setCurrentChatid: (id: string) => void;
12+
setChatId: (id: string) => void;
13+
}
14+
15+
export const ProjectReadyToast = ({
16+
chatId,
17+
close,
18+
router,
19+
setCurrentChatid,
20+
setChatId,
21+
}: ProjectReadyToastProps) => {
22+
return (
23+
<motion.div
24+
className="w-[360px] p-4 rounded-xl bg-green-50 dark:bg-green-900 text-green-800 dark:text-green-100 shadow-lg flex flex-col gap-2 relative"
25+
initial={{ opacity: 0, y: 30, scale: 0.95 }}
26+
animate={{ opacity: 1, y: 0, scale: 1 }}
27+
exit={{ opacity: 0, y: 30, scale: 0.95 }}
28+
transition={{ type: 'spring', stiffness: 260, damping: 20 }}
29+
>
30+
{/* Close button */}
31+
<button
32+
onClick={close}
33+
className="absolute top-2 right-2 text-green-500 hover:text-green-700 dark:hover:text-green-300"
34+
>
35+
<X size={18} />
36+
</button>
37+
38+
{/* Title */}
39+
<p className="text-base font-semibold text-center mt-2">
40+
🎉 Project is ready!
41+
</p>
42+
43+
{/* Centered Button */}
44+
<div className="flex justify-center mt-2">
45+
<button
46+
onClick={() => {
47+
redirectChatPage(chatId, setCurrentChatid, setChatId, router);
48+
close();
49+
}}
50+
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-all"
51+
>
52+
Open Chat
53+
</button>
54+
</div>
55+
</motion.div>
56+
);
57+
};

0 commit comments

Comments
 (0)