Skip to content

Commit 1a4ee99

Browse files
authored
Merge pull request #8 from LIlGG/fix/multi-page-render
fix: resolve logical issues when generating multi-page data
2 parents e96c2da + 884f518 commit 1a4ee99

File tree

6 files changed

+104
-46
lines changed

6 files changed

+104
-46
lines changed

app/components/chat/Artifact.tsx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
22
import classNames from 'classnames';
33
import { AnimatePresence, motion } from 'framer-motion';
44
import { computed } from 'nanostores';
5-
import { memo, useEffect, useRef, useState } from 'react';
5+
import { memo, useEffect, useMemo, useRef, useState } from 'react';
66
import { type BundledLanguage, type BundledTheme, createHighlighter, type HighlighterGeneric } from 'shiki';
77
import type { ActionState } from '~/lib/runtime/action-runner';
88
import { webBuilderStore } from '~/lib/stores/web-builder';
@@ -22,18 +22,26 @@ if (import.meta.hot && import.meta.hot.data) {
2222

2323
interface ArtifactProps {
2424
messageId: string;
25+
pageName: string;
2526
}
2627

27-
export const Artifact = memo(({ messageId }: ArtifactProps) => {
28+
export const Artifact = memo(({ messageId, pageName }: ArtifactProps) => {
2829
const userToggledActions = useRef(false);
2930
const [showActions, setShowActions] = useState(false);
3031
const [allActionFinished, setAllActionFinished] = useState(false);
3132

3233
const artifacts = useStore(webBuilderStore.chatStore.artifacts);
33-
const artifact = artifacts[messageId];
34+
const artifact = useMemo(() => {
35+
const artifactsByPageName = artifacts.get(messageId);
36+
if (!artifactsByPageName) {
37+
return undefined;
38+
}
39+
40+
return artifactsByPageName.get(pageName);
41+
}, [artifacts, messageId, pageName]);
3442

3543
const actions = useStore(
36-
computed(artifact.runner.actions, (actions) => {
44+
computed(artifact?.runner.actions!, (actions) => {
3745
return Object.values(actions);
3846
}),
3947
);
@@ -44,11 +52,14 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
4452
};
4553

4654
useEffect(() => {
47-
const actionsMap = artifact.runner.actions.get();
55+
const actionsMap = artifact?.runner.actions.get();
56+
if (!actionsMap) {
57+
return;
58+
}
4859

4960
Object.entries(actionsMap).forEach(([actionId, action]) => {
5061
if (action.status === 'running' || action.status === 'pending') {
51-
artifact.runner.actions.setKey(actionId, {
62+
artifact?.runner.actions.setKey(actionId, {
5263
...action,
5364
status: 'aborted',
5465
});
@@ -61,7 +72,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
6172
setShowActions(true);
6273
}
6374

64-
if (actions.length !== 0 && artifact.type === 'bundled') {
75+
if (actions.length !== 0 && artifact?.type === 'bundled') {
6576
const finished = !actions.find((action) => action.status !== 'complete');
6677

6778
if (allActionFinished !== finished) {
@@ -80,7 +91,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
8091
webBuilderStore.showWorkbench.set(!showWorkbench);
8192
}}
8293
>
83-
{artifact.type == 'bundled' && (
94+
{artifact?.type == 'bundled' && (
8495
<>
8596
<div className="p-4">
8697
{allActionFinished ? (
@@ -101,7 +112,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
101112
</button>
102113
<div className="bg-upage-elements-artifacts-borderColor w-[1px]" />
103114
<AnimatePresence>
104-
{actions.length && artifact.type !== 'bundled' && (
115+
{actions.length && artifact?.type !== 'bundled' && (
105116
<motion.button
106117
initial={{ width: 0 }}
107118
animate={{ width: 'auto' }}
@@ -118,7 +129,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
118129
</AnimatePresence>
119130
</div>
120131
<AnimatePresence>
121-
{artifact.type !== 'bundled' && showActions && actions.length > 0 && (
132+
{artifact?.type !== 'bundled' && showActions && actions.length > 0 && (
122133
<motion.div
123134
className="actions"
124135
initial={{ height: 0 }}
@@ -152,6 +163,7 @@ function openArtifactInWebBuilder(pageName: string, rootDomId: string) {
152163
webBuilderStore.currentView.set('code');
153164
}
154165
webBuilderStore.setSelectedPage(pageName);
166+
webBuilderStore.setActiveSectionByPageName(pageName);
155167
webBuilderStore.editorStore.scrollToElement(rootDomId);
156168
}
157169

app/components/chat/Markdown.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
2525
div: ({ className, children, node, ...props }) => {
2626
if (className?.includes('__uPageArtifact__')) {
2727
const messageId = node?.properties.dataMessageId as string;
28+
const pageName = node?.properties.dataPageName as string;
2829

2930
if (!messageId) {
3031
logger.error(`Invalid message id ${messageId}`);
3132
}
33+
if (!pageName) {
34+
logger.error(`Invalid page name ${pageName}`);
35+
}
3236

33-
return <Artifact messageId={messageId} />;
37+
return <Artifact messageId={messageId} pageName={pageName} />;
3438
}
3539

3640
if (className?.includes('__uPageThought__')) {

app/lib/runtime/message-parser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface ParserCallbacks {
3434

3535
interface ElementFactoryProps {
3636
messageId: string;
37+
pageName: string;
3738
}
3839

3940
type ElementFactory = (props: ElementFactoryProps) => string;
@@ -207,7 +208,7 @@ export class StreamingMessageParser {
207208

208209
const artifactFactory = this._options.artifactElement ?? createArtifactElement;
209210

210-
output += artifactFactory({ messageId });
211+
output += artifactFactory({ messageId, pageName: artifactName });
211212

212213
i = openTagEnd + 1;
213214
} else {

app/lib/stores/chat.ts

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ export interface ArtifactState {
1818
}
1919

2020
export type ArtifactUpdateState = Pick<ArtifactState, 'title' | 'closed'>;
21-
22-
type Artifacts = MapStore<Record<string, ArtifactState>>;
21+
type ArtifactsByPageName = Map<string, ArtifactState>;
22+
type ArtifactsByMessageId = Map<string, ArtifactsByPageName>;
23+
type Artifacts = MapStore<ArtifactsByMessageId>;
2324

2425
export class ChatStore {
2526
private globalExecutionQueue = Promise.resolve();
@@ -31,8 +32,8 @@ export class ChatStore {
3132
currentDescription: WritableAtom<string | undefined> =
3233
import.meta.hot?.data?.currentDescription ?? atom<string | undefined>(undefined);
3334

34-
artifacts: Artifacts = import.meta.hot?.data?.artifacts ?? map({});
35-
artifactIdList: string[] = [];
35+
artifacts: Artifacts = import.meta.hot?.data?.artifacts ?? map(new Map());
36+
artifactIdList: { messageId: string; pageName: string }[] = [];
3637
actionAlert: WritableAtom<ActionAlert | undefined> =
3738
import.meta.hot?.data?.actionAlert ?? atom<ActionAlert | undefined>(undefined);
3839

@@ -60,7 +61,12 @@ export class ChatStore {
6061
}
6162

6263
get firstArtifact(): ArtifactState | undefined {
63-
return this.getArtifact(this.artifactIdList[0]);
64+
if (this.artifactIdList.length === 0) {
65+
return undefined;
66+
}
67+
68+
const { messageId, pageName } = this.artifactIdList[0];
69+
return this.getArtifact(messageId, pageName);
6470
}
6571

6672
get description() {
@@ -76,31 +82,30 @@ export class ChatStore {
7682
}
7783

7884
abortAllActions() {
79-
// TODO: what do we wanna do and how do we wanna recover from this?
8085
const artifacts = this.artifacts.get();
8186

82-
Object.values(artifacts).forEach((artifact) => {
83-
const actions = artifact.runner.actions.get();
84-
85-
Object.values(actions).forEach((action) => {
86-
if (action.status === 'running' || action.status === 'pending') {
87-
action.abort();
88-
}
87+
artifacts.values().forEach((artifactByPageNames) => {
88+
artifactByPageNames.values().forEach((artifact) => {
89+
const actions = artifact.runner.actions.get();
90+
Object.values(actions).forEach((action) => {
91+
if (action.status === 'running' || action.status === 'pending') {
92+
action.abort();
93+
}
94+
});
8995
});
9096
});
9197
}
9298

9399
addArtifact({ messageId, name, title, id }: ArtifactCallbackData) {
94-
const artifact = this.getArtifact(messageId);
100+
const artifact = this.getArtifact(messageId, name);
95101
if (artifact) {
96102
return;
97103
}
98104

99-
if (!this.artifactIdList.includes(messageId)) {
100-
this.artifactIdList.push(messageId);
105+
if (!this.artifactIdList.includes({ messageId, pageName: name })) {
106+
this.artifactIdList.push({ messageId, pageName: name });
101107
}
102-
103-
this.artifacts.setKey(messageId, {
108+
const newArtifact = {
104109
id,
105110
name,
106111
title,
@@ -112,21 +117,56 @@ export class ChatStore {
112117

113118
this.actionAlert.set(alert);
114119
}),
115-
});
120+
};
121+
122+
const artifactsByMessageId = this.artifacts.get();
123+
let artifactsByPageName = artifactsByMessageId.get(messageId);
124+
if (!artifactsByPageName) {
125+
artifactsByPageName = new Map();
126+
artifactsByMessageId.set(messageId, artifactsByPageName);
127+
}
128+
129+
artifactsByPageName.set(name, newArtifact);
130+
131+
this.artifacts.set(artifactsByMessageId);
116132
}
117133

118-
updateArtifact({ messageId }: ArtifactCallbackData, state: Partial<ArtifactUpdateState>) {
119-
const artifact = this.getArtifact(messageId);
134+
updateArtifact({ messageId, name }: ArtifactCallbackData, state: Partial<ArtifactUpdateState>) {
135+
const artifact = this.getArtifact(messageId, name);
120136
if (!artifact) {
121137
return;
122138
}
123139

124-
this.artifacts.setKey(messageId, { ...artifact, ...state });
140+
const artifactsByMessageId = this.artifacts.get();
141+
const artifactsByPageName = artifactsByMessageId.get(messageId);
142+
if (!artifactsByPageName) {
143+
return;
144+
}
145+
artifactsByPageName.set(name, { ...artifact, ...state });
146+
artifactsByMessageId.set(messageId, artifactsByPageName);
147+
148+
this.artifacts.set(artifactsByMessageId);
125149
}
126150

127-
private getArtifact(id: string) {
151+
private getArtifact(messageId: string, pageName: string) {
128152
const artifacts = this.artifacts.get();
129-
return artifacts[id];
153+
const artifactsByPageName = artifacts.get(messageId);
154+
if (!artifactsByPageName) {
155+
return undefined;
156+
}
157+
158+
return artifactsByPageName.get(pageName);
159+
}
160+
161+
private getArtifactByArtifactId(messageId: string, artifactId: string) {
162+
const artifacts = this.artifacts.get();
163+
164+
const artifactsByPageName = artifacts.get(messageId);
165+
if (!artifactsByPageName) {
166+
return undefined;
167+
}
168+
169+
return artifactsByPageName.values().find((artifact) => artifact.id === artifactId);
130170
}
131171

132172
setReloadedMessages(messages: string[]) {
@@ -138,8 +178,8 @@ export class ChatStore {
138178
}
139179

140180
private async _addAction(data: ActionCallbackData) {
141-
const { messageId } = data;
142-
const artifact = this.getArtifact(messageId);
181+
const { messageId, artifactId } = data;
182+
const artifact = this.getArtifactByArtifactId(messageId, artifactId);
143183

144184
if (!artifact) {
145185
unreachable('Artifact not found');
@@ -157,9 +197,9 @@ export class ChatStore {
157197
}
158198

159199
async _runAction(data: ActionCallbackData, isRunning: boolean = false) {
160-
const { messageId } = data;
200+
const { messageId, artifactId } = data;
161201

162-
const artifact = this.getArtifact(messageId);
202+
const artifact = this.getArtifactByArtifactId(messageId, artifactId);
163203
if (!artifact) {
164204
unreachable('Artifact not found');
165205
}

app/lib/stores/web-builder.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,20 @@ export class WebBuilderStore {
7777
// 找到第一个页面并选中
7878
for (const [pageName] of Object.entries(pages)) {
7979
this.setSelectedPage(pageName);
80+
this.setActiveSectionByPageName(pageName);
8081
break;
8182
}
8283
}
8384
}
8485

8586
setSelectedPage(pageName: string | undefined) {
8687
this.pagesStore.setActivePage(pageName);
88+
}
8789

88-
if (pageName) {
89-
const page = this.pagesStore.getPage(pageName);
90-
if (page) {
91-
this.setActiveSection(page.actionIds[page.actionIds.length - 1]);
92-
}
90+
setActiveSectionByPageName(pageName: string) {
91+
const page = this.pagesStore.getPage(pageName);
92+
if (page) {
93+
this.setActiveSection(page.actionIds[page.actionIds.length - 1]);
9394
}
9495
}
9596

app/types/artifact.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
export interface UPageArtifactData {
66
// artifact id,唯一
77
id: string;
8-
// 页面名称,最终渲染为页面文件名,如 `index.html`,不包含后缀。唯一
8+
// 页面名称,如 `index`,最终渲染为页面文件名,唯一
99
name: string;
1010
// 页面标题,最终渲染为页面标题
1111
title: string;

0 commit comments

Comments
 (0)