@morscherlab/mint-sdk 1.0.63 → 1.1.0-beta.10
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/README.md +119 -6
- package/dist/EmptyState-DgBRoYAy.js +647 -0
- package/dist/EmptyState-DgBRoYAy.js.map +1 -0
- package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
- package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-BBWXC7KW.js} +12 -216
- package/dist/ExperimentSelectorModal-BBWXC7KW.js.map +1 -0
- package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-Dpg-X1OM.js} +12 -9
- package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
- package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPathInput.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
- package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
- package/dist/__tests__/components/SectionCard.test.d.ts +1 -0
- package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
- package/dist/__tests__/generatedUi.test.d.ts +1 -0
- package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
- package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
- package/dist/{analysisArtifactTypes-Cu40dzSG.js.map → analysisArtifactTypes-ejqR8MX3.js.map} +1 -1
- package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
- package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
- package/dist/chunk-BoAXSpZd.js +33 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AutoGroupModal.vue.d.ts +2 -2
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/CollapsibleCard.vue.d.ts +1 -1
- package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
- package/dist/components/ConfirmDialog.vue.d.ts +1 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +4 -2
- package/dist/components/FitPanel.vue.d.ts +2 -2
- package/dist/components/FormActions.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +1 -1
- package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
- package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
- package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
- package/dist/components/JobsStatusTray.vue.d.ts +5 -1
- package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +2 -2
- package/dist/components/SectionCard.vue.d.ts +47 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +8 -9
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
- package/dist/{components-DdzHh3a_.js → components-Ci_BTx00.js} +2322 -2292
- package/dist/components-Ci_BTx00.js.map +1 -0
- package/dist/composables/index.d.ts +3 -2
- package/dist/composables/index.js +10 -11
- package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
- package/dist/composables/useJobsStatusTray.d.ts +48 -4
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/composables/usePluginClient.d.ts +17 -8
- package/dist/{composables-BUUphBqg.js → composables-C0VEJ73O.js} +154 -38
- package/dist/composables-C0VEJ73O.js.map +1 -0
- package/dist/experiment-utils-CxkruZ3G.js +392 -0
- package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
- package/dist/generated/schema.d.ts +7 -0
- package/dist/generated-ui/main.d.ts +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -18
- package/dist/index.js.map +1 -1
- package/dist/install.js +7 -8
- package/dist/install.js.map +1 -1
- package/dist/plotly.min-BR53hja4.js +180634 -0
- package/dist/plotly.min-BR53hja4.js.map +1 -0
- package/dist/stores/auth.d.ts +2 -2
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +8657 -8217
- package/dist/tailwind.preset.d.ts +5 -0
- package/dist/tailwind.preset.js +7 -2
- package/dist/tailwind.preset.js.map +1 -1
- package/dist/templates/index.js +4 -3
- package/dist/templates/packs.d.ts +0 -1
- package/dist/{templates-CNbPQNID.js → templates-Cdj93S9C.js} +2 -4
- package/dist/templates-Cdj93S9C.js.map +1 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/types/generated-ui.d.ts +183 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -1
- package/dist/types/instrument.d.ts +1 -0
- package/dist/types/jobs.d.ts +7 -1
- package/dist/types/platform.d.ts +2 -0
- package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-B_qgU4rW.js} +1882 -172
- package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
- package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
- package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
- package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BuQZRgMt.js} +4 -255
- package/dist/useExperimentSelector-BuQZRgMt.js.map +1 -0
- package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-D1G5J5Z5.js} +2 -2
- package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
- package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-DeaOmMaw.js} +307 -475
- package/dist/useProtocolTemplates-DeaOmMaw.js.map +1 -0
- package/package.json +7 -3
- package/src/__tests__/components/BaseSelect.test.ts +52 -0
- package/src/__tests__/components/BaseToggle.test.ts +40 -0
- package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +240 -0
- package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
- package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
- package/src/__tests__/components/SectionCard.test.ts +181 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
- package/src/__tests__/composables/useApi.test.ts +8 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1742 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
- package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
- package/src/__tests__/composables/usePluginClient.test.ts +402 -21
- package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
- package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
- package/src/__tests__/generatedUi.test.ts +119 -0
- package/src/__tests__/templates/templates.test.ts +1 -1
- package/src/__tests__/types/GeneratedJsonSchema.test.ts +62 -0
- package/src/components/BaseSelect.vue +16 -2
- package/src/components/BaseToggle.vue +3 -1
- package/src/components/FileUploader.vue +49 -11
- package/src/components/GeneratedPathInput.vue +218 -0
- package/src/components/GeneratedPlotlyResult.vue +87 -0
- package/src/components/GeneratedPluginView.vue +279 -0
- package/src/components/GeneratedResultRenderer.vue +137 -0
- package/src/components/InstrumentAlertLog.vue +2 -2
- package/src/components/JobsStatusTray.vue +14 -8
- package/src/components/PluginWorkspaceView.props.ts +1 -1
- package/src/components/PluginWorkspaceView.vue +1 -5
- package/src/components/SectionCard.story.vue +163 -0
- package/src/components/SectionCard.vue +98 -0
- package/src/components/index.ts +5 -0
- package/src/composables/formBuilderRegistry.ts +2 -0
- package/src/composables/index.ts +19 -0
- package/src/composables/useApi.ts +1 -0
- package/src/composables/useGeneratedAnalysis.ts +1270 -0
- package/src/composables/useJobsStatusTray.ts +234 -45
- package/src/composables/usePluginClient.ts +164 -30
- package/src/composables/usePluginConfig.ts +156 -20
- package/src/generated/schema.ts +130 -0
- package/src/generated-ui/main.ts +21 -0
- package/src/index.ts +3 -0
- package/src/jobs.ts +5 -0
- package/src/plotly-dist.d.ts +3 -0
- package/src/styles/components/section-card.css +161 -0
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +25 -0
- package/src/tailwind.preset.ts +5 -0
- package/src/templates/packs.ts +0 -3
- package/src/types/components.ts +12 -0
- package/src/types/form-builder.ts +1 -0
- package/src/types/generated-ui.ts +189 -0
- package/src/types/index.ts +4 -0
- package/src/types/instrument.ts +1 -0
- package/src/types/jobs.ts +10 -1
- package/src/types/platform.ts +2 -0
- package/dist/BaseButton-Dgqrze41.js +0 -74
- package/dist/BaseButton-Dgqrze41.js.map +0 -1
- package/dist/BaseModal-B9UA8Y_I.js +0 -165
- package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
- package/dist/BaseSelect-ekgr9fDo.js +0 -179
- package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
- package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
- package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
- package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
- package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
- package/dist/components-DdzHh3a_.js.map +0 -1
- package/dist/composables-BUUphBqg.js.map +0 -1
- package/dist/experiment-utils-Bfa7CwPU.js +0 -108
- package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
- package/dist/templates-CNbPQNID.js.map +0 -1
- package/dist/useAnalysisArtifacts-C3l0a0vu.js.map +0 -1
- package/dist/useControlSchema-BZNdalmL.js.map +0 -1
- package/dist/useEventListener-CfVkP9Xz.js +0 -57
- package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
- package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
|
@@ -19,7 +19,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
const emit = defineEmits<{
|
|
22
|
-
upload: [files: File[]]
|
|
22
|
+
upload: [files: File[], rootName?: string, relativePaths?: string[]]
|
|
23
|
+
remove: [files: File[]]
|
|
23
24
|
error: [message: string]
|
|
24
25
|
}>()
|
|
25
26
|
|
|
@@ -116,20 +117,31 @@ function validateFiles(files: FileList | File[]): File[] {
|
|
|
116
117
|
return validFiles
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
function handleFiles(
|
|
120
|
+
function handleFiles(
|
|
121
|
+
files: FileList | File[],
|
|
122
|
+
folder?: string,
|
|
123
|
+
explicitRelativePaths?: string[],
|
|
124
|
+
) {
|
|
125
|
+
const candidates = Array.from(files)
|
|
120
126
|
const validFiles = validateFiles(files)
|
|
121
127
|
if (validFiles.length === 0) return
|
|
128
|
+
const relativePaths = validFiles.map((file) => {
|
|
129
|
+
const candidateIndex = candidates.indexOf(file)
|
|
130
|
+
return explicitRelativePaths?.[candidateIndex]
|
|
131
|
+
|| file.webkitRelativePath
|
|
132
|
+
|| (folder ? `${folder}/${file.name}` : file.name)
|
|
133
|
+
})
|
|
122
134
|
|
|
123
135
|
if (isFolder.value) {
|
|
124
136
|
folderName.value = folder || extractFolderName(validFiles)
|
|
125
137
|
selectedFiles.value = validFiles
|
|
126
|
-
emit('upload', validFiles)
|
|
138
|
+
emit('upload', validFiles, folderName.value ?? undefined, relativePaths)
|
|
127
139
|
} else if (!props.multiple) {
|
|
128
140
|
selectedFiles.value = [validFiles[0]]
|
|
129
|
-
emit('upload', [validFiles[0]])
|
|
141
|
+
emit('upload', [validFiles[0]], undefined, [relativePaths[0]])
|
|
130
142
|
} else {
|
|
131
143
|
selectedFiles.value = [...selectedFiles.value, ...validFiles]
|
|
132
|
-
emit('upload', validFiles)
|
|
144
|
+
emit('upload', validFiles, undefined, relativePaths)
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
|
|
@@ -156,25 +168,39 @@ function handleDrop(event: DragEvent) {
|
|
|
156
168
|
|
|
157
169
|
async function handleFolderDrop(items: DataTransferItemList) {
|
|
158
170
|
const files: File[] = []
|
|
171
|
+
const relativePaths: string[] = []
|
|
159
172
|
let folderNameFromDrop: string | undefined
|
|
160
173
|
|
|
161
174
|
for (const item of Array.from(items)) {
|
|
162
175
|
const entry = item.webkitGetAsEntry?.()
|
|
163
176
|
if (entry?.isDirectory) {
|
|
164
177
|
folderNameFromDrop = entry.name
|
|
165
|
-
await readDirectory(
|
|
178
|
+
await readDirectory(
|
|
179
|
+
entry as FileSystemDirectoryEntry,
|
|
180
|
+
files,
|
|
181
|
+
relativePaths,
|
|
182
|
+
entry.name,
|
|
183
|
+
)
|
|
166
184
|
} else if (entry?.isFile) {
|
|
167
185
|
const file = await getFile(entry as FileSystemFileEntry)
|
|
168
|
-
if (file)
|
|
186
|
+
if (file) {
|
|
187
|
+
files.push(file)
|
|
188
|
+
relativePaths.push(file.name)
|
|
189
|
+
}
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
192
|
|
|
172
193
|
if (files.length > 0) {
|
|
173
|
-
handleFiles(files, folderNameFromDrop)
|
|
194
|
+
handleFiles(files, folderNameFromDrop, relativePaths)
|
|
174
195
|
}
|
|
175
196
|
}
|
|
176
197
|
|
|
177
|
-
async function readDirectory(
|
|
198
|
+
async function readDirectory(
|
|
199
|
+
directory: FileSystemDirectoryEntry,
|
|
200
|
+
files: File[],
|
|
201
|
+
relativePaths: string[],
|
|
202
|
+
prefix: string,
|
|
203
|
+
): Promise<void> {
|
|
178
204
|
const reader = directory.createReader()
|
|
179
205
|
// readEntries returns at most 100 entries per call per spec — must loop
|
|
180
206
|
let batch: FileSystemEntry[]
|
|
@@ -185,9 +211,17 @@ async function readDirectory(directory: FileSystemDirectoryEntry, files: File[])
|
|
|
185
211
|
for (const entry of batch) {
|
|
186
212
|
if (entry.isFile) {
|
|
187
213
|
const file = await getFile(entry as FileSystemFileEntry)
|
|
188
|
-
if (file)
|
|
214
|
+
if (file) {
|
|
215
|
+
files.push(file)
|
|
216
|
+
relativePaths.push(`${prefix}/${file.name}`)
|
|
217
|
+
}
|
|
189
218
|
} else if (entry.isDirectory) {
|
|
190
|
-
await readDirectory(
|
|
219
|
+
await readDirectory(
|
|
220
|
+
entry as FileSystemDirectoryEntry,
|
|
221
|
+
files,
|
|
222
|
+
relativePaths,
|
|
223
|
+
`${prefix}/${entry.name}`,
|
|
224
|
+
)
|
|
191
225
|
}
|
|
192
226
|
}
|
|
193
227
|
} while (batch.length > 0)
|
|
@@ -225,15 +259,19 @@ function openFilePicker() {
|
|
|
225
259
|
}
|
|
226
260
|
|
|
227
261
|
function removeFile(index: number) {
|
|
262
|
+
const removed = selectedFiles.value[index]
|
|
228
263
|
selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index)
|
|
229
264
|
if (isFolder.value && selectedFiles.value.length === 0) {
|
|
230
265
|
folderName.value = null
|
|
231
266
|
}
|
|
267
|
+
if (removed) emit('remove', [removed])
|
|
232
268
|
}
|
|
233
269
|
|
|
234
270
|
function clearAll() {
|
|
271
|
+
const removed = [...selectedFiles.value]
|
|
235
272
|
selectedFiles.value = []
|
|
236
273
|
folderName.value = null
|
|
274
|
+
if (removed.length) emit('remove', removed)
|
|
237
275
|
}
|
|
238
276
|
|
|
239
277
|
function formatFileSize(bytes: number): string {
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { createGeneratedAnalysisTransport } from '../composables/useGeneratedAnalysis'
|
|
5
|
+
import {
|
|
6
|
+
getInjectedPlatformContext,
|
|
7
|
+
resolveCurrentExperimentId,
|
|
8
|
+
} from '../composables/platformContextHelpers'
|
|
9
|
+
import type { AnalysisArtifactDetail } from '../types/analysisArtifactTypes'
|
|
10
|
+
import type { PluginJobPathInput } from '../types/generated-ui'
|
|
11
|
+
import AlertBox from './AlertBox.vue'
|
|
12
|
+
import ArtifactSelectorModal from './ArtifactSelectorModal.vue'
|
|
13
|
+
import BaseButton from './BaseButton.vue'
|
|
14
|
+
import FileUploader from './FileUploader.vue'
|
|
15
|
+
|
|
16
|
+
interface ArtifactFileReference {
|
|
17
|
+
key: string
|
|
18
|
+
plugin_id?: string
|
|
19
|
+
experiment_id?: number
|
|
20
|
+
filename?: string
|
|
21
|
+
content_type?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const props = defineProps<{
|
|
25
|
+
modelValue?: PluginJobPathInput | PluginJobPathInput[] | null
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
multiple?: boolean
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
'update:modelValue': [value: PluginJobPathInput | PluginJobPathInput[] | null]
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const mode = ref<'file' | 'folder'>('file')
|
|
35
|
+
const artifactOpen = ref(false)
|
|
36
|
+
const artifactLoading = ref(false)
|
|
37
|
+
const artifactError = ref('')
|
|
38
|
+
const platform = getInjectedPlatformContext()
|
|
39
|
+
const experimentId = resolveCurrentExperimentId(platform)
|
|
40
|
+
const canSelectArtifact = computed(() => Boolean(platform?.isIntegrated && experimentId))
|
|
41
|
+
const transport = createGeneratedAnalysisTransport()
|
|
42
|
+
|
|
43
|
+
function selectMode(nextMode: 'file' | 'folder'): void {
|
|
44
|
+
mode.value = nextMode
|
|
45
|
+
emit('update:modelValue', null)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function selectFiles(
|
|
49
|
+
files: File[],
|
|
50
|
+
rootName?: string,
|
|
51
|
+
relativePaths?: string[],
|
|
52
|
+
): void {
|
|
53
|
+
const paths = relativePaths ?? files.map(file => file.name)
|
|
54
|
+
const selections: PluginJobPathInput[] = props.multiple && !rootName
|
|
55
|
+
? files.map((file, index) => ({
|
|
56
|
+
kind: 'path',
|
|
57
|
+
files: [file],
|
|
58
|
+
relativePaths: [paths[index] ?? file.name],
|
|
59
|
+
}))
|
|
60
|
+
: [{
|
|
61
|
+
kind: 'path',
|
|
62
|
+
files,
|
|
63
|
+
relativePaths: paths,
|
|
64
|
+
...(rootName ? { rootName } : {}),
|
|
65
|
+
}]
|
|
66
|
+
if (props.multiple) {
|
|
67
|
+
const existing = Array.isArray(props.modelValue) ? props.modelValue : []
|
|
68
|
+
emit('update:modelValue', [...existing, ...selections])
|
|
69
|
+
} else {
|
|
70
|
+
emit('update:modelValue', selections[0] ?? null)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function removeFiles(files: File[]): void {
|
|
75
|
+
const removed = new Set(files)
|
|
76
|
+
const current = Array.isArray(props.modelValue)
|
|
77
|
+
? props.modelValue
|
|
78
|
+
: props.modelValue
|
|
79
|
+
? [props.modelValue]
|
|
80
|
+
: []
|
|
81
|
+
const remaining = current.flatMap((selection) => {
|
|
82
|
+
const indexes = selection.files
|
|
83
|
+
.map((file, index) => removed.has(file) ? -1 : index)
|
|
84
|
+
.filter(index => index >= 0)
|
|
85
|
+
if (!indexes.length) return []
|
|
86
|
+
return [{
|
|
87
|
+
...selection,
|
|
88
|
+
files: indexes.map(index => selection.files[index]!),
|
|
89
|
+
relativePaths: indexes.map(index => selection.relativePaths[index]!),
|
|
90
|
+
}]
|
|
91
|
+
})
|
|
92
|
+
emit(
|
|
93
|
+
'update:modelValue',
|
|
94
|
+
props.multiple ? remaining : remaining[0] ?? null,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function selectArtifact(detail: AnalysisArtifactDetail): Promise<void> {
|
|
99
|
+
artifactLoading.value = true
|
|
100
|
+
artifactError.value = ''
|
|
101
|
+
try {
|
|
102
|
+
const reference = artifactFileReference(detail)
|
|
103
|
+
const resolvedExperimentId = reference.experiment_id ?? detail.experiment_id
|
|
104
|
+
const pluginId = reference.plugin_id ?? detail.plugin_id
|
|
105
|
+
const baseUrl = (platform?.platformApiUrl ?? '/api').replace(/\/$/, '')
|
|
106
|
+
const objectPath = reference.key.split('/').map(encodeURIComponent).join('/')
|
|
107
|
+
const url = `${baseUrl}/experiments/${resolvedExperimentId}/objects/${objectPath}`
|
|
108
|
+
+ `?plugin_id=${encodeURIComponent(pluginId)}&format=raw`
|
|
109
|
+
const response = await transport(url, { method: 'GET' })
|
|
110
|
+
if (!response.ok) throw new Error(`Artifact download failed (${response.status})`)
|
|
111
|
+
const resultFilename = String(detail.result.filename ?? '')
|
|
112
|
+
const keySegments = reference.key.split('/')
|
|
113
|
+
const filename = reference.filename
|
|
114
|
+
|| resultFilename
|
|
115
|
+
|| keySegments[keySegments.length - 1]
|
|
116
|
+
|| 'artifact.bin'
|
|
117
|
+
const contentType = reference.content_type
|
|
118
|
+
?? response.headers.get('Content-Type')
|
|
119
|
+
?? 'application/octet-stream'
|
|
120
|
+
const file = new File([await response.blob()], filename, { type: contentType })
|
|
121
|
+
selectFiles([file], undefined, [filename])
|
|
122
|
+
} catch (cause) {
|
|
123
|
+
artifactError.value = cause instanceof Error ? cause.message : 'Failed to load artifact'
|
|
124
|
+
} finally {
|
|
125
|
+
artifactLoading.value = false
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function artifactFileReference(detail: AnalysisArtifactDetail): ArtifactFileReference {
|
|
130
|
+
if (detail.result.schema_version !== 'mint.analysis_file.v1') {
|
|
131
|
+
throw new Error('Only file-backed analysis artifacts can be used as a path')
|
|
132
|
+
}
|
|
133
|
+
const reference = detail.result.file
|
|
134
|
+
if (!reference || typeof reference !== 'object' || !('key' in reference)) {
|
|
135
|
+
throw new Error('The selected artifact has no file reference')
|
|
136
|
+
}
|
|
137
|
+
const candidate = reference as Record<string, unknown>
|
|
138
|
+
if (typeof candidate.key !== 'string' || !candidate.key) {
|
|
139
|
+
throw new Error('The selected artifact has no object key')
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
key: candidate.key,
|
|
143
|
+
...(typeof candidate.plugin_id === 'string' ? { plugin_id: candidate.plugin_id } : {}),
|
|
144
|
+
...(typeof candidate.experiment_id === 'number'
|
|
145
|
+
? { experiment_id: candidate.experiment_id }
|
|
146
|
+
: {}),
|
|
147
|
+
...(typeof candidate.filename === 'string' ? { filename: candidate.filename } : {}),
|
|
148
|
+
...(typeof candidate.content_type === 'string'
|
|
149
|
+
? { content_type: candidate.content_type }
|
|
150
|
+
: {}),
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<template>
|
|
156
|
+
<div class="mint-generated-path-input">
|
|
157
|
+
<div class="mint-generated-path-input__modes" role="group" aria-label="Path type">
|
|
158
|
+
<BaseButton
|
|
159
|
+
size="sm"
|
|
160
|
+
:variant="mode === 'file' ? 'primary' : 'secondary'"
|
|
161
|
+
:disabled="disabled"
|
|
162
|
+
@click="selectMode('file')"
|
|
163
|
+
>
|
|
164
|
+
File
|
|
165
|
+
</BaseButton>
|
|
166
|
+
<BaseButton
|
|
167
|
+
size="sm"
|
|
168
|
+
:variant="mode === 'folder' ? 'primary' : 'secondary'"
|
|
169
|
+
:disabled="disabled"
|
|
170
|
+
@click="selectMode('folder')"
|
|
171
|
+
>
|
|
172
|
+
Folder
|
|
173
|
+
</BaseButton>
|
|
174
|
+
<BaseButton
|
|
175
|
+
v-if="canSelectArtifact"
|
|
176
|
+
size="sm"
|
|
177
|
+
variant="secondary"
|
|
178
|
+
:disabled="disabled"
|
|
179
|
+
:loading="artifactLoading"
|
|
180
|
+
@click="artifactOpen = true"
|
|
181
|
+
>
|
|
182
|
+
Existing artifact
|
|
183
|
+
</BaseButton>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<FileUploader
|
|
187
|
+
:key="mode"
|
|
188
|
+
:mode="mode"
|
|
189
|
+
:multiple="multiple && mode === 'file'"
|
|
190
|
+
:disabled="disabled"
|
|
191
|
+
@upload="selectFiles"
|
|
192
|
+
@remove="removeFiles"
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<AlertBox v-if="artifactError" type="error" :message="artifactError" />
|
|
196
|
+
<ArtifactSelectorModal
|
|
197
|
+
v-if="canSelectArtifact"
|
|
198
|
+
v-model="artifactOpen"
|
|
199
|
+
:experiment-id="experimentId"
|
|
200
|
+
:fetch-detail-on-select="true"
|
|
201
|
+
title="Select a file artifact"
|
|
202
|
+
@select-detail="selectArtifact"
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<style scoped>
|
|
208
|
+
.mint-generated-path-input {
|
|
209
|
+
display: grid;
|
|
210
|
+
gap: 0.75rem;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.mint-generated-path-input__modes {
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-wrap: wrap;
|
|
216
|
+
gap: 0.5rem;
|
|
217
|
+
}
|
|
218
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import type * as PlotlyType from 'plotly.js'
|
|
4
|
+
|
|
5
|
+
import ChartContainer from './ChartContainer.vue'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
spec: Record<string, unknown>
|
|
9
|
+
fallback?: { media_type: string; data: string }
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const chartElement = ref<HTMLDivElement | null>(null)
|
|
13
|
+
const failed = ref(false)
|
|
14
|
+
let plotly: typeof PlotlyType | null = null
|
|
15
|
+
|
|
16
|
+
async function loadPlotly(): Promise<typeof PlotlyType> {
|
|
17
|
+
const module = await import('plotly.js-dist-min')
|
|
18
|
+
return ('default' in module ? module.default : module) as unknown as typeof PlotlyType
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function renderChart(): Promise<void> {
|
|
22
|
+
if (!chartElement.value) return
|
|
23
|
+
failed.value = false
|
|
24
|
+
await nextTick()
|
|
25
|
+
try {
|
|
26
|
+
const renderer = plotly ?? await loadPlotly()
|
|
27
|
+
plotly = renderer
|
|
28
|
+
const data = Array.isArray(props.spec.data) ? props.spec.data : []
|
|
29
|
+
const layout = props.spec.layout && typeof props.spec.layout === 'object'
|
|
30
|
+
? props.spec.layout
|
|
31
|
+
: {}
|
|
32
|
+
await renderer.react(
|
|
33
|
+
chartElement.value,
|
|
34
|
+
data as PlotlyType.Data[],
|
|
35
|
+
layout as Partial<PlotlyType.Layout>,
|
|
36
|
+
{ responsive: true, displaylogo: false },
|
|
37
|
+
)
|
|
38
|
+
failed.value = false
|
|
39
|
+
} catch {
|
|
40
|
+
failed.value = true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onMounted(renderChart)
|
|
45
|
+
watch(() => props.spec, async () => {
|
|
46
|
+
await nextTick()
|
|
47
|
+
await renderChart()
|
|
48
|
+
})
|
|
49
|
+
onBeforeUnmount(() => {
|
|
50
|
+
if (plotly && chartElement.value) plotly.purge(chartElement.value)
|
|
51
|
+
})
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<ChartContainer title="Interactive chart">
|
|
56
|
+
<img
|
|
57
|
+
v-if="failed && fallback"
|
|
58
|
+
class="mint-generated-result__image"
|
|
59
|
+
:src="`data:${fallback.media_type};base64,${fallback.data}`"
|
|
60
|
+
alt="Static Plotly chart fallback"
|
|
61
|
+
>
|
|
62
|
+
<p
|
|
63
|
+
v-else-if="failed"
|
|
64
|
+
class="mint-generated-result__plotly-error"
|
|
65
|
+
role="alert"
|
|
66
|
+
>
|
|
67
|
+
Unable to display this chart.
|
|
68
|
+
</p>
|
|
69
|
+
<div
|
|
70
|
+
ref="chartElement"
|
|
71
|
+
v-show="!failed"
|
|
72
|
+
class="mint-generated-result__plotly"
|
|
73
|
+
aria-label="Interactive Plotly chart"
|
|
74
|
+
/>
|
|
75
|
+
</ChartContainer>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style scoped>
|
|
79
|
+
.mint-generated-result__plotly-error {
|
|
80
|
+
margin: 0;
|
|
81
|
+
padding: 1rem;
|
|
82
|
+
border: 1px solid var(--color-danger-border, var(--color-border));
|
|
83
|
+
border-radius: 0.75rem;
|
|
84
|
+
background: var(--color-danger-bg, var(--color-bg-secondary));
|
|
85
|
+
color: var(--color-danger-text, var(--color-text-primary));
|
|
86
|
+
}
|
|
87
|
+
</style>
|