@stigmer/react 3.1.3 → 3.1.4
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/composer/SessionComposer.d.ts +31 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +12 -4
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactApplyButton.d.ts +25 -0
- package/execution/ArtifactApplyButton.d.ts.map +1 -0
- package/execution/ArtifactApplyButton.js +21 -0
- package/execution/ArtifactApplyButton.js.map +1 -0
- package/execution/ArtifactContentBody.d.ts +36 -0
- package/execution/ArtifactContentBody.d.ts.map +1 -0
- package/execution/ArtifactContentBody.js +62 -0
- package/execution/ArtifactContentBody.js.map +1 -0
- package/execution/ArtifactDocument.d.ts +48 -0
- package/execution/ArtifactDocument.d.ts.map +1 -0
- package/execution/ArtifactDocument.js +89 -0
- package/execution/ArtifactDocument.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +4 -4
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +15 -142
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactRow.d.ts +49 -0
- package/execution/ArtifactRow.d.ts.map +1 -0
- package/execution/ArtifactRow.js +56 -0
- package/execution/ArtifactRow.js.map +1 -0
- package/execution/ArtifactsWidget.d.ts +6 -9
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +15 -19
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/index.d.ts +8 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +4 -1
- package/execution/index.js.map +1 -1
- package/execution/useArtifactContent.d.ts +6 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +7 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useArtifactInspection.d.ts +85 -0
- package/execution/useArtifactInspection.d.ts.map +1 -0
- package/execution/useArtifactInspection.js +143 -0
- package/execution/useArtifactInspection.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +11 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/internal/MermaidDiagram.d.ts +47 -0
- package/internal/MermaidDiagram.d.ts.map +1 -0
- package/internal/MermaidDiagram.js +118 -0
- package/internal/MermaidDiagram.js.map +1 -0
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +34 -0
- package/internal/markdown-components.js.map +1 -1
- package/internal/mermaid-loader.d.ts +26 -0
- package/internal/mermaid-loader.d.ts.map +1 -0
- package/internal/mermaid-loader.js +34 -0
- package/internal/mermaid-loader.js.map +1 -0
- package/internal/mermaid-theme-css.d.ts +41 -0
- package/internal/mermaid-theme-css.d.ts.map +1 -0
- package/internal/mermaid-theme-css.js +179 -0
- package/internal/mermaid-theme-css.js.map +1 -0
- package/internal/slug.d.ts +9 -5
- package/internal/slug.d.ts.map +1 -1
- package/internal/slug.js +10 -6
- package/internal/slug.js.map +1 -1
- package/package.json +5 -4
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +91 -18
- package/session/SessionViewer.js.map +1 -1
- package/session/artifact-document.d.ts +13 -0
- package/session/artifact-document.d.ts.map +1 -0
- package/session/artifact-document.js +22 -0
- package/session/artifact-document.js.map +1 -0
- package/session/facets/ArtifactsTab.d.ts +30 -4
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +32 -10
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/index.d.ts +2 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +4 -1
- package/session/index.js.map +1 -1
- package/session/useSessionArtifacts.d.ts +17 -0
- package/session/useSessionArtifacts.d.ts.map +1 -1
- package/session/useSessionArtifacts.js +16 -5
- package/session/useSessionArtifacts.js.map +1 -1
- package/session/useSessionConversation.d.ts +17 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +32 -4
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionPanel.d.ts +20 -0
- package/session/useSessionPanel.d.ts.map +1 -1
- package/session/useSessionPanel.js +13 -0
- package/session/useSessionPanel.js.map +1 -1
- package/session/useSessionRailViews.d.ts +13 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +5 -3
- package/session/useSessionRailViews.js.map +1 -1
- package/src/composer/SessionComposer.tsx +69 -1
- package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
- package/src/execution/ArtifactApplyButton.tsx +79 -0
- package/src/execution/ArtifactContentBody.tsx +255 -0
- package/src/execution/ArtifactDocument.tsx +344 -0
- package/src/execution/ArtifactPreviewModal.tsx +39 -405
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/message-entry.test.tsx +50 -2
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +14 -2
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +12 -0
- package/src/index.ts +11 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
- package/src/internal/__tests__/markdown-components.test.tsx +93 -1
- package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
- package/src/internal/markdown-components.tsx +36 -1
- package/src/internal/mermaid-loader.ts +44 -0
- package/src/internal/mermaid-theme-css.ts +178 -0
- package/src/internal/slug.ts +10 -6
- package/src/session/SessionViewer.tsx +177 -33
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +172 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- package/src/session/artifact-document.ts +23 -0
- package/src/session/facets/ArtifactsTab.tsx +66 -18
- package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
- package/src/session/index.ts +5 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +48 -6
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/styles.css +1 -1
- package/execution/ArtifactCard.d.ts +0 -81
- package/execution/ArtifactCard.d.ts.map +0 -1
- package/execution/ArtifactCard.js +0 -130
- package/execution/ArtifactCard.js.map +0 -1
- package/src/execution/ArtifactCard.tsx +0 -320
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useRef
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
4
4
|
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
5
|
-
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
6
5
|
import { cn } from "@stigmer/theme";
|
|
7
|
-
import { useArtifactContent } from "./useArtifactContent.js";
|
|
8
6
|
import { useArtifactDownload } from "./useArtifactDownload.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import type {
|
|
14
|
-
import {
|
|
15
|
-
useApplyResource,
|
|
16
|
-
type ApplyResourceResult,
|
|
17
|
-
} from "../library/useApplyResource.js";
|
|
18
|
-
|
|
19
|
-
const COPIED_FEEDBACK_MS = 2000;
|
|
7
|
+
import { formatArtifactSize } from "./artifact-utils.js";
|
|
8
|
+
import { ArtifactContentBody } from "./ArtifactContentBody.js";
|
|
9
|
+
import { ArtifactApplyButton } from "./ArtifactApplyButton.js";
|
|
10
|
+
import { useArtifactInspection } from "./useArtifactInspection.js";
|
|
11
|
+
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
20
12
|
|
|
21
13
|
// ---------------------------------------------------------------------------
|
|
22
14
|
// ArtifactPreviewContent — standalone content component
|
|
@@ -66,7 +58,7 @@ export interface ArtifactPreviewContentProps {
|
|
|
66
58
|
* it inside a `<dialog>`, modal, sheet, overlay, or inline context as
|
|
67
59
|
* needed.
|
|
68
60
|
*
|
|
69
|
-
* Orchestrates the
|
|
61
|
+
* Orchestrates the shared detection pipeline (see {@link useArtifactInspection}):
|
|
70
62
|
*
|
|
71
63
|
* - **FILE artifacts**: Fetches text content via {@link useArtifactContent},
|
|
72
64
|
* renders via {@link ArtifactContentRenderer} (markdown, YAML, JSON, or
|
|
@@ -96,7 +88,7 @@ export interface ArtifactPreviewContentProps {
|
|
|
96
88
|
* ```
|
|
97
89
|
*
|
|
98
90
|
* @see {@link ArtifactPreviewModal} — wraps this component in a native `<dialog>`
|
|
99
|
-
* @see {@link
|
|
91
|
+
* @see {@link ArtifactRow} — dense list row that triggers preview via `onOpen`
|
|
100
92
|
* @see {@link useArtifactContent} — content-fetching hook (headless alternative)
|
|
101
93
|
* @see {@link useDetectStigmerResource} — YAML resource detection (headless)
|
|
102
94
|
* @see {@link useDetectSkillPackage} — skill package detection (headless)
|
|
@@ -112,121 +104,12 @@ export function ArtifactPreviewContent({
|
|
|
112
104
|
onImplement,
|
|
113
105
|
className,
|
|
114
106
|
}: ArtifactPreviewContentProps) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Detection orchestration
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
const {
|
|
123
|
-
content,
|
|
124
|
-
contentType,
|
|
125
|
-
isTruncated,
|
|
126
|
-
isLoading: isContentLoading,
|
|
127
|
-
error: contentError,
|
|
128
|
-
} = useArtifactContent(
|
|
129
|
-
canFetchContent ? executionId : null,
|
|
130
|
-
canFetchContent ? artifact.storageKey : null,
|
|
131
|
-
undefined,
|
|
132
|
-
artifact.contentHash || undefined,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const yamlDetection = useDetectStigmerResource(
|
|
136
|
-
canFetchContent ? content : null,
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
const { detection: skillDetection, isLoading: isSkillLoading } =
|
|
140
|
-
useDetectSkillPackage(
|
|
141
|
-
isDirectory ? artifact : null,
|
|
142
|
-
isDirectory ? executionId : null,
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
// Derived detection state
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
const isDetected = yamlDetection.detected || skillDetection.detected;
|
|
150
|
-
const isDetecting =
|
|
151
|
-
(canFetchContent && isContentLoading) || (isDirectory && isSkillLoading);
|
|
152
|
-
|
|
153
|
-
let detectionLabel: string | null = null;
|
|
154
|
-
if (yamlDetection.detected) {
|
|
155
|
-
detectionLabel = `${yamlDetection.displayName} detected`;
|
|
156
|
-
} else if (skillDetection.detected) {
|
|
157
|
-
const count = skillDetection.fileCount;
|
|
158
|
-
detectionLabel = `Skill \u00B7 ${count} ${count === 1 ? "file" : "files"}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
// Apply state
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
|
|
165
|
-
const {
|
|
166
|
-
applyYamlResource,
|
|
167
|
-
pushSkillPackage,
|
|
168
|
-
isApplying,
|
|
169
|
-
error: applyError,
|
|
170
|
-
clearError,
|
|
171
|
-
} = useApplyResource();
|
|
172
|
-
|
|
173
|
-
const [applyResult, setApplyResult] = useState<ApplyResourceResult | null>(
|
|
174
|
-
null,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
const handleApply = useCallback(async () => {
|
|
178
|
-
clearError();
|
|
179
|
-
try {
|
|
180
|
-
let result: ApplyResourceResult;
|
|
181
|
-
if (yamlDetection.detected && content) {
|
|
182
|
-
result = await applyYamlResource(content, org);
|
|
183
|
-
} else if (skillDetection.detected) {
|
|
184
|
-
result = await pushSkillPackage({
|
|
185
|
-
org,
|
|
186
|
-
executionId,
|
|
187
|
-
storageKey: artifact.storageKey,
|
|
188
|
-
});
|
|
189
|
-
} else {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
setApplyResult(result);
|
|
193
|
-
onApplied?.(result);
|
|
194
|
-
} catch {
|
|
195
|
-
// error state managed by useApplyResource
|
|
196
|
-
}
|
|
197
|
-
}, [
|
|
198
|
-
yamlDetection.detected,
|
|
199
|
-
skillDetection.detected,
|
|
200
|
-
content,
|
|
201
|
-
org,
|
|
202
|
-
executionId,
|
|
203
|
-
artifact.storageKey,
|
|
204
|
-
applyYamlResource,
|
|
205
|
-
pushSkillPackage,
|
|
206
|
-
clearError,
|
|
107
|
+
// The whole inspect-and-act pipeline (content, detection, apply/push, copy)
|
|
108
|
+
// lives in one headless hook, shared with the editor-area ArtifactDocument so
|
|
109
|
+
// the two surfaces can never drift.
|
|
110
|
+
const inspection = useArtifactInspection(artifact, executionId, org, {
|
|
207
111
|
onApplied,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
// Copy to clipboard
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
const [copied, setCopied] = useState(false);
|
|
215
|
-
|
|
216
|
-
const handleCopy = useCallback(() => {
|
|
217
|
-
if (!content) return;
|
|
218
|
-
navigator.clipboard.writeText(content).then(() => {
|
|
219
|
-
setCopied(true);
|
|
220
|
-
setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
|
|
221
|
-
});
|
|
222
|
-
}, [content]);
|
|
223
|
-
|
|
224
|
-
let ctaLabel: string | null = null;
|
|
225
|
-
if (yamlDetection.detected) {
|
|
226
|
-
ctaLabel = `Apply to ${org}`;
|
|
227
|
-
} else if (skillDetection.detected) {
|
|
228
|
-
ctaLabel = `Push Skill to ${org}`;
|
|
229
|
-
}
|
|
112
|
+
});
|
|
230
113
|
|
|
231
114
|
// "Build" runs the plan; closing the modal lets the host's submit pipeline
|
|
232
115
|
// take over (switch to Agent + send). Single combined handler keeps the
|
|
@@ -236,52 +119,41 @@ export function ArtifactPreviewContent({
|
|
|
236
119
|
onClose();
|
|
237
120
|
}, [onImplement, onClose]);
|
|
238
121
|
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
// Render
|
|
241
|
-
// ---------------------------------------------------------------------------
|
|
242
|
-
|
|
243
122
|
return (
|
|
244
123
|
<div className={cn("flex max-h-[80vh] flex-col", className)}>
|
|
245
124
|
<ContentHeader
|
|
246
125
|
artifact={artifact}
|
|
247
|
-
isDirectory={isDirectory}
|
|
248
|
-
detectionLabel={detectionLabel}
|
|
249
|
-
isDetecting={isDetecting}
|
|
126
|
+
isDirectory={inspection.isDirectory}
|
|
127
|
+
detectionLabel={inspection.detectionLabel}
|
|
128
|
+
isDetecting={inspection.isDetecting}
|
|
250
129
|
onClose={onClose}
|
|
251
130
|
/>
|
|
252
131
|
|
|
253
|
-
<
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
contentType={contentType}
|
|
264
|
-
isLoading={isContentLoading}
|
|
265
|
-
error={contentError}
|
|
266
|
-
isTruncated={isTruncated}
|
|
267
|
-
/>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
132
|
+
<ArtifactContentBody
|
|
133
|
+
artifact={artifact}
|
|
134
|
+
content={inspection.content}
|
|
135
|
+
contentType={inspection.contentType}
|
|
136
|
+
isLoading={inspection.isLoading}
|
|
137
|
+
error={inspection.error}
|
|
138
|
+
isTruncated={inspection.isTruncated}
|
|
139
|
+
skillDetection={inspection.skillDetection}
|
|
140
|
+
className="min-h-0 flex-1 overflow-y-auto border-t border-border"
|
|
141
|
+
/>
|
|
270
142
|
|
|
271
143
|
<ActionBar
|
|
272
144
|
artifact={artifact}
|
|
273
145
|
executionId={executionId}
|
|
274
|
-
isDirectory={isDirectory}
|
|
275
|
-
hasContent={content !== null}
|
|
276
|
-
copied={copied}
|
|
277
|
-
onCopy={
|
|
278
|
-
isDetected={isDetected}
|
|
279
|
-
ctaLabel={ctaLabel}
|
|
146
|
+
isDirectory={inspection.isDirectory}
|
|
147
|
+
hasContent={inspection.content !== null}
|
|
148
|
+
copied={inspection.copied}
|
|
149
|
+
onCopy={inspection.copy}
|
|
150
|
+
isDetected={inspection.isDetected}
|
|
151
|
+
ctaLabel={inspection.ctaLabel}
|
|
280
152
|
isTerminal={isTerminal}
|
|
281
|
-
isApplying={isApplying}
|
|
282
|
-
applyResult={applyResult}
|
|
283
|
-
applyError={applyError}
|
|
284
|
-
onApply={
|
|
153
|
+
isApplying={inspection.isApplying}
|
|
154
|
+
applyResult={inspection.applyResult}
|
|
155
|
+
applyError={inspection.applyError}
|
|
156
|
+
onApply={inspection.apply}
|
|
285
157
|
onImplement={onImplement ? handleImplement : undefined}
|
|
286
158
|
/>
|
|
287
159
|
|
|
@@ -291,7 +163,7 @@ export function ArtifactPreviewContent({
|
|
|
291
163
|
aria-atomic="true"
|
|
292
164
|
className="sr-only"
|
|
293
165
|
>
|
|
294
|
-
{copied && "Content copied to clipboard"}
|
|
166
|
+
{inspection.copied && "Content copied to clipboard"}
|
|
295
167
|
</div>
|
|
296
168
|
</div>
|
|
297
169
|
);
|
|
@@ -368,7 +240,7 @@ export interface ArtifactPreviewModalProps {
|
|
|
368
240
|
* ```
|
|
369
241
|
*
|
|
370
242
|
* @see {@link ArtifactPreviewContent} — the content component (use directly for non-dialog contexts)
|
|
371
|
-
* @see {@link
|
|
243
|
+
* @see {@link ArtifactRow} — dense list row that triggers preview via `onOpen`
|
|
372
244
|
*/
|
|
373
245
|
export function ArtifactPreviewModal({
|
|
374
246
|
artifact,
|
|
@@ -496,128 +368,6 @@ function ContentHeader({
|
|
|
496
368
|
);
|
|
497
369
|
}
|
|
498
370
|
|
|
499
|
-
// ---------------------------------------------------------------------------
|
|
500
|
-
// FileContentStateView (internal — loading/error/empty states + renderer)
|
|
501
|
-
// ---------------------------------------------------------------------------
|
|
502
|
-
|
|
503
|
-
const SKELETON_LINE_WIDTHS = [85, 72, 90, 65, 78, 88, 70, 82] as const;
|
|
504
|
-
|
|
505
|
-
function FileContentStateView({
|
|
506
|
-
artifact,
|
|
507
|
-
content,
|
|
508
|
-
contentType,
|
|
509
|
-
isLoading,
|
|
510
|
-
error,
|
|
511
|
-
isTruncated,
|
|
512
|
-
}: {
|
|
513
|
-
readonly artifact: ExecutionArtifact;
|
|
514
|
-
readonly content: string | null;
|
|
515
|
-
readonly contentType: string | null;
|
|
516
|
-
readonly isLoading: boolean;
|
|
517
|
-
readonly error: Error | null;
|
|
518
|
-
readonly isTruncated: boolean;
|
|
519
|
-
}) {
|
|
520
|
-
if (isLoading) {
|
|
521
|
-
return (
|
|
522
|
-
<div className="space-y-2 p-4" aria-busy="true" aria-label="Loading content">
|
|
523
|
-
{SKELETON_LINE_WIDTHS.map((width, i) => (
|
|
524
|
-
<div
|
|
525
|
-
key={i}
|
|
526
|
-
className="h-4 animate-pulse rounded bg-muted"
|
|
527
|
-
style={{ width: `${width}%` }}
|
|
528
|
-
aria-hidden="true"
|
|
529
|
-
/>
|
|
530
|
-
))}
|
|
531
|
-
</div>
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (error) {
|
|
536
|
-
return (
|
|
537
|
-
<div className="flex flex-col items-center justify-center gap-2 p-8 text-center">
|
|
538
|
-
<ErrorAlertIcon />
|
|
539
|
-
<p className="text-sm text-destructive">{error.message}</p>
|
|
540
|
-
</div>
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (content === null) {
|
|
545
|
-
return (
|
|
546
|
-
<div className="p-8 text-center text-sm text-muted-foreground">
|
|
547
|
-
Content not available for preview.
|
|
548
|
-
</div>
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
return (
|
|
553
|
-
<ArtifactContentRenderer
|
|
554
|
-
content={content}
|
|
555
|
-
fileName={artifact.name}
|
|
556
|
-
contentType={contentType}
|
|
557
|
-
isTruncated={isTruncated}
|
|
558
|
-
/>
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// ---------------------------------------------------------------------------
|
|
563
|
-
// DirectoryContentView (internal)
|
|
564
|
-
// ---------------------------------------------------------------------------
|
|
565
|
-
|
|
566
|
-
function DirectoryContentView({
|
|
567
|
-
artifact,
|
|
568
|
-
skillDetection,
|
|
569
|
-
}: {
|
|
570
|
-
readonly artifact: ExecutionArtifact;
|
|
571
|
-
readonly skillDetection: SkillPackageDetection;
|
|
572
|
-
}) {
|
|
573
|
-
const entries = artifact.entries;
|
|
574
|
-
|
|
575
|
-
return (
|
|
576
|
-
<div className="p-4">
|
|
577
|
-
{skillDetection.detected && (
|
|
578
|
-
<div className="mb-4 rounded-md bg-primary-subtle p-3">
|
|
579
|
-
<p className="text-sm font-medium text-foreground">
|
|
580
|
-
{skillDetection.skillName}
|
|
581
|
-
</p>
|
|
582
|
-
{skillDetection.skillDescription && (
|
|
583
|
-
<p className="mt-1 text-xs text-muted-foreground">
|
|
584
|
-
{skillDetection.skillDescription}
|
|
585
|
-
</p>
|
|
586
|
-
)}
|
|
587
|
-
</div>
|
|
588
|
-
)}
|
|
589
|
-
|
|
590
|
-
{entries.length > 0 ? (
|
|
591
|
-
<div>
|
|
592
|
-
<h3 className="mb-2 text-xs font-medium text-muted-foreground">
|
|
593
|
-
Files ({entries.length})
|
|
594
|
-
</h3>
|
|
595
|
-
<ul className="space-y-0.5" role="list">
|
|
596
|
-
{entries.map((entry) => (
|
|
597
|
-
<li
|
|
598
|
-
key={entry}
|
|
599
|
-
className="flex items-center gap-2 rounded-sm px-2 py-1 font-mono text-xs text-foreground"
|
|
600
|
-
>
|
|
601
|
-
<EntryIcon name={entry} />
|
|
602
|
-
<span className="min-w-0 truncate">{entry}</span>
|
|
603
|
-
</li>
|
|
604
|
-
))}
|
|
605
|
-
</ul>
|
|
606
|
-
</div>
|
|
607
|
-
) : (
|
|
608
|
-
<p className="text-sm text-muted-foreground">
|
|
609
|
-
File listing not available.
|
|
610
|
-
</p>
|
|
611
|
-
)}
|
|
612
|
-
</div>
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function EntryIcon({ name }: { readonly name: string }) {
|
|
617
|
-
if (name.endsWith("/")) return <FolderSmallIcon />;
|
|
618
|
-
return <FileSmallIcon />;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
371
|
// ---------------------------------------------------------------------------
|
|
622
372
|
// ActionBar (internal)
|
|
623
373
|
// ---------------------------------------------------------------------------
|
|
@@ -727,7 +477,7 @@ function ActionBar({
|
|
|
727
477
|
</span>
|
|
728
478
|
) : isDetected && ctaLabel ? (
|
|
729
479
|
<span data-cursor-target="apply-resource-button">
|
|
730
|
-
<
|
|
480
|
+
<ArtifactApplyButton
|
|
731
481
|
label={ctaLabel}
|
|
732
482
|
isTerminal={isTerminal}
|
|
733
483
|
isApplying={isApplying}
|
|
@@ -740,46 +490,6 @@ function ActionBar({
|
|
|
740
490
|
);
|
|
741
491
|
}
|
|
742
492
|
|
|
743
|
-
function ApplyButton({
|
|
744
|
-
label,
|
|
745
|
-
isTerminal,
|
|
746
|
-
isApplying,
|
|
747
|
-
onApply,
|
|
748
|
-
}: {
|
|
749
|
-
readonly label: string;
|
|
750
|
-
readonly isTerminal: boolean;
|
|
751
|
-
readonly isApplying: boolean;
|
|
752
|
-
readonly onApply: () => void;
|
|
753
|
-
}) {
|
|
754
|
-
const canApply = isTerminal && !isApplying;
|
|
755
|
-
|
|
756
|
-
return (
|
|
757
|
-
<button
|
|
758
|
-
type="button"
|
|
759
|
-
onClick={canApply ? onApply : undefined}
|
|
760
|
-
disabled={!canApply}
|
|
761
|
-
aria-busy={isApplying}
|
|
762
|
-
className={cn(
|
|
763
|
-
"rounded-md px-4 py-1.5 text-xs font-medium transition-colors",
|
|
764
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
765
|
-
"disabled:cursor-not-allowed",
|
|
766
|
-
canApply
|
|
767
|
-
? "bg-primary text-primary-foreground hover:bg-primary-hover"
|
|
768
|
-
: "bg-muted text-muted-foreground",
|
|
769
|
-
)}
|
|
770
|
-
>
|
|
771
|
-
{isApplying ? (
|
|
772
|
-
<span className="inline-flex items-center gap-1.5">
|
|
773
|
-
<SpinnerIcon />
|
|
774
|
-
Applying{"\u2026"}
|
|
775
|
-
</span>
|
|
776
|
-
) : (
|
|
777
|
-
label
|
|
778
|
-
)}
|
|
779
|
-
</button>
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
493
|
// ---------------------------------------------------------------------------
|
|
784
494
|
// Inline SVG icons (SDK pattern: no external icon dependency)
|
|
785
495
|
// ---------------------------------------------------------------------------
|
|
@@ -840,45 +550,6 @@ function FolderIcon() {
|
|
|
840
550
|
);
|
|
841
551
|
}
|
|
842
552
|
|
|
843
|
-
function FileSmallIcon() {
|
|
844
|
-
return (
|
|
845
|
-
<svg
|
|
846
|
-
width="12"
|
|
847
|
-
height="12"
|
|
848
|
-
viewBox="0 0 14 14"
|
|
849
|
-
fill="none"
|
|
850
|
-
stroke="currentColor"
|
|
851
|
-
strokeWidth="1.5"
|
|
852
|
-
strokeLinecap="round"
|
|
853
|
-
strokeLinejoin="round"
|
|
854
|
-
className="shrink-0 text-muted-foreground"
|
|
855
|
-
aria-hidden="true"
|
|
856
|
-
>
|
|
857
|
-
<path d="M8 1H4C3.45 1 3 1.45 3 2V12C3 12.55 3.45 13 4 13H10C10.55 13 11 12.55 11 12V4L8 1Z" />
|
|
858
|
-
<path d="M8 1V4H11" />
|
|
859
|
-
</svg>
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
function FolderSmallIcon() {
|
|
864
|
-
return (
|
|
865
|
-
<svg
|
|
866
|
-
width="12"
|
|
867
|
-
height="12"
|
|
868
|
-
viewBox="0 0 14 14"
|
|
869
|
-
fill="none"
|
|
870
|
-
stroke="currentColor"
|
|
871
|
-
strokeWidth="1.5"
|
|
872
|
-
strokeLinecap="round"
|
|
873
|
-
strokeLinejoin="round"
|
|
874
|
-
className="shrink-0 text-muted-foreground"
|
|
875
|
-
aria-hidden="true"
|
|
876
|
-
>
|
|
877
|
-
<path d="M13 11C13 11.55 12.55 12 12 12H2C1.45 12 1 11.55 1 11V3C1 2.45 1.45 2 2 2H5L7 4H12C12.55 4 13 4.45 13 5V11Z" />
|
|
878
|
-
</svg>
|
|
879
|
-
);
|
|
880
|
-
}
|
|
881
|
-
|
|
882
553
|
function CopyIcon() {
|
|
883
554
|
return (
|
|
884
555
|
<svg
|
|
@@ -958,40 +629,3 @@ function CheckIcon() {
|
|
|
958
629
|
);
|
|
959
630
|
}
|
|
960
631
|
|
|
961
|
-
function SpinnerIcon() {
|
|
962
|
-
return (
|
|
963
|
-
<svg
|
|
964
|
-
width="12"
|
|
965
|
-
height="12"
|
|
966
|
-
viewBox="0 0 12 12"
|
|
967
|
-
fill="none"
|
|
968
|
-
stroke="currentColor"
|
|
969
|
-
strokeWidth="1.5"
|
|
970
|
-
className="shrink-0 animate-spin"
|
|
971
|
-
aria-hidden="true"
|
|
972
|
-
>
|
|
973
|
-
<path d="M6 1.5A4.5 4.5 0 1 1 1.5 6" strokeLinecap="round" />
|
|
974
|
-
</svg>
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
function ErrorAlertIcon() {
|
|
979
|
-
return (
|
|
980
|
-
<svg
|
|
981
|
-
width="16"
|
|
982
|
-
height="16"
|
|
983
|
-
viewBox="0 0 16 16"
|
|
984
|
-
fill="none"
|
|
985
|
-
stroke="currentColor"
|
|
986
|
-
strokeWidth="1.5"
|
|
987
|
-
strokeLinecap="round"
|
|
988
|
-
strokeLinejoin="round"
|
|
989
|
-
className="text-destructive"
|
|
990
|
-
aria-hidden="true"
|
|
991
|
-
>
|
|
992
|
-
<circle cx="8" cy="8" r="6.5" />
|
|
993
|
-
<path d="M8 5.5V8.5" />
|
|
994
|
-
<circle cx="8" cy="11" r="0.5" fill="currentColor" stroke="none" />
|
|
995
|
-
</svg>
|
|
996
|
-
);
|
|
997
|
-
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
4
|
+
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
import { FileTypeIcon, FolderTypeIcon } from "../internal/file-icons/index.js";
|
|
7
|
+
import { useArtifactDownload } from "./useArtifactDownload.js";
|
|
8
|
+
import { formatArtifactSize } from "./artifact-utils.js";
|
|
9
|
+
|
|
10
|
+
/** Props for {@link ArtifactRow}. */
|
|
11
|
+
export interface ArtifactRowProps {
|
|
12
|
+
/** The execution artifact to render. */
|
|
13
|
+
readonly artifact: ExecutionArtifact;
|
|
14
|
+
/** ID of the execution that produced this artifact — used to mint its download URL. */
|
|
15
|
+
readonly executionId: string;
|
|
16
|
+
/**
|
|
17
|
+
* When `true`, another artifact in the list shares this artifact's display
|
|
18
|
+
* `name` but has a different `sandbox_path`; the row renders the parent
|
|
19
|
+
* directory as a muted subtitle to disambiguate. Typically supplied from
|
|
20
|
+
* {@link SessionArtifactEntry.hasNameCollision}.
|
|
21
|
+
*/
|
|
22
|
+
readonly hasNameCollision?: boolean;
|
|
23
|
+
/** Single click / Enter / Space: open the artifact (a preview tab, or a modal). */
|
|
24
|
+
readonly onOpen: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* Double click: promote the artifact to a persistent (pinned) tab. Mirrors the
|
|
27
|
+
* file tree's `onOpenFile` (preview) / `onActivateFile` (pin) split — the
|
|
28
|
+
* leading single click of the double click has already opened the preview, so
|
|
29
|
+
* this promotes it. Omit in panel-less hosts (e.g. the modal-only widget):
|
|
30
|
+
* with no handler the row binds no `onDoubleClick`, so a double click is just
|
|
31
|
+
* two harmless single clicks, exactly as `FileTreeNode` behaves.
|
|
32
|
+
*/
|
|
33
|
+
readonly onActivate?: () => void;
|
|
34
|
+
/** Additional CSS classes for the row's `<li>`. */
|
|
35
|
+
readonly className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One dense, VS Code-style artifact row: a full-width open button (shared
|
|
40
|
+
* file-type icon + name + optional collision subtitle + size) with a sibling,
|
|
41
|
+
* hover/focus-revealed Download button.
|
|
42
|
+
*
|
|
43
|
+
* The open target and the Download control are SIBLINGS, never nested — a
|
|
44
|
+
* `<button>` inside a `<button>` is an axe `nested-interactive` (WCAG 4.1.2)
|
|
45
|
+
* violation. This mirrors `ExplorerRoot`'s header + remove-control pattern.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately execution-pure and presentational: it takes only the artifact,
|
|
48
|
+
* its `executionId`, and open/activate closures — no session-domain types and
|
|
49
|
+
* no content fetch (resource detection and Apply/Push live in the opened
|
|
50
|
+
* document/modal, not the list). The two hosts — the session panel's
|
|
51
|
+
* `ArtifactsTab` facet and the panel-less {@link ArtifactsWidget} — map
|
|
52
|
+
* their entries onto these props and own the behavior wiring.
|
|
53
|
+
*
|
|
54
|
+
* @see ArtifactsTab — session-panel facet (rows open editor-pane document tabs)
|
|
55
|
+
* @see ArtifactsWidget — panel-less embeddable (rows open the preview modal)
|
|
56
|
+
*/
|
|
57
|
+
export function ArtifactRow({
|
|
58
|
+
artifact,
|
|
59
|
+
executionId,
|
|
60
|
+
hasNameCollision = false,
|
|
61
|
+
onOpen,
|
|
62
|
+
onActivate,
|
|
63
|
+
className,
|
|
64
|
+
}: ArtifactRowProps) {
|
|
65
|
+
const isDirectory = artifact.kind === ExecutionArtifactKind.DIRECTORY;
|
|
66
|
+
const { download, isDownloading } = useArtifactDownload(executionId);
|
|
67
|
+
|
|
68
|
+
const parentDir =
|
|
69
|
+
hasNameCollision && artifact.sandboxPath
|
|
70
|
+
? parentDirectory(artifact.sandboxPath)
|
|
71
|
+
: null;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<li className={cn("group flex items-stretch", className)}>
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
onClick={onOpen}
|
|
78
|
+
onDoubleClick={onActivate}
|
|
79
|
+
title={artifact.sandboxPath || artifact.name}
|
|
80
|
+
className={cn(
|
|
81
|
+
"flex min-w-0 flex-1 items-center gap-2 px-2 py-1 text-left text-xs text-muted-foreground transition-colors",
|
|
82
|
+
"hover:bg-muted hover:text-foreground",
|
|
83
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<span className="shrink-0 text-muted-foreground">
|
|
87
|
+
{isDirectory ? (
|
|
88
|
+
<FolderTypeIcon open={false} />
|
|
89
|
+
) : (
|
|
90
|
+
<FileTypeIcon fileName={artifact.name} />
|
|
91
|
+
)}
|
|
92
|
+
</span>
|
|
93
|
+
<span className="min-w-0 flex-1 truncate text-foreground">
|
|
94
|
+
{artifact.name}
|
|
95
|
+
{isDirectory && "/"}
|
|
96
|
+
{parentDir && (
|
|
97
|
+
<span className="ml-1.5 text-[0.65rem] text-muted-foreground">
|
|
98
|
+
{parentDir}
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
</span>
|
|
102
|
+
<span className="shrink-0 tabular-nums text-[0.65rem] text-muted-foreground-faint">
|
|
103
|
+
{formatArtifactSize(artifact.sizeBytes)}
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
onClick={() => download(artifact.storageKey, artifact.name)}
|
|
109
|
+
disabled={isDownloading}
|
|
110
|
+
aria-label={
|
|
111
|
+
isDownloading
|
|
112
|
+
? `Preparing ${artifact.name}`
|
|
113
|
+
: `Download ${artifact.name}`
|
|
114
|
+
}
|
|
115
|
+
title={isDirectory ? "Download ZIP" : "Download"}
|
|
116
|
+
className={cn(
|
|
117
|
+
"flex shrink-0 items-center px-2 text-muted-foreground opacity-0 transition-opacity",
|
|
118
|
+
"group-hover:opacity-100 focus-visible:opacity-100",
|
|
119
|
+
"hover:text-foreground disabled:opacity-50",
|
|
120
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
<DownloadIcon />
|
|
124
|
+
</button>
|
|
125
|
+
</li>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Helpers
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Extracts a human-readable parent directory label from a sandbox path.
|
|
135
|
+
* Given `/workspace/configs/agent.yaml` returns `configs/`.
|
|
136
|
+
* Returns `null` when the path has no meaningful parent segment.
|
|
137
|
+
*/
|
|
138
|
+
function parentDirectory(sandboxPath: string): string | null {
|
|
139
|
+
const lastSlash = sandboxPath.lastIndexOf("/");
|
|
140
|
+
if (lastSlash <= 0) return null;
|
|
141
|
+
const parent = sandboxPath.slice(0, lastSlash);
|
|
142
|
+
const segment = parent.slice(parent.lastIndexOf("/") + 1);
|
|
143
|
+
return segment ? `${segment}/` : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function DownloadIcon() {
|
|
147
|
+
return (
|
|
148
|
+
<svg
|
|
149
|
+
width="12"
|
|
150
|
+
height="12"
|
|
151
|
+
viewBox="0 0 12 12"
|
|
152
|
+
fill="none"
|
|
153
|
+
stroke="currentColor"
|
|
154
|
+
strokeWidth="1.5"
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
strokeLinejoin="round"
|
|
157
|
+
className="shrink-0"
|
|
158
|
+
aria-hidden="true"
|
|
159
|
+
>
|
|
160
|
+
<path d="M6 1.5V8.5" />
|
|
161
|
+
<path d="M3 6L6 9L9 6" />
|
|
162
|
+
<path d="M2 10.5H10" />
|
|
163
|
+
</svg>
|
|
164
|
+
);
|
|
165
|
+
}
|