Skip to content

Commit a320a10

Browse files
committed
[refactor] simplify Status Check polling of Prototype Generation
[optimize] move Prototype Generator to Evaluation Scope lines [optimize] update Upstream packages
1 parent c5893d3 commit a320a10

File tree

6 files changed

+442
-427
lines changed

6 files changed

+442
-427
lines changed

components/Project/EvaluationDisplay.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
import { RequirementEvaluation, UserRole } from '@idea2app/data-server';
1+
import {
2+
PrototypeType,
3+
PrototypeVersion,
4+
RequirementEvaluation,
5+
UserRole,
6+
} from '@idea2app/data-server';
27
import { Box, Typography } from '@mui/material';
38
import { observer } from 'mobx-react';
49
import { FC, useContext } from 'react';
510

611
import { i18n, I18nContext } from '../../models/Translation';
712
import userStore from '../../models/User';
13+
import {
14+
PrototypeGeneratorToolbar,
15+
PrototypeGeneratorToolbarProps,
16+
} from './PrototypeGeneratorToolbar';
817

918
export const DevelopmentScopeName = ({ t }: typeof i18n) => [
1019
t('product_prototype'),
@@ -14,7 +23,13 @@ export const DevelopmentScopeName = ({ t }: typeof i18n) => [
1423
t('server'),
1524
];
1625

17-
export const EvaluationDisplay: FC<RequirementEvaluation> = observer(
26+
export interface EvaluationDisplayProps
27+
extends RequirementEvaluation,
28+
Pick<PrototypeGeneratorToolbarProps, 'projectId' | 'messageId'> {
29+
prototypes?: PrototypeVersion[];
30+
}
31+
32+
export const EvaluationDisplay: FC<EvaluationDisplayProps> = observer(
1833
({
1934
title,
2035
scopes = [],
@@ -25,6 +40,9 @@ export const EvaluationDisplay: FC<RequirementEvaluation> = observer(
2540
monthPeriod,
2641
budget,
2742
factor,
43+
projectId,
44+
messageId,
45+
prototypes,
2846
}) => {
2947
const i18n = useContext(I18nContext);
3048
const { t } = i18n,
@@ -64,11 +82,29 @@ export const EvaluationDisplay: FC<RequirementEvaluation> = observer(
6482
{t('development_scopes')}
6583
</Typography>
6684
<Box component="ul" sx={{ mt: 0.5 }}>
67-
{scopes.map(scope => (
68-
<Box key={scope} component="li" sx={{ ml: 1 }}>
69-
{DevelopmentScopeName(i18n)[scope]}
70-
</Box>
71-
))}
85+
{scopes.map(scope => {
86+
const prototypeType = (
87+
scope === 2 ? 'desktop' : scope === 3 ? 'mobile' : undefined
88+
) as PrototypeType;
89+
90+
return (
91+
<Box
92+
key={scope}
93+
component="li"
94+
sx={{ ml: 1, display: 'flex', alignItems: 'center', gap: 1 }}
95+
>
96+
{DevelopmentScopeName(i18n)[scope]}
97+
98+
{prototypeType && (
99+
<PrototypeGeneratorToolbar
100+
{...{ projectId, messageId }}
101+
type={prototypeType}
102+
prototype={prototypes?.find(({ type }) => type === prototypeType)}
103+
/>
104+
)}
105+
</Box>
106+
);
107+
})}
72108
</Box>
73109
</Box>
74110
)}

components/Project/PrototypeGeneratorToolbar.tsx

Lines changed: 35 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
import { PrototypeVersion, PrototypeVersionStatus } from '@idea2app/data-server';
1+
import { PrototypeType, PrototypeVersion } from '@idea2app/data-server';
22
import { Box, Button, CircularProgress, Link, Typography } from '@mui/material';
33
import { observable } from 'mobx';
44
import { observer } from 'mobx-react';
55
import { ObservedComponent } from 'mobx-react-helper';
6-
import { sleep } from 'web-utility';
6+
import { createRef } from 'react';
7+
import { inViewport, sleep } from 'web-utility';
78

89
import { PrototypeVersionModel } from '../../models/PrototypeVersion';
910
import { i18n, I18nContext } from '../../models/Translation';
1011

1112
export interface PrototypeGeneratorToolbarProps {
1213
projectId: number;
1314
messageId: number;
15+
type: PrototypeType;
16+
prototype?: PrototypeVersion;
1417
}
1518

1619
@observer
@@ -20,86 +23,55 @@ export class PrototypeGeneratorToolbar extends ObservedComponent<
2023
> {
2124
static contextType = I18nContext;
2225

23-
versionStore = new PrototypeVersionModel(this.props.projectId);
26+
versionStore = new PrototypeVersionModel(this.props.projectId, this.props.type);
2427

2528
@observable
26-
accessor version: PrototypeVersion | undefined;
29+
accessor version = this.props.prototype;
2730

28-
@observable
29-
accessor isPolling = false;
31+
private root = createRef<HTMLElement>();
3032

3133
componentDidMount() {
32-
void this.loadVersion();
33-
}
34-
35-
componentDidUpdate(prevProps: PrototypeGeneratorToolbarProps) {
36-
if (prevProps.messageId !== this.props.messageId) {
37-
void this.loadVersion();
38-
}
39-
40-
if (this.version?.status === 'processing') {
41-
this.startPolling();
42-
}
43-
}
44-
45-
componentWillUnmount() {
46-
this.isPolling = false;
47-
}
34+
super.componentDidMount();
4835

49-
async loadVersion() {
50-
const existingVersion = await this.versionStore.getVersionByMessageId(this.props.messageId);
51-
this.version = existingVersion;
36+
this.pollStatusCheck();
5237
}
5338

54-
async startPolling() {
55-
if (this.isPolling) return;
39+
async pollStatusCheck() {
40+
const { props, version } = this,
41+
rootElement = this.root.current;
5642

57-
this.isPolling = true;
43+
while (version?.status === 'pending' || version?.status === 'processing') {
44+
if (!rootElement?.isConnected) break;
5845

59-
for (let i = 0; i < 100; i++) {
60-
if (!this.isPolling) break;
46+
if (inViewport(rootElement))
47+
this.version = await this.versionStore.getOne(props.prototype!.id);
6148

6249
await sleep(3);
63-
64-
const updatedVersion = await this.versionStore.getVersionByMessageId(this.props.messageId);
65-
this.version = updatedVersion;
66-
67-
if (updatedVersion?.status === 'completed' || updatedVersion?.status === 'failed') {
68-
this.isPolling = false;
69-
break;
70-
}
7150
}
72-
73-
this.isPolling = false;
7451
}
7552

7653
handleGenerateClick = async () => {
77-
try {
78-
const newVersion = await this.versionStore.updateOne({
79-
evaluationMessage: this.props.messageId,
80-
});
81-
if (newVersion) {
82-
this.version = newVersion;
83-
}
84-
} catch (error) {
85-
console.error('Failed to create prototype version:', error);
86-
}
54+
this.version = await this.versionStore.updateOne({
55+
evaluationMessage: this.props.messageId,
56+
});
57+
58+
return this.pollStatusCheck();
8759
};
8860

8961
renderPending() {
9062
const { t } = this.observedContext;
91-
const { versionStore } = this;
63+
const loading = this.versionStore.uploading > 0;
9264

9365
return (
9466
<Button
9567
variant="contained"
9668
color="primary"
9769
size="small"
98-
disabled={versionStore.uploading > 0}
70+
disabled={loading}
9971
sx={{ textTransform: 'none' }}
10072
onClick={this.handleGenerateClick}
10173
>
102-
{versionStore.uploading > 0 ? t('generating') : t('generate_prototype')}
74+
{loading ? t('generating') : t('generate_prototype')}
10375
</Button>
10476
);
10577
}
@@ -117,13 +89,13 @@ export class PrototypeGeneratorToolbar extends ObservedComponent<
11789

11890
renderCompleted() {
11991
const { t } = this.observedContext;
120-
const { version } = this;
92+
const { previewLink, gitLogsLink } = this.version || {};
12193

12294
return (
12395
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
124-
{version!.previewLink && (
96+
{previewLink && (
12597
<Link
126-
href={version!.previewLink}
98+
href={previewLink}
12799
target="_blank"
128100
rel="noopener noreferrer"
129101
sx={{
@@ -136,9 +108,9 @@ export class PrototypeGeneratorToolbar extends ObservedComponent<
136108
{t('view_preview')}
137109
</Link>
138110
)}
139-
{version!.gitLogsLink && (
111+
{gitLogsLink && (
140112
<Link
141-
href={version!.gitLogsLink}
113+
href={gitLogsLink}
142114
target="_blank"
143115
rel="noopener noreferrer"
144116
sx={{
@@ -157,16 +129,16 @@ export class PrototypeGeneratorToolbar extends ObservedComponent<
157129

158130
renderFailed() {
159131
const { t } = this.observedContext;
160-
const { version } = this;
132+
const { errorMessage, gitLogsLink } = this.version || {};
161133

162134
return (
163135
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
164136
<Typography variant="body2" color="error" sx={{ fontSize: '0.875rem' }}>
165-
{version!.errorMessage || t('prototype_generation_failed')}
137+
{errorMessage || t('prototype_generation_failed')}
166138
</Typography>
167-
{version!.gitLogsLink && (
139+
{gitLogsLink && (
168140
<Link
169-
href={version!.gitLogsLink}
141+
href={gitLogsLink}
170142
target="_blank"
171143
rel="noopener noreferrer"
172144
sx={{
@@ -187,14 +159,7 @@ export class PrototypeGeneratorToolbar extends ObservedComponent<
187159
const { version } = this;
188160

189161
return (
190-
<Box
191-
sx={{
192-
mt: 1.5,
193-
pt: 1.5,
194-
borderTop: '1px solid',
195-
borderColor: 'divider',
196-
}}
197-
>
162+
<Box ref={this.root} sx={{ borderTop: '1px solid', borderColor: 'divider' }}>
198163
{!version || version.status === 'pending'
199164
? this.renderPending()
200165
: version.status === 'processing'

models/PrototypeVersion.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PrototypeVersion } from '@idea2app/data-server';
1+
import { PrototypeType, PrototypeVersion } from '@idea2app/data-server';
22
import { toggle } from 'mobx-restful';
33

44
import { TableModel } from './Base';
@@ -8,9 +8,12 @@ export class PrototypeVersionModel extends TableModel<PrototypeVersion> {
88
baseURI = '';
99
client = userStore.client;
1010

11-
constructor(public projectId: number) {
11+
constructor(
12+
public projectId: number,
13+
public type: PrototypeType,
14+
) {
1215
super();
13-
this.baseURI = `project/${projectId}/prototype-version`;
16+
this.baseURI = `project/${projectId}/prototype/${type}/version`;
1417
}
1518

1619
@toggle('downloading')

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@mui/material": "^7.3.4",
1818
"@mui/material-nextjs": "^7.3.3",
1919
"@passwordless-id/webauthn": "^2.3.1",
20-
"@sentry/nextjs": "^10.20.0",
20+
"@sentry/nextjs": "^10.21.0",
2121
"file-type": "^21.0.0",
2222
"idb-keyval": "^6.2.2",
2323
"jsonwebtoken": "^9.0.2",
@@ -30,7 +30,7 @@
3030
"mobx": "^6.15.0",
3131
"mobx-github": "^0.6.0",
3232
"mobx-i18n": "^0.7.2",
33-
"mobx-lark": "^2.4.3",
33+
"mobx-lark": "^2.5.0",
3434
"mobx-react": "^9.2.1",
3535
"mobx-react-helper": "^0.5.1",
3636
"mobx-restful": "^2.1.4",
@@ -39,17 +39,17 @@
3939
"next-ssr-middleware": "^1.0.3",
4040
"react": "^19.2.0",
4141
"react-dom": "^19.2.0",
42-
"web-utility": "^4.6.2",
42+
"web-utility": "^4.6.3",
4343
"webpack": "^5.102.1"
4444
},
4545
"devDependencies": {
4646
"@babel/plugin-proposal-decorators": "^7.28.0",
4747
"@babel/plugin-transform-typescript": "^7.28.0",
4848
"@babel/preset-react": "^7.27.1",
49-
"@cspell/eslint-plugin": "^9.2.1",
49+
"@cspell/eslint-plugin": "^9.2.2",
5050
"@eslint/compat": "^1.4.0",
5151
"@eslint/js": "^9.38.0",
52-
"@idea2app/data-server": "^1.0.0-rc.2",
52+
"@idea2app/data-server": "^1.0.0-rc.3",
5353
"@next/eslint-plugin-next": "^15.5.6",
5454
"@stylistic/eslint-plugin": "^5.5.0",
5555
"@tailwindcss/postcss": "^4.1.15",
@@ -59,7 +59,7 @@
5959
"@types/koa": "^3.0.0",
6060
"@types/lodash.debounce": "^4.0.9",
6161
"@types/next-pwa": "^5.6.9",
62-
"@types/node": "^22.18.11",
62+
"@types/node": "^22.18.12",
6363
"@types/react": "^19.2.2",
6464
"eslint": "^9.38.0",
6565
"eslint-config-next": "^15.5.6",

pages/dashboard/project/[id].tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default class ProjectEvaluationPage extends ObservedComponent<
7171
};
7272

7373
renderChatMessage = (
74-
{ id, content, evaluation, createdAt, createdBy }: ConsultMessage,
74+
{ id, content, evaluation, prototypes, createdAt, createdBy }: ConsultMessage,
7575
index = 0,
7676
{ length }: ConsultMessage[],
7777
) => {
@@ -123,10 +123,12 @@ export default class ProjectEvaluationPage extends ObservedComponent<
123123
/>
124124
)}
125125
{evaluation && (
126-
<>
127-
<EvaluationDisplay {...evaluation} />
128-
<PrototypeGeneratorToolbar projectId={this.projectId} messageId={id as number} />
129-
</>
126+
<EvaluationDisplay
127+
{...evaluation}
128+
projectId={this.projectId}
129+
messageId={id}
130+
prototypes={prototypes}
131+
/>
130132
)}
131133
{createdAt && (
132134
<Typography variant="caption" sx={{ opacity: 0.6, fontSize: '0.75rem' }}>

0 commit comments

Comments
 (0)