@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.
@@ -0,0 +1,700 @@
1
+ import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineNativePlugin, defineSessionStore, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
2
+ import { Text } from "react-native-unistyles/components/native/Text";
3
+ import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
4
+ import { Button, CodeBlock, FolderIcon, Markdown, fontOf } from "@agimon-ai/po-native-components";
5
+ import { StyleSheet } from "react-native-unistyles";
6
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
7
+ import { View } from "react-native-unistyles/components/native/View";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ const api = createApiClient({
10
+ list: {
11
+ method: "GET",
12
+ path: "/",
13
+ response: void 0
14
+ },
15
+ preview: {
16
+ method: "GET",
17
+ path: "/preview",
18
+ query: ["path"],
19
+ response: void 0
20
+ },
21
+ content: {
22
+ method: "GET",
23
+ path: "/content",
24
+ query: ["path"],
25
+ raw: true
26
+ }
27
+ }, {
28
+ scopes: ["session"],
29
+ basePath: "artifacts",
30
+ transport: nativeApiTransport,
31
+ sessionAddress: sessionApiAddress
32
+ });
33
+ //#endregion
34
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/artifactView.ts
35
+ function sortedArtifacts(items) {
36
+ return [...items].sort((left, right) => right.modifiedAt - left.modifiedAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
37
+ }
38
+ function artifactTabId(sessionId, path) {
39
+ return `artifact:${encodeURIComponent(sessionId)}:${encodeURIComponent(path)}`;
40
+ }
41
+ /** Resolves document assets within the artifact folder, never into another filesystem root. */
42
+ function artifactAssetPath(documentPath, imagePath) {
43
+ if (!imagePath || imagePath.startsWith("/") || imagePath.includes("\\") || /^[a-z][a-z0-9+.-]*:/iu.test(imagePath)) throw new Error("Artifact images must use a relative path.");
44
+ const segments = documentPath.split("/").slice(0, -1);
45
+ for (const segment of imagePath.split("/")) {
46
+ if (segment === "" || segment === ".") continue;
47
+ if (segment === "..") {
48
+ if (segments.length === 0) throw new Error("The image is outside this artifact folder.");
49
+ segments.pop();
50
+ } else segments.push(segment);
51
+ }
52
+ if (segments.length === 0) throw new Error("The image path is empty.");
53
+ return segments.join("/");
54
+ }
55
+ function artifactPreviewMode(path) {
56
+ if (/\.(?:png|jpe?g|gif|webp|avif|bmp|svg|ico|mp3|wav|m4a|ogg|mp4|mov|webm|pdf)$/iu.test(path)) return "media";
57
+ if (/\.(?:md|markdown)$/iu.test(path)) return "markdown";
58
+ if (/\.html?$/iu.test(path)) return "html";
59
+ 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";
60
+ return "text";
61
+ }
62
+ /** A malformed channel frame must not replace the current session's known inventory. */
63
+ function parseArtifactSnapshot(input) {
64
+ if (typeof input !== "object" || input === null || !("items" in input) || !Array.isArray(input.items)) return null;
65
+ if ("error" in input && input.error !== void 0 && typeof input.error !== "string") return null;
66
+ const items = [];
67
+ for (const item of input.items) {
68
+ 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;
69
+ items.push({
70
+ path: item.path,
71
+ name: item.name,
72
+ size: item.size,
73
+ modifiedAt: item.modifiedAt
74
+ });
75
+ }
76
+ return {
77
+ items: sortedArtifacts(items),
78
+ ..."error" in input && typeof input.error === "string" ? { error: input.error } : {}
79
+ };
80
+ }
81
+ //#endregion
82
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/artifactsApi.native.ts
83
+ const MAX_MEDIA_BYTES = 5242880;
84
+ async function fetchArtifacts(sessionId, signal) {
85
+ const result = await api.session(sessionId).list({ signal });
86
+ if (!result.ok) throw new Error(result.error || "Artifacts could not be loaded.");
87
+ const snapshot = parseArtifactSnapshot(result.data);
88
+ if (!snapshot) throw new Error("The session returned an invalid artifact list.");
89
+ return snapshot;
90
+ }
91
+ async function fetchArtifactPreview(sessionId, path, signal) {
92
+ const result = await api.session(sessionId).preview({
93
+ query: { path },
94
+ signal
95
+ });
96
+ if (!result.ok) throw new Error(result.error || "This artifact could not be read.");
97
+ const preview = result.data;
98
+ if (!preview || typeof preview.path !== "string" || typeof preview.content !== "string" || typeof preview.mimeType !== "string") throw new Error("The session returned an invalid artifact preview.");
99
+ return preview;
100
+ }
101
+ /** No browser btoa or Node Buffer is available in an independently loaded native plugin. */
102
+ function base64(bytes) {
103
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
104
+ const parts = [];
105
+ let chunk = "";
106
+ for (let offset = 0; offset < bytes.length; offset += 3) {
107
+ const first = bytes[offset];
108
+ const second = bytes[offset + 1];
109
+ const third = bytes[offset + 2];
110
+ chunk += alphabet[first >> 2] + alphabet[(first & 3) << 4 | (second ?? 0) >> 4] + (second === void 0 ? "=" : alphabet[(second & 15) << 2 | (third ?? 0) >> 6]) + (third === void 0 ? "=" : alphabet[third & 63]);
111
+ if (chunk.length >= 8192) {
112
+ parts.push(chunk);
113
+ chunk = "";
114
+ }
115
+ }
116
+ parts.push(chunk);
117
+ return parts.join("");
118
+ }
119
+ async function fetchArtifactMedia(sessionId, path, signal) {
120
+ const result = await api.session(sessionId).content({
121
+ query: { path },
122
+ signal
123
+ });
124
+ if (!result.ok) throw new Error(result.error || "This media could not be loaded.");
125
+ const declaredLength = result.response.headers.get("content-length");
126
+ const length = declaredLength !== null && /^\d+$/u.test(declaredLength) ? Number(declaredLength) : NaN;
127
+ if (!Number.isSafeInteger(length) || length < 0 || length > MAX_MEDIA_BYTES) throw new Error("This media is too large to preview.");
128
+ const bytes = new Uint8Array(await result.response.arrayBuffer());
129
+ if (signal.aborted) throw new Error("Media loading cancelled.");
130
+ if (bytes.byteLength > MAX_MEDIA_BYTES) throw new Error("This media is too large to preview.");
131
+ const mimeType = result.response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
132
+ if (!/^(?:image\/[a-z0-9.+-]+|audio\/[a-z0-9.+-]+|video\/[a-z0-9.+-]+|application\/pdf)$/u.test(mimeType)) throw new Error("This media type cannot be previewed.");
133
+ return {
134
+ data: base64(bytes),
135
+ mimeType
136
+ };
137
+ }
138
+ /** The native host owns saving, with bounded version-checked reads supplied by this package. */
139
+ async function prepareArtifactSave(sessionId, path, signal) {
140
+ const load = async (headers, signal) => {
141
+ if (signal.aborted) throw new Error("Download cancelled.");
142
+ const result = await api.session(sessionId).content({
143
+ query: { path },
144
+ headers,
145
+ signal
146
+ });
147
+ if (!result.ok && result.status !== 416) throw new Error(result.error || "This artifact could not be read.");
148
+ if (!result.response) throw new Error("This artifact could not be read.");
149
+ return result.response;
150
+ };
151
+ const probe = await load({ Range: "bytes=0-0" }, signal);
152
+ const range = /^bytes 0-0\/(\d+)$/u.exec(probe.headers.get("content-range") ?? "");
153
+ const empty = probe.status === 416 && probe.headers.get("content-range") === "bytes */0" || probe.status === 200 && probe.headers.get("content-length") === "0";
154
+ if (!empty && (probe.status !== 206 || !range)) throw new Error("This artifact does not support bounded downloads.");
155
+ const size = empty ? 0 : Number(range[1]);
156
+ if (!Number.isSafeInteger(size) || size < 0) throw new Error("Invalid artifact size.");
157
+ if ((await probe.arrayBuffer()).byteLength !== (empty ? 0 : 1)) throw new Error("Invalid artifact response.");
158
+ if (signal.aborted) throw new Error("Download cancelled.");
159
+ const version = probe.headers.get("etag");
160
+ if (!version) throw new Error("The artifact has no version validator.");
161
+ return {
162
+ name: path.split("/").at(-1) ?? "artifact",
163
+ mimeType: probe.headers.get("content-type")?.split(";")[0]?.trim() || "application/octet-stream",
164
+ size,
165
+ signal,
166
+ read: async (offset, count, signal) => {
167
+ if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(count) || offset < 0 || count < 1 || count > 1048576 || offset + count > size) throw new Error("Invalid download range.");
168
+ const end = offset + count - 1;
169
+ const response = await load({
170
+ Range: `bytes=${offset}-${end}`,
171
+ "If-Match": version
172
+ }, signal);
173
+ if (response.status !== 206 || response.headers.get("etag") !== version || response.headers.get("content-range") !== `bytes ${offset}-${end}/${size}`) throw new Error("The artifact changed or its range response is invalid.");
174
+ const bytes = new Uint8Array(await response.arrayBuffer());
175
+ if (signal.aborted) throw new Error("Download cancelled.");
176
+ if (bytes.byteLength !== count) throw new Error("The artifact response is incomplete.");
177
+ return bytes;
178
+ }
179
+ };
180
+ }
181
+ //#endregion
182
+ //#region src/types/artifacts.ts
183
+ const artifactsChannelType = "artifacts";
184
+ //#endregion
185
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/artifactsStore.native.ts
186
+ const artifacts = defineSessionStore({
187
+ items: [],
188
+ loaded: false
189
+ });
190
+ const artifactsChannel = artifacts.channel({
191
+ channel: artifactsChannelType,
192
+ parse: parseArtifactSnapshot,
193
+ reduce: (_current, snapshot) => ({
194
+ ...snapshot,
195
+ items: sortedArtifacts(snapshot.items),
196
+ loaded: true
197
+ })
198
+ });
199
+ /** An idle library remains reachable, without marking it as background work. */
200
+ const artifactActivitySource = {
201
+ subscribe: (listener) => {
202
+ const subscription = artifacts.store.subscribe(listener);
203
+ return () => subscription.unsubscribe();
204
+ },
205
+ isActive: () => false
206
+ };
207
+ //#endregion
208
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/artifactHooks.native.ts
209
+ /** A fresh grid read complements the live session channel and seeds a newly attached rail. */
210
+ function useArtifacts(sessionId, refreshOnMount = false) {
211
+ const snapshot = useSyncExternalStore(artifactActivitySource.subscribe, () => artifacts.select(artifacts.store.state, sessionId));
212
+ useEffect(() => {
213
+ if (sessionId === null) return;
214
+ const before = artifacts.select(artifacts.store.state, sessionId);
215
+ if (before.loaded && !refreshOnMount) return;
216
+ const controller = new AbortController();
217
+ fetchArtifacts(sessionId, controller.signal).then((next) => {
218
+ if (!controller.signal.aborted) artifacts.update(sessionId, (current) => current === before ? {
219
+ ...next,
220
+ loaded: true
221
+ } : current);
222
+ }).catch((error) => {
223
+ if (!controller.signal.aborted) artifacts.update(sessionId, (current) => current === before ? {
224
+ ...current,
225
+ loaded: true,
226
+ error: error instanceof Error ? error.message : String(error)
227
+ } : current);
228
+ });
229
+ return () => controller.abort();
230
+ }, [sessionId, refreshOnMount]);
231
+ return snapshot;
232
+ }
233
+ //#endregion
234
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactList.native.tsx
235
+ /** Presentation only. The session store and guarded API belong to the containers. */
236
+ function ArtifactList({ items, onOpen, loading, error, grid = false }) {
237
+ return /* @__PURE__ */ jsxs(View, {
238
+ testID: grid ? "artifacts-grid" : "artifacts-recent",
239
+ style: styles$3.list,
240
+ children: [
241
+ error ? /* @__PURE__ */ jsx(Text, {
242
+ accessibilityRole: "alert",
243
+ style: styles$3.error,
244
+ children: error
245
+ }) : null,
246
+ loading ? /* @__PURE__ */ jsx(Text, {
247
+ style: styles$3.meta,
248
+ children: "Loading artifacts..."
249
+ }) : null,
250
+ !loading && !error && items.length === 0 ? /* @__PURE__ */ jsx(Text, {
251
+ style: styles$3.meta,
252
+ children: "Documents saved for this session will appear here."
253
+ }) : null,
254
+ items.map((item) => /* @__PURE__ */ jsxs(Button, {
255
+ variant: grid ? "outline" : "ghost",
256
+ size: "card",
257
+ accessibilityLabel: `Open ${item.path}`,
258
+ testID: `artifact-${item.path}`,
259
+ onPress: () => onOpen(item.path),
260
+ style: styles$3.item,
261
+ children: [/* @__PURE__ */ jsx(FolderIcon, {
262
+ size: 18,
263
+ color: "dim"
264
+ }), /* @__PURE__ */ jsxs(View, {
265
+ style: styles$3.label,
266
+ children: [
267
+ /* @__PURE__ */ jsx(Text, {
268
+ numberOfLines: 2,
269
+ style: styles$3.name,
270
+ children: item.name
271
+ }),
272
+ grid || item.path !== item.name ? /* @__PURE__ */ jsx(Text, {
273
+ numberOfLines: 2,
274
+ style: styles$3.path,
275
+ children: item.path
276
+ }) : null,
277
+ grid ? /* @__PURE__ */ jsxs(Text, {
278
+ style: styles$3.meta,
279
+ children: [item.size.toLocaleString("en-US"), " bytes"]
280
+ }) : null
281
+ ]
282
+ })]
283
+ }, item.path))
284
+ ]
285
+ });
286
+ }
287
+ const styles$3 = StyleSheet.create((theme) => ({
288
+ list: {
289
+ gap: theme.spacing(2),
290
+ uni__dependencies: [0]
291
+ },
292
+ item: {
293
+ minHeight: 44,
294
+ justifyContent: "flex-start",
295
+ alignItems: "center",
296
+ gap: theme.spacing(2),
297
+ uni__dependencies: [0]
298
+ },
299
+ label: {
300
+ flex: 1,
301
+ gap: theme.spacing(1),
302
+ minWidth: 0,
303
+ uni__dependencies: [0]
304
+ },
305
+ name: {
306
+ fontFamily: fontOf(theme.fontFamily.sans),
307
+ fontWeight: "600",
308
+ fontSize: theme.fontSize.base,
309
+ color: theme.colors.text,
310
+ uni__dependencies: [0]
311
+ },
312
+ path: {
313
+ fontFamily: fontOf(theme.fontFamily.mono),
314
+ fontSize: theme.fontSize.sm,
315
+ color: theme.colors.dim,
316
+ uni__dependencies: [0]
317
+ },
318
+ meta: {
319
+ fontFamily: fontOf(theme.fontFamily.sans),
320
+ fontSize: theme.fontSize.sm,
321
+ color: theme.colors.dim,
322
+ uni__dependencies: [0]
323
+ },
324
+ error: {
325
+ fontFamily: fontOf(theme.fontFamily.sans),
326
+ fontSize: theme.fontSize.sm,
327
+ color: theme.colors.red,
328
+ uni__dependencies: [0]
329
+ }
330
+ }));
331
+ //#endregion
332
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPreview.native.tsx
333
+ /** The host receives only content fetched through the artifact's bounded route. */
334
+ function ArtifactPreview({ preview, sessionId, renderFilePreview, renderMediaPreview, media, mediaError, onOpen }) {
335
+ const mode = artifactPreviewMode(preview.path);
336
+ if (mode === "media") {
337
+ if (mediaError) return /* @__PURE__ */ jsx(Text, {
338
+ accessibilityRole: "alert",
339
+ style: styles$2.error,
340
+ children: mediaError
341
+ });
342
+ if (!renderMediaPreview) return /* @__PURE__ */ jsx(Text, {
343
+ style: styles$2.meta,
344
+ children: "This host cannot display this media preview."
345
+ });
346
+ if (!media) return /* @__PURE__ */ jsx(Text, {
347
+ style: styles$2.meta,
348
+ children: "Loading media..."
349
+ });
350
+ return /* @__PURE__ */ jsx(Fragment, { children: renderMediaPreview({
351
+ key: `artifact-media-${sessionId}-${preview.path}`,
352
+ title: preview.path,
353
+ path: preview.path,
354
+ data: media.data,
355
+ mimeType: media.mimeType
356
+ }) });
357
+ }
358
+ if (preview.unavailable) return /* @__PURE__ */ jsx(Text, {
359
+ style: styles$2.meta,
360
+ children: preview.reason ?? "This artifact cannot be previewed."
361
+ });
362
+ if (mode === "html") return renderFilePreview ? /* @__PURE__ */ jsx(View, {
363
+ style: styles$2.html,
364
+ children: renderFilePreview({
365
+ key: `artifact-html-${sessionId}-${preview.path}`,
366
+ html: preview.content,
367
+ title: preview.path
368
+ })
369
+ }) : /* @__PURE__ */ jsx(Text, {
370
+ style: styles$2.meta,
371
+ children: "This host cannot display HTML previews."
372
+ });
373
+ if (mode === "markdown") return /* @__PURE__ */ jsx(Markdown, {
374
+ text: preview.content,
375
+ onFileLink: (path) => {
376
+ try {
377
+ const resolved = artifactAssetPath(preview.path, path);
378
+ return () => onOpen(resolved);
379
+ } catch {
380
+ return;
381
+ }
382
+ }
383
+ });
384
+ if (mode === "code") return /* @__PURE__ */ jsx(CodeBlock, {
385
+ text: preview.content,
386
+ language: preview.path.split(".").at(-1)
387
+ });
388
+ return /* @__PURE__ */ jsx(Text, {
389
+ selectable: true,
390
+ style: styles$2.text,
391
+ children: preview.content
392
+ });
393
+ }
394
+ const styles$2 = StyleSheet.create((theme) => ({
395
+ html: {
396
+ minHeight: theme.spacing(90),
397
+ uni__dependencies: [0]
398
+ },
399
+ text: {
400
+ fontFamily: fontOf(theme.fontFamily.mono),
401
+ fontSize: theme.fontSize.sm,
402
+ lineHeight: theme.lineHeight.sm,
403
+ color: theme.colors.text,
404
+ uni__dependencies: [0]
405
+ },
406
+ meta: {
407
+ fontFamily: fontOf(theme.fontFamily.sans),
408
+ fontSize: theme.fontSize.sm,
409
+ color: theme.colors.dim,
410
+ uni__dependencies: [0]
411
+ },
412
+ error: {
413
+ fontFamily: fontOf(theme.fontFamily.sans),
414
+ fontSize: theme.fontSize.sm,
415
+ color: theme.colors.red,
416
+ uni__dependencies: [0]
417
+ }
418
+ }));
419
+ //#endregion
420
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPanel.native.tsx
421
+ function ArtifactPanel({ path, ...props }) {
422
+ const item = useArtifacts(props.sessionId).items.find((item) => item.path === path);
423
+ const version = `${item?.modifiedAt ?? ""}:${item?.size ?? ""}`;
424
+ const downloadKey = `${props.sessionId}:${path}`;
425
+ const key = `${downloadKey}:${version}`;
426
+ const [loaded, setLoaded] = useState();
427
+ useEffect(() => {
428
+ if (props.sessionId === null) return;
429
+ const sessionId = props.sessionId;
430
+ const controller = new AbortController();
431
+ fetchArtifactPreview(sessionId, path, controller.signal).then(async (preview) => {
432
+ if (controller.signal.aborted) return;
433
+ setLoaded({
434
+ key,
435
+ preview
436
+ });
437
+ if (artifactPreviewMode(path) !== "media" || !props.renderMediaPreview) return;
438
+ try {
439
+ const media = await fetchArtifactMedia(sessionId, path, controller.signal);
440
+ if (!controller.signal.aborted) setLoaded({
441
+ key,
442
+ preview,
443
+ media
444
+ });
445
+ } catch (error) {
446
+ if (!controller.signal.aborted) setLoaded({
447
+ key,
448
+ preview,
449
+ mediaError: error instanceof Error ? error.message : String(error)
450
+ });
451
+ }
452
+ }).catch((error) => {
453
+ if (!controller.signal.aborted) setLoaded({
454
+ key,
455
+ error: error instanceof Error ? error.message : String(error)
456
+ });
457
+ });
458
+ return () => controller.abort();
459
+ }, [
460
+ props.sessionId,
461
+ path,
462
+ key,
463
+ props.renderMediaPreview
464
+ ]);
465
+ const current = loaded?.key === key ? loaded : void 0;
466
+ const downloadController = useRef(void 0);
467
+ const [download, setDownload] = useState();
468
+ useEffect(() => () => {
469
+ downloadController.current?.abort();
470
+ downloadController.current = void 0;
471
+ }, [props.sessionId, path]);
472
+ const saving = download?.key === downloadKey && download.saving === true;
473
+ const save = async () => {
474
+ if (props.sessionId === null || !props.nativeSaveFile || downloadController.current) return;
475
+ const controller = new AbortController();
476
+ downloadController.current = controller;
477
+ setDownload({
478
+ key: downloadKey,
479
+ saving: true
480
+ });
481
+ try {
482
+ const file = await prepareArtifactSave(props.sessionId, path, controller.signal);
483
+ if (controller.signal.aborted || downloadController.current !== controller) return;
484
+ await props.nativeSaveFile(file);
485
+ if (!controller.signal.aborted && downloadController.current === controller) setDownload({
486
+ key: downloadKey,
487
+ note: "Saved."
488
+ });
489
+ } catch (error) {
490
+ if (!controller.signal.aborted && downloadController.current === controller) setDownload({
491
+ key: downloadKey,
492
+ note: error instanceof Error ? error.message : String(error)
493
+ });
494
+ } finally {
495
+ if (downloadController.current === controller) downloadController.current = void 0;
496
+ }
497
+ };
498
+ return /* @__PURE__ */ jsxs(ScrollView, {
499
+ contentContainerStyle: styles$1.content,
500
+ children: [
501
+ /* @__PURE__ */ jsx(Text, {
502
+ selectable: true,
503
+ style: styles$1.path,
504
+ children: path
505
+ }),
506
+ props.nativeSaveFile ? /* @__PURE__ */ jsxs(View, {
507
+ style: styles$1.actions,
508
+ children: [/* @__PURE__ */ jsx(Button, {
509
+ variant: "outline",
510
+ disabled: saving || props.sessionId === null,
511
+ onPress: () => void save(),
512
+ children: "Download"
513
+ }), saving ? /* @__PURE__ */ jsx(Button, {
514
+ variant: "ghost",
515
+ onPress: () => {
516
+ downloadController.current?.abort();
517
+ downloadController.current = void 0;
518
+ setDownload({
519
+ key: downloadKey,
520
+ note: "Download cancelled."
521
+ });
522
+ },
523
+ children: "Cancel download"
524
+ }) : null]
525
+ }) : /* @__PURE__ */ jsx(Text, {
526
+ style: styles$1.meta,
527
+ children: "This host cannot save artifact downloads."
528
+ }),
529
+ download?.key === downloadKey && download.note ? /* @__PURE__ */ jsx(Text, {
530
+ accessibilityLiveRegion: "polite",
531
+ style: styles$1.meta,
532
+ children: download.note
533
+ }) : null,
534
+ props.sessionId === null ? /* @__PURE__ */ jsx(Text, {
535
+ accessibilityRole: "alert",
536
+ style: styles$1.error,
537
+ children: "The session is unavailable."
538
+ }) : current?.error ? /* @__PURE__ */ jsx(Text, {
539
+ accessibilityRole: "alert",
540
+ style: styles$1.error,
541
+ children: current.error
542
+ }) : current?.preview ? /* @__PURE__ */ jsx(ArtifactPreview, {
543
+ preview: current.preview,
544
+ sessionId: props.sessionId,
545
+ renderFilePreview: props.renderFilePreview,
546
+ renderMediaPreview: props.renderMediaPreview,
547
+ media: current.media,
548
+ mediaError: current.mediaError,
549
+ onOpen: (path) => {
550
+ if (props.sessionId !== null) props.openTransientTab(artifactTab(props.sessionId, path));
551
+ }
552
+ }) : /* @__PURE__ */ jsx(Text, {
553
+ style: styles$1.meta,
554
+ children: "Reading this artifact..."
555
+ })
556
+ ]
557
+ });
558
+ }
559
+ function artifactTab(sessionId, path) {
560
+ return {
561
+ id: artifactTabId(sessionId, path),
562
+ label: path.split("/").at(-1) ?? path,
563
+ panel: (props) => /* @__PURE__ */ jsx(ArtifactPanel, {
564
+ ...props,
565
+ sessionId,
566
+ path
567
+ })
568
+ };
569
+ }
570
+ const styles$1 = StyleSheet.create((theme) => ({
571
+ content: {
572
+ padding: theme.spacing(4),
573
+ gap: theme.spacing(3),
574
+ uni__dependencies: [0]
575
+ },
576
+ actions: {
577
+ flexDirection: "row",
578
+ flexWrap: "wrap",
579
+ gap: theme.spacing(2),
580
+ uni__dependencies: [0]
581
+ },
582
+ path: {
583
+ fontFamily: fontOf(theme.fontFamily.mono),
584
+ fontSize: theme.fontSize.sm,
585
+ color: theme.colors.dim,
586
+ uni__dependencies: [0]
587
+ },
588
+ meta: {
589
+ fontFamily: fontOf(theme.fontFamily.sans),
590
+ fontSize: theme.fontSize.sm,
591
+ color: theme.colors.dim,
592
+ uni__dependencies: [0]
593
+ },
594
+ error: {
595
+ fontFamily: fontOf(theme.fontFamily.sans),
596
+ fontSize: theme.fontSize.sm,
597
+ color: theme.colors.red,
598
+ uni__dependencies: [0]
599
+ }
600
+ }));
601
+ //#endregion
602
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactsPanel.native.tsx
603
+ function ArtifactsPanel(props) {
604
+ const snapshot = useArtifacts(props.sessionId, true);
605
+ return /* @__PURE__ */ jsxs(ScrollView, {
606
+ contentContainerStyle: styles.content,
607
+ children: [/* @__PURE__ */ jsx(Text, {
608
+ accessibilityRole: "header",
609
+ style: styles.heading,
610
+ children: "Artifacts"
611
+ }), /* @__PURE__ */ jsx(ArtifactList, {
612
+ grid: true,
613
+ items: snapshot.items,
614
+ loading: !snapshot.loaded,
615
+ error: props.sessionId === null ? "The session is unavailable." : snapshot.error,
616
+ onOpen: (path) => {
617
+ if (props.sessionId !== null) props.openTransientTab(artifactTab(props.sessionId, path));
618
+ }
619
+ })]
620
+ });
621
+ }
622
+ function ArtifactsActivitySection(props) {
623
+ const snapshot = useArtifacts(props.sessionId);
624
+ return /* @__PURE__ */ jsx(ArtifactList, {
625
+ items: snapshot.items.slice(0, 3),
626
+ loading: !snapshot.loaded,
627
+ error: props.sessionId === null ? "The session is unavailable." : snapshot.error,
628
+ onOpen: (path) => {
629
+ if (props.sessionId !== null) props.openTransientTab(artifactTab(props.sessionId, path));
630
+ }
631
+ });
632
+ }
633
+ function artifactsTab() {
634
+ return {
635
+ id: "artifacts",
636
+ label: "Artifacts",
637
+ panel: ArtifactsPanel
638
+ };
639
+ }
640
+ const styles = StyleSheet.create((theme) => ({
641
+ content: {
642
+ padding: theme.spacing(4),
643
+ gap: theme.spacing(4),
644
+ uni__dependencies: [0]
645
+ },
646
+ heading: {
647
+ fontFamily: fontOf(theme.fontFamily.sans),
648
+ fontWeight: "600",
649
+ fontSize: theme.fontSize.lg,
650
+ color: theme.colors.hi,
651
+ uni__dependencies: [0]
652
+ }
653
+ }));
654
+ //#endregion
655
+ //#region src/extensions/workspaces/sessions/(frontend)/activity-group/artifacts.native.ts
656
+ var artifacts_native_default$1 = defineActivityGroup({
657
+ name: "artifacts",
658
+ keys: "",
659
+ activeSource: artifactActivitySource,
660
+ marksBackgroundWork: false,
661
+ transientTab: artifactsTab,
662
+ order: 15
663
+ });
664
+ //#endregion
665
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/artifacts.native.ts
666
+ var artifacts_native_default = defineChannelFile(artifactsChannel);
667
+ //#endregion
668
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/Artifacts.activity.artifacts.native.tsx
669
+ var Artifacts_activity_artifacts_native_default = defineFill({
670
+ slot: "activity.artifacts",
671
+ id: "artifacts",
672
+ component: ArtifactsActivitySection
673
+ });
674
+ //#endregion
675
+ //#region generated/native.ts
676
+ const via = (identity, value) => value === void 0 ? void 0 : {
677
+ ...identity,
678
+ ...value
679
+ };
680
+ const nativePlugin = defineNativePlugin({
681
+ id: "artifacts",
682
+ session: {
683
+ get activityGroups() {
684
+ return [via({ name: "artifacts" }, artifacts_native_default$1)];
685
+ },
686
+ get channels() {
687
+ return [via({ channel: "artifacts" }, artifacts_native_default)];
688
+ },
689
+ get fills() {
690
+ return [via({
691
+ slot: "activity.artifacts",
692
+ id: "artifacts"
693
+ }, Artifacts_activity_artifacts_native_default)];
694
+ }
695
+ }
696
+ });
697
+ //#endregion
698
+ export { nativePlugin };
699
+
700
+ //# sourceMappingURL=native.mjs.map