@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.
Files changed (181) hide show
  1. package/README.md +119 -6
  2. package/dist/EmptyState-DgBRoYAy.js +647 -0
  3. package/dist/EmptyState-DgBRoYAy.js.map +1 -0
  4. package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
  5. package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
  6. package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-BBWXC7KW.js} +12 -216
  7. package/dist/ExperimentSelectorModal-BBWXC7KW.js.map +1 -0
  8. package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-Dpg-X1OM.js} +12 -9
  9. package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
  10. package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
  11. package/dist/__tests__/components/GeneratedPathInput.test.d.ts +1 -0
  12. package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
  13. package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
  14. package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
  15. package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
  16. package/dist/__tests__/components/SectionCard.test.d.ts +1 -0
  17. package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
  18. package/dist/__tests__/generatedUi.test.d.ts +1 -0
  19. package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
  20. package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
  21. package/dist/{analysisArtifactTypes-Cu40dzSG.js.map → analysisArtifactTypes-ejqR8MX3.js.map} +1 -1
  22. package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
  23. package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
  24. package/dist/chunk-BoAXSpZd.js +33 -0
  25. package/dist/components/AppSidebar.vue.d.ts +1 -1
  26. package/dist/components/AutoGroupModal.vue.d.ts +2 -2
  27. package/dist/components/BaseSelect.vue.d.ts +1 -1
  28. package/dist/components/BaseToggle.vue.d.ts +2 -0
  29. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  30. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  31. package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
  32. package/dist/components/ConfirmDialog.vue.d.ts +1 -1
  33. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  34. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  35. package/dist/components/FileUploader.vue.d.ts +4 -2
  36. package/dist/components/FitPanel.vue.d.ts +2 -2
  37. package/dist/components/FormActions.vue.d.ts +1 -1
  38. package/dist/components/FormBuilder.vue.d.ts +1 -1
  39. package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
  40. package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
  41. package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
  42. package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
  43. package/dist/components/JobsStatusTray.vue.d.ts +5 -1
  44. package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
  45. package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
  46. package/dist/components/SampleLegend.vue.d.ts +1 -1
  47. package/dist/components/SampleSelector.vue.d.ts +2 -2
  48. package/dist/components/SectionCard.vue.d.ts +47 -0
  49. package/dist/components/SettingsModal.vue.d.ts +1 -1
  50. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
  51. package/dist/components/index.d.ts +5 -0
  52. package/dist/components/index.js +8 -9
  53. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
  54. package/dist/{components-DdzHh3a_.js → components-Ci_BTx00.js} +2322 -2292
  55. package/dist/components-Ci_BTx00.js.map +1 -0
  56. package/dist/composables/index.d.ts +3 -2
  57. package/dist/composables/index.js +10 -11
  58. package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
  59. package/dist/composables/useJobsStatusTray.d.ts +48 -4
  60. package/dist/composables/usePlatformContext.d.ts +3 -0
  61. package/dist/composables/usePluginClient.d.ts +17 -8
  62. package/dist/{composables-BUUphBqg.js → composables-C0VEJ73O.js} +154 -38
  63. package/dist/composables-C0VEJ73O.js.map +1 -0
  64. package/dist/experiment-utils-CxkruZ3G.js +392 -0
  65. package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
  66. package/dist/generated/schema.d.ts +7 -0
  67. package/dist/generated-ui/main.d.ts +0 -0
  68. package/dist/index.d.ts +1 -0
  69. package/dist/index.js +15 -18
  70. package/dist/index.js.map +1 -1
  71. package/dist/install.js +7 -8
  72. package/dist/install.js.map +1 -1
  73. package/dist/plotly.min-BR53hja4.js +180634 -0
  74. package/dist/plotly.min-BR53hja4.js.map +1 -0
  75. package/dist/stores/auth.d.ts +2 -2
  76. package/dist/stores/index.js +1 -1
  77. package/dist/styles.css +8657 -8217
  78. package/dist/tailwind.preset.d.ts +5 -0
  79. package/dist/tailwind.preset.js +7 -2
  80. package/dist/tailwind.preset.js.map +1 -1
  81. package/dist/templates/index.js +4 -3
  82. package/dist/templates/packs.d.ts +0 -1
  83. package/dist/{templates-CNbPQNID.js → templates-Cdj93S9C.js} +2 -4
  84. package/dist/templates-Cdj93S9C.js.map +1 -0
  85. package/dist/types/components.d.ts +2 -0
  86. package/dist/types/form-builder.d.ts +1 -1
  87. package/dist/types/generated-ui.d.ts +183 -0
  88. package/dist/types/index.d.ts +2 -1
  89. package/dist/types/index.js +1 -1
  90. package/dist/types/instrument.d.ts +1 -0
  91. package/dist/types/jobs.d.ts +7 -1
  92. package/dist/types/platform.d.ts +2 -0
  93. package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-B_qgU4rW.js} +1882 -172
  94. package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
  95. package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
  96. package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
  97. package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BuQZRgMt.js} +4 -255
  98. package/dist/useExperimentSelector-BuQZRgMt.js.map +1 -0
  99. package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-D1G5J5Z5.js} +2 -2
  100. package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
  101. package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-DeaOmMaw.js} +307 -475
  102. package/dist/useProtocolTemplates-DeaOmMaw.js.map +1 -0
  103. package/package.json +7 -3
  104. package/src/__tests__/components/BaseSelect.test.ts +52 -0
  105. package/src/__tests__/components/BaseToggle.test.ts +40 -0
  106. package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
  107. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
  108. package/src/__tests__/components/GeneratedPluginView.test.ts +240 -0
  109. package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
  110. package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
  111. package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
  112. package/src/__tests__/components/SectionCard.test.ts +181 -0
  113. package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
  114. package/src/__tests__/composables/useApi.test.ts +8 -0
  115. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1742 -0
  116. package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
  117. package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
  118. package/src/__tests__/composables/usePluginClient.test.ts +402 -21
  119. package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
  120. package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
  121. package/src/__tests__/generatedUi.test.ts +119 -0
  122. package/src/__tests__/templates/templates.test.ts +1 -1
  123. package/src/__tests__/types/GeneratedJsonSchema.test.ts +62 -0
  124. package/src/components/BaseSelect.vue +16 -2
  125. package/src/components/BaseToggle.vue +3 -1
  126. package/src/components/FileUploader.vue +49 -11
  127. package/src/components/GeneratedPathInput.vue +218 -0
  128. package/src/components/GeneratedPlotlyResult.vue +87 -0
  129. package/src/components/GeneratedPluginView.vue +279 -0
  130. package/src/components/GeneratedResultRenderer.vue +137 -0
  131. package/src/components/InstrumentAlertLog.vue +2 -2
  132. package/src/components/JobsStatusTray.vue +14 -8
  133. package/src/components/PluginWorkspaceView.props.ts +1 -1
  134. package/src/components/PluginWorkspaceView.vue +1 -5
  135. package/src/components/SectionCard.story.vue +163 -0
  136. package/src/components/SectionCard.vue +98 -0
  137. package/src/components/index.ts +5 -0
  138. package/src/composables/formBuilderRegistry.ts +2 -0
  139. package/src/composables/index.ts +19 -0
  140. package/src/composables/useApi.ts +1 -0
  141. package/src/composables/useGeneratedAnalysis.ts +1270 -0
  142. package/src/composables/useJobsStatusTray.ts +234 -45
  143. package/src/composables/usePluginClient.ts +164 -30
  144. package/src/composables/usePluginConfig.ts +156 -20
  145. package/src/generated/schema.ts +130 -0
  146. package/src/generated-ui/main.ts +21 -0
  147. package/src/index.ts +3 -0
  148. package/src/jobs.ts +5 -0
  149. package/src/plotly-dist.d.ts +3 -0
  150. package/src/styles/components/section-card.css +161 -0
  151. package/src/styles/index.css +1 -0
  152. package/src/styles/variables.css +25 -0
  153. package/src/tailwind.preset.ts +5 -0
  154. package/src/templates/packs.ts +0 -3
  155. package/src/types/components.ts +12 -0
  156. package/src/types/form-builder.ts +1 -0
  157. package/src/types/generated-ui.ts +189 -0
  158. package/src/types/index.ts +4 -0
  159. package/src/types/instrument.ts +1 -0
  160. package/src/types/jobs.ts +10 -1
  161. package/src/types/platform.ts +2 -0
  162. package/dist/BaseButton-Dgqrze41.js +0 -74
  163. package/dist/BaseButton-Dgqrze41.js.map +0 -1
  164. package/dist/BaseModal-B9UA8Y_I.js +0 -165
  165. package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
  166. package/dist/BaseSelect-ekgr9fDo.js +0 -179
  167. package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
  168. package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
  169. package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
  170. package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
  171. package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
  172. package/dist/components-DdzHh3a_.js.map +0 -1
  173. package/dist/composables-BUUphBqg.js.map +0 -1
  174. package/dist/experiment-utils-Bfa7CwPU.js +0 -108
  175. package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
  176. package/dist/templates-CNbPQNID.js.map +0 -1
  177. package/dist/useAnalysisArtifacts-C3l0a0vu.js.map +0 -1
  178. package/dist/useControlSchema-BZNdalmL.js.map +0 -1
  179. package/dist/useEventListener-CfVkP9Xz.js +0 -57
  180. package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
  181. package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
@@ -0,0 +1,279 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, reactive, ref, watch } from 'vue'
3
+
4
+ import { generatedJobDefaults, generatedJobFormSchema, normalizeGeneratedJobInput } from '../generated/schema'
5
+ import type { FormSchema } from '../types/form-builder'
6
+ import type {
7
+ GeneratedAnalysisJob,
8
+ GeneratedAnalysisResult,
9
+ GeneratedJobDefinition,
10
+ } from '../types/generated-ui'
11
+ import type { PillNavItem, SidebarToolSection } from '../types'
12
+ import {
13
+ useGeneratedAnalysis,
14
+ type GeneratedAnalysisTransport,
15
+ } from '../composables/useGeneratedAnalysis'
16
+ import AlertBox from './AlertBox.vue'
17
+ import EmptyState from './EmptyState.vue'
18
+ import GeneratedResultRenderer from './GeneratedResultRenderer.vue'
19
+ import JobsStatusTray from './JobsStatusTray.vue'
20
+ import LoadingSpinner from './LoadingSpinner.vue'
21
+ import PluginWorkspaceView from './PluginWorkspaceView.vue'
22
+
23
+ const props = withDefaults(defineProps<{
24
+ baseUrl: string
25
+ showStandaloneLabel?: boolean
26
+ transport?: GeneratedAnalysisTransport
27
+ }>(), {
28
+ showStandaloneLabel: false,
29
+ })
30
+
31
+ const generated = useGeneratedAnalysis({
32
+ baseUrl: props.baseUrl,
33
+ transport: props.transport,
34
+ })
35
+ const activeDefinitionId = ref('')
36
+ const selectedJobId = ref('')
37
+ const valuesByDefinition = reactive<Record<string, Record<string, unknown>>>({})
38
+ const definitions = computed(() => generated.manifest.value?.jobs ?? [])
39
+ const activeDefinition = computed(() => (
40
+ definitions.value.find(definition => definition.id === activeDefinitionId.value) ?? null
41
+ ))
42
+
43
+ const pillNav = computed<PillNavItem[]>(() => definitions.value.map(definition => ({
44
+ id: definition.id,
45
+ label: definition.title,
46
+ })))
47
+
48
+ const panels = computed<Record<string, SidebarToolSection[]>>(() => Object.fromEntries(
49
+ definitions.value.map(definition => [definition.id, [{
50
+ id: definition.id,
51
+ label: 'Parameters',
52
+ subtitle: resourceSummary(definition),
53
+ defaultOpen: true,
54
+ }]]),
55
+ ))
56
+
57
+ const forms = computed<Record<string, FormSchema>>(() => Object.fromEntries(
58
+ definitions.value.map(definition => [definition.id, generatedJobFormSchema(definition)]),
59
+ ))
60
+
61
+ const currentValues = computed(() => valuesByDefinition[activeDefinitionId.value] ?? {})
62
+ const currentJob = computed(() => {
63
+ const currentSessionId = generated.session.value?.id
64
+ const currentDefinitionId = activeDefinitionId.value
65
+ const selected = generated.jobs.value.find(candidate => (
66
+ candidate.job_id === selectedJobId.value
67
+ && candidate.status === 'completed'
68
+ && candidate.metadata.definition_id === currentDefinitionId
69
+ && candidate.metadata.has_result === true
70
+ ))
71
+ return selected ?? generated.jobs.value.find(candidate => (
72
+ candidate.session_id === currentSessionId
73
+ && candidate.status === 'completed'
74
+ && candidate.metadata.definition_id === currentDefinitionId
75
+ && candidate.metadata.has_result === true
76
+ ))
77
+ })
78
+ const currentResult = computed<GeneratedAnalysisResult | null>(() => (
79
+ currentJob.value ? generated.resultForJob(currentJob.value.job_id) : null
80
+ ))
81
+
82
+ watch(definitions, (nextDefinitions) => {
83
+ for (const definition of nextDefinitions) {
84
+ valuesByDefinition[definition.id] ??= generatedJobDefaults(definition)
85
+ }
86
+ if (!nextDefinitions.some(definition => definition.id === activeDefinitionId.value)) {
87
+ activeDefinitionId.value = nextDefinitions[0]?.id ?? ''
88
+ }
89
+ }, { immediate: true })
90
+
91
+ watch(currentJob, (job) => {
92
+ if (job) void generated.loadResult(job)
93
+ }, { immediate: true })
94
+
95
+ function updateValues(values: Record<string, unknown>): void {
96
+ if (activeDefinitionId.value) valuesByDefinition[activeDefinitionId.value] = { ...values }
97
+ }
98
+
99
+ async function submitJob(sectionId: string, values: Record<string, unknown>): Promise<void> {
100
+ const definition = definitions.value.find(candidate => candidate.id === sectionId)
101
+ if (!definition) return
102
+ selectedJobId.value = ''
103
+ valuesByDefinition[definition.id] = { ...values }
104
+ await generated.submit(definition.id, normalizeGeneratedJobInput(definition, values))
105
+ }
106
+
107
+ function selectJob(job: GeneratedAnalysisJob): void {
108
+ selectedJobId.value = job.job_id
109
+ activeDefinitionId.value = String(job.metadata.definition_id ?? activeDefinitionId.value)
110
+ }
111
+
112
+ function resourceSummary(definition: GeneratedJobDefinition): string {
113
+ if (definition.lifecycle === 'service' || definition.profile?.kind === 'service') {
114
+ return 'Host service'
115
+ }
116
+ if (definition.lifecycle === 'staged' || definition.profile?.kind === 'staged') {
117
+ return 'Staged upload'
118
+ }
119
+ return `Up to ${definition.cpu} CPU`
120
+ }
121
+
122
+ onMounted(() => {
123
+ void generated.initialize().catch(() => undefined)
124
+ })
125
+ </script>
126
+
127
+ <template>
128
+ <PluginWorkspaceView
129
+ :title="generated.manifest.value?.ui.title ?? 'Analysis'"
130
+ :subtitle="generated.manifest.value?.ui.description"
131
+ :show-standalone-label="showStandaloneLabel"
132
+ :show-settings="false"
133
+ :pill-nav="pillNav"
134
+ :active-view="activeDefinitionId"
135
+ :panels="panels"
136
+ :forms="forms"
137
+ sidebar-title="Parameters"
138
+ sidebar-position="left"
139
+ :values="currentValues"
140
+ :show-form-actions="true"
141
+ :form-loading="generated.isSubmitting.value"
142
+ :form-disabled="generated.isSubmitting.value"
143
+ @update:active-view="activeDefinitionId = $event"
144
+ @update:values="updateValues"
145
+ @form-submit="submitJob"
146
+ >
147
+ <main class="mint-generated-workspace">
148
+ <div v-if="generated.isLoading.value" class="mint-generated-workspace__loading">
149
+ <LoadingSpinner size="lg" />
150
+ <span>Loading analysis workspace</span>
151
+ </div>
152
+
153
+ <template v-else>
154
+ <AlertBox
155
+ v-if="generated.error.value"
156
+ type="error"
157
+ :message="generated.error.value"
158
+ />
159
+
160
+ <header v-if="activeDefinition" class="mint-generated-workspace__header">
161
+ <div>
162
+ <p class="mint-generated-workspace__eyebrow">Analysis result</p>
163
+ <h1>{{ activeDefinition.title }}</h1>
164
+ <p v-if="activeDefinition.description">{{ activeDefinition.description }}</p>
165
+ </div>
166
+ <div class="mint-generated-workspace__resources" aria-label="Job resource requirements">
167
+ <span>{{ resourceSummary(activeDefinition) }}</span>
168
+ </div>
169
+ </header>
170
+
171
+ <section class="mint-generated-workspace__result">
172
+ <GeneratedResultRenderer
173
+ v-if="currentResult"
174
+ :result="currentResult"
175
+ />
176
+ <EmptyState
177
+ v-else
178
+ title="No result yet"
179
+ description="Set the parameters and run the analysis. Jobs continue independently if you switch tabs."
180
+ size="md"
181
+ />
182
+ </section>
183
+ </template>
184
+
185
+ <JobsStatusTray
186
+ :source="generated"
187
+ :teleport-to="false"
188
+ @select="selectJob"
189
+ />
190
+ </main>
191
+ </PluginWorkspaceView>
192
+ </template>
193
+
194
+ <style scoped>
195
+ .mint-generated-workspace {
196
+ position: relative;
197
+ min-width: 0;
198
+ min-height: calc(100vh - 5rem);
199
+ padding: clamp(1rem, 2.5vw, 2.5rem);
200
+ background: var(--color-bg-primary);
201
+ }
202
+
203
+ .mint-generated-workspace__loading {
204
+ display: flex;
205
+ min-height: 24rem;
206
+ align-items: center;
207
+ justify-content: center;
208
+ gap: 0.75rem;
209
+ color: var(--color-text-secondary);
210
+ }
211
+
212
+ .mint-generated-workspace__header {
213
+ display: flex;
214
+ align-items: flex-start;
215
+ justify-content: space-between;
216
+ gap: 1.5rem;
217
+ max-width: 76rem;
218
+ margin: 0 auto 1.25rem;
219
+ }
220
+
221
+ .mint-generated-workspace__header h1 {
222
+ margin: 0;
223
+ color: var(--color-text-primary);
224
+ font-size: clamp(1.35rem, 2vw, 1.8rem);
225
+ line-height: 1.2;
226
+ }
227
+
228
+ .mint-generated-workspace__header p:not(.mint-generated-workspace__eyebrow) {
229
+ margin: 0.4rem 0 0;
230
+ color: var(--color-text-secondary);
231
+ }
232
+
233
+ .mint-generated-workspace__eyebrow {
234
+ margin: 0 0 0.35rem;
235
+ color: var(--color-text-muted);
236
+ font-size: 0.72rem;
237
+ font-weight: 700;
238
+ letter-spacing: 0.08em;
239
+ text-transform: uppercase;
240
+ }
241
+
242
+ .mint-generated-workspace__resources {
243
+ display: flex;
244
+ flex-wrap: wrap;
245
+ justify-content: flex-end;
246
+ gap: 0.4rem;
247
+ }
248
+
249
+ .mint-generated-workspace__resources span {
250
+ padding: 0.3rem 0.55rem;
251
+ border: 1px solid var(--color-border);
252
+ border-radius: 999px;
253
+ background: var(--color-bg-secondary);
254
+ color: var(--color-text-secondary);
255
+ font-size: 0.75rem;
256
+ font-weight: 600;
257
+ }
258
+
259
+ .mint-generated-workspace__result {
260
+ max-width: 76rem;
261
+ min-height: 24rem;
262
+ margin: 0 auto;
263
+ padding: clamp(1rem, 2vw, 1.75rem);
264
+ border: 1px solid var(--color-border);
265
+ border-radius: 1rem;
266
+ background: var(--color-bg-secondary);
267
+ box-shadow: var(--shadow-sm);
268
+ }
269
+
270
+ @media (max-width: 640px) {
271
+ .mint-generated-workspace__header {
272
+ flex-direction: column;
273
+ }
274
+
275
+ .mint-generated-workspace__resources {
276
+ justify-content: flex-start;
277
+ }
278
+ }
279
+ </style>
@@ -0,0 +1,137 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ import type { DataFrameColumn } from '../types'
5
+ import type { GeneratedAnalysisResult } from '../types/generated-ui'
6
+ import ChartContainer from './ChartContainer.vue'
7
+ import DataFrame from './DataFrame.vue'
8
+ import GeneratedPlotlyResult from './GeneratedPlotlyResult.vue'
9
+
10
+ const props = defineProps<{
11
+ result: GeneratedAnalysisResult
12
+ }>()
13
+
14
+ const tableColumns = computed<DataFrameColumn[]>(() => {
15
+ if (props.result.kind !== 'table') return []
16
+ return props.result.columns.map(key => ({
17
+ key,
18
+ label: humanize(key),
19
+ sortable: true,
20
+ }))
21
+ })
22
+
23
+ const formattedJson = computed(() => (
24
+ props.result.kind === 'json'
25
+ ? JSON.stringify(props.result.value, null, 2)
26
+ : ''
27
+ ))
28
+
29
+ const artifactHref = computed(() => {
30
+ if (props.result.kind !== 'artifact') return ''
31
+ if (props.result.download_url) return props.result.download_url
32
+ return props.result.data
33
+ ? `data:${props.result.media_type};base64,${props.result.data}`
34
+ : ''
35
+ })
36
+
37
+ function humanize(value: string): string {
38
+ return value.replace(/[_-]+/g, ' ').replace(/^\w/, character => character.toUpperCase())
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <article class="mint-generated-result" aria-live="polite">
44
+ <p v-if="result.kind === 'text'" class="mint-generated-result__text">
45
+ {{ result.text }}
46
+ </p>
47
+
48
+ <pre v-else-if="result.kind === 'json'" class="mint-generated-result__json">{{ formattedJson }}</pre>
49
+
50
+ <DataFrame
51
+ v-else-if="result.kind === 'table'"
52
+ :data="result.rows"
53
+ :columns="tableColumns"
54
+ sortable
55
+ searchable
56
+ sticky-header
57
+ max-height="32rem"
58
+ />
59
+
60
+ <ChartContainer v-else-if="result.kind === 'image'" title="Analysis chart">
61
+ <img
62
+ class="mint-generated-result__image"
63
+ :src="`data:${result.media_type};base64,${result.data}`"
64
+ :alt="result.alt"
65
+ >
66
+ </ChartContainer>
67
+
68
+ <GeneratedPlotlyResult
69
+ v-else-if="result.kind === 'plotly'"
70
+ :spec="result.spec"
71
+ :fallback="result.fallback"
72
+ />
73
+
74
+ <a
75
+ v-else
76
+ class="mint-generated-result__artifact"
77
+ :href="artifactHref"
78
+ :download="result.name"
79
+ >
80
+ <span>{{ result.name }}</span>
81
+ <small>{{ result.media_type }}</small>
82
+ </a>
83
+ </article>
84
+ </template>
85
+
86
+ <style scoped>
87
+ .mint-generated-result {
88
+ width: 100%;
89
+ }
90
+
91
+ .mint-generated-result__text,
92
+ .mint-generated-result__json {
93
+ margin: 0;
94
+ color: var(--color-text-primary);
95
+ }
96
+
97
+ .mint-generated-result__text {
98
+ max-width: 72ch;
99
+ white-space: pre-wrap;
100
+ line-height: 1.65;
101
+ }
102
+
103
+ .mint-generated-result__json {
104
+ overflow: auto;
105
+ padding: 1rem;
106
+ border: 1px solid var(--color-border);
107
+ border-radius: 0.75rem;
108
+ background: var(--color-bg-secondary);
109
+ font-size: 0.8125rem;
110
+ line-height: 1.55;
111
+ white-space: pre-wrap;
112
+ }
113
+
114
+ .mint-generated-result__image {
115
+ display: block;
116
+ max-width: 100%;
117
+ height: auto;
118
+ margin: 0 auto;
119
+ }
120
+
121
+ .mint-generated-result__plotly {
122
+ width: 100%;
123
+ min-height: 24rem;
124
+ }
125
+
126
+ .mint-generated-result__artifact {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: space-between;
130
+ gap: 1rem;
131
+ padding: 1rem;
132
+ border: 1px solid var(--color-border);
133
+ border-radius: 0.75rem;
134
+ color: var(--color-primary);
135
+ text-decoration: none;
136
+ }
137
+ </style>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- /** Filterable instrument alert/event list with optional acknowledge actions. */
2
+ /** Filterable instrument alert/event list keyed by each alert's required stable event_key. */
3
3
  import { computed, ref } from 'vue'
4
4
  import type { InstrumentAlert, InstrumentAlertLevel } from '../types'
5
5
 
@@ -146,7 +146,7 @@ function alertDetail(alert: InstrumentAlert): string {
146
146
  <TransitionGroup name="mint-instrument-alert" tag="div" class="mint-instrument-alert-log__list">
147
147
  <div
148
148
  v-for="alert in filteredAlerts"
149
- :key="alert.id ?? `${alert.instrument_id}-${alert.rule ?? alert.message}-${alert.timestamp ?? ''}`"
149
+ :key="alert.event_key"
150
150
  class="mint-instrument-alert"
151
151
  :class="[
152
152
  levelConfig[alert.level]?.cssClass,
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- /** Floating, adapter-driven job status tray for plugin and platform workflows. */
2
+ /** Floating, source-driven job status tray for plugin and platform workflows. */
3
3
  import { computed, nextTick, ref, toRef, useId } from 'vue'
4
4
 
5
5
  import {
@@ -7,8 +7,9 @@ import {
7
7
  normalizeJobPercent,
8
8
  } from '../jobs'
9
9
  import {
10
- useJobsStatusTray,
10
+ usePluginJobCenter,
11
11
  type JobsStatusTrayAdapter,
12
+ type PluginJobCenterSource,
12
13
  } from '../composables/useJobsStatusTray'
13
14
  import type { UsePluginEventStreamReturn } from '../composables/usePluginClient'
14
15
  import type {
@@ -18,8 +19,12 @@ import type {
18
19
  } from '../types/jobs'
19
20
 
20
21
  interface Props {
22
+ source?: PluginJobCenterSource
23
+ /** @deprecated Pass a source instead. */
21
24
  jobs?: readonly JobStateInput[]
25
+ /** @deprecated Pass source actions instead. */
22
26
  adapter?: JobsStatusTrayAdapter
27
+ /** @deprecated Event streams belong to the source runtime. */
23
28
  eventStream?: UsePluginEventStreamReturn<JobStreamData>
24
29
  loadOnMount?: boolean
25
30
  title?: string
@@ -42,7 +47,8 @@ const titleId = `${panelId}-title`
42
47
  const triggerRef = ref<HTMLButtonElement | null>(null)
43
48
  const panelRef = ref<HTMLElement | null>(null)
44
49
  const teleportTarget = computed(() => props.teleportTo || 'body')
45
- const tray = useJobsStatusTray({
50
+ const tray = usePluginJobCenter({
51
+ source: toRef(props, 'source'),
46
52
  jobs: toRef(props, 'jobs'),
47
53
  adapter: toRef(props, 'adapter'),
48
54
  eventStream: toRef(props, 'eventStream'),
@@ -50,7 +56,7 @@ const tray = useJobsStatusTray({
50
56
  })
51
57
  const canClear = computed(() => (
52
58
  tray.deletableJobs.value.length > 0
53
- && Boolean(props.adapter?.clearFinishedJobs || props.adapter?.deleteJob)
59
+ && tray.canClearFinished.value
54
60
  ))
55
61
 
56
62
  async function openTray(): Promise<void> {
@@ -201,7 +207,7 @@ async function clearFinished(): Promise<void> {
201
207
  </div>
202
208
  <div class="mint-jobs-tray__header-actions">
203
209
  <button
204
- v-if="adapter?.listJobs"
210
+ v-if="tray.canRefresh.value"
205
211
  type="button"
206
212
  class="mint-jobs-tray__icon-button"
207
213
  aria-label="Refresh jobs"
@@ -297,7 +303,7 @@ async function clearFinished(): Promise<void> {
297
303
  <span>{{ progressText(job) }}</span>
298
304
  <div class="mint-jobs-tray__job-actions">
299
305
  <button
300
- v-if="job.can_cancel && adapter?.cancelJob"
306
+ v-if="job.can_cancel && tray.canCancel.value"
301
307
  type="button"
302
308
  class="mint-jobs-tray__action mint-jobs-tray__action--danger"
303
309
  aria-label="Cancel job"
@@ -307,7 +313,7 @@ async function clearFinished(): Promise<void> {
307
313
  Cancel
308
314
  </button>
309
315
  <button
310
- v-if="job.can_delete && adapter?.deleteJob"
316
+ v-if="job.can_delete && tray.canDelete.value"
311
317
  type="button"
312
318
  class="mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger"
313
319
  aria-label="Delete job"
@@ -359,7 +365,7 @@ async function clearFinished(): Promise<void> {
359
365
  <div class="mint-jobs-tray__job-footer">
360
366
  <span>{{ formatTimestamp(job.completed_at || job.created_at) }}</span>
361
367
  <button
362
- v-if="job.can_delete && adapter?.deleteJob"
368
+ v-if="job.can_delete && tray.canDelete.value"
363
369
  type="button"
364
370
  class="mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger"
365
371
  aria-label="Delete job"
@@ -34,7 +34,7 @@ export interface PluginWorkspaceViewProps {
34
34
  homePath?: string
35
35
  /** Show the default MINT logo when no icon/logo slot is provided. */
36
36
  showLogo?: boolean
37
- /** Preferred route-level page selector entries. */
37
+ /** Preferred route-level page selector entries. Pass [] to suppress integrated platform fallback. */
38
38
  pageSelector?: PageSelectorItemInput[]
39
39
  /** Active page selector id. Defaults to activeView. */
40
40
  currentPageSelectorId?: string
@@ -208,10 +208,6 @@ const {
208
208
  emitPillSelect: (item) => emit('pill-select', item),
209
209
  })
210
210
 
211
- const visiblePageSelector = computed(() =>
212
- (props.pageSelector?.length ?? 0) > 1 ? props.pageSelector : undefined,
213
- )
214
-
215
211
  const hasSidebarSurface = computed(() => {
216
212
  return hasPluginWorkspaceSidebarSurface({
217
213
  showSidebar: props.showSidebar,
@@ -274,7 +270,7 @@ function handleFormCancel(sectionId: string) {
274
270
  :variant="topBarVariant"
275
271
  :home-path="homePath"
276
272
  :show-logo="showLogo"
277
- :page-selector="visiblePageSelector"
273
+ :page-selector="pageSelector"
278
274
  :current-page-selector-id="resolvedCurrentPageSelectorId"
279
275
  :plugin-switcher="pluginSwitcher"
280
276
  :pill-nav="resolvedPillNav"