Skip to content

Commit ab30fa6

Browse files
author
pengyu
committed
finish all the logical part
1 parent 4640f02 commit ab30fa6

File tree

5 files changed

+223
-80
lines changed

5 files changed

+223
-80
lines changed

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

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export interface ProjectContextType {
5454
setRecentlyCompletedProjectId: (id: string | null) => void;
5555
chatId: string | null;
5656
setChatId: (chatId: string | null) => void;
57+
pendingProjects: Project[];
58+
setPendingProjects: React.Dispatch<React.SetStateAction<Project[]>>;
59+
refetchPublicProjects: () => Promise<any>;
60+
setRefetchPublicProjects: React.Dispatch<
61+
React.SetStateAction<() => Promise<any>>
62+
>;
63+
tempLoadingProjectId: string | null;
64+
setTempLoadingProjectId: React.Dispatch<React.SetStateAction<string | null>>;
5765
}
5866

5967
export const ProjectContext = createContext<ProjectContextType | undefined>(
@@ -102,13 +110,14 @@ const checkUrlStatus = async (
102110

103111
export function ProjectProvider({ children }: { children: ReactNode }) {
104112
const router = useRouter();
105-
const { isAuthorized } = useAuthContext();
113+
const { isAuthorized, user } = useAuthContext();
106114
const [projects, setProjects] = useState<Project[]>([]);
107115
const [curProject, setCurProject] = useState<Project | undefined>(undefined);
108116
const [projectLoading, setProjectLoading] = useState<boolean>(true);
109117
const [filePath, setFilePath] = useState<string | null>(null);
110118
const [isLoading, setIsLoading] = useState<boolean>(false);
111119
const editorRef = useRef<any>(null);
120+
const [pendingProjects, setPendingProjects] = useState<Project[]>([]);
112121
const [recentlyCompletedProjectIdRaw, setRecentlyCompletedProjectIdRaw] =
113122
useState<string | null>(() =>
114123
typeof window !== 'undefined'
@@ -128,6 +137,9 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
128137
const [chatId, setChatId] = useState<string | null>(null);
129138
const [pollTime, setPollTime] = useState(Date.now());
130139
const [isCreateButtonClicked, setIsCreateButtonClicked] = useState(false);
140+
const [tempLoadingProjectId, setTempLoadingProjectId] = useState<
141+
string | null
142+
>(null);
131143
interface ChatProjectCacheEntry {
132144
project: Project | null;
133145
timestamp: number;
@@ -153,7 +165,9 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
153165
const MAX_RETRIES = 30;
154166
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes TTL for cache
155167
const SYNC_DEBOUNCE_TIME = 1000; // 1 second debounce for sync operations
156-
168+
const [refetchPublicProjects, setRefetchPublicProjects] = useState<
169+
() => Promise<any>
170+
>(() => async () => {});
157171
// Mounted ref to prevent state updates after unmount
158172
const isMounted = useRef(true);
159173

@@ -710,18 +724,13 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
710724
model = 'gpt-4o-mini'
711725
): Promise<string> => {
712726
if (!prompt.trim()) {
713-
if (isMounted.current) {
714-
toast.error('Please enter a project description');
715-
}
727+
toast.error('Please enter a project description');
716728
throw new Error('Invalid prompt');
717729
}
718730

719731
try {
720-
if (isMounted.current) {
721-
setIsLoading(true);
722-
}
732+
setIsLoading(true);
723733

724-
// Default packages based on typical web project needs
725734
const defaultPackages = [
726735
{ name: 'react', version: '^18.2.0' },
727736
{ name: 'next', version: '^13.4.0' },
@@ -734,32 +743,29 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
734743
description: prompt,
735744
packages: defaultPackages,
736745
public: isPublic,
737-
model: model,
746+
model,
738747
},
739748
},
740749
});
750+
741751
const createdChat = result.data?.createProject;
742752
if (createdChat?.id) {
743753
setChatId(createdChat.id);
744754
setIsCreateButtonClicked(true);
745755
localStorage.setItem('pendingChatId', createdChat.id);
756+
setTempLoadingProjectId(createdChat.id);
746757
return createdChat.id;
747758
} else {
748759
throw new Error('Project creation failed: no chatId');
749760
}
750761
} catch (error) {
751-
logger.error('Error creating project:', error);
752-
if (isMounted.current) {
753-
toast.error('Failed to create project from prompt');
754-
}
762+
toast.error('Failed to create project from prompt');
755763
throw error;
756764
} finally {
757-
if (isMounted.current) {
758-
setIsLoading(false);
759-
}
765+
setIsLoading(false);
760766
}
761767
},
762-
[createProject, setChatId]
768+
[createProject, setChatId, user]
763769
);
764770

765771
// New function to fork a project
@@ -870,6 +876,30 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
870876
retryCount: retries,
871877
});
872878

879+
// First update the project list to ensure it exists in allProjects
880+
setProjects((prev) => {
881+
const exists = prev.find((p) => p.id === project.id);
882+
return exists ? prev : [...prev, project];
883+
});
884+
885+
// Then more aggressively clean up pending projects
886+
setPendingProjects((prev) => {
887+
const filtered = prev.filter((p) => p.id !== project.id);
888+
if (filtered.length !== prev.length) {
889+
logger.info(
890+
`Removed project ${project.id} from pending projects`
891+
);
892+
}
893+
return filtered;
894+
});
895+
896+
// Then trigger the public projects refetch
897+
await refetchPublicProjects();
898+
console.log(
899+
'[pollChatProject] refetchPublicProjects triggered after project is ready:',
900+
project.id
901+
);
902+
873903
// Trigger state sync if needed
874904
if (
875905
now - projectSyncState.current.lastSyncTime >=
@@ -887,6 +917,9 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
887917
});
888918
}
889919

920+
if (isMounted.current) {
921+
setTempLoadingProjectId(null);
922+
}
890923
return project;
891924
}
892925
} catch (error) {
@@ -921,6 +954,7 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
921954
MAX_RETRIES,
922955
CACHE_TTL,
923956
SYNC_DEBOUNCE_TIME,
957+
refetchPublicProjects,
924958
]
925959
);
926960

@@ -969,6 +1003,12 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
9691003
setChatId,
9701004
recentlyCompletedProjectId: recentlyCompletedProjectIdRaw,
9711005
setRecentlyCompletedProjectId,
1006+
pendingProjects,
1007+
setPendingProjects,
1008+
refetchPublicProjects,
1009+
setRefetchPublicProjects,
1010+
tempLoadingProjectId,
1011+
setTempLoadingProjectId,
9721012
}),
9731013
[
9741014
projects,
@@ -988,6 +1028,12 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
9881028
chatId,
9891029
setChatId,
9901030
recentlyCompletedProjectIdRaw,
1031+
pendingProjects,
1032+
setPendingProjects,
1033+
refetchPublicProjects,
1034+
setRefetchPublicProjects,
1035+
tempLoadingProjectId,
1036+
setTempLoadingProjectId,
9911037
]
9921038
);
9931039

frontend/src/components/global-toast-listener.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use client';
2+
23
import { useContext, useEffect, useRef } from 'react';
34
import { toast } from 'sonner';
45
import { useRouter } from 'next/navigation';
@@ -34,6 +35,9 @@ const GlobalToastListener = () => {
3435
setRecentlyCompletedProjectId,
3536
pollChatProject,
3637
setChatId,
38+
refreshProjects,
39+
refetchPublicProjects,
40+
setTempLoadingProjectId,
3741
} = useContext(ProjectContext);
3842
const router = useRouter();
3943
const intervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -43,14 +47,15 @@ const GlobalToastListener = () => {
4347

4448
useEffect(() => {
4549
const chatId = recentlyCompletedProjectId;
46-
4750
if (!chatId || completedIdsRef.current.has(chatId)) return;
4851

4952
intervalRef.current = setInterval(async () => {
5053
try {
5154
const project = await pollChatProject(chatId);
52-
5355
if (project?.projectPath) {
56+
await refreshProjects();
57+
await refetchPublicProjects(); // 🚀 确保刷新公共项目视图
58+
setTempLoadingProjectId(null);
5459
toast.custom(
5560
(t) => (
5661
<ProjectReadyToast
@@ -66,7 +71,6 @@ const GlobalToastListener = () => {
6671

6772
completedIdsRef.current.add(chatId);
6873
saveCompletedToLocalStorage(completedIdsRef.current);
69-
7074
setRecentlyCompletedProjectId(null);
7175

7276
if (intervalRef.current) {

frontend/src/components/root/expand-card.tsx

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
'use client';
2+
23
import Image from 'next/image';
34
import React, { useContext, useEffect, useRef, useState } from 'react';
45
import { AnimatePresence, motion } from 'framer-motion';
56
import { X } from 'lucide-react';
67
import { ProjectContext } from '../chat/code-engine/project-context';
78
import { URL_PROTOCOL_PREFIX } from '@/utils/const';
89
import { logger } from '@/app/log/logger';
10+
import { Button } from '@/components/ui/button';
911

10-
export function ExpandableCard({ projects }) {
12+
export function ExpandableCard({ projects, isGenerating = false, onOpenChat }) {
1113
const [active, setActive] = useState(null);
1214
const [iframeUrl, setIframeUrl] = useState('');
1315
const ref = useRef<HTMLDivElement>(null);
14-
const { getWebUrl, takeProjectScreenshot } = useContext(ProjectContext);
16+
const { getWebUrl } = useContext(ProjectContext);
1517
const cachedUrls = useRef(new Map());
1618

1719
useEffect(() => {
@@ -29,7 +31,13 @@ export function ExpandableCard({ projects }) {
2931
window.addEventListener('keydown', onKeyDown);
3032
return () => window.removeEventListener('keydown', onKeyDown);
3133
}, [active]);
34+
3235
const handleCardClick = async (project) => {
36+
if (isGenerating && onOpenChat) {
37+
onOpenChat();
38+
return;
39+
}
40+
3341
setActive(project);
3442
setIframeUrl('');
3543
if (cachedUrls.current.has(project.id)) {
@@ -46,6 +54,7 @@ export function ExpandableCard({ projects }) {
4654
logger.error('Error fetching project URL:', error);
4755
}
4856
};
57+
4958
return (
5059
<>
5160
<AnimatePresence mode="wait">
@@ -55,10 +64,7 @@ export function ExpandableCard({ projects }) {
5564
initial={{ opacity: 0 }}
5665
animate={{ opacity: 1 }}
5766
exit={{ opacity: 0 }}
58-
transition={{
59-
duration: 0.3,
60-
ease: [0.4, 0, 0.2, 1],
61-
}}
67+
transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
6268
className="fixed inset-0 backdrop-blur-[2px] bg-black/20 h-full w-full z-50"
6369
style={{ willChange: 'opacity' }}
6470
/>
@@ -118,15 +124,7 @@ export function ExpandableCard({ projects }) {
118124
<motion.div
119125
key={project.id}
120126
layoutId={`card-${project.id}`}
121-
onClick={async () => {
122-
const data = await getWebUrl(project.path);
123-
124-
logger.info(project.image);
125-
const url = `${URL_PROTOCOL_PREFIX}://${data.domain}`;
126-
setIframeUrl(url);
127-
handleCardClick(project);
128-
setActive(project);
129-
}}
127+
onClick={() => handleCardClick(project)}
130128
className="group cursor-pointer"
131129
>
132130
<motion.div
@@ -135,7 +133,7 @@ export function ExpandableCard({ projects }) {
135133
>
136134
<motion.div layoutId={`image-${project.id}`}>
137135
<Image
138-
src={project.image}
136+
src={isGenerating ? '/placeholder-black.png' : project.image}
139137
alt={project.name}
140138
width={600}
141139
height={200}
@@ -150,7 +148,7 @@ export function ExpandableCard({ projects }) {
150148
className="absolute inset-0 bg-black/40 flex items-center justify-center"
151149
>
152150
<span className="text-white font-medium px-4 py-2 rounded-lg bg-white/20 backdrop-blur-sm">
153-
View Project
151+
{isGenerating ? 'Generating...' : 'View Project'}
154152
</span>
155153
</motion.div>
156154
</motion.div>
@@ -168,6 +166,20 @@ export function ExpandableCard({ projects }) {
168166
>
169167
{project.author}
170168
</motion.div>
169+
{isGenerating && onOpenChat && (
170+
<div className="mt-2">
171+
<Button
172+
variant="ghost"
173+
size="sm"
174+
onClick={(e) => {
175+
e.stopPropagation();
176+
onOpenChat();
177+
}}
178+
>
179+
Open Chat
180+
</Button>
181+
</div>
182+
)}
171183
</motion.div>
172184
</motion.div>
173185
))}

0 commit comments

Comments
 (0)