codacus commited on
Commit
05a5f85
·
unverified ·
2 Parent(s): f562984 417dbca

Merge pull request #504 from thecodacus/bundle-artifact

Browse files

feat(UI): added artifact bundling for custom long artifacts like uploading folder

app/components/chat/Artifact.tsx CHANGED
@@ -28,6 +28,7 @@ interface ArtifactProps {
28
  export const Artifact = memo(({ messageId }: ArtifactProps) => {
29
  const userToggledActions = useRef(false);
30
  const [showActions, setShowActions] = useState(false);
 
31
 
32
  const artifacts = useStore(workbenchStore.artifacts);
33
  const artifact = artifacts[messageId];
@@ -47,6 +48,11 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
47
  if (actions.length && !showActions && !userToggledActions.current) {
48
  setShowActions(true);
49
  }
 
 
 
 
 
50
  }, [actions]);
51
 
52
  return (
@@ -59,6 +65,18 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
59
  workbenchStore.showWorkbench.set(!showWorkbench);
60
  }}
61
  >
 
 
 
 
 
 
 
 
 
 
 
 
62
  <div className="px-5 p-3.5 w-full text-left">
63
  <div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
64
  <div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
@@ -66,7 +84,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
66
  </button>
67
  <div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
68
  <AnimatePresence>
69
- {actions.length && (
70
  <motion.button
71
  initial={{ width: 0 }}
72
  animate={{ width: 'auto' }}
@@ -83,7 +101,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
83
  </AnimatePresence>
84
  </div>
85
  <AnimatePresence>
86
- {showActions && actions.length > 0 && (
87
  <motion.div
88
  className="actions"
89
  initial={{ height: 0 }}
@@ -92,6 +110,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
92
  transition={{ duration: 0.15 }}
93
  >
94
  <div className="bg-bolt-elements-artifacts-borderColor h-[1px]" />
 
95
  <div className="p-5 text-left bg-bolt-elements-actions-background">
96
  <ActionList actions={actions} />
97
  </div>
 
28
  export const Artifact = memo(({ messageId }: ArtifactProps) => {
29
  const userToggledActions = useRef(false);
30
  const [showActions, setShowActions] = useState(false);
31
+ const [allActionFinished, setAllActionFinished] = useState(false);
32
 
33
  const artifacts = useStore(workbenchStore.artifacts);
34
  const artifact = artifacts[messageId];
 
48
  if (actions.length && !showActions && !userToggledActions.current) {
49
  setShowActions(true);
50
  }
51
+
52
+ if (actions.length !== 0) {
53
+ const finished = !actions.find((action) => action.status !== 'complete');
54
+ setAllActionFinished(finished);
55
+ }
56
  }, [actions]);
57
 
58
  return (
 
65
  workbenchStore.showWorkbench.set(!showWorkbench);
66
  }}
67
  >
68
+ {artifact.type == 'bundled' && (
69
+ <>
70
+ <div className="p-4">
71
+ {allActionFinished ? (
72
+ <div className={'i-ph:files-light'} style={{ fontSize: '2rem' }}></div>
73
+ ) : (
74
+ <div className={'i-svg-spinners:90-ring-with-bg'} style={{ fontSize: '2rem' }}></div>
75
+ )}
76
+ </div>
77
+ <div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
78
+ </>
79
+ )}
80
  <div className="px-5 p-3.5 w-full text-left">
81
  <div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
82
  <div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
 
84
  </button>
85
  <div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
86
  <AnimatePresence>
87
+ {actions.length && artifact.type !== 'bundled' && (
88
  <motion.button
89
  initial={{ width: 0 }}
90
  animate={{ width: 'auto' }}
 
101
  </AnimatePresence>
102
  </div>
103
  <AnimatePresence>
104
+ {artifact.type !== 'bundled' && showActions && actions.length > 0 && (
105
  <motion.div
106
  className="actions"
107
  initial={{ height: 0 }}
 
110
  transition={{ duration: 0.15 }}
111
  >
112
  <div className="bg-bolt-elements-artifacts-borderColor h-[1px]" />
113
+
114
  <div className="p-5 text-left bg-bolt-elements-actions-background">
115
  <ActionList actions={actions} />
116
  </div>
app/components/chat/ImportFolderButton.tsx CHANGED
@@ -79,7 +79,7 @@ ${content}
79
  role: 'assistant',
80
  content: `I'll help you set up these files.${binaryFilesMessage}
81
 
82
- <boltArtifact id="imported-files" title="Imported Files">
83
  ${fileArtifacts.join('\n\n')}
84
  </boltArtifact>`,
85
  id: generateId(),
 
79
  role: 'assistant',
80
  content: `I'll help you set up these files.${binaryFilesMessage}
81
 
82
+ <boltArtifact id="imported-files" title="Imported Files" type="bundled">
83
  ${fileArtifacts.join('\n\n')}
84
  </boltArtifact>`,
85
  id: generateId(),
app/lib/runtime/__snapshots__/message-parser.spec.ts.snap CHANGED
@@ -29,6 +29,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
29
  "id": "artifact_1",
30
  "messageId": "message_1",
31
  "title": "Some title",
 
32
  }
33
  `;
34
 
@@ -37,6 +38,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
37
  "id": "artifact_1",
38
  "messageId": "message_1",
39
  "title": "Some title",
 
40
  }
41
  `;
42
 
@@ -96,6 +98,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
96
  "id": "artifact_1",
97
  "messageId": "message_1",
98
  "title": "Some title",
 
99
  }
100
  `;
101
 
@@ -104,6 +107,7 @@ exports[`StreamingMessageParser > valid artifacts with actions > should correctl
104
  "id": "artifact_1",
105
  "messageId": "message_1",
106
  "title": "Some title",
 
107
  }
108
  `;
109
 
@@ -112,6 +116,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
112
  "id": "artifact_1",
113
  "messageId": "message_1",
114
  "title": "Some title",
 
115
  }
116
  `;
117
 
@@ -120,6 +125,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
120
  "id": "artifact_1",
121
  "messageId": "message_1",
122
  "title": "Some title",
 
123
  }
124
  `;
125
 
@@ -128,6 +134,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
128
  "id": "artifact_1",
129
  "messageId": "message_1",
130
  "title": "Some title",
 
131
  }
132
  `;
133
 
@@ -136,6 +143,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
136
  "id": "artifact_1",
137
  "messageId": "message_1",
138
  "title": "Some title",
 
139
  }
140
  `;
141
 
@@ -144,6 +152,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
144
  "id": "artifact_1",
145
  "messageId": "message_1",
146
  "title": "Some title",
 
147
  }
148
  `;
149
 
@@ -152,6 +161,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
152
  "id": "artifact_1",
153
  "messageId": "message_1",
154
  "title": "Some title",
 
155
  }
156
  `;
157
 
@@ -160,6 +170,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
160
  "id": "artifact_1",
161
  "messageId": "message_1",
162
  "title": "Some title",
 
163
  }
164
  `;
165
 
@@ -168,6 +179,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
168
  "id": "artifact_1",
169
  "messageId": "message_1",
170
  "title": "Some title",
 
171
  }
172
  `;
173
 
@@ -176,6 +188,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
176
  "id": "artifact_1",
177
  "messageId": "message_1",
178
  "title": "Some title",
 
179
  }
180
  `;
181
 
@@ -184,6 +197,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
184
  "id": "artifact_1",
185
  "messageId": "message_1",
186
  "title": "Some title",
 
187
  }
188
  `;
189
 
@@ -192,6 +206,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
192
  "id": "artifact_1",
193
  "messageId": "message_1",
194
  "title": "Some title",
 
195
  }
196
  `;
197
 
@@ -200,6 +215,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
200
  "id": "artifact_1",
201
  "messageId": "message_1",
202
  "title": "Some title",
 
203
  }
204
  `;
205
 
@@ -208,6 +224,7 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
208
  "id": "artifact_1",
209
  "messageId": "message_1",
210
  "title": "Some title",
 
211
  }
212
  `;
213
 
@@ -216,5 +233,6 @@ exports[`StreamingMessageParser > valid artifacts without actions > should corre
216
  "id": "artifact_1",
217
  "messageId": "message_1",
218
  "title": "Some title",
 
219
  }
220
  `;
 
29
  "id": "artifact_1",
30
  "messageId": "message_1",
31
  "title": "Some title",
32
+ "type": undefined,
33
  }
34
  `;
35
 
 
38
  "id": "artifact_1",
39
  "messageId": "message_1",
40
  "title": "Some title",
41
+ "type": undefined,
42
  }
43
  `;
44
 
 
98
  "id": "artifact_1",
99
  "messageId": "message_1",
100
  "title": "Some title",
101
+ "type": undefined,
102
  }
103
  `;
104
 
 
107
  "id": "artifact_1",
108
  "messageId": "message_1",
109
  "title": "Some title",
110
+ "type": undefined,
111
  }
112
  `;
113
 
 
116
  "id": "artifact_1",
117
  "messageId": "message_1",
118
  "title": "Some title",
119
+ "type": undefined,
120
  }
121
  `;
122
 
 
125
  "id": "artifact_1",
126
  "messageId": "message_1",
127
  "title": "Some title",
128
+ "type": undefined,
129
  }
130
  `;
131
 
 
134
  "id": "artifact_1",
135
  "messageId": "message_1",
136
  "title": "Some title",
137
+ "type": "bundled",
138
  }
139
  `;
140
 
 
143
  "id": "artifact_1",
144
  "messageId": "message_1",
145
  "title": "Some title",
146
+ "type": "bundled",
147
  }
148
  `;
149
 
 
152
  "id": "artifact_1",
153
  "messageId": "message_1",
154
  "title": "Some title",
155
+ "type": undefined,
156
  }
157
  `;
158
 
 
161
  "id": "artifact_1",
162
  "messageId": "message_1",
163
  "title": "Some title",
164
+ "type": undefined,
165
  }
166
  `;
167
 
 
170
  "id": "artifact_1",
171
  "messageId": "message_1",
172
  "title": "Some title",
173
+ "type": undefined,
174
  }
175
  `;
176
 
 
179
  "id": "artifact_1",
180
  "messageId": "message_1",
181
  "title": "Some title",
182
+ "type": undefined,
183
  }
184
  `;
185
 
 
188
  "id": "artifact_1",
189
  "messageId": "message_1",
190
  "title": "Some title",
191
+ "type": undefined,
192
  }
193
  `;
194
 
 
197
  "id": "artifact_1",
198
  "messageId": "message_1",
199
  "title": "Some title",
200
+ "type": undefined,
201
  }
202
  `;
203
 
 
206
  "id": "artifact_1",
207
  "messageId": "message_1",
208
  "title": "Some title",
209
+ "type": undefined,
210
  }
211
  `;
212
 
 
215
  "id": "artifact_1",
216
  "messageId": "message_1",
217
  "title": "Some title",
218
+ "type": undefined,
219
  }
220
  `;
221
 
 
224
  "id": "artifact_1",
225
  "messageId": "message_1",
226
  "title": "Some title",
227
+ "type": undefined,
228
  }
229
  `;
230
 
 
233
  "id": "artifact_1",
234
  "messageId": "message_1",
235
  "title": "Some title",
236
+ "type": undefined,
237
  }
238
  `;
app/lib/runtime/message-parser.spec.ts CHANGED
@@ -59,7 +59,11 @@ describe('StreamingMessageParser', () => {
59
  },
60
  ],
61
  [
62
- ['Some text before <boltArti', 'fact', ' title="Some title" id="artifact_1">foo</boltArtifact> Some more text'],
 
 
 
 
63
  {
64
  output: 'Some text before Some more text',
65
  callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
 
59
  },
60
  ],
61
  [
62
+ [
63
+ 'Some text before <boltArti',
64
+ 'fact',
65
+ ' title="Some title" id="artifact_1" type="bundled" >foo</boltArtifact> Some more text',
66
+ ],
67
  {
68
  output: 'Some text before Some more text',
69
  callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
app/lib/runtime/message-parser.ts CHANGED
@@ -192,6 +192,7 @@ export class StreamingMessageParser {
192
  const artifactTag = input.slice(i, openTagEnd + 1);
193
 
194
  const artifactTitle = this.#extractAttribute(artifactTag, 'title') as string;
 
195
  const artifactId = this.#extractAttribute(artifactTag, 'id') as string;
196
 
197
  if (!artifactTitle) {
@@ -207,6 +208,7 @@ export class StreamingMessageParser {
207
  const currentArtifact = {
208
  id: artifactId,
209
  title: artifactTitle,
 
210
  } satisfies BoltArtifactData;
211
 
212
  state.currentArtifact = currentArtifact;
 
192
  const artifactTag = input.slice(i, openTagEnd + 1);
193
 
194
  const artifactTitle = this.#extractAttribute(artifactTag, 'title') as string;
195
+ const type = this.#extractAttribute(artifactTag, 'type') as string;
196
  const artifactId = this.#extractAttribute(artifactTag, 'id') as string;
197
 
198
  if (!artifactTitle) {
 
208
  const currentArtifact = {
209
  id: artifactId,
210
  title: artifactTitle,
211
+ type,
212
  } satisfies BoltArtifactData;
213
 
214
  state.currentArtifact = currentArtifact;
app/lib/stores/workbench.ts CHANGED
@@ -19,6 +19,7 @@ import { description } from '~/lib/persistence';
19
  export interface ArtifactState {
20
  id: string;
21
  title: string;
 
22
  closed: boolean;
23
  runner: ActionRunner;
24
  }
@@ -230,7 +231,7 @@ export class WorkbenchStore {
230
  // TODO: what do we wanna do and how do we wanna recover from this?
231
  }
232
 
233
- addArtifact({ messageId, title, id }: ArtifactCallbackData) {
234
  const artifact = this.#getArtifact(messageId);
235
 
236
  if (artifact) {
@@ -245,6 +246,7 @@ export class WorkbenchStore {
245
  id,
246
  title,
247
  closed: false,
 
248
  runner: new ActionRunner(webcontainer, () => this.boltTerminal),
249
  });
250
  }
 
19
  export interface ArtifactState {
20
  id: string;
21
  title: string;
22
+ type?: string;
23
  closed: boolean;
24
  runner: ActionRunner;
25
  }
 
231
  // TODO: what do we wanna do and how do we wanna recover from this?
232
  }
233
 
234
+ addArtifact({ messageId, title, id, type }: ArtifactCallbackData) {
235
  const artifact = this.#getArtifact(messageId);
236
 
237
  if (artifact) {
 
246
  id,
247
  title,
248
  closed: false,
249
+ type,
250
  runner: new ActionRunner(webcontainer, () => this.boltTerminal),
251
  });
252
  }
app/types/artifact.ts CHANGED
@@ -1,4 +1,5 @@
1
  export interface BoltArtifactData {
2
  id: string;
3
  title: string;
 
4
  }
 
1
  export interface BoltArtifactData {
2
  id: string;
3
  title: string;
4
+ type?: string | undefined;
5
  }