diff --git a/app/components/chat/Artifact.tsx b/app/components/chat/Artifact.tsx
index be42075..ddff627 100644
--- a/app/components/chat/Artifact.tsx
+++ b/app/components/chat/Artifact.tsx
@@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
import classNames from 'classnames';
import { AnimatePresence, motion } from 'framer-motion';
import { computed } from 'nanostores';
-import { memo, useEffect, useRef, useState } from 'react';
+import { memo, useEffect, useMemo, useRef, useState } from 'react';
import { type BundledLanguage, type BundledTheme, createHighlighter, type HighlighterGeneric } from 'shiki';
import type { ActionState } from '~/lib/runtime/action-runner';
import { webBuilderStore } from '~/lib/stores/web-builder';
@@ -22,18 +22,26 @@ if (import.meta.hot && import.meta.hot.data) {
interface ArtifactProps {
messageId: string;
+ pageName: string;
}
-export const Artifact = memo(({ messageId }: ArtifactProps) => {
+export const Artifact = memo(({ messageId, pageName }: ArtifactProps) => {
const userToggledActions = useRef(false);
const [showActions, setShowActions] = useState(false);
const [allActionFinished, setAllActionFinished] = useState(false);
const artifacts = useStore(webBuilderStore.chatStore.artifacts);
- const artifact = artifacts[messageId];
+ const artifact = useMemo(() => {
+ const artifactsByPageName = artifacts.get(messageId);
+ if (!artifactsByPageName) {
+ return undefined;
+ }
+
+ return artifactsByPageName.get(pageName);
+ }, [artifacts, messageId, pageName]);
const actions = useStore(
- computed(artifact.runner.actions, (actions) => {
+ computed(artifact?.runner.actions!, (actions) => {
return Object.values(actions);
}),
);
@@ -44,11 +52,14 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
};
useEffect(() => {
- const actionsMap = artifact.runner.actions.get();
+ const actionsMap = artifact?.runner.actions.get();
+ if (!actionsMap) {
+ return;
+ }
Object.entries(actionsMap).forEach(([actionId, action]) => {
if (action.status === 'running' || action.status === 'pending') {
- artifact.runner.actions.setKey(actionId, {
+ artifact?.runner.actions.setKey(actionId, {
...action,
status: 'aborted',
});
@@ -61,7 +72,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
setShowActions(true);
}
- if (actions.length !== 0 && artifact.type === 'bundled') {
+ if (actions.length !== 0 && artifact?.type === 'bundled') {
const finished = !actions.find((action) => action.status !== 'complete');
if (allActionFinished !== finished) {
@@ -80,7 +91,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
webBuilderStore.showWorkbench.set(!showWorkbench);
}}
>
- {artifact.type == 'bundled' && (
+ {artifact?.type == 'bundled' && (
<>
{allActionFinished ? (
@@ -101,7 +112,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
- {actions.length && artifact.type !== 'bundled' && (
+ {actions.length && artifact?.type !== 'bundled' && (
{
- {artifact.type !== 'bundled' && showActions && actions.length > 0 && (
+ {artifact?.type !== 'bundled' && showActions && actions.length > 0 && (
{
if (className?.includes('__uPageArtifact__')) {
const messageId = node?.properties.dataMessageId as string;
+ const pageName = node?.properties.dataPageName as string;
if (!messageId) {
logger.error(`Invalid message id ${messageId}`);
}
+ if (!pageName) {
+ logger.error(`Invalid page name ${pageName}`);
+ }
- return ;
+ return ;
}
if (className?.includes('__uPageThought__')) {
diff --git a/app/lib/runtime/message-parser.ts b/app/lib/runtime/message-parser.ts
index d65e086..1c17d17 100644
--- a/app/lib/runtime/message-parser.ts
+++ b/app/lib/runtime/message-parser.ts
@@ -34,6 +34,7 @@ export interface ParserCallbacks {
interface ElementFactoryProps {
messageId: string;
+ pageName: string;
}
type ElementFactory = (props: ElementFactoryProps) => string;
@@ -207,7 +208,7 @@ export class StreamingMessageParser {
const artifactFactory = this._options.artifactElement ?? createArtifactElement;
- output += artifactFactory({ messageId });
+ output += artifactFactory({ messageId, pageName: artifactName });
i = openTagEnd + 1;
} else {
diff --git a/app/lib/stores/chat.ts b/app/lib/stores/chat.ts
index 6266074..787aa1b 100644
--- a/app/lib/stores/chat.ts
+++ b/app/lib/stores/chat.ts
@@ -18,8 +18,9 @@ export interface ArtifactState {
}
export type ArtifactUpdateState = Pick;
-
-type Artifacts = MapStore>;
+type ArtifactsByPageName = Map;
+type ArtifactsByMessageId = Map;
+type Artifacts = MapStore;
export class ChatStore {
private globalExecutionQueue = Promise.resolve();
@@ -31,8 +32,8 @@ export class ChatStore {
currentDescription: WritableAtom =
import.meta.hot?.data?.currentDescription ?? atom(undefined);
- artifacts: Artifacts = import.meta.hot?.data?.artifacts ?? map({});
- artifactIdList: string[] = [];
+ artifacts: Artifacts = import.meta.hot?.data?.artifacts ?? map(new Map());
+ artifactIdList: { messageId: string; pageName: string }[] = [];
actionAlert: WritableAtom =
import.meta.hot?.data?.actionAlert ?? atom(undefined);
@@ -60,7 +61,12 @@ export class ChatStore {
}
get firstArtifact(): ArtifactState | undefined {
- return this.getArtifact(this.artifactIdList[0]);
+ if (this.artifactIdList.length === 0) {
+ return undefined;
+ }
+
+ const { messageId, pageName } = this.artifactIdList[0];
+ return this.getArtifact(messageId, pageName);
}
get description() {
@@ -76,31 +82,30 @@ export class ChatStore {
}
abortAllActions() {
- // TODO: what do we wanna do and how do we wanna recover from this?
const artifacts = this.artifacts.get();
- Object.values(artifacts).forEach((artifact) => {
- const actions = artifact.runner.actions.get();
-
- Object.values(actions).forEach((action) => {
- if (action.status === 'running' || action.status === 'pending') {
- action.abort();
- }
+ artifacts.values().forEach((artifactByPageNames) => {
+ artifactByPageNames.values().forEach((artifact) => {
+ const actions = artifact.runner.actions.get();
+ Object.values(actions).forEach((action) => {
+ if (action.status === 'running' || action.status === 'pending') {
+ action.abort();
+ }
+ });
});
});
}
addArtifact({ messageId, name, title, id }: ArtifactCallbackData) {
- const artifact = this.getArtifact(messageId);
+ const artifact = this.getArtifact(messageId, name);
if (artifact) {
return;
}
- if (!this.artifactIdList.includes(messageId)) {
- this.artifactIdList.push(messageId);
+ if (!this.artifactIdList.includes({ messageId, pageName: name })) {
+ this.artifactIdList.push({ messageId, pageName: name });
}
-
- this.artifacts.setKey(messageId, {
+ const newArtifact = {
id,
name,
title,
@@ -112,21 +117,56 @@ export class ChatStore {
this.actionAlert.set(alert);
}),
- });
+ };
+
+ const artifactsByMessageId = this.artifacts.get();
+ let artifactsByPageName = artifactsByMessageId.get(messageId);
+ if (!artifactsByPageName) {
+ artifactsByPageName = new Map();
+ artifactsByMessageId.set(messageId, artifactsByPageName);
+ }
+
+ artifactsByPageName.set(name, newArtifact);
+
+ this.artifacts.set(artifactsByMessageId);
}
- updateArtifact({ messageId }: ArtifactCallbackData, state: Partial) {
- const artifact = this.getArtifact(messageId);
+ updateArtifact({ messageId, name }: ArtifactCallbackData, state: Partial) {
+ const artifact = this.getArtifact(messageId, name);
if (!artifact) {
return;
}
- this.artifacts.setKey(messageId, { ...artifact, ...state });
+ const artifactsByMessageId = this.artifacts.get();
+ const artifactsByPageName = artifactsByMessageId.get(messageId);
+ if (!artifactsByPageName) {
+ return;
+ }
+ artifactsByPageName.set(name, { ...artifact, ...state });
+ artifactsByMessageId.set(messageId, artifactsByPageName);
+
+ this.artifacts.set(artifactsByMessageId);
}
- private getArtifact(id: string) {
+ private getArtifact(messageId: string, pageName: string) {
const artifacts = this.artifacts.get();
- return artifacts[id];
+ const artifactsByPageName = artifacts.get(messageId);
+ if (!artifactsByPageName) {
+ return undefined;
+ }
+
+ return artifactsByPageName.get(pageName);
+ }
+
+ private getArtifactByArtifactId(messageId: string, artifactId: string) {
+ const artifacts = this.artifacts.get();
+
+ const artifactsByPageName = artifacts.get(messageId);
+ if (!artifactsByPageName) {
+ return undefined;
+ }
+
+ return artifactsByPageName.values().find((artifact) => artifact.id === artifactId);
}
setReloadedMessages(messages: string[]) {
@@ -138,8 +178,8 @@ export class ChatStore {
}
private async _addAction(data: ActionCallbackData) {
- const { messageId } = data;
- const artifact = this.getArtifact(messageId);
+ const { messageId, artifactId } = data;
+ const artifact = this.getArtifactByArtifactId(messageId, artifactId);
if (!artifact) {
unreachable('Artifact not found');
@@ -157,9 +197,9 @@ export class ChatStore {
}
async _runAction(data: ActionCallbackData, isRunning: boolean = false) {
- const { messageId } = data;
+ const { messageId, artifactId } = data;
- const artifact = this.getArtifact(messageId);
+ const artifact = this.getArtifactByArtifactId(messageId, artifactId);
if (!artifact) {
unreachable('Artifact not found');
}
diff --git a/app/lib/stores/web-builder.ts b/app/lib/stores/web-builder.ts
index 27da7f2..f71082b 100644
--- a/app/lib/stores/web-builder.ts
+++ b/app/lib/stores/web-builder.ts
@@ -77,6 +77,7 @@ export class WebBuilderStore {
// 找到第一个页面并选中
for (const [pageName] of Object.entries(pages)) {
this.setSelectedPage(pageName);
+ this.setActiveSectionByPageName(pageName);
break;
}
}
@@ -84,12 +85,12 @@ export class WebBuilderStore {
setSelectedPage(pageName: string | undefined) {
this.pagesStore.setActivePage(pageName);
+ }
- if (pageName) {
- const page = this.pagesStore.getPage(pageName);
- if (page) {
- this.setActiveSection(page.actionIds[page.actionIds.length - 1]);
- }
+ setActiveSectionByPageName(pageName: string) {
+ const page = this.pagesStore.getPage(pageName);
+ if (page) {
+ this.setActiveSection(page.actionIds[page.actionIds.length - 1]);
}
}
diff --git a/app/types/artifact.ts b/app/types/artifact.ts
index 3c37256..4665d93 100644
--- a/app/types/artifact.ts
+++ b/app/types/artifact.ts
@@ -5,7 +5,7 @@
export interface UPageArtifactData {
// artifact id,唯一
id: string;
- // 页面名称,最终渲染为页面文件名,如 `index.html`,不包含后缀。唯一
+ // 页面名称,如 `index`,最终渲染为页面文件名,唯一
name: string;
// 页面标题,最终渲染为页面标题
title: string;