@agimon-ai/po-artifact 0.0.0-alpha.21 → 0.0.0-alpha.22

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.
@@ -1,9 +1,9 @@
1
- import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineGlobalStore, defineNativePlugin, defineSessionStore, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
1
+ import { createApiClient, defineActivityGroup, defineChannelFile, defineFileLinks, defineFill, defineGlobalStore, defineNativePlugin, defineSessionStore, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
2
+ import { Button, ChevronDownIcon, ChevronRightIcon, CloseIcon, CodeBlock, CopyButton, DownloadIcon, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, FileIcon, FolderIcon, ImageIcon, Input, Markdown, SearchIcon, SegmentedControl, SlidersIcon, fontOf } from "@agimon-ai/po-native-components";
3
+ import { createElement, useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
2
4
  import { View } from "react-native-unistyles/components/native/View";
3
5
  import { Text } from "react-native-unistyles/components/native/Text";
4
6
  import { Image } from "react-native-unistyles/components/native/Image";
5
- import { Button, ChevronDownIcon, ChevronRightIcon, CloseIcon, CodeBlock, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, FileIcon, FolderIcon, ImageIcon, Input, Markdown, SearchIcon, SegmentedControl, SlidersIcon, fontOf } from "@agimon-ai/po-native-components";
6
- import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
7
7
  import { StyleSheet } from "react-native-unistyles";
8
8
  import { FlatList } from "react-native-unistyles/components/native/FlatList";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -126,6 +126,14 @@ function artifactTree(items, isOpen) {
126
126
  walk(root, 0);
127
127
  return rows;
128
128
  }
129
+ /** `Modified today` on the same calendar day, otherwise the short date. */
130
+ function modifiedLabel(modifiedAt, now) {
131
+ const date = new Date(modifiedAt);
132
+ return date.toDateString() === new Date(now).toDateString() ? "Modified today" : `Modified ${date.toLocaleDateString("en-US", {
133
+ month: "short",
134
+ day: "numeric"
135
+ })}`;
136
+ }
129
137
  function sizeLabel(size) {
130
138
  return size < 1024 ? `${size} B` : size < 1048576 ? `${Math.ceil(size / 1024)} KB` : `${(size / 1048576).toFixed(1)} MB`;
131
139
  }
@@ -381,19 +389,19 @@ function ArtifactList({ items, onOpen, loading, error, grid = false, browser })
381
389
  testID: `artifact-${item.path}`,
382
390
  onPress: () => onOpen(item.path),
383
391
  style: styles$3.item,
384
- children: [/* @__PURE__ */ jsx(FolderIcon, {
392
+ children: [/* @__PURE__ */ jsx(FileIcon, {
385
393
  size: 18,
386
394
  color: "dim"
387
395
  }), /* @__PURE__ */ jsxs(View, {
388
396
  style: styles$3.label,
389
397
  children: [
390
398
  /* @__PURE__ */ jsx(Text, {
391
- numberOfLines: 2,
399
+ numberOfLines: 1,
392
400
  style: styles$3.name,
393
401
  children: item.name
394
402
  }),
395
403
  grid || item.path !== item.name ? /* @__PURE__ */ jsx(Text, {
396
- numberOfLines: 2,
404
+ numberOfLines: 1,
397
405
  style: styles$3.path,
398
406
  children: item.path
399
407
  }) : null,
@@ -799,9 +807,10 @@ const styles$3 = StyleSheet.create((theme) => ({
799
807
  },
800
808
  item: {
801
809
  minHeight: 44,
810
+ flexDirection: "row",
802
811
  justifyContent: "flex-start",
803
812
  alignItems: "center",
804
- gap: theme.spacing(2),
813
+ gap: theme.spacing(3),
805
814
  uni__dependencies: [0]
806
815
  },
807
816
  label: {
@@ -926,6 +935,7 @@ const styles$2 = StyleSheet.create((theme) => ({
926
935
  }));
927
936
  //#endregion
928
937
  //#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactPanel.native.tsx
938
+ const HostCopyButton = CopyButton;
929
939
  function ArtifactPanel({ path, scope = "artifacts", ...props }) {
930
940
  const item = useArtifacts(props.sessionId).items.find((item) => item.path === path);
931
941
  const version = `${item?.modifiedAt ?? ""}:${item?.size ?? ""}`;
@@ -1008,67 +1018,166 @@ function ArtifactPanel({ path, scope = "artifacts", ...props }) {
1008
1018
  if (downloadController.current === controller) downloadController.current = void 0;
1009
1019
  }
1010
1020
  };
1011
- return /* @__PURE__ */ jsxs(ScrollView, {
1012
- contentContainerStyle: styles$1.content,
1021
+ const cancel = () => {
1022
+ downloadController.current?.abort();
1023
+ downloadController.current = void 0;
1024
+ setDownload({
1025
+ key: downloadKey,
1026
+ note: "Download cancelled."
1027
+ });
1028
+ };
1029
+ const handlers = useRef({
1030
+ save,
1031
+ cancel
1032
+ });
1033
+ useEffect(() => {
1034
+ handlers.current = {
1035
+ save,
1036
+ cancel
1037
+ };
1038
+ });
1039
+ const { setTabHeader } = props;
1040
+ const canSave = props.nativeSaveFile !== void 0;
1041
+ const media = artifactPreviewMode(path) === "media" && props.renderMediaPreview !== void 0 && current?.media !== void 0;
1042
+ const subtitle = `${artifactKind(path)}${item ? ` · ${sizeLabel(item.size)}` : ""}`;
1043
+ const offline = props.sessionId === null;
1044
+ useEffect(() => {
1045
+ if (!setTabHeader || media) return;
1046
+ setTabHeader({
1047
+ subtitle,
1048
+ action: canSave ? /* @__PURE__ */ jsx(Button, {
1049
+ variant: "ghost",
1050
+ size: "icon",
1051
+ disabled: offline,
1052
+ accessibilityLabel: saving ? "Cancel download" : "Download",
1053
+ onPress: () => saving ? handlers.current.cancel() : void handlers.current.save(),
1054
+ children: saving ? /* @__PURE__ */ jsx(CloseIcon, { color: "hi" }) : /* @__PURE__ */ jsx(DownloadIcon, { color: "hi" })
1055
+ }) : void 0
1056
+ });
1057
+ }, [
1058
+ setTabHeader,
1059
+ media,
1060
+ subtitle,
1061
+ canSave,
1062
+ saving,
1063
+ offline
1064
+ ]);
1065
+ const note = download?.key === downloadKey && download.note ? /* @__PURE__ */ jsx(Text, {
1066
+ accessibilityLiveRegion: "polite",
1067
+ style: styles$1.meta,
1068
+ children: download.note
1069
+ }) : null;
1070
+ const body = props.sessionId === null ? /* @__PURE__ */ jsx(Text, {
1071
+ accessibilityRole: "alert",
1072
+ style: styles$1.error,
1073
+ children: "The session is unavailable."
1074
+ }) : current?.error ? /* @__PURE__ */ jsx(Text, {
1075
+ accessibilityRole: "alert",
1076
+ style: styles$1.error,
1077
+ children: current.error
1078
+ }) : current?.preview ? /* @__PURE__ */ jsx(ArtifactPreview, {
1079
+ preview: current.preview,
1080
+ sessionId: props.sessionId,
1081
+ renderFilePreview: props.renderFilePreview,
1082
+ renderMediaPreview: props.renderMediaPreview,
1083
+ media: current.media,
1084
+ mediaError: current.mediaError,
1085
+ onOpen: (path) => {
1086
+ if (props.sessionId !== null) props.openTransientTab(artifactTab(props.sessionId, path, scope));
1087
+ }
1088
+ }) : /* @__PURE__ */ jsx(Text, {
1089
+ style: styles$1.meta,
1090
+ children: "Reading this artifact..."
1091
+ });
1092
+ return /* @__PURE__ */ jsxs(View, {
1093
+ style: styles$1.screen,
1094
+ children: [media ? /* @__PURE__ */ jsx(View, {
1095
+ style: styles$1.stage,
1096
+ children: body
1097
+ }) : /* @__PURE__ */ jsxs(ScrollView, {
1098
+ style: styles$1.stage,
1099
+ contentContainerStyle: styles$1.content,
1100
+ children: [
1101
+ setTabHeader ? null : /* @__PURE__ */ jsx(ArtifactPanelHeader, {
1102
+ path,
1103
+ size: item?.size,
1104
+ saving,
1105
+ canSave,
1106
+ disabled: offline,
1107
+ onDownload: () => void save(),
1108
+ onCancel: cancel
1109
+ }),
1110
+ note,
1111
+ body
1112
+ ]
1113
+ }), /* @__PURE__ */ jsx(ArtifactPathStrip, {
1114
+ path,
1115
+ modifiedAt: item?.modifiedAt
1116
+ })]
1117
+ });
1118
+ }
1119
+ /** Where the file lives and when it last changed, pinned under the preview. */
1120
+ function ArtifactPathStrip({ path, modifiedAt }) {
1121
+ const [now] = useState(Date.now);
1122
+ return /* @__PURE__ */ jsxs(View, {
1123
+ style: styles$1.strip,
1013
1124
  children: [
1014
1125
  /* @__PURE__ */ jsx(Text, {
1015
1126
  selectable: true,
1016
- style: styles$1.path,
1127
+ numberOfLines: 1,
1128
+ ellipsizeMode: "middle",
1129
+ style: [styles$1.path, styles$1.stripPath],
1017
1130
  children: path
1018
1131
  }),
1019
- props.nativeSaveFile ? /* @__PURE__ */ jsxs(View, {
1020
- style: styles$1.actions,
1021
- children: [/* @__PURE__ */ jsx(Button, {
1022
- variant: "outline",
1023
- disabled: saving || props.sessionId === null,
1024
- onPress: () => void save(),
1025
- children: "Download"
1026
- }), saving ? /* @__PURE__ */ jsx(Button, {
1027
- variant: "ghost",
1028
- onPress: () => {
1029
- downloadController.current?.abort();
1030
- downloadController.current = void 0;
1031
- setDownload({
1032
- key: downloadKey,
1033
- note: "Download cancelled."
1034
- });
1035
- },
1036
- children: "Cancel download"
1037
- }) : null]
1038
- }) : /* @__PURE__ */ jsx(Text, {
1039
- style: styles$1.meta,
1040
- children: "This host cannot save artifact downloads."
1041
- }),
1042
- download?.key === downloadKey && download.note ? /* @__PURE__ */ jsx(Text, {
1043
- accessibilityLiveRegion: "polite",
1044
- style: styles$1.meta,
1045
- children: download.note
1132
+ HostCopyButton ? /* @__PURE__ */ jsx(HostCopyButton, {
1133
+ text: path,
1134
+ label: "path"
1046
1135
  }) : null,
1047
- props.sessionId === null ? /* @__PURE__ */ jsx(Text, {
1048
- accessibilityRole: "alert",
1049
- style: styles$1.error,
1050
- children: "The session is unavailable."
1051
- }) : current?.error ? /* @__PURE__ */ jsx(Text, {
1052
- accessibilityRole: "alert",
1053
- style: styles$1.error,
1054
- children: current.error
1055
- }) : current?.preview ? /* @__PURE__ */ jsx(ArtifactPreview, {
1056
- preview: current.preview,
1057
- sessionId: props.sessionId,
1058
- renderFilePreview: props.renderFilePreview,
1059
- renderMediaPreview: props.renderMediaPreview,
1060
- media: current.media,
1061
- mediaError: current.mediaError,
1062
- onOpen: (path) => {
1063
- if (props.sessionId !== null) props.openTransientTab(artifactTab(props.sessionId, path, scope));
1064
- }
1065
- }) : /* @__PURE__ */ jsx(Text, {
1136
+ modifiedAt === void 0 ? null : /* @__PURE__ */ jsx(Text, {
1066
1137
  style: styles$1.meta,
1067
- children: "Reading this artifact..."
1138
+ children: modifiedLabel(modifiedAt, now)
1068
1139
  })
1069
1140
  ]
1070
1141
  });
1071
1142
  }
1143
+ /** The preview's quiet header: where the file lives, what it is, and the one action on it. */
1144
+ function ArtifactPanelHeader({ path, size, saving, canSave, disabled, onDownload, onCancel }) {
1145
+ return /* @__PURE__ */ jsxs(View, {
1146
+ style: styles$1.header,
1147
+ children: [
1148
+ /* @__PURE__ */ jsxs(View, {
1149
+ style: styles$1.label,
1150
+ children: [/* @__PURE__ */ jsx(Text, {
1151
+ selectable: true,
1152
+ numberOfLines: 1,
1153
+ ellipsizeMode: "middle",
1154
+ style: styles$1.path,
1155
+ children: path
1156
+ }), /* @__PURE__ */ jsxs(Text, {
1157
+ style: styles$1.meta,
1158
+ children: [
1159
+ artifactKind(path),
1160
+ size === void 0 ? "" : ` · ${sizeLabel(size)}`,
1161
+ canSave ? "" : " · This host cannot save artifact downloads."
1162
+ ]
1163
+ })]
1164
+ }),
1165
+ canSave && saving ? /* @__PURE__ */ jsx(Button, {
1166
+ variant: "ghost",
1167
+ size: "sm",
1168
+ onPress: onCancel,
1169
+ children: "Cancel"
1170
+ }) : null,
1171
+ canSave ? /* @__PURE__ */ jsx(Button, {
1172
+ variant: "outline",
1173
+ size: "sm",
1174
+ disabled: saving || disabled,
1175
+ onPress: onDownload,
1176
+ children: "Download"
1177
+ }) : null
1178
+ ]
1179
+ });
1180
+ }
1072
1181
  function artifactTab(sessionId, path, scope = "artifacts") {
1073
1182
  return {
1074
1183
  id: `${scope === "temporary" ? "temporary:" : ""}${artifactTabId(sessionId, path)}`,
@@ -1081,16 +1190,40 @@ function artifactTab(sessionId, path, scope = "artifacts") {
1081
1190
  })
1082
1191
  };
1083
1192
  }
1084
- const styles$1 = StyleSheet.create((theme) => ({
1193
+ const styles$1 = StyleSheet.create((theme, rt) => ({
1194
+ screen: { flex: 1 },
1195
+ stage: { flex: 1 },
1085
1196
  content: {
1086
1197
  padding: theme.spacing(4),
1087
1198
  gap: theme.spacing(3),
1088
1199
  uni__dependencies: [0]
1089
1200
  },
1090
- actions: {
1201
+ strip: {
1091
1202
  flexDirection: "row",
1092
- flexWrap: "wrap",
1203
+ alignItems: "center",
1204
+ gap: theme.spacing(2),
1205
+ paddingHorizontal: theme.spacing(4),
1206
+ paddingTop: theme.spacing(2),
1207
+ paddingBottom: theme.spacing(2) + rt.insets.bottom,
1208
+ borderTopWidth: 1,
1209
+ borderTopColor: theme.colors["border-soft"],
1210
+ backgroundColor: theme.colors.bg,
1211
+ uni__dependencies: [0, 9]
1212
+ },
1213
+ stripPath: { flexShrink: 1 },
1214
+ header: {
1215
+ flexDirection: "row",
1216
+ alignItems: "center",
1093
1217
  gap: theme.spacing(2),
1218
+ paddingBottom: theme.spacing(3),
1219
+ borderBottomWidth: 1,
1220
+ borderBottomColor: theme.colors["border-soft"],
1221
+ uni__dependencies: [0]
1222
+ },
1223
+ label: {
1224
+ flex: 1,
1225
+ minWidth: 0,
1226
+ gap: theme.spacing(.5),
1094
1227
  uni__dependencies: [0]
1095
1228
  },
1096
1229
  path: {
@@ -1261,9 +1394,10 @@ const styles = StyleSheet.create((theme) => ({
1261
1394
  }));
1262
1395
  //#endregion
1263
1396
  //#region src/extensions/workspaces/sessions/(frontend)/activity-group/artifacts.native.ts
1264
- var artifacts_native_default$1 = defineActivityGroup({
1397
+ var artifacts_native_default$2 = defineActivityGroup({
1265
1398
  name: "artifacts",
1266
1399
  keys: "",
1400
+ icon: () => createElement(FileIcon),
1267
1401
  activeSource: artifactActivitySource,
1268
1402
  marksBackgroundWork: false,
1269
1403
  transientTab: artifactsTab,
@@ -1271,7 +1405,42 @@ var artifacts_native_default$1 = defineActivityGroup({
1271
1405
  });
1272
1406
  //#endregion
1273
1407
  //#region src/extensions/workspaces/sessions/(frontend)/channel/artifacts.native.ts
1274
- var artifacts_native_default = defineChannelFile(artifactsChannel);
1408
+ var artifacts_native_default$1 = defineChannelFile(artifactsChannel);
1409
+ //#endregion
1410
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/temporaryFiles.ts
1411
+ /** Matching links is presentation only. The server independently selects and guards the root. */
1412
+ function temporaryFileReference(reference, root) {
1413
+ const value = reference.trim();
1414
+ const prefix = "temporary:";
1415
+ let relative;
1416
+ if (value.startsWith(prefix)) relative = value.slice(10);
1417
+ else {
1418
+ if (!root || !root.startsWith("/")) return void 0;
1419
+ const base = root.replace(/\/+$/u, "");
1420
+ if (!value.startsWith(`${base}/`)) return void 0;
1421
+ relative = value.slice(base.length + 1);
1422
+ }
1423
+ if (!relative || relative.includes("\\") || relative.includes("\0") || relative.split("/").some((part) => !part || part === "." || part === "..")) return void 0;
1424
+ return relative;
1425
+ }
1426
+ //#endregion
1427
+ //#region src/extensions/workspaces/sessions/(frontend)/file-links/artifacts.native.ts
1428
+ var artifacts_native_default = defineFileLinks({
1429
+ subscribe(listener) {
1430
+ const subscription = artifacts.store.subscribe(listener);
1431
+ return () => subscription.unsubscribe();
1432
+ },
1433
+ fingerprint: (sessionId) => artifacts.select(artifacts.store.state, sessionId).temporaryRoot ?? "",
1434
+ resolve(sessionId, path) {
1435
+ if (sessionId === null) return void 0;
1436
+ const reference = path.trim();
1437
+ const temporary = temporaryFileReference(reference, artifacts.select(artifacts.store.state, sessionId).temporaryRoot);
1438
+ if (temporary) return artifactTab(sessionId, temporary, "temporary");
1439
+ if (!reference.startsWith("artifact:")) return void 0;
1440
+ const relative = reference.slice(9);
1441
+ return relative ? artifactTab(sessionId, relative) : void 0;
1442
+ }
1443
+ });
1275
1444
  //#endregion
1276
1445
  //#region src/extensions/workspaces/sessions/(frontend)/fill/Artifacts.activity.artifacts.native.tsx
1277
1446
  var Artifacts_activity_artifacts_native_default = defineFill({
@@ -1289,10 +1458,13 @@ const nativePlugin = defineNativePlugin({
1289
1458
  id: "artifacts",
1290
1459
  session: {
1291
1460
  get activityGroups() {
1292
- return [via({ name: "artifacts" }, artifacts_native_default$1)];
1461
+ return [via({ name: "artifacts" }, artifacts_native_default$2)];
1293
1462
  },
1294
1463
  get channels() {
1295
- return [via({ channel: "artifacts" }, artifacts_native_default)];
1464
+ return [via({ channel: "artifacts" }, artifacts_native_default$1)];
1465
+ },
1466
+ get fileLinks() {
1467
+ return artifacts_native_default;
1296
1468
  },
1297
1469
  get fills() {
1298
1470
  return [via({