@agimon-ai/po-artifact 0.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/apiContracts.cjs +112 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +107 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/native.mjs +700 -0
- package/dist/extensions/native.mjs.map +1 -0
- package/dist/extensions/server.cjs +561 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +532 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +507 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +0 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +152 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineSessionStore, defineWebPlugin, sessionApiAddress } from "@agimon-ai/po-core/web";
|
|
2
|
+
import { Breadcrumb, Button, CodeEditor, EmptyState, FileIcon, Markdown, MediaPreview } from "@agimon-ai/po-web-components";
|
|
3
|
+
import { useStore } from "@tanstack/react-store";
|
|
4
|
+
import { useCallback, useEffect, useState } from "react";
|
|
5
|
+
import { sealedTransport } from "@agimon-ai/po-web-security/browser";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
const api = createApiClient({
|
|
8
|
+
list: {
|
|
9
|
+
method: "GET",
|
|
10
|
+
path: "/",
|
|
11
|
+
response: void 0
|
|
12
|
+
},
|
|
13
|
+
preview: {
|
|
14
|
+
method: "GET",
|
|
15
|
+
path: "/preview",
|
|
16
|
+
query: ["path"],
|
|
17
|
+
response: void 0
|
|
18
|
+
},
|
|
19
|
+
content: {
|
|
20
|
+
method: "GET",
|
|
21
|
+
path: "/content",
|
|
22
|
+
query: ["path"],
|
|
23
|
+
raw: true
|
|
24
|
+
}
|
|
25
|
+
}, {
|
|
26
|
+
scopes: ["session"],
|
|
27
|
+
basePath: "artifacts",
|
|
28
|
+
transport: (input, init) => sealedTransport.fetch(input, init),
|
|
29
|
+
sessionAddress: sessionApiAddress
|
|
30
|
+
});
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/types/artifacts.ts
|
|
33
|
+
const artifactsChannelType = "artifacts";
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/artifactView.ts
|
|
36
|
+
function sortedArtifacts(items) {
|
|
37
|
+
return [...items].sort((left, right) => right.modifiedAt - left.modifiedAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
38
|
+
}
|
|
39
|
+
function artifactTabId(sessionId, path) {
|
|
40
|
+
return `artifact:${encodeURIComponent(sessionId)}:${encodeURIComponent(path)}`;
|
|
41
|
+
}
|
|
42
|
+
/** Resolves document assets within the artifact folder, never into another filesystem root. */
|
|
43
|
+
function artifactAssetPath(documentPath, imagePath) {
|
|
44
|
+
if (!imagePath || imagePath.startsWith("/") || imagePath.includes("\\") || /^[a-z][a-z0-9+.-]*:/iu.test(imagePath)) throw new Error("Artifact images must use a relative path.");
|
|
45
|
+
const segments = documentPath.split("/").slice(0, -1);
|
|
46
|
+
for (const segment of imagePath.split("/")) {
|
|
47
|
+
if (segment === "" || segment === ".") continue;
|
|
48
|
+
if (segment === "..") {
|
|
49
|
+
if (segments.length === 0) throw new Error("The image is outside this artifact folder.");
|
|
50
|
+
segments.pop();
|
|
51
|
+
} else segments.push(segment);
|
|
52
|
+
}
|
|
53
|
+
if (segments.length === 0) throw new Error("The image path is empty.");
|
|
54
|
+
return segments.join("/");
|
|
55
|
+
}
|
|
56
|
+
function artifactPreviewMode(path) {
|
|
57
|
+
if (/\.(?:png|jpe?g|gif|webp|avif|bmp|svg|ico|mp3|wav|m4a|ogg|mp4|mov|webm|pdf)$/iu.test(path)) return "media";
|
|
58
|
+
if (/\.(?:md|markdown)$/iu.test(path)) return "markdown";
|
|
59
|
+
if (/\.html?$/iu.test(path)) return "html";
|
|
60
|
+
if (/\.(?:[cm]?[jt]sx?|json|ya?ml|css|scss|py|sh|bash|go|rs|rb|java|c|cpp|h|sql|xml|toml)$/iu.test(path)) return "code";
|
|
61
|
+
return "text";
|
|
62
|
+
}
|
|
63
|
+
/** A malformed channel frame must not replace the current session's known inventory. */
|
|
64
|
+
function parseArtifactSnapshot(input) {
|
|
65
|
+
if (typeof input !== "object" || input === null || !("items" in input) || !Array.isArray(input.items)) return null;
|
|
66
|
+
if ("error" in input && input.error !== void 0 && typeof input.error !== "string") return null;
|
|
67
|
+
const items = [];
|
|
68
|
+
for (const item of input.items) {
|
|
69
|
+
if (typeof item !== "object" || item === null || typeof item.path !== "string" || !item.path || item.path.startsWith("/") || item.path.includes("\\") || item.path.split("/").some((part) => part === ".." || part === ".") || typeof item.name !== "string" || !item.name || typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0 || typeof item.modifiedAt !== "number" || !Number.isFinite(item.modifiedAt) || item.modifiedAt < 0) return null;
|
|
70
|
+
items.push({
|
|
71
|
+
path: item.path,
|
|
72
|
+
name: item.name,
|
|
73
|
+
size: item.size,
|
|
74
|
+
modifiedAt: item.modifiedAt
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
items: sortedArtifacts(items),
|
|
79
|
+
..."error" in input && typeof input.error === "string" ? { error: input.error } : {}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/artifactsStore.ts
|
|
84
|
+
const artifacts = defineSessionStore({
|
|
85
|
+
items: [],
|
|
86
|
+
loaded: false
|
|
87
|
+
});
|
|
88
|
+
const artifactsChannel = artifacts.channel({
|
|
89
|
+
channel: artifactsChannelType,
|
|
90
|
+
parse: parseArtifactSnapshot,
|
|
91
|
+
reduce: (_current, snapshot) => ({
|
|
92
|
+
...snapshot,
|
|
93
|
+
loaded: true
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
/** Artifact documents are outcomes, not background work. The source also keeps the empty launcher visible. */
|
|
97
|
+
const artifactActivitySource = {
|
|
98
|
+
subscribe(listener) {
|
|
99
|
+
const subscription = artifacts.store.subscribe(listener);
|
|
100
|
+
return () => subscription.unsubscribe();
|
|
101
|
+
},
|
|
102
|
+
isActive: () => false
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/artifactsApi.ts
|
|
106
|
+
async function refreshArtifacts(sessionId, signal) {
|
|
107
|
+
const before = artifacts.select(artifacts.store.state, sessionId);
|
|
108
|
+
try {
|
|
109
|
+
const result = await api.session(sessionId).list({ signal });
|
|
110
|
+
if (signal.aborted || artifacts.select(artifacts.store.state, sessionId) !== before) return;
|
|
111
|
+
const snapshot = result.ok ? parseArtifactSnapshot(result.data) : null;
|
|
112
|
+
artifacts.update(sessionId, () => snapshot ? {
|
|
113
|
+
...snapshot,
|
|
114
|
+
loaded: true
|
|
115
|
+
} : {
|
|
116
|
+
...before,
|
|
117
|
+
loaded: true,
|
|
118
|
+
error: result.ok ? "The session answered with no artifact list." : result.error || "Artifacts could not be loaded."
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (!signal.aborted && artifacts.select(artifacts.store.state, sessionId) === before) artifacts.update(sessionId, () => ({
|
|
122
|
+
...before,
|
|
123
|
+
loaded: true,
|
|
124
|
+
error: error instanceof Error ? error.message : String(error)
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function fetchArtifactPreview(sessionId, path, signal) {
|
|
129
|
+
const result = await api.session(sessionId).preview({
|
|
130
|
+
query: { path },
|
|
131
|
+
signal
|
|
132
|
+
});
|
|
133
|
+
if (!result.ok) throw new Error(result.error || "This artifact could not be read.");
|
|
134
|
+
const preview = result.data;
|
|
135
|
+
if (!preview || typeof preview.path !== "string" || typeof preview.content !== "string" || typeof preview.mimeType !== "string") throw new Error("The session answered with no artifact.");
|
|
136
|
+
return preview;
|
|
137
|
+
}
|
|
138
|
+
/** Generated raw calls keep media and Markdown asset reads on the sealed artifact transport. */
|
|
139
|
+
async function artifactBlob(sessionId, path, signal) {
|
|
140
|
+
const result = await api.session(sessionId).content({
|
|
141
|
+
query: { path },
|
|
142
|
+
...signal ? { signal } : {}
|
|
143
|
+
});
|
|
144
|
+
if (!result.ok) throw new Error(result.error || "This artifact could not be loaded.");
|
|
145
|
+
return result.response.blob();
|
|
146
|
+
}
|
|
147
|
+
async function loadArtifactImage(sessionId, documentPath, imagePath) {
|
|
148
|
+
const blob = await artifactBlob(sessionId, artifactAssetPath(documentPath, imagePath));
|
|
149
|
+
if (!blob.type.startsWith("image/")) throw new Error("This artifact asset is not an image.");
|
|
150
|
+
const url = URL.createObjectURL(blob);
|
|
151
|
+
return {
|
|
152
|
+
url,
|
|
153
|
+
dispose: () => URL.revokeObjectURL(url)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPreviewView.tsx
|
|
158
|
+
function ArtifactPreviewView({ path, preview, error, media, loadImage }) {
|
|
159
|
+
const mode = artifactPreviewMode(path);
|
|
160
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
161
|
+
"data-testid": "artifact-preview",
|
|
162
|
+
"data-artifact-path": path,
|
|
163
|
+
className: "flex min-h-0 flex-1 flex-col bg-doom-bg text-doom-text",
|
|
164
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
165
|
+
className: "flex shrink-0 items-center gap-2 border-b border-doom-border px-4 py-3",
|
|
166
|
+
children: [/* @__PURE__ */ jsx(Breadcrumb, {
|
|
167
|
+
path,
|
|
168
|
+
className: "min-w-0 flex-1"
|
|
169
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
170
|
+
className: "text-sm text-doom-dim",
|
|
171
|
+
children: "Artifact"
|
|
172
|
+
})]
|
|
173
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
174
|
+
className: "min-h-0 flex-1 overflow-auto p-4",
|
|
175
|
+
children: error ? /* @__PURE__ */ jsx("p", {
|
|
176
|
+
role: "alert",
|
|
177
|
+
className: "text-sm text-doom-red",
|
|
178
|
+
children: error
|
|
179
|
+
}) : !preview ? /* @__PURE__ */ jsx("p", {
|
|
180
|
+
role: "status",
|
|
181
|
+
className: "text-sm text-doom-dim",
|
|
182
|
+
children: "Reading this artifact..."
|
|
183
|
+
}) : mode === "media" ? media : preview.unavailable ? /* @__PURE__ */ jsx("p", {
|
|
184
|
+
className: "text-sm text-doom-dim",
|
|
185
|
+
children: preview.reason ?? "This artifact cannot be previewed."
|
|
186
|
+
}) : mode === "markdown" ? /* @__PURE__ */ jsx(Markdown, {
|
|
187
|
+
text: preview.content,
|
|
188
|
+
loadImage
|
|
189
|
+
}) : mode === "html" ? /* @__PURE__ */ jsx("iframe", {
|
|
190
|
+
title: path,
|
|
191
|
+
sandbox: "",
|
|
192
|
+
srcDoc: preview.content,
|
|
193
|
+
className: "h-96 w-full rounded-md border border-doom-border bg-doom-deep"
|
|
194
|
+
}) : mode === "code" ? /* @__PURE__ */ jsx(CodeEditor, {
|
|
195
|
+
value: preview.content,
|
|
196
|
+
path,
|
|
197
|
+
readOnly: true,
|
|
198
|
+
className: "min-h-64 rounded-md border border-doom-border"
|
|
199
|
+
}) : /* @__PURE__ */ jsx("pre", {
|
|
200
|
+
className: "whitespace-pre-wrap break-words font-mono text-sm text-doom-text",
|
|
201
|
+
children: preview.content
|
|
202
|
+
})
|
|
203
|
+
})]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPreviewPanel.tsx
|
|
208
|
+
function ArtifactMedia({ sessionId, path, revision }) {
|
|
209
|
+
const [loaded, setLoaded] = useState();
|
|
210
|
+
const source = `${sessionId}:${path}:${revision}`;
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const controller = new AbortController();
|
|
213
|
+
let url;
|
|
214
|
+
artifactBlob(sessionId, path, controller.signal).then((blob) => {
|
|
215
|
+
if (controller.signal.aborted) return;
|
|
216
|
+
url = URL.createObjectURL(blob);
|
|
217
|
+
setLoaded({
|
|
218
|
+
source,
|
|
219
|
+
url,
|
|
220
|
+
mimeType: blob.type
|
|
221
|
+
});
|
|
222
|
+
}).catch((error) => {
|
|
223
|
+
if (!controller.signal.aborted) setLoaded({
|
|
224
|
+
source,
|
|
225
|
+
error: error instanceof Error ? error.message : String(error)
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
return () => {
|
|
229
|
+
controller.abort();
|
|
230
|
+
if (url !== void 0) URL.revokeObjectURL(url);
|
|
231
|
+
};
|
|
232
|
+
}, [
|
|
233
|
+
sessionId,
|
|
234
|
+
path,
|
|
235
|
+
source
|
|
236
|
+
]);
|
|
237
|
+
const current = loaded?.source === source ? loaded : void 0;
|
|
238
|
+
return current?.url ? /* @__PURE__ */ jsx(MediaPreview, {
|
|
239
|
+
src: current.url,
|
|
240
|
+
path,
|
|
241
|
+
contentType: current.mimeType
|
|
242
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
243
|
+
role: current?.error ? "alert" : "status",
|
|
244
|
+
className: current?.error ? "text-sm text-doom-red" : "text-sm text-doom-dim",
|
|
245
|
+
children: current?.error ?? "Loading artifact media..."
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function ArtifactPreviewPanel({ path, sessionId }) {
|
|
249
|
+
const snapshot = useStore(artifacts.store, (state) => artifacts.select(state, sessionId));
|
|
250
|
+
const item = snapshot.items.find((candidate) => candidate.path === path);
|
|
251
|
+
const revision = item ? `${item.modifiedAt}:${item.size}` : snapshot.loaded ? "missing" : "initial";
|
|
252
|
+
const [loaded, setLoaded] = useState();
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (sessionId === null || revision === "missing") return;
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
fetchArtifactPreview(sessionId, path, controller.signal).then((preview) => {
|
|
257
|
+
if (!controller.signal.aborted) setLoaded({
|
|
258
|
+
sessionId,
|
|
259
|
+
path,
|
|
260
|
+
revision,
|
|
261
|
+
preview
|
|
262
|
+
});
|
|
263
|
+
}).catch((error) => {
|
|
264
|
+
if (!controller.signal.aborted) setLoaded({
|
|
265
|
+
sessionId,
|
|
266
|
+
path,
|
|
267
|
+
revision,
|
|
268
|
+
error: error instanceof Error ? error.message : String(error)
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
return () => controller.abort();
|
|
272
|
+
}, [
|
|
273
|
+
sessionId,
|
|
274
|
+
path,
|
|
275
|
+
revision
|
|
276
|
+
]);
|
|
277
|
+
const current = loaded?.sessionId === sessionId && loaded.path === path && loaded.revision === revision ? loaded : void 0;
|
|
278
|
+
const loadImage = useCallback((imagePath) => {
|
|
279
|
+
if (sessionId === null) return Promise.reject(/* @__PURE__ */ new Error("The session is unavailable."));
|
|
280
|
+
return loadArtifactImage(sessionId, path, imagePath);
|
|
281
|
+
}, [sessionId, path]);
|
|
282
|
+
return /* @__PURE__ */ jsx(ArtifactPreviewView, {
|
|
283
|
+
path,
|
|
284
|
+
preview: current?.preview,
|
|
285
|
+
error: sessionId === null ? "The session is unavailable." : revision === "missing" ? "This artifact no longer exists." : current?.error,
|
|
286
|
+
loadImage,
|
|
287
|
+
media: sessionId === null ? void 0 : /* @__PURE__ */ jsx(ArtifactMedia, {
|
|
288
|
+
sessionId,
|
|
289
|
+
path,
|
|
290
|
+
revision
|
|
291
|
+
})
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function artifactTab(sessionId, path) {
|
|
295
|
+
return {
|
|
296
|
+
id: artifactTabId(sessionId, path),
|
|
297
|
+
label: path.split("/").at(-1) ?? path,
|
|
298
|
+
panel: (props) => /* @__PURE__ */ jsx(ArtifactPreviewPanel, {
|
|
299
|
+
...props,
|
|
300
|
+
sessionId,
|
|
301
|
+
path
|
|
302
|
+
})
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactsView.tsx
|
|
307
|
+
function sizeLabel(size) {
|
|
308
|
+
return size < 1024 ? `${size} B` : size < 1048576 ? `${Math.ceil(size / 1024)} KB` : `${(size / 1048576).toFixed(1)} MB`;
|
|
309
|
+
}
|
|
310
|
+
function RecentArtifactsView({ items, loading, error, onOpen }) {
|
|
311
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
312
|
+
"data-testid": "artifacts-recent-items",
|
|
313
|
+
className: "flex flex-col gap-1",
|
|
314
|
+
children: [
|
|
315
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
316
|
+
role: "alert",
|
|
317
|
+
className: "text-sm text-doom-red",
|
|
318
|
+
children: error
|
|
319
|
+
}) : null,
|
|
320
|
+
loading ? /* @__PURE__ */ jsx("p", {
|
|
321
|
+
role: "status",
|
|
322
|
+
className: "text-sm text-doom-dim",
|
|
323
|
+
children: "Loading artifacts..."
|
|
324
|
+
}) : null,
|
|
325
|
+
!loading && !error && items.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
326
|
+
className: "text-sm text-doom-dim",
|
|
327
|
+
children: "No artifacts yet"
|
|
328
|
+
}) : null,
|
|
329
|
+
sortedArtifacts(items).slice(0, 3).map((item) => /* @__PURE__ */ jsxs(Button, {
|
|
330
|
+
variant: "ghost",
|
|
331
|
+
size: "card",
|
|
332
|
+
"data-artifact-path": item.path,
|
|
333
|
+
onClick: () => onOpen(item),
|
|
334
|
+
className: "gap-0.5 px-2 py-1.5",
|
|
335
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
336
|
+
className: "flex min-w-0 items-center gap-2 text-base text-doom-text",
|
|
337
|
+
children: [/* @__PURE__ */ jsx(FileIcon, {
|
|
338
|
+
"aria-hidden": true,
|
|
339
|
+
className: "size-3.5 text-doom-dim"
|
|
340
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
341
|
+
className: "truncate",
|
|
342
|
+
children: item.name
|
|
343
|
+
})]
|
|
344
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
345
|
+
className: "truncate font-mono text-sm text-doom-dim",
|
|
346
|
+
title: item.path,
|
|
347
|
+
children: item.path
|
|
348
|
+
})]
|
|
349
|
+
}, item.path))
|
|
350
|
+
]
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function ArtifactGridView({ items, loading, error, onOpen }) {
|
|
354
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
355
|
+
className: "flex min-h-0 flex-1 flex-col overflow-auto bg-doom-bg text-doom-text",
|
|
356
|
+
children: [
|
|
357
|
+
/* @__PURE__ */ jsxs("header", {
|
|
358
|
+
className: "flex shrink-0 items-center gap-3 border-b border-doom-border px-4 py-3",
|
|
359
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
360
|
+
className: "text-lg font-semibold text-doom-hi",
|
|
361
|
+
children: "Artifacts"
|
|
362
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
363
|
+
"data-testid": "artifacts-count",
|
|
364
|
+
className: "text-sm text-doom-dim",
|
|
365
|
+
children: [
|
|
366
|
+
items.length,
|
|
367
|
+
" ",
|
|
368
|
+
items.length === 1 ? "artifact" : "artifacts"
|
|
369
|
+
]
|
|
370
|
+
})]
|
|
371
|
+
}),
|
|
372
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
373
|
+
role: "alert",
|
|
374
|
+
className: "px-4 py-3 text-sm text-doom-red",
|
|
375
|
+
children: error
|
|
376
|
+
}) : null,
|
|
377
|
+
loading ? /* @__PURE__ */ jsx("p", {
|
|
378
|
+
role: "status",
|
|
379
|
+
className: "px-4 py-3 text-sm text-doom-dim",
|
|
380
|
+
children: "Loading artifacts..."
|
|
381
|
+
}) : null,
|
|
382
|
+
!loading && !error && items.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
383
|
+
title: "No artifacts yet",
|
|
384
|
+
description: "Documents Po prepares for this session will appear here.",
|
|
385
|
+
className: "px-4 py-8"
|
|
386
|
+
}) : null,
|
|
387
|
+
/* @__PURE__ */ jsx("div", {
|
|
388
|
+
"data-testid": "artifacts-grid",
|
|
389
|
+
className: "grid grid-cols-1 gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3",
|
|
390
|
+
children: sortedArtifacts(items).map((item) => /* @__PURE__ */ jsxs(Button, {
|
|
391
|
+
variant: "outline",
|
|
392
|
+
size: "card",
|
|
393
|
+
"data-artifact-path": item.path,
|
|
394
|
+
onClick: () => onOpen(item),
|
|
395
|
+
className: "min-w-0 gap-2 p-4",
|
|
396
|
+
children: [
|
|
397
|
+
/* @__PURE__ */ jsx(FileIcon, {
|
|
398
|
+
"aria-hidden": true,
|
|
399
|
+
className: "size-5 text-doom-green"
|
|
400
|
+
}),
|
|
401
|
+
/* @__PURE__ */ jsx("span", {
|
|
402
|
+
className: "truncate text-base font-semibold text-doom-hi",
|
|
403
|
+
title: item.name,
|
|
404
|
+
children: item.name
|
|
405
|
+
}),
|
|
406
|
+
/* @__PURE__ */ jsx("span", {
|
|
407
|
+
className: "truncate font-mono text-sm text-doom-dim",
|
|
408
|
+
title: item.path,
|
|
409
|
+
children: item.path
|
|
410
|
+
}),
|
|
411
|
+
/* @__PURE__ */ jsx("span", {
|
|
412
|
+
className: "text-sm text-doom-dim",
|
|
413
|
+
children: sizeLabel(item.size)
|
|
414
|
+
})
|
|
415
|
+
]
|
|
416
|
+
}, item.path))
|
|
417
|
+
})
|
|
418
|
+
]
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactsPanel.tsx
|
|
423
|
+
function artifactsTab() {
|
|
424
|
+
return {
|
|
425
|
+
id: "artifacts-grid",
|
|
426
|
+
label: "Artifacts",
|
|
427
|
+
panel: ArtifactsPanel
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function ArtifactsActivitySection({ sessionId, openTransientTab }) {
|
|
431
|
+
const snapshot = useStore(artifacts.store, (state) => artifacts.select(state, sessionId));
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
if (sessionId === null || snapshot.loaded) return;
|
|
434
|
+
const controller = new AbortController();
|
|
435
|
+
refreshArtifacts(sessionId, controller.signal);
|
|
436
|
+
return () => controller.abort();
|
|
437
|
+
}, [sessionId, snapshot.loaded]);
|
|
438
|
+
return /* @__PURE__ */ jsx(RecentArtifactsView, {
|
|
439
|
+
items: snapshot.items,
|
|
440
|
+
loading: !snapshot.loaded,
|
|
441
|
+
error: snapshot.error,
|
|
442
|
+
onOpen: (item) => {
|
|
443
|
+
if (sessionId !== null) openTransientTab(artifactTab(sessionId, item.path));
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function ArtifactsPanel({ sessionId, openTransientTab }) {
|
|
448
|
+
const snapshot = useStore(artifacts.store, (state) => artifacts.select(state, sessionId));
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
if (sessionId === null) return;
|
|
451
|
+
const controller = new AbortController();
|
|
452
|
+
refreshArtifacts(sessionId, controller.signal);
|
|
453
|
+
return () => controller.abort();
|
|
454
|
+
}, [sessionId]);
|
|
455
|
+
return /* @__PURE__ */ jsx(ArtifactGridView, {
|
|
456
|
+
items: snapshot.items,
|
|
457
|
+
loading: !snapshot.loaded && sessionId !== null,
|
|
458
|
+
error: sessionId === null ? "The session is unavailable." : snapshot.error,
|
|
459
|
+
onOpen: (item) => {
|
|
460
|
+
if (sessionId !== null) openTransientTab(artifactTab(sessionId, item.path));
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/artifacts.web.ts
|
|
466
|
+
var artifacts_web_default$1 = defineActivityGroup({
|
|
467
|
+
icon: FileIcon,
|
|
468
|
+
keys: "",
|
|
469
|
+
statusKey: "doom-artifacts",
|
|
470
|
+
activeSource: artifactActivitySource,
|
|
471
|
+
transientTab: artifactsTab,
|
|
472
|
+
marksBackgroundWork: false,
|
|
473
|
+
order: 15
|
|
474
|
+
});
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/artifacts.web.ts
|
|
477
|
+
var artifacts_web_default = defineChannelFile(artifactsChannel);
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/Artifacts.activity.artifacts.web.tsx
|
|
480
|
+
var Artifacts_activity_artifacts_web_default = defineFill({ component: ArtifactsActivitySection });
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region generated/web.ts
|
|
483
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
484
|
+
...identity,
|
|
485
|
+
...value
|
|
486
|
+
};
|
|
487
|
+
const webPlugin = defineWebPlugin({
|
|
488
|
+
id: "artifacts",
|
|
489
|
+
session: {
|
|
490
|
+
get activityGroups() {
|
|
491
|
+
return [via({ name: "artifacts" }, artifacts_web_default$1)];
|
|
492
|
+
},
|
|
493
|
+
get channels() {
|
|
494
|
+
return [via({ channel: "artifacts" }, artifacts_web_default)];
|
|
495
|
+
},
|
|
496
|
+
get fills() {
|
|
497
|
+
return [via({
|
|
498
|
+
slot: "activity.artifacts",
|
|
499
|
+
id: "artifacts"
|
|
500
|
+
}, Artifacts_activity_artifacts_web_default)];
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
//#endregion
|
|
505
|
+
export { webPlugin };
|
|
506
|
+
|
|
507
|
+
//# sourceMappingURL=web.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.mjs","names":["activityGroupArtifacts","channelArtifacts","fillArtifacts"],"sources":["../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/types/artifacts.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/artifactView.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/artifactsStore.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/artifactsApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPreviewView.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPreviewPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/ArtifactsView.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/ArtifactsPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/activity-group/artifacts.web.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/artifacts.web.ts","../../src/extensions/workspaces/sessions/(frontend)/fill/Artifacts.activity.artifacts.web.tsx","../../generated/web.ts"],"sourcesContent":["import type { ApiResponseOf, ApiRoutes } from '@agimon-ai/po-core/web';\n\nimport type { ArtifactPreview, ArtifactSnapshot } from './artifacts';\n\nconst routes = {\n list: {\n method: 'GET',\n path: '/',\n response: undefined as unknown as ApiResponseOf<ArtifactSnapshot>,\n },\n preview: {\n method: 'GET',\n path: '/preview',\n query: ['path'],\n response: undefined as unknown as ApiResponseOf<ArtifactPreview>,\n },\n content: {\n method: 'GET',\n path: '/content',\n query: ['path'],\n raw: true,\n },\n} as const satisfies ApiRoutes;\n\nexport default routes;\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/po-core/web';\nimport { sealedTransport } from '@agimon-ai/po-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'artifacts' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n scopes: apiScopes,\n basePath: 'artifacts',\n transport: (input, init) => sealedTransport.fetch(input, init),\n sessionAddress: sessionApiAddress,\n});\n","export interface ArtifactItem {\n /** Relative to the current session's artifact directory. */\n path: string;\n name: string;\n size: number;\n modifiedAt: number;\n}\n\nexport interface ArtifactSnapshot {\n items: ArtifactItem[];\n error?: string;\n}\n\nexport interface ArtifactPreview {\n path: string;\n content: string;\n mimeType: string;\n unavailable?: boolean;\n reason?: string;\n}\n\nexport const artifactsChannelType = 'artifacts';\n","import type { ArtifactItem, ArtifactSnapshot } from '../../../../../types/artifacts';\n\nexport function sortedArtifacts(items: readonly ArtifactItem[]): ArtifactItem[] {\n return [...items].sort(\n (left, right) =>\n right.modifiedAt - left.modifiedAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0),\n );\n}\n\nexport function artifactTabId(sessionId: string, path: string): string {\n return `artifact:${encodeURIComponent(sessionId)}:${encodeURIComponent(path)}`;\n}\n\nexport function artifactsTabId(sessionId: string): string {\n return `artifacts-${encodeURIComponent(sessionId)}`;\n}\n\n/** Resolves document assets within the artifact folder, never into another filesystem root. */\nexport function artifactAssetPath(documentPath: string, imagePath: string): string {\n if (!imagePath || imagePath.startsWith('/') || imagePath.includes('\\\\') || /^[a-z][a-z0-9+.-]*:/iu.test(imagePath))\n throw new Error('Artifact images must use a relative path.');\n const segments = documentPath.split('/').slice(0, -1);\n for (const segment of imagePath.split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n if (segments.length === 0) throw new Error('The image is outside this artifact folder.');\n segments.pop();\n } else segments.push(segment);\n }\n if (segments.length === 0) throw new Error('The image path is empty.');\n return segments.join('/');\n}\n\nexport function artifactPreviewMode(path: string): 'markdown' | 'html' | 'media' | 'code' | 'text' {\n if (/\\.(?:png|jpe?g|gif|webp|avif|bmp|svg|ico|mp3|wav|m4a|ogg|mp4|mov|webm|pdf)$/iu.test(path)) return 'media';\n if (/\\.(?:md|markdown)$/iu.test(path)) return 'markdown';\n if (/\\.html?$/iu.test(path)) return 'html';\n if (/\\.(?:[cm]?[jt]sx?|json|ya?ml|css|scss|py|sh|bash|go|rs|rb|java|c|cpp|h|sql|xml|toml)$/iu.test(path))\n return 'code';\n return 'text';\n}\n\n/** A malformed channel frame must not replace the current session's known inventory. */\nexport function parseArtifactSnapshot(input: unknown): ArtifactSnapshot | null {\n if (typeof input !== 'object' || input === null || !('items' in input) || !Array.isArray(input.items)) return null;\n if ('error' in input && input.error !== undefined && typeof input.error !== 'string') return null;\n const items: ArtifactItem[] = [];\n for (const item of input.items) {\n if (\n typeof item !== 'object' ||\n item === null ||\n typeof item.path !== 'string' ||\n !item.path ||\n item.path.startsWith('/') ||\n item.path.includes('\\\\') ||\n item.path.split('/').some((part: string) => part === '..' || part === '.') ||\n typeof item.name !== 'string' ||\n !item.name ||\n typeof item.size !== 'number' ||\n !Number.isFinite(item.size) ||\n item.size < 0 ||\n typeof item.modifiedAt !== 'number' ||\n !Number.isFinite(item.modifiedAt) ||\n item.modifiedAt < 0\n )\n return null;\n items.push({ path: item.path, name: item.name, size: item.size, modifiedAt: item.modifiedAt });\n }\n return {\n items: sortedArtifacts(items),\n ...('error' in input && typeof input.error === 'string' ? { error: input.error } : {}),\n };\n}\n","import { defineSessionStore } from '@agimon-ai/po-core/web';\n\nimport { artifactsChannelType, type ArtifactSnapshot } from '../../../../../types/artifacts';\nimport { parseArtifactSnapshot } from './artifactView';\n\nexport const artifacts = defineSessionStore<ArtifactSnapshot & { loaded: boolean }>({ items: [], loaded: false });\n\nexport const artifactsChannel = artifacts.channel<ArtifactSnapshot>({\n channel: artifactsChannelType,\n parse: parseArtifactSnapshot,\n reduce: (_current, snapshot) => ({ ...snapshot, loaded: true }),\n});\n\n/** Artifact documents are outcomes, not background work. The source also keeps the empty launcher visible. */\nexport const artifactActivitySource = {\n subscribe(listener: () => void) {\n const subscription = artifacts.store.subscribe(listener);\n return () => subscription.unsubscribe();\n },\n isActive: () => false,\n};\n","import { api } from '../../../../../../generated/client';\nimport type { ArtifactPreview } from '../../../../../types/artifacts';\nimport { artifacts } from './artifactsStore';\nimport { artifactAssetPath, parseArtifactSnapshot } from './artifactView';\n\nexport async function refreshArtifacts(sessionId: string, signal: AbortSignal): Promise<void> {\n const before = artifacts.select(artifacts.store.state, sessionId);\n try {\n const result = await api.session(sessionId).list({ signal });\n if (signal.aborted || artifacts.select(artifacts.store.state, sessionId) !== before) return;\n const snapshot = result.ok ? parseArtifactSnapshot(result.data) : null;\n artifacts.update(sessionId, () =>\n snapshot\n ? { ...snapshot, loaded: true }\n : {\n ...before,\n loaded: true,\n error: result.ok\n ? 'The session answered with no artifact list.'\n : result.error || 'Artifacts could not be loaded.',\n },\n );\n } catch (error) {\n if (!signal.aborted && artifacts.select(artifacts.store.state, sessionId) === before)\n artifacts.update(sessionId, () => ({\n ...before,\n loaded: true,\n error: error instanceof Error ? error.message : String(error),\n }));\n }\n}\n\nexport async function fetchArtifactPreview(\n sessionId: string,\n path: string,\n signal: AbortSignal,\n): Promise<ArtifactPreview> {\n const result = await api.session(sessionId).preview({ query: { path }, signal });\n if (!result.ok) throw new Error(result.error || 'This artifact could not be read.');\n const preview = result.data;\n if (\n !preview ||\n typeof preview.path !== 'string' ||\n typeof preview.content !== 'string' ||\n typeof preview.mimeType !== 'string'\n )\n throw new Error('The session answered with no artifact.');\n return preview;\n}\n\n/** Generated raw calls keep media and Markdown asset reads on the sealed artifact transport. */\nexport async function artifactBlob(sessionId: string, path: string, signal?: AbortSignal): Promise<Blob> {\n const result = await api.session(sessionId).content({ query: { path }, ...(signal ? { signal } : {}) });\n if (!result.ok) throw new Error(result.error || 'This artifact could not be loaded.');\n return result.response.blob();\n}\n\nexport async function loadArtifactImage(sessionId: string, documentPath: string, imagePath: string) {\n const blob = await artifactBlob(sessionId, artifactAssetPath(documentPath, imagePath));\n if (!blob.type.startsWith('image/')) throw new Error('This artifact asset is not an image.');\n const url = URL.createObjectURL(blob);\n return { url, dispose: () => URL.revokeObjectURL(url) };\n}\n","import { Breadcrumb, CodeEditor, Markdown } from '@agimon-ai/po-web-components';\nimport type { ReactNode } from 'react';\n\nimport type { ArtifactPreview } from '../../../../../types/artifacts';\nimport { artifactPreviewMode } from '../_lib/artifactView';\n\ninterface ArtifactPreviewViewProps {\n path: string;\n preview?: ArtifactPreview;\n error?: string;\n media?: ReactNode;\n loadImage?: (path: string) => Promise<{ url: string; dispose: () => void }>;\n}\n\nexport function ArtifactPreviewView({ path, preview, error, media, loadImage }: ArtifactPreviewViewProps) {\n const mode = artifactPreviewMode(path);\n return (\n <div\n data-testid=\"artifact-preview\"\n data-artifact-path={path}\n className=\"flex min-h-0 flex-1 flex-col bg-doom-bg text-doom-text\"\n >\n <header className=\"flex shrink-0 items-center gap-2 border-b border-doom-border px-4 py-3\">\n <Breadcrumb path={path} className=\"min-w-0 flex-1\" />\n <span className=\"text-sm text-doom-dim\">Artifact</span>\n </header>\n <div className=\"min-h-0 flex-1 overflow-auto p-4\">\n {error ? (\n <p role=\"alert\" className=\"text-sm text-doom-red\">\n {error}\n </p>\n ) : !preview ? (\n <p role=\"status\" className=\"text-sm text-doom-dim\">\n Reading this artifact...\n </p>\n ) : mode === 'media' ? (\n media\n ) : preview.unavailable ? (\n <p className=\"text-sm text-doom-dim\">{preview.reason ?? 'This artifact cannot be previewed.'}</p>\n ) : mode === 'markdown' ? (\n <Markdown text={preview.content} loadImage={loadImage} />\n ) : mode === 'html' ? (\n <iframe\n title={path}\n sandbox=\"\"\n srcDoc={preview.content}\n className=\"h-96 w-full rounded-md border border-doom-border bg-doom-deep\"\n />\n ) : mode === 'code' ? (\n <CodeEditor\n value={preview.content}\n path={path}\n readOnly\n className=\"min-h-64 rounded-md border border-doom-border\"\n />\n ) : (\n <pre className=\"whitespace-pre-wrap break-words font-mono text-sm text-doom-text\">{preview.content}</pre>\n )}\n </div>\n </div>\n );\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/po-core/web';\nimport { MediaPreview } from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport type { ArtifactPreview } from '../../../../../types/artifacts';\nimport { artifactBlob, fetchArtifactPreview, loadArtifactImage } from '../_lib/artifactsApi';\nimport { artifacts } from '../_lib/artifactsStore';\nimport { artifactTabId } from '../_lib/artifactView';\nimport { ArtifactPreviewView } from './ArtifactPreviewView';\n\nfunction ArtifactMedia({ sessionId, path, revision }: { sessionId: string; path: string; revision: string }) {\n const [loaded, setLoaded] = useState<{ source: string; url?: string; mimeType?: string; error?: string }>();\n const source = `${sessionId}:${path}:${revision}`;\n useEffect(() => {\n const controller = new AbortController();\n let url: string | undefined;\n void artifactBlob(sessionId, path, controller.signal)\n .then((blob) => {\n if (controller.signal.aborted) return;\n url = URL.createObjectURL(blob);\n setLoaded({ source, url, mimeType: blob.type });\n })\n .catch((error: unknown) => {\n if (!controller.signal.aborted)\n setLoaded({ source, error: error instanceof Error ? error.message : String(error) });\n });\n return () => {\n controller.abort();\n if (url !== undefined) URL.revokeObjectURL(url);\n };\n }, [sessionId, path, source]);\n const current = loaded?.source === source ? loaded : undefined;\n return current?.url ? (\n <MediaPreview src={current.url} path={path} contentType={current.mimeType} />\n ) : (\n <p\n role={current?.error ? 'alert' : 'status'}\n className={current?.error ? 'text-sm text-doom-red' : 'text-sm text-doom-dim'}\n >\n {current?.error ?? 'Loading artifact media...'}\n </p>\n );\n}\n\nexport function ArtifactPreviewPanel({ path, sessionId }: WebPluginSlotProps & { path: string }) {\n const snapshot = useStore(artifacts.store, (state) => artifacts.select(state, sessionId));\n const item = snapshot.items.find((candidate) => candidate.path === path);\n const revision = item ? `${item.modifiedAt}:${item.size}` : snapshot.loaded ? 'missing' : 'initial';\n const [loaded, setLoaded] = useState<{\n sessionId: string;\n path: string;\n revision: string;\n preview?: ArtifactPreview;\n error?: string;\n }>();\n useEffect(() => {\n if (sessionId === null || revision === 'missing') return;\n const controller = new AbortController();\n void fetchArtifactPreview(sessionId, path, controller.signal)\n .then((preview) => {\n if (!controller.signal.aborted) setLoaded({ sessionId, path, revision, preview });\n })\n .catch((error: unknown) => {\n if (!controller.signal.aborted)\n setLoaded({ sessionId, path, revision, error: error instanceof Error ? error.message : String(error) });\n });\n return () => controller.abort();\n }, [sessionId, path, revision]);\n const current =\n loaded?.sessionId === sessionId && loaded.path === path && loaded.revision === revision ? loaded : undefined;\n const loadImage = useCallback(\n (imagePath: string) => {\n if (sessionId === null) return Promise.reject(new Error('The session is unavailable.'));\n return loadArtifactImage(sessionId, path, imagePath);\n },\n [sessionId, path],\n );\n return (\n <ArtifactPreviewView\n path={path}\n preview={current?.preview}\n error={\n sessionId === null\n ? 'The session is unavailable.'\n : revision === 'missing'\n ? 'This artifact no longer exists.'\n : current?.error\n }\n loadImage={loadImage}\n media={sessionId === null ? undefined : <ArtifactMedia sessionId={sessionId} path={path} revision={revision} />}\n />\n );\n}\n\nexport function artifactTab(sessionId: string, path: string): TransientTab {\n return {\n id: artifactTabId(sessionId, path),\n label: path.split('/').at(-1) ?? path,\n panel: (props) => <ArtifactPreviewPanel {...props} sessionId={sessionId} path={path} />,\n };\n}\n","import { Button, EmptyState, FileIcon } from '@agimon-ai/po-web-components';\n\nimport type { ArtifactItem } from '../../../../../types/artifacts';\nimport { sortedArtifacts } from '../_lib/artifactView';\n\ninterface ArtifactsViewProps {\n items: readonly ArtifactItem[];\n loading?: boolean;\n error?: string;\n onOpen: (item: ArtifactItem) => void;\n}\n\nfunction sizeLabel(size: number): string {\n return size < 1024\n ? `${size} B`\n : size < 1024 * 1024\n ? `${Math.ceil(size / 1024)} KB`\n : `${(size / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function RecentArtifactsView({ items, loading, error, onOpen }: ArtifactsViewProps) {\n return (\n <div data-testid=\"artifacts-recent-items\" className=\"flex flex-col gap-1\">\n {error ? (\n <p role=\"alert\" className=\"text-sm text-doom-red\">\n {error}\n </p>\n ) : null}\n {loading ? (\n <p role=\"status\" className=\"text-sm text-doom-dim\">\n Loading artifacts...\n </p>\n ) : null}\n {!loading && !error && items.length === 0 ? <p className=\"text-sm text-doom-dim\">No artifacts yet</p> : null}\n {sortedArtifacts(items)\n .slice(0, 3)\n .map((item) => (\n <Button\n key={item.path}\n variant=\"ghost\"\n size=\"card\"\n data-artifact-path={item.path}\n onClick={() => onOpen(item)}\n className=\"gap-0.5 px-2 py-1.5\"\n >\n <span className=\"flex min-w-0 items-center gap-2 text-base text-doom-text\">\n <FileIcon aria-hidden className=\"size-3.5 text-doom-dim\" />\n <span className=\"truncate\">{item.name}</span>\n </span>\n <span className=\"truncate font-mono text-sm text-doom-dim\" title={item.path}>\n {item.path}\n </span>\n </Button>\n ))}\n </div>\n );\n}\n\nexport function ArtifactGridView({ items, loading, error, onOpen }: ArtifactsViewProps) {\n return (\n <div className=\"flex min-h-0 flex-1 flex-col overflow-auto bg-doom-bg text-doom-text\">\n <header className=\"flex shrink-0 items-center gap-3 border-b border-doom-border px-4 py-3\">\n <h1 className=\"text-lg font-semibold text-doom-hi\">Artifacts</h1>\n <span data-testid=\"artifacts-count\" className=\"text-sm text-doom-dim\">\n {items.length} {items.length === 1 ? 'artifact' : 'artifacts'}\n </span>\n </header>\n {error ? (\n <p role=\"alert\" className=\"px-4 py-3 text-sm text-doom-red\">\n {error}\n </p>\n ) : null}\n {loading ? (\n <p role=\"status\" className=\"px-4 py-3 text-sm text-doom-dim\">\n Loading artifacts...\n </p>\n ) : null}\n {!loading && !error && items.length === 0 ? (\n <EmptyState\n title=\"No artifacts yet\"\n description=\"Documents Po prepares for this session will appear here.\"\n className=\"px-4 py-8\"\n />\n ) : null}\n <div data-testid=\"artifacts-grid\" className=\"grid grid-cols-1 gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n {sortedArtifacts(items).map((item) => (\n <Button\n key={item.path}\n variant=\"outline\"\n size=\"card\"\n data-artifact-path={item.path}\n onClick={() => onOpen(item)}\n className=\"min-w-0 gap-2 p-4\"\n >\n <FileIcon aria-hidden className=\"size-5 text-doom-green\" />\n <span className=\"truncate text-base font-semibold text-doom-hi\" title={item.name}>\n {item.name}\n </span>\n <span className=\"truncate font-mono text-sm text-doom-dim\" title={item.path}>\n {item.path}\n </span>\n <span className=\"text-sm text-doom-dim\">{sizeLabel(item.size)}</span>\n </Button>\n ))}\n </div>\n </div>\n );\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/po-core/web';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect } from 'react';\n\nimport { refreshArtifacts } from '../_lib/artifactsApi';\nimport { artifacts } from '../_lib/artifactsStore';\nimport { artifactTab } from './ArtifactPreviewPanel';\nimport { ArtifactGridView, RecentArtifactsView } from './ArtifactsView';\n\nexport function artifactsTab(): TransientTab {\n // The host stores transient tabs per session, including this heading factory's fixed id.\n return { id: 'artifacts-grid', label: 'Artifacts', panel: ArtifactsPanel };\n}\n\nexport function ArtifactsActivitySection({ sessionId, openTransientTab }: WebPluginSlotProps) {\n const snapshot = useStore(artifacts.store, (state) => artifacts.select(state, sessionId));\n useEffect(() => {\n if (sessionId === null || snapshot.loaded) return;\n const controller = new AbortController();\n void refreshArtifacts(sessionId, controller.signal);\n return () => controller.abort();\n }, [sessionId, snapshot.loaded]);\n return (\n <RecentArtifactsView\n items={snapshot.items}\n loading={!snapshot.loaded}\n error={snapshot.error}\n onOpen={(item) => {\n if (sessionId !== null) openTransientTab(artifactTab(sessionId, item.path));\n }}\n />\n );\n}\n\nexport function ArtifactsPanel({ sessionId, openTransientTab }: WebPluginSlotProps) {\n const snapshot = useStore(artifacts.store, (state) => artifacts.select(state, sessionId));\n useEffect(() => {\n if (sessionId === null) return;\n const controller = new AbortController();\n void refreshArtifacts(sessionId, controller.signal);\n return () => controller.abort();\n }, [sessionId]);\n return (\n <ArtifactGridView\n items={snapshot.items}\n loading={!snapshot.loaded && sessionId !== null}\n error={sessionId === null ? 'The session is unavailable.' : snapshot.error}\n onOpen={(item) => {\n if (sessionId !== null) openTransientTab(artifactTab(sessionId, item.path));\n }}\n />\n );\n}\n","import { defineActivityGroup } from '@agimon-ai/po-core/web';\nimport { FileIcon } from '@agimon-ai/po-web-components';\n\nimport { artifactsTab } from '../_components/ArtifactsPanel';\nimport { artifactActivitySource } from '../_lib/artifactsStore';\nexport default defineActivityGroup({\n icon: FileIcon,\n keys: '',\n statusKey: 'doom-artifacts',\n activeSource: artifactActivitySource,\n transientTab: artifactsTab,\n marksBackgroundWork: false,\n order: 15,\n});\n","import { defineChannelFile } from '@agimon-ai/po-core/web';\n\nimport { artifactsChannel } from '../_lib/artifactsStore';\nexport default defineChannelFile(artifactsChannel);\n","import { defineFill } from '@agimon-ai/po-core/web';\n\nimport { ArtifactsActivitySection } from '../_components/ArtifactsPanel';\nexport default defineFill({ component: ArtifactsActivitySection });\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/po-core/web';\n\nimport activityGroupArtifacts from '../src/extensions/workspaces/sessions/(frontend)/activity-group/artifacts.web';\nimport channelArtifacts from '../src/extensions/workspaces/sessions/(frontend)/channel/artifacts.web';\nimport fillArtifacts from '../src/extensions/workspaces/sessions/(frontend)/fill/Artifacts.activity.artifacts.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'artifacts',\n session: {\n get activityGroups(): WebPluginContributions['activityGroups'] { return [via({ name: 'artifacts' }, activityGroupArtifacts)]; },\n get channels(): WebPluginContributions['channels'] { return [via({ channel: 'artifacts' }, channelArtifacts)]; },\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'activity.artifacts', id: 'artifacts' }, fillArtifacts)]; },\n },\n});\n"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
File without changes
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/types/artifacts.d.ts
|
|
2
|
+
interface ArtifactItem {
|
|
3
|
+
/** Relative to the current session's artifact directory. */
|
|
4
|
+
path: string;
|
|
5
|
+
name: string;
|
|
6
|
+
size: number;
|
|
7
|
+
modifiedAt: number;
|
|
8
|
+
}
|
|
9
|
+
interface ArtifactSnapshot {
|
|
10
|
+
items: ArtifactItem[];
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
interface ArtifactPreview {
|
|
14
|
+
path: string;
|
|
15
|
+
content: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
unavailable?: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export type { ArtifactItem, ArtifactPreview, ArtifactSnapshot };
|
|
22
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/artifacts.ts"],"mappings":";UAAiB;;EAEf;EACA;EACA;EACA;;UAGe;EACf,OAAO;EACP;;UAGe;EACf;EACA;EACA;EACA;EACA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|