Skip to content

Commit c4d4ec1

Browse files
author
pengyu
committed
current progress
1 parent 58d7553 commit c4d4ec1

File tree

8 files changed

+175
-79
lines changed

8 files changed

+175
-79
lines changed

backend/.env.example

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ OPENAI_BASE_URI="http://localhost:3001"
1313

1414
# S3/Cloudflare R2 Configuration (Optional)
1515
# If not provided, local file storage will be used
16-
S3_ACCESS_KEY_ID="your_s3_access_key_id" # Must be 32 characters for Cloudflare R2
17-
S3_SECRET_ACCESS_KEY="your_s3_secret_access_key"
16+
S3_ACCESS_KEY_ID="6215aeddb7631fc0d6284ddbb63364f8" # Must be 32 characters for Cloudflare R2
17+
S3_SECRET_ACCESS_KEY="66c6c645f27e72b6bbb1de479163eb454cd4aaee212f5767a660525cb4bd813a"
1818
S3_REGION="auto" # Use 'auto' for Cloudflare R2
19-
S3_BUCKET_NAME="your_bucket_name"
20-
S3_ENDPOINT="https://<account_id>.r2.cloudflarestorage.com" # Cloudflare R2 endpoint
21-
S3_ACCOUNT_ID="your_cloudflare_account_id" # Your Cloudflare account ID
22-
S3_PUBLIC_URL="https://pub-xxx.r2.dev" # Your R2 public bucket URL
19+
S3_BUCKET_NAME="pengyucdn"
20+
S3_ENDPOINT="https://a85330980c7cb2d6526f81850a64fced.r2.cloudflarestorage.com" # Cloudflare R2 endpoint
21+
S3_ACCOUNT_ID="a85330980c7cb2d6526f81850a64fced" # Your Cloudflare account ID
22+
S3_PUBLIC_URL="https://pub-47e72e8a290d495a81fe0741fd1a2f1a.r2.dev" # Your R2 public bucket URL
2323

2424
# mail
2525
# Set to false to disable all email functionality

backend/src/project/project.service.ts

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -198,37 +198,27 @@ export class ProjectService {
198198
this.logger.debug(`Generated project name: ${projectName}`);
199199
}
200200

201-
// Create project entity with "(Generating...)" suffix
202-
const project = new Project();
203-
project.projectName = `${projectName} (Generating...)`;
204-
project.projectPath = ''; // Will be updated when actual project is generated
205-
project.userId = userId;
206-
project.isPublic = input.public || false;
207-
project.uniqueProjectId = uuidv4();
208-
project.projectPackages = [];
209-
210-
// Save project
211-
const savedProject = await this.projectsRepository.save(project);
212-
213201
// Create chat with proper title
214202
const defaultChat = await this.chatService.createChatWithMessage(userId, {
215203
title: projectName || 'New Project Chat',
216204
message: input.description,
217205
});
218206

219-
// Bind chat to project
220-
await this.bindProjectAndChat(savedProject, defaultChat);
221-
222-
// Perform project creation asynchronously
223-
this.createProjectInBackground(input, projectName, userId, defaultChat, savedProject);
207+
// Perform the rest of project creation asynchronously
208+
this.createProjectInBackground(input, projectName, userId, defaultChat);
224209

225210
// Return chat immediately so user can start interacting
226211
return defaultChat;
227212
} catch (error) {
228-
this.logger.error(`Error creating project: ${error.message}`, error.stack);
229-
throw new InternalServerErrorException(
230-
`Failed to create project: ${error.message}`,
213+
if (error instanceof ProjectRateLimitException) {
214+
throw error.getGraphQLError(); // Throw as a GraphQL error for the client
215+
}
216+
217+
this.logger.error(
218+
`Error in createProject: ${error.message}`,
219+
error.stack,
231220
);
221+
throw new InternalServerErrorException('Error creating the project.');
232222
}
233223
}
234224

@@ -238,7 +228,6 @@ export class ProjectService {
238228
projectName: string,
239229
userId: string,
240230
chat: Chat,
241-
project: Project,
242231
): Promise<void> {
243232
try {
244233
// Build project sequence and execute
@@ -249,9 +238,13 @@ export class ProjectService {
249238
const context = new BuilderContext(sequence, sequence.id);
250239
const projectPath = await context.execute();
251240

252-
// Update project with actual data
253-
project.projectName = projectName; // Remove "(Generating...)" suffix
241+
// Create project entity and set properties
242+
const project = new Project();
243+
project.projectName = projectName;
254244
project.projectPath = projectPath;
245+
project.userId = userId;
246+
project.isPublic = input.public || false;
247+
project.uniqueProjectId = uuidv4();
255248

256249
// Set project packages
257250
try {
@@ -260,13 +253,25 @@ export class ProjectService {
260253
);
261254
} catch (packageError) {
262255
this.logger.error(`Error processing packages: ${packageError.message}`);
256+
// Continue even if packages processing fails
263257
project.projectPackages = [];
264258
}
265259

266-
// Save updated project
260+
// Save project
267261
const savedProject = await this.projectsRepository.save(project);
268-
this.logger.debug(`Project updated: ${savedProject.id}`);
262+
this.logger.debug(`Project created: ${savedProject.id}`);
269263

264+
// Bind chat to project
265+
const bindSuccess = await this.bindProjectAndChat(savedProject, chat);
266+
if (!bindSuccess) {
267+
this.logger.error(
268+
`Failed to bind project and chat: ${savedProject.id} -> ${chat.id}`,
269+
);
270+
} else {
271+
this.logger.debug(
272+
`Project and chat bound: ${savedProject.id} -> ${chat.id}`,
273+
);
274+
}
270275
} catch (error) {
271276
this.logger.error(
272277
`Error in background project creation: ${error.message}`,
@@ -803,7 +808,7 @@ export class ProjectService {
803808
this.logger.log(
804809
'check if the github project exist: ' + project.isSyncedWithGitHub,
805810
);
806-
// 2) Check user's GitHub installation
811+
// 2) Check users GitHub installation
807812
if (!user.githubInstallationId) {
808813
throw new Error('GitHub App not installed for this user');
809814
}
@@ -814,7 +819,7 @@ export class ProjectService {
814819
);
815820
const userOAuthToken = user.githubAccessToken;
816821

817-
// 4) Create the repo if the project doesn't have it yet
822+
// 4) Create the repo if the project doesnt have it yet
818823
if (!project.githubRepoName || !project.githubOwner) {
819824
// Use project.projectName or generate a safe name
820825

@@ -853,4 +858,4 @@ export class ProjectService {
853858
project.isSyncedWithGitHub = true;
854859
return this.projectsRepository.save(project);
855860
}
856-
}
861+
}

frontend/src/app/api/screenshot/route.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ export async function GET(req: Request) {
4949
timeout: 60000, // Increased timeout to 60 seconds
5050
});
5151

52+
// 等待额外的时间让页面完全渲染
53+
await page.waitForTimeout(3000);
54+
55+
// 尝试等待页面上的内容加载,如果失败也继续处理
56+
try {
57+
// 等待页面上可能存在的主要内容元素
58+
await Promise.race([
59+
page.waitForSelector('main', { timeout: 2000 }),
60+
page.waitForSelector('#root', { timeout: 2000 }),
61+
page.waitForSelector('.app', { timeout: 2000 }),
62+
page.waitForSelector('h1', { timeout: 2000 }),
63+
]);
64+
} catch (waitError) {
65+
// 忽略等待选择器的错误,继续截图
66+
logger.info('Unable to find common page elements, continuing with screenshot');
67+
}
68+
5269
// Take screenshot
5370
const screenshot = await page.screenshot({
5471
type: 'png',

frontend/src/components/global-project-poller.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
66
import { ProjectContext } from './chat/code-engine/project-context';
77
import { logger } from '@/app/log/logger';
88
import { ProjectReadyToast } from './project-ready-toast';
9+
import { URL_PROTOCOL_PREFIX } from '@/utils/const';
910

1011
const COMPLETED_CACHE_KEY = 'completedChatIds';
1112

@@ -38,6 +39,8 @@ const GlobalToastListener = () => {
3839
refreshProjects,
3940
refetchPublicProjects,
4041
setTempLoadingProjectId,
42+
getWebUrl,
43+
takeProjectScreenshot
4144
} = useContext(ProjectContext);
4245
const router = useRouter();
4346
const intervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -56,6 +59,26 @@ const GlobalToastListener = () => {
5659
await refreshProjects();
5760
await refetchPublicProjects();
5861
setTempLoadingProjectId(null);
62+
63+
// 确保为项目截图
64+
try {
65+
if (project.id && project.projectPath) {
66+
logger.info(`Taking screenshot for project ${project.id}`);
67+
// 获取项目URL并进行截图
68+
const { domain } = await getWebUrl(project.projectPath);
69+
const baseUrl = `${URL_PROTOCOL_PREFIX}://${domain}`;
70+
71+
// 等待5秒钟让服务完全启动
72+
logger.info(`Waiting for service to fully start before taking screenshot for project ${project.id}`);
73+
await new Promise(resolve => setTimeout(resolve, 5000));
74+
75+
await takeProjectScreenshot(project.id, baseUrl);
76+
logger.info(`Screenshot taken for project ${project.id}`);
77+
}
78+
} catch (screenshotError) {
79+
logger.error('Error taking project screenshot:', screenshotError);
80+
}
81+
5982
toast.custom(
6083
(t) => (
6184
<ProjectReadyToast
@@ -88,7 +111,8 @@ const GlobalToastListener = () => {
88111
return () => {
89112
if (intervalRef.current) clearInterval(intervalRef.current);
90113
};
91-
}, [recentlyCompletedProjectId]);
114+
}, [recentlyCompletedProjectId, pollChatProject, refreshProjects, refetchPublicProjects,
115+
setTempLoadingProjectId, getWebUrl, takeProjectScreenshot, router, setChatId]);
92116

93117
return null;
94118
};

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export function ExpandableCard({ projects, isGenerating = false, onOpenChat }) {
119119
) : null}
120120
</AnimatePresence>
121121

122-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
122+
<div className="grid grid-cols-1 gap-4">
123123
{projects.map((project) => (
124124
<motion.div
125125
key={project.id}
@@ -138,12 +138,12 @@ export function ExpandableCard({ projects, isGenerating = false, onOpenChat }) {
138138
alt={project.name}
139139
width={500}
140140
height={250}
141-
className="w-full h-48 object-cover transition duration-300 group-hover:scale-105"
141+
className="w-full h-[120px] object-cover transition duration-300 group-hover:scale-105"
142142
/>
143143
) : (
144-
<div className="w-full h-48 flex items-center justify-center bg-white dark:bg-zinc-800">
144+
<div className="w-full h-[120px] flex items-center justify-center bg-white dark:bg-zinc-800">
145145
<svg
146-
className="animate-spin h-10 w-10 text-gray-400 dark:text-gray-500"
146+
className="animate-spin h-8 w-8 text-gray-400 dark:text-gray-500"
147147
xmlns="http://www.w3.org/2000/svg"
148148
fill="none"
149149
viewBox="0 0 24 24"
@@ -178,16 +178,17 @@ export function ExpandableCard({ projects, isGenerating = false, onOpenChat }) {
178178
</motion.div>
179179
</motion.div>
180180

181-
<motion.div layoutId={`content-${project.id}`} className="mt-3">
181+
<motion.div layoutId={`content-${project.id}`} className="mt-2">
182182
<motion.h3
183183
layoutId={`title-${project.id}`}
184-
className="font-medium text-gray-900 dark:text-gray-100"
184+
className="font-medium text-gray-900 dark:text-gray-100 flex items-center text-sm truncate"
185185
>
186186
{project.name}
187+
187188
</motion.h3>
188189
<motion.div
189190
layoutId={`meta-${project.id}`}
190-
className="mt-1 text-sm text-gray-500"
191+
className="mt-0.5 text-xs text-gray-500 truncate"
191192
>
192193
{project.author}
193194
</motion.div>

frontend/src/components/root/projects-section.tsx

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,54 @@ export function ProjectsSection() {
118118
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
119119
.map((project) => ({
120120
id: project.id,
121-
name: project.projectName || project.title || 'Untitled Project',
121+
name: project.projectName || 'Untitled Project',
122122
path: project.projectPath ?? '',
123123
isReady: !!project.projectPath,
124124
createDate: project.createdAt
125125
? new Date(project.createdAt).toISOString().split('T')[0]
126126
: 'N/A',
127127
author: project.user?.username || user?.username || 'Unknown',
128128
forkNum: project.subNumber || 0,
129-
image: project.projectPath
130-
? project.photoUrl || `https://picsum.photos/500/250?random=${project.id}`
131-
: null,
129+
image: project.photoUrl || (project.projectPath
130+
? `https://picsum.photos/500/250?random=${project.id}`
131+
: null),
132132
}));
133133

134+
// 添加临时生成中的项目
135+
const allProjects = [...transformedProjects];
136+
137+
// 添加当前正在加载的项目(如果有且不在已有列表中)
138+
if (view === 'my' && tempLoadingProjectId && !allProjects.some(p => p.id === tempLoadingProjectId)) {
139+
allProjects.unshift({
140+
id: tempLoadingProjectId,
141+
name: 'Generating Project...',
142+
path: '',
143+
isReady: false,
144+
createDate: new Date().toISOString().split('T')[0],
145+
author: user?.username || 'Unknown',
146+
forkNum: 0,
147+
image: null,
148+
});
149+
}
150+
151+
// 添加其他待处理项目
152+
if (view === 'my') {
153+
pendingProjects
154+
.filter(p => !p.projectPath && p.id !== tempLoadingProjectId && !allProjects.some(proj => proj.id === p.id))
155+
.forEach(project => {
156+
allProjects.unshift({
157+
id: project.id,
158+
name: project.projectName || 'Generating Project...',
159+
path: '',
160+
isReady: false,
161+
createDate: project.createdAt || new Date().toISOString().split('T')[0],
162+
author: user?.username || 'Unknown',
163+
forkNum: 0,
164+
image: null,
165+
});
166+
});
167+
}
168+
134169
const handleOpenChat = (chatId: string) => {
135170
redirectChatPage(chatId, setCurrentChatid, setChatId, router);
136171
};
@@ -169,36 +204,9 @@ export function ProjectsSection() {
169204
</div>
170205
) : (
171206
<>
172-
{/* {view === 'my' && tempLoadingProjectId && (
173-
<ExpandableCard
174-
key={`loading-${tempLoadingProjectId}`}
175-
projects={[
176-
{
177-
id: tempLoadingProjectId,
178-
name: 'Generating Project...',
179-
image: null,
180-
isReady: false,
181-
createDate: new Date().toISOString().split('T')[0],
182-
author: user?.username || 'Unknown',
183-
forkNum: 0,
184-
path: '',
185-
},
186-
]}
187-
isGenerating={true}
188-
onOpenChat={() =>
189-
redirectChatPage(
190-
tempLoadingProjectId,
191-
setCurrentChatid,
192-
setChatId,
193-
router
194-
)
195-
}
196-
/>
197-
)} */}
198-
199-
{transformedProjects.length > 0 ? (
200-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
201-
{transformedProjects.map((project) => (
207+
{allProjects.length > 0 ? (
208+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 pr-2">
209+
{allProjects.map((project) => (
202210
<ExpandableCard
203211
key={project.id}
204212
projects={[project]}

0 commit comments

Comments
 (0)