@morscherlab/mint-sdk 1.1.25 → 1.2.0-beta.2
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 +9 -7
- package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-DyPAUCLw.js} +3 -3
- package/dist/{ExperimentSelectorModal-B6P2msT4.js.map → ExperimentSelectorModal-DyPAUCLw.js.map} +1 -1
- package/dist/{SettingsModal-BW3Ar-uy.js → SettingsModal-BgKY93zh.js} +4 -4
- package/dist/{SettingsModal-BW3Ar-uy.js.map → SettingsModal-BgKY93zh.js.map} +1 -1
- package/dist/__tests__/composables/useEventStream.test.d.ts +1 -0
- package/dist/components/AutoGroupModal.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/JobsStatusTray.vue.d.ts +2 -9
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +1 -1
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
- package/dist/components/index.js +4 -4
- package/dist/{components-DQt0tcpY.js → components-DkHddRMl.js} +10 -16
- package/dist/components-DkHddRMl.js.map +1 -0
- package/dist/composables/index.d.ts +2 -3
- package/dist/composables/index.js +8 -8
- package/dist/composables/useEventStream.d.ts +43 -0
- package/dist/composables/useGeneratedAnalysis.d.ts +1 -3
- package/dist/composables/useJobsStatusTray.d.ts +1 -25
- package/dist/composables/usePluginClient.d.ts +3 -18
- package/dist/{composables-CkXDe7nm.js → composables-C_i90sGS.js} +32 -308
- package/dist/composables-C_i90sGS.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-Cq145oEW.js} +2 -2
- package/dist/{experiment-Y9Iz7KYi.js.map → experiment-Cq145oEW.js.map} +1 -1
- package/dist/index.js +12 -12
- package/dist/install.js +4 -4
- package/dist/stores/auth.d.ts +0 -6
- package/dist/stores/index.js +2 -2
- package/dist/templates/index.js +2 -2
- package/dist/{templates-CovvRZ-9.js → templates-DsP51i-S.js} +2 -2
- package/dist/{templates-CovvRZ-9.js.map → templates-DsP51i-S.js.map} +1 -1
- package/dist/types/auth.d.ts +0 -1
- package/dist/{useApi-CDBq6z23.js → useApi-J6zFkHmn.js} +1 -2
- package/dist/useApi-J6zFkHmn.js.map +1 -0
- package/dist/{useControlSchema-BhoJzWzx.js → useControlSchema-BDx2Olha.js} +239 -103
- package/dist/useControlSchema-BDx2Olha.js.map +1 -0
- package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-CV4iNfMS.js} +2 -2
- package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-CV4iNfMS.js.map} +1 -1
- package/dist/{useFormBuilder-DiEcDis2.js → useFormBuilder-BLxG3BOX.js} +2 -2
- package/dist/{useFormBuilder-DiEcDis2.js.map → useFormBuilder-BLxG3BOX.js.map} +1 -1
- package/dist/{useProtocolTemplates-DHwSveE6.js → useProtocolTemplates-CS_SQl6U.js} +22 -113
- package/dist/useProtocolTemplates-CS_SQl6U.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/GeneratedPluginView.test.ts +0 -5
- package/src/__tests__/components/JobsStatusTray.test.ts +20 -18
- package/src/__tests__/composables/useAuth.test.ts +23 -0
- package/src/__tests__/composables/useEventStream.test.ts +140 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +103 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +25 -161
- package/src/__tests__/composables/usePluginClient.test.ts +8 -13
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +22 -1
- package/src/components/JobsStatusTray.story.vue +6 -10
- package/src/components/JobsStatusTray.vue +1 -16
- package/src/composables/index.ts +0 -7
- package/src/composables/useAuth.ts +0 -3
- package/src/composables/useEventStream.ts +314 -0
- package/src/composables/useGeneratedAnalysis.ts +40 -109
- package/src/composables/useJobsStatusTray.ts +17 -184
- package/src/composables/usePluginClient.ts +45 -249
- package/src/stores/auth.ts +0 -1
- package/src/types/auth.ts +0 -1
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +0 -15
- package/dist/components-DQt0tcpY.js.map +0 -1
- package/dist/composables/usePluginConfig.d.ts +0 -25
- package/dist/composables-CkXDe7nm.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-BhoJzWzx.js.map +0 -1
- package/dist/useProtocolTemplates-DHwSveE6.js.map +0 -1
- package/src/__tests__/composables/usePluginConfig.test.ts +0 -497
- package/src/composables/usePluginConfig.ts +0 -250
package/dist/types/auth.d.ts
CHANGED
|
@@ -239,7 +239,6 @@ var useAuthStore = defineStore("mint-auth", () => {
|
|
|
239
239
|
passkeyEnabled: false,
|
|
240
240
|
passkeyRegistered: false,
|
|
241
241
|
registrationEnabled: false,
|
|
242
|
-
databaseMode: "none",
|
|
243
242
|
experimentVisibilityMode: "open"
|
|
244
243
|
});
|
|
245
244
|
const isInitialized = ref(false);
|
|
@@ -538,4 +537,4 @@ function useApi(options = {}) {
|
|
|
538
537
|
//#endregion
|
|
539
538
|
export { colorPalettes as a, useAuthStore as i, authenticationRequiredError as n, useSettingsStore as o, useApi as r, AuthenticationRequiredError as t };
|
|
540
539
|
|
|
541
|
-
//# sourceMappingURL=useApi-
|
|
540
|
+
//# sourceMappingURL=useApi-J6zFkHmn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useApi-J6zFkHmn.js","names":[],"sources":["../src/stores/settings.ts","../src/stores/auth.ts","../src/composables/useApi.ts"],"sourcesContent":["import { defineStore } from 'pinia'\nimport { ref, watch } from 'vue'\nimport type { ThemeMode, ColorPalette, TableDensity } from '../types'\n\ndeclare global {\n interface ImportMetaEnv {\n readonly VITE_API_PREFIX?: string\n }\n\n interface ImportMeta {\n readonly env: ImportMetaEnv\n }\n}\n\nexport interface SettingsState {\n serverHost: string\n serverPort: number\n requestTimeout: number\n wsAutoReconnect: boolean\n wsReconnectInterval: number\n theme: ThemeMode\n colorPalette: ColorPalette\n tableDensity: TableDensity\n}\n\nconst STORAGE_KEY = 'mint-settings'\nconst LEGACY_STORAGE_KEY = 'mld-settings'\n\nfunction getDefaultServerHost(): string {\n if (typeof window !== 'undefined' && window.location.hostname !== 'localhost') {\n return window.location.hostname\n }\n return 'localhost'\n}\n\nfunction getDefaultServerPort(): number {\n if (typeof window !== 'undefined') {\n if (window.location.port) {\n return parseInt(window.location.port, 10)\n }\n // Standard ports: 443 for HTTPS, 80 for HTTP (browser omits from location.port)\n return window.location.protocol === 'https:' ? 443 : 80\n }\n return 8000\n}\n\nconst defaultSettings: SettingsState = {\n serverHost: getDefaultServerHost(),\n serverPort: getDefaultServerPort(),\n requestTimeout: 120000,\n wsAutoReconnect: true,\n wsReconnectInterval: 5000,\n theme: 'system',\n colorPalette: 'default',\n tableDensity: 'normal',\n}\n\nexport const colorPalettes: Record<ColorPalette, { name: string; hues: [number, number] }> = {\n default: { name: 'Default (Cyan-Pink)', hues: [180, 320] },\n colorblind: { name: 'Colorblind-friendly', hues: [45, 260] },\n viridis: { name: 'Viridis', hues: [280, 80] },\n pastel: { name: 'Pastel', hues: [200, 340] },\n}\n\nfunction loadSettings(): SettingsState {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored)\n return { ...defaultSettings, ...parsed }\n }\n\n const legacyStored = localStorage.getItem(LEGACY_STORAGE_KEY)\n if (legacyStored) {\n const parsed = JSON.parse(legacyStored)\n const migrated = { ...defaultSettings, ...parsed }\n localStorage.setItem(STORAGE_KEY, JSON.stringify(migrated))\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n return migrated\n }\n } catch (e) {\n console.warn('Failed to load settings from localStorage:', e)\n }\n return { ...defaultSettings }\n}\n\nfunction saveSettings(settings: SettingsState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))\n } catch (e) {\n console.warn('Failed to save settings to localStorage:', e)\n }\n}\n\nfunction getSystemPrefersDark(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n}\n\nexport const useSettingsStore = defineStore('mint-settings', () => {\n // State\n const serverHost = ref(defaultSettings.serverHost)\n const serverPort = ref(defaultSettings.serverPort)\n const requestTimeout = ref(defaultSettings.requestTimeout)\n const wsAutoReconnect = ref(defaultSettings.wsAutoReconnect)\n const wsReconnectInterval = ref(defaultSettings.wsReconnectInterval)\n const theme = ref<ThemeMode>(defaultSettings.theme)\n const systemPrefersDark = ref(getSystemPrefersDark())\n const colorPalette = ref<ColorPalette>(defaultSettings.colorPalette)\n const tableDensity = ref<TableDensity>(defaultSettings.tableDensity)\n\n // API prefix - can be configured via env variable VITE_API_PREFIX\n const apiPrefix: string = (import.meta.env?.VITE_API_PREFIX as string | undefined) ?? '/api'\n\n function _isSameOrigin(): { same: boolean; currentHost: string; currentPort: string } {\n const currentHost = typeof window !== 'undefined' ? window.location.hostname : 'localhost'\n const currentPort = typeof window !== 'undefined' ? window.location.port : '8000'\n const effectivePort = currentPort || (window.location.protocol === 'https:' ? '443' : '80')\n const same =\n (serverHost.value === currentHost && String(serverPort.value) === effectivePort) ||\n (serverHost.value === 'localhost' && currentHost !== 'localhost')\n return { same, currentHost, currentPort }\n }\n\n function getApiBaseUrl(): string {\n const { same } = _isSameOrigin()\n if (same) return apiPrefix\n return `http://${serverHost.value}:${serverPort.value}${apiPrefix}`\n }\n\n function getWsBaseUrl(): string {\n const { same, currentHost, currentPort } = _isSameOrigin()\n const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:'\n if (same) return `${protocol}//${currentHost}${currentPort ? ':' + currentPort : ''}${apiPrefix}`\n return `ws://${serverHost.value}:${serverPort.value}${apiPrefix}`\n }\n\n let _initialized = false\n\n function initialize() {\n if (_initialized) return\n _initialized = true\n\n const loaded = loadSettings()\n serverHost.value = loaded.serverHost\n serverPort.value = loaded.serverPort\n requestTimeout.value = loaded.requestTimeout\n wsAutoReconnect.value = loaded.wsAutoReconnect\n wsReconnectInterval.value = loaded.wsReconnectInterval\n theme.value = loaded.theme\n colorPalette.value = loaded.colorPalette\n tableDensity.value = loaded.tableDensity\n\n applyTheme()\n }\n\n function applyTheme() {\n if (typeof document === 'undefined') return\n const dark = theme.value === 'system' ? systemPrefersDark.value : theme.value === 'dark'\n document.documentElement.classList.toggle('dark', dark)\n }\n\n watch(theme, () => {\n applyTheme()\n persistSettings()\n })\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {\n systemPrefersDark.value = event.matches\n if (theme.value === 'system') {\n applyTheme()\n }\n })\n }\n\n function persistSettings() {\n saveSettings({\n serverHost: serverHost.value,\n serverPort: serverPort.value,\n requestTimeout: requestTimeout.value,\n wsAutoReconnect: wsAutoReconnect.value,\n wsReconnectInterval: wsReconnectInterval.value,\n theme: theme.value,\n colorPalette: colorPalette.value,\n tableDensity: tableDensity.value,\n })\n }\n\n watch([serverHost, serverPort, requestTimeout, wsAutoReconnect, wsReconnectInterval, colorPalette, tableDensity], () => {\n persistSettings()\n })\n\n function resetToDefaults() {\n serverHost.value = defaultSettings.serverHost\n serverPort.value = defaultSettings.serverPort\n requestTimeout.value = defaultSettings.requestTimeout\n wsAutoReconnect.value = defaultSettings.wsAutoReconnect\n wsReconnectInterval.value = defaultSettings.wsReconnectInterval\n theme.value = defaultSettings.theme\n colorPalette.value = defaultSettings.colorPalette\n tableDensity.value = defaultSettings.tableDensity\n }\n\n function getPaletteHues(): [number, number] {\n return colorPalettes[colorPalette.value].hues\n }\n\n function isDark(): boolean {\n return theme.value === 'system' ? systemPrefersDark.value : theme.value === 'dark'\n }\n\n return {\n serverHost,\n serverPort,\n requestTimeout,\n wsAutoReconnect,\n wsReconnectInterval,\n theme,\n systemPrefersDark,\n colorPalette,\n tableDensity,\n initialize,\n applyTheme,\n persistSettings,\n resetToDefaults,\n getPaletteHues,\n isDark,\n getApiBaseUrl,\n getWsBaseUrl,\n }\n})\n","import { defineStore } from 'pinia'\nimport { ref, computed } from 'vue'\nimport type { AuthConfig, UserInfo } from '../types'\nimport {\n canAccessAdmin as canUserAccessAdmin,\n canAccessPlugin as canUserAccessPlugin,\n getAccessAudience,\n getUserPermissions,\n hasAllPermissions,\n hasAnyPermission,\n isAdminUser,\n} from '../permissions'\n\nconst AUTH_TOKEN_KEY = 'mint-auth-token'\nconst AUTH_EXPIRES_KEY = 'mint-auth-expires'\n\nfunction getLocalStorage(): Storage | null {\n try {\n const storage = globalThis.localStorage\n return typeof storage?.getItem === 'function' ? storage : null\n } catch {\n return null\n }\n}\n\nfunction readStoredItem(key: string): string | null {\n try {\n return getLocalStorage()?.getItem(key) ?? null\n } catch {\n return null\n }\n}\n\nfunction writeStoredItem(key: string, value: string): void {\n try {\n getLocalStorage()?.setItem(key, value)\n } catch {\n // Keep auth usable in-memory when browser storage is blocked.\n }\n}\n\nfunction removeStoredItem(key: string): void {\n try {\n getLocalStorage()?.removeItem(key)\n } catch {\n // Keep auth cleanup usable in-memory when browser storage is blocked.\n }\n}\n\nexport const useAuthStore = defineStore('mint-auth', () => {\n // State\n const token = ref<string | null>(null)\n const tokenExpires = ref<Date | null>(null)\n const username = ref<string | null>(null)\n const userInfo = ref<UserInfo | null>(null)\n const authConfig = ref<AuthConfig>({\n authRequired: true,\n passkeyEnabled: false,\n passkeyRegistered: false,\n registrationEnabled: false,\n experimentVisibilityMode: 'open',\n })\n const isInitialized = ref(false)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n // Computed\n const isAuthenticated = computed(() => {\n if (!authConfig.value.authRequired) {\n return true\n }\n if (!token.value) {\n return false\n }\n if (tokenExpires.value && tokenExpires.value < new Date()) {\n return false\n }\n return true\n })\n\n const needsAuth = computed(() => {\n return authConfig.value.authRequired && !isAuthenticated.value\n })\n\n const isAdmin = computed(() => isAdminUser(userInfo.value))\n const userType = computed(() => getAccessAudience(userInfo.value))\n const permissions = computed(() => getUserPermissions(userInfo.value))\n const canAccessAdmin = computed(() => canUserAccessAdmin(userInfo.value))\n\n const canRegister = computed(() => {\n return authConfig.value.registrationEnabled\n })\n\n // Actions\n function initialize() {\n const storedToken = readStoredItem(AUTH_TOKEN_KEY)\n const storedExpires = readStoredItem(AUTH_EXPIRES_KEY)\n\n if (storedToken) {\n token.value = storedToken\n\n if (storedExpires) {\n const expires = new Date(storedExpires)\n if (expires > new Date()) {\n tokenExpires.value = expires\n } else {\n clearToken()\n }\n }\n }\n\n isInitialized.value = true\n }\n\n function setToken(accessToken: string, expiresIn: number) {\n token.value = accessToken\n const expires = new Date(Date.now() + expiresIn * 1000)\n tokenExpires.value = expires\n\n writeStoredItem(AUTH_TOKEN_KEY, accessToken)\n writeStoredItem(AUTH_EXPIRES_KEY, expires.toISOString())\n }\n\n function clearToken() {\n token.value = null\n tokenExpires.value = null\n username.value = null\n userInfo.value = null\n\n removeStoredItem(AUTH_TOKEN_KEY)\n removeStoredItem(AUTH_EXPIRES_KEY)\n }\n\n function setUserInfo(info: UserInfo) {\n userInfo.value = info\n username.value = info.username\n }\n\n function hasPermission(...perms: string[]): boolean {\n return hasAllPermissions(userInfo.value, perms)\n }\n\n function hasAnyPermissionForUser(...perms: string[]): boolean {\n return hasAnyPermission(userInfo.value, perms)\n }\n\n function canAccessPlugin(pluginName: string): boolean {\n return canUserAccessPlugin(userInfo.value, pluginName)\n }\n\n function setAuthConfig(config: AuthConfig) {\n authConfig.value = config\n }\n\n function setUsername(name: string) {\n username.value = name\n }\n\n function setError(message: string | null) {\n error.value = message\n }\n\n function setLoading(loading: boolean) {\n isLoading.value = loading\n }\n\n function logout() {\n clearToken()\n }\n\n return {\n // State\n token,\n tokenExpires,\n username,\n userInfo,\n authConfig,\n isInitialized,\n isLoading,\n error,\n\n // Computed\n isAuthenticated,\n needsAuth,\n isAdmin,\n userType,\n permissions,\n canAccessAdmin,\n canRegister,\n\n // Actions\n initialize,\n setToken,\n clearToken,\n setAuthConfig,\n setUsername,\n setUserInfo,\n setError,\n setLoading,\n hasPermission,\n hasAnyPermission: hasAnyPermissionForUser,\n canAccessPlugin,\n logout,\n }\n})\n","import axios, { type AxiosInstance, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\ninterface MintAxiosRequestConfig extends AxiosRequestConfig {\n _mintSkipAuth?: boolean\n _mintAuthToken?: string\n}\n\ninterface MintInternalAxiosRequestConfig extends InternalAxiosRequestConfig {\n _mintSkipAuth?: boolean\n _mintAuthToken?: string\n}\n\ntype MutableHeaders = Record<string, unknown> & {\n has?: (header: string) => boolean\n set?: (header: string, value: string) => void\n delete?: (header: string) => void\n}\n\nfunction joinUrlPath(baseUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path\n if (!path) return baseUrl\n if (path.startsWith('?') || path.startsWith('#')) return `${baseUrl.replace(/\\/+$/, '')}${path}`\n const normalizedBase = baseUrl.replace(/\\/+$/, '')\n const normalizedPath = path.replace(/^\\/+/, '/')\n return `${normalizedBase}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`\n}\n\nfunction getBasePath(baseUrl: string): string {\n if (!baseUrl) return '/'\n\n try {\n const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n return new URL(baseUrl, origin).pathname.replace(/\\/+$/, '') || '/'\n } catch {\n const path = baseUrl.replace(/^https?:\\/\\/[^/]+/i, '')\n return path.replace(/\\/+$/, '') || '/'\n }\n}\n\nfunction normalizeRequestUrl(baseUrl: string, url: string): string {\n if (!url || /^https?:\\/\\//.test(url)) return url\n\n const basePath = getBasePath(baseUrl)\n if (basePath === '/') return url\n\n const normalizedUrl = url.startsWith('/') ? url : `/${url}`\n if (\n normalizedUrl === basePath\n || normalizedUrl.startsWith(`${basePath}?`)\n || normalizedUrl.startsWith(`${basePath}#`)\n ) {\n return normalizedUrl.slice(basePath.length)\n }\n if (normalizedUrl.startsWith(`${basePath}/`)) {\n return normalizedUrl.slice(basePath.length) || ''\n }\n return url\n}\n\nfunction asMutableHeaders(headers: AxiosRequestConfig['headers']): MutableHeaders | null {\n return headers ? (headers as MutableHeaders) : null\n}\n\nfunction hasAuthorizationHeader(headers: AxiosRequestConfig['headers']): boolean {\n const bag = asMutableHeaders(headers)\n if (!bag) return false\n if (typeof bag.has === 'function') return bag.has('Authorization')\n return Object.keys(bag).some((key) => key.toLowerCase() === 'authorization')\n}\n\nfunction setAuthorizationHeader(headers: AxiosRequestConfig['headers'], value: string): void {\n const bag = asMutableHeaders(headers)\n if (!bag) return\n if (typeof bag.set === 'function') {\n bag.set('Authorization', value)\n return\n }\n bag.Authorization = value\n}\n\nfunction deleteAuthorizationHeader(headers: AxiosRequestConfig['headers']): void {\n const bag = asMutableHeaders(headers)\n if (!bag) return\n if (typeof bag.delete === 'function') {\n bag.delete('Authorization')\n return\n }\n for (const key of Object.keys(bag)) {\n if (key.toLowerCase() === 'authorization') delete bag[key]\n }\n}\n\nfunction getAuthorizationHeader(headers: AxiosRequestConfig['headers']): unknown {\n const bag = asMutableHeaders(headers) as (MutableHeaders & { get?: (header: string) => unknown }) | null\n if (!bag) return undefined\n if (typeof bag.get === 'function') return bag.get('Authorization')\n const key = Object.keys(bag).find((candidate) => candidate.toLowerCase() === 'authorization')\n return key ? bag[key] : undefined\n}\n\n/** Raised when an authenticated SDK request is rejected because the login is no longer valid. */\nexport class AuthenticationRequiredError extends Error {\n readonly code = 'AUTHENTICATION_REQUIRED'\n readonly status = 401\n\n constructor(readonly cause?: unknown) {\n super('Authentication required. Please sign in again.')\n this.name = 'AuthenticationRequiredError'\n }\n}\n\n/** @internal Convert a rejected authenticated request into the SDK's public auth error. */\nexport function authenticationRequiredError(\n cause: unknown,\n rejectedToken?: string,\n): AuthenticationRequiredError {\n const currentAuthStore = useAuthStore()\n if (rejectedToken && currentAuthStore.token === rejectedToken) {\n currentAuthStore.clearToken()\n }\n return new AuthenticationRequiredError(cause)\n}\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\n/** Axios-based API client that injects the plugin base URL and JWT auth header on every request. */\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Ensure auth store is initialized (reads token from localStorage)\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n\n // Attach auth interceptor only once (reads token dynamically, not from closure)\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n const request = config as MintInternalAxiosRequestConfig\n if (request._mintSkipAuth) {\n deleteAuthorizationHeader(request.headers)\n return request\n }\n\n const currentAuthStore = useAuthStore()\n if (currentAuthStore.token && config.headers && !hasAuthorizationHeader(config.headers)) {\n setAuthorizationHeader(config.headers, `Bearer ${currentAuthStore.token}`)\n }\n if (getAuthorizationHeader(config.headers) === `Bearer ${currentAuthStore.token}`) {\n request._mintAuthToken = currentAuthStore.token ?? undefined\n }\n return config\n })\n apiClient.interceptors.response.use(\n (response) => response,\n (error: unknown) => {\n if (axios.isAxiosError(error) && error.response?.status === 401) {\n const request = error.config as MintInternalAxiosRequestConfig | undefined\n if (request?._mintSkipAuth !== true) {\n return Promise.reject(authenticationRequiredError(error, request?._mintAuthToken))\n }\n }\n return Promise.reject(error)\n },\n )\n interceptorAttached = true\n }\n\n function getBaseUrl(): string {\n return options.baseUrl ?? settingsStore.getApiBaseUrl()\n }\n\n function normalizeUrl(url: string): string {\n return normalizeRequestUrl(getBaseUrl(), url)\n }\n\n // Build per-request config that applies this caller's options\n function requestConfig(config?: AxiosRequestConfig): MintAxiosRequestConfig {\n const base: MintAxiosRequestConfig = {\n baseURL: getBaseUrl(),\n timeout: options.timeout ?? settingsStore.requestTimeout,\n ...config,\n }\n // Strip auth header if explicitly disabled\n if (options.withAuth === false) {\n base._mintSkipAuth = true\n deleteAuthorizationHeader(base.headers)\n }\n return base\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(normalizeUrl(url), requestConfig(config))\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(normalizeUrl(url), data, requestConfig(config))\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(normalizeUrl(url), requestConfig(config))\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(normalizeUrl(url), formData, requestConfig({\n // Let Axios set Content-Type with the correct multipart boundary\n headers: { 'Content-Type': undefined },\n }))\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(normalizeUrl(url), requestConfig({ responseType: 'blob' }))\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n // Revoke after a short delay to allow download to start;\n // return empty string since the URL will be invalidated\n setTimeout(() => URL.revokeObjectURL(blobUrl), 100)\n return ''\n }\n\n // Caller is responsible for revoking when no filename is provided\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = getBaseUrl()\n return joinUrlPath(baseUrl, normalizeRequestUrl(baseUrl, path))\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return joinUrlPath(settingsStore.getWsBaseUrl(), path)\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { c as formatExperimentDate, f as BaseModal_default, m as BaseButton_default, p as useEventListener } from "./experiment-utils-CLnzPPq5.js";
|
|
2
2
|
import { a as BaseSelect_default, i as BasePill_default, n as Skeleton_default, o as normalizeOptionInput, r as useRequestSyncState, s as BaseInput_default, t as EmptyState_default } from "./EmptyState-p90quq8Z.js";
|
|
3
3
|
import { t as useDropdownState } from "./useDropdownState-DSb3BC3N.js";
|
|
4
|
-
import { r as useApi } from "./useApi-
|
|
4
|
+
import { r as useApi } from "./useApi-J6zFkHmn.js";
|
|
5
5
|
import { hasAllPermissions } from "./permissions.js";
|
|
6
|
-
import { Fragment, Teleport, Transition, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, createVNode, defineComponent, isRef, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, openBlock, reactive, ref, renderList, renderSlot, shallowRef, toDisplayString, toValue, unref, useSlots, vModelText, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
|
|
6
|
+
import { Fragment, Teleport, Transition, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, createVNode, defineComponent, getCurrentInstance, getCurrentScope, isRef, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, openBlock, reactive, ref, renderList, renderSlot, shallowRef, toDisplayString, toValue, unref, useSlots, vModelText, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
|
|
7
7
|
//#region src/components/BaseTextarea.vue?vue&type=script&setup=true&lang.ts
|
|
8
8
|
var _hoisted_1$21 = [
|
|
9
9
|
"value",
|
|
@@ -3816,6 +3816,206 @@ var DateTimePicker_default = /* @__PURE__ */ defineComponent({
|
|
|
3816
3816
|
}
|
|
3817
3817
|
});
|
|
3818
3818
|
//#endregion
|
|
3819
|
+
//#region src/composables/useEventStream.ts
|
|
3820
|
+
var EventStreamResponseError = class extends Error {
|
|
3821
|
+
constructor(response, label) {
|
|
3822
|
+
super(`${label} failed with HTTP ${response.status}`);
|
|
3823
|
+
this.response = response;
|
|
3824
|
+
this.name = "EventStreamResponseError";
|
|
3825
|
+
}
|
|
3826
|
+
};
|
|
3827
|
+
/** Parse one complete SSE block without owning connection policy. */
|
|
3828
|
+
function parseEventStreamBlock(block, parseJson) {
|
|
3829
|
+
let event = "message";
|
|
3830
|
+
let id;
|
|
3831
|
+
let retry;
|
|
3832
|
+
const dataLines = [];
|
|
3833
|
+
for (const line of block.split(/\r\n|\r|\n/)) {
|
|
3834
|
+
if (!line || line.startsWith(":")) continue;
|
|
3835
|
+
const separator = line.indexOf(":");
|
|
3836
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
3837
|
+
const value = separator === -1 ? "" : line.slice(separator + 1).replace(/^ /, "");
|
|
3838
|
+
if (field === "event") event = value || "message";
|
|
3839
|
+
if (field === "data") dataLines.push(value);
|
|
3840
|
+
if (field === "id" && !value.includes("\0")) id = value;
|
|
3841
|
+
if (field === "retry" && /^\d+$/.test(value)) retry = Number(value);
|
|
3842
|
+
}
|
|
3843
|
+
if (!dataLines.length && id === void 0 && retry === void 0) return null;
|
|
3844
|
+
const data = dataLines.join("\n");
|
|
3845
|
+
return {
|
|
3846
|
+
message: dataLines.length ? {
|
|
3847
|
+
event,
|
|
3848
|
+
data: parseJson && data ? JSON.parse(data) : data,
|
|
3849
|
+
id,
|
|
3850
|
+
retry,
|
|
3851
|
+
raw: block
|
|
3852
|
+
} : void 0,
|
|
3853
|
+
id,
|
|
3854
|
+
retry
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
/** Shared fetch-based SSE lifecycle with cancellation and reconnect safety. */
|
|
3858
|
+
function useEventStream(url, options = {}) {
|
|
3859
|
+
const isConnected = ref(false);
|
|
3860
|
+
const isConnecting = ref(false);
|
|
3861
|
+
const error = ref(null);
|
|
3862
|
+
const lastMessage = shallowRef(null);
|
|
3863
|
+
const lastEventId = ref(null);
|
|
3864
|
+
let controller = null;
|
|
3865
|
+
let reader = null;
|
|
3866
|
+
let reconnectTimer = null;
|
|
3867
|
+
let reconnectDelayMs = options.reconnectDelayMs ?? 2e3;
|
|
3868
|
+
let stopped = true;
|
|
3869
|
+
let generation = 0;
|
|
3870
|
+
function clearReconnectTimer() {
|
|
3871
|
+
if (reconnectTimer === null) return;
|
|
3872
|
+
clearTimeout(reconnectTimer);
|
|
3873
|
+
reconnectTimer = null;
|
|
3874
|
+
}
|
|
3875
|
+
function ownsConnection(connectionGeneration, connectionController) {
|
|
3876
|
+
return !stopped && generation === connectionGeneration && controller === connectionController;
|
|
3877
|
+
}
|
|
3878
|
+
function scheduleReconnect(connectionGeneration, cause) {
|
|
3879
|
+
if (stopped || generation !== connectionGeneration) return;
|
|
3880
|
+
if (options.reconnect === false || options.shouldReconnect?.(cause) === false) {
|
|
3881
|
+
stopped = true;
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
clearReconnectTimer();
|
|
3885
|
+
reconnectTimer = setTimeout(() => {
|
|
3886
|
+
reconnectTimer = null;
|
|
3887
|
+
if (stopped || generation !== connectionGeneration) return;
|
|
3888
|
+
connect(connectionGeneration);
|
|
3889
|
+
}, reconnectDelayMs);
|
|
3890
|
+
}
|
|
3891
|
+
function dispatchBlock(block, connectionGeneration, connectionController) {
|
|
3892
|
+
const parsed = parseEventStreamBlock(block, options.parseJson ?? false);
|
|
3893
|
+
if (!parsed || !ownsConnection(connectionGeneration, connectionController)) return;
|
|
3894
|
+
if (parsed.id !== void 0) {
|
|
3895
|
+
lastEventId.value = parsed.id;
|
|
3896
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3897
|
+
}
|
|
3898
|
+
if (parsed.retry !== void 0) reconnectDelayMs = parsed.retry;
|
|
3899
|
+
const message = parsed.message;
|
|
3900
|
+
if (!message || !ownsConnection(connectionGeneration, connectionController)) return;
|
|
3901
|
+
lastMessage.value = message;
|
|
3902
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3903
|
+
options.onMessage?.(message);
|
|
3904
|
+
}
|
|
3905
|
+
async function consume(body, connectionGeneration, connectionController) {
|
|
3906
|
+
const connectionReader = body.getReader();
|
|
3907
|
+
reader = connectionReader;
|
|
3908
|
+
const decoder = new TextDecoder();
|
|
3909
|
+
let buffer = "";
|
|
3910
|
+
try {
|
|
3911
|
+
for (;;) {
|
|
3912
|
+
const { value, done } = await connectionReader.read();
|
|
3913
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3914
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
3915
|
+
let boundary = /(?:\r\n|\r|\n){2}/.exec(buffer);
|
|
3916
|
+
while (boundary?.index !== void 0) {
|
|
3917
|
+
const block = buffer.slice(0, boundary.index);
|
|
3918
|
+
buffer = buffer.slice(boundary.index + boundary[0].length);
|
|
3919
|
+
dispatchBlock(block, connectionGeneration, connectionController);
|
|
3920
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3921
|
+
boundary = /(?:\r\n|\r|\n){2}/.exec(buffer);
|
|
3922
|
+
}
|
|
3923
|
+
if (!done) continue;
|
|
3924
|
+
if (buffer) dispatchBlock(buffer, connectionGeneration, connectionController);
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
} finally {
|
|
3928
|
+
if (reader === connectionReader) reader = null;
|
|
3929
|
+
connectionReader.releaseLock();
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
async function connect(connectionGeneration) {
|
|
3933
|
+
if (stopped || generation !== connectionGeneration) return;
|
|
3934
|
+
controller?.abort();
|
|
3935
|
+
const connectionController = new AbortController();
|
|
3936
|
+
controller = connectionController;
|
|
3937
|
+
isConnecting.value = true;
|
|
3938
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3939
|
+
error.value = null;
|
|
3940
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3941
|
+
let cause;
|
|
3942
|
+
try {
|
|
3943
|
+
const headers = new Headers(options.headers);
|
|
3944
|
+
headers.set("Accept", "text/event-stream");
|
|
3945
|
+
if (lastEventId.value && !headers.has("Last-Event-ID")) headers.set("Last-Event-ID", lastEventId.value);
|
|
3946
|
+
const transport = options.transport ?? globalThis.fetch?.bind(globalThis);
|
|
3947
|
+
if (!transport) throw new Error("[MINT SDK] fetch is required for event streams.");
|
|
3948
|
+
const response = await transport(typeof url === "function" ? url() : url, {
|
|
3949
|
+
method: "GET",
|
|
3950
|
+
headers,
|
|
3951
|
+
credentials: options.credentials ?? "same-origin",
|
|
3952
|
+
signal: connectionController.signal
|
|
3953
|
+
});
|
|
3954
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3955
|
+
if (!response.ok) throw new EventStreamResponseError(response, options.errorLabel ?? "Event stream");
|
|
3956
|
+
if (!response.body) throw new Error(`${options.errorLabel ?? "Event stream"} response did not include a readable body.`);
|
|
3957
|
+
isConnected.value = true;
|
|
3958
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3959
|
+
isConnecting.value = false;
|
|
3960
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3961
|
+
options.onOpen?.();
|
|
3962
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3963
|
+
await consume(response.body, connectionGeneration, connectionController);
|
|
3964
|
+
} catch (errorCause) {
|
|
3965
|
+
cause = errorCause;
|
|
3966
|
+
if (ownsConnection(connectionGeneration, connectionController) && !isAbortError(errorCause)) {
|
|
3967
|
+
error.value = errorCause instanceof Error ? errorCause.message : `${options.errorLabel ?? "Event stream"} failed`;
|
|
3968
|
+
if (ownsConnection(connectionGeneration, connectionController)) options.onError?.(errorCause);
|
|
3969
|
+
}
|
|
3970
|
+
} finally {
|
|
3971
|
+
if (ownsConnection(connectionGeneration, connectionController)) {
|
|
3972
|
+
isConnecting.value = false;
|
|
3973
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3974
|
+
isConnected.value = false;
|
|
3975
|
+
if (!ownsConnection(connectionGeneration, connectionController)) return;
|
|
3976
|
+
controller = null;
|
|
3977
|
+
options.onClose?.(cause);
|
|
3978
|
+
if (!stopped && generation === connectionGeneration) scheduleReconnect(connectionGeneration, cause);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
function start() {
|
|
3983
|
+
if (!stopped) return;
|
|
3984
|
+
stopped = false;
|
|
3985
|
+
clearReconnectTimer();
|
|
3986
|
+
generation += 1;
|
|
3987
|
+
connect(generation);
|
|
3988
|
+
}
|
|
3989
|
+
function stop() {
|
|
3990
|
+
if (stopped) return;
|
|
3991
|
+
stopped = true;
|
|
3992
|
+
generation += 1;
|
|
3993
|
+
clearReconnectTimer();
|
|
3994
|
+
controller?.abort();
|
|
3995
|
+
controller = null;
|
|
3996
|
+
if (reader) reader.cancel().catch(() => void 0);
|
|
3997
|
+
reader = null;
|
|
3998
|
+
isConnecting.value = false;
|
|
3999
|
+
isConnected.value = false;
|
|
4000
|
+
}
|
|
4001
|
+
const currentInstance = getCurrentInstance();
|
|
4002
|
+
if (currentInstance && options.immediate !== false && typeof window === "undefined") onMounted(start);
|
|
4003
|
+
if (getCurrentScope()) onScopeDispose(stop);
|
|
4004
|
+
if (options.immediate !== false && (typeof window !== "undefined" || !currentInstance)) start();
|
|
4005
|
+
return {
|
|
4006
|
+
isConnected,
|
|
4007
|
+
isConnecting,
|
|
4008
|
+
error,
|
|
4009
|
+
lastMessage,
|
|
4010
|
+
lastEventId,
|
|
4011
|
+
start,
|
|
4012
|
+
stop
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
function isAbortError(cause) {
|
|
4016
|
+
return cause instanceof DOMException && cause.name === "AbortError";
|
|
4017
|
+
}
|
|
4018
|
+
//#endregion
|
|
3819
4019
|
//#region src/jobs.ts
|
|
3820
4020
|
var ACTIVE_JOB_STATUSES = new Set([
|
|
3821
4021
|
"awaiting_input",
|
|
@@ -3945,10 +4145,6 @@ function createPluginJobs(options, manifestPath) {
|
|
|
3945
4145
|
const error = shallowRef(null);
|
|
3946
4146
|
const status = shallowRef("idle");
|
|
3947
4147
|
let pollTimer;
|
|
3948
|
-
let streamReconnectTimer;
|
|
3949
|
-
let streamController;
|
|
3950
|
-
let streamConnected = false;
|
|
3951
|
-
let streamConnecting = false;
|
|
3952
4148
|
let disposed = false;
|
|
3953
4149
|
let paused = false;
|
|
3954
4150
|
let initialized = false;
|
|
@@ -3962,6 +4158,17 @@ function createPluginJobs(options, manifestPath) {
|
|
|
3962
4158
|
const resultRetryAttempts = /* @__PURE__ */ new Map();
|
|
3963
4159
|
const resultRetryTimers = /* @__PURE__ */ new Map();
|
|
3964
4160
|
const downloadRefreshTimers = /* @__PURE__ */ new Map();
|
|
4161
|
+
const jobStream = useEventStream(`${baseUrl}/jobs/events`, {
|
|
4162
|
+
transport,
|
|
4163
|
+
immediate: false,
|
|
4164
|
+
reconnectDelayMs: streamReconnectDelay,
|
|
4165
|
+
parseJson: true,
|
|
4166
|
+
errorLabel: "Job stream",
|
|
4167
|
+
onOpen: clearPoll,
|
|
4168
|
+
onMessage: applyJobStreamMessage,
|
|
4169
|
+
onError: schedulePollForActiveJobs,
|
|
4170
|
+
onClose: schedulePollForActiveJobs
|
|
4171
|
+
});
|
|
3965
4172
|
const latestResult = computed(() => {
|
|
3966
4173
|
const job = currentCompletedJob();
|
|
3967
4174
|
return job ? resultForJob(job.job_id) : null;
|
|
@@ -3985,7 +4192,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
3985
4192
|
if (disposed) return;
|
|
3986
4193
|
initialized = true;
|
|
3987
4194
|
status.value = paused ? "paused" : "ready";
|
|
3988
|
-
if (streamEnabled && !paused)
|
|
4195
|
+
if (streamEnabled && !paused) jobStream.start();
|
|
3989
4196
|
})().catch((cause) => {
|
|
3990
4197
|
if (!disposed) {
|
|
3991
4198
|
error.value = errorMessage(cause, "Failed to initialize generated analysis");
|
|
@@ -4030,7 +4237,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4030
4237
|
jobInput = await submitToSession(session.value.id, clientRequestId, await stagePathInputs(inputs, session.value.id, definitionId));
|
|
4031
4238
|
}
|
|
4032
4239
|
const job = upsertJob(jobInput, definitionId);
|
|
4033
|
-
if (!
|
|
4240
|
+
if (!jobStream.isConnected.value) schedulePoll();
|
|
4034
4241
|
return job;
|
|
4035
4242
|
} catch (cause) {
|
|
4036
4243
|
error.value = errorMessage(cause, "Failed to submit analysis");
|
|
@@ -4131,7 +4338,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4131
4338
|
}
|
|
4132
4339
|
replaceJobs(payload.jobs);
|
|
4133
4340
|
error.value = null;
|
|
4134
|
-
if (!paused && !
|
|
4341
|
+
if (!paused && !jobStream.isConnected.value && jobs.value.some((job) => ACTIVE_STATUSES.has(job.status))) schedulePoll();
|
|
4135
4342
|
return jobs.value;
|
|
4136
4343
|
}).finally(() => {
|
|
4137
4344
|
if (refreshPromise === pending) refreshPromise = void 0;
|
|
@@ -4185,12 +4392,12 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4185
4392
|
waiting.add(waiter);
|
|
4186
4393
|
jobWaiters.set(jobId, waiting);
|
|
4187
4394
|
if (!paused) {
|
|
4188
|
-
if (streamEnabled)
|
|
4189
|
-
if (!
|
|
4395
|
+
if (streamEnabled) jobStream.start();
|
|
4396
|
+
if (!jobStream.isConnected.value) schedulePoll();
|
|
4190
4397
|
}
|
|
4191
4398
|
getJob(jobId, { signal: options.signal }).catch((cause) => {
|
|
4192
4399
|
if (current && isRetryableJobLookupError(cause)) {
|
|
4193
|
-
if (!
|
|
4400
|
+
if (!jobStream.isConnected.value) schedulePoll();
|
|
4194
4401
|
return;
|
|
4195
4402
|
}
|
|
4196
4403
|
removeJobWaiter(jobId, waiter);
|
|
@@ -4418,7 +4625,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4418
4625
|
resultRetryAttempts.delete(jobId);
|
|
4419
4626
|
}
|
|
4420
4627
|
function schedulePoll() {
|
|
4421
|
-
if (disposed || paused ||
|
|
4628
|
+
if (disposed || paused || jobStream.isConnected.value || pollTimer !== void 0) return;
|
|
4422
4629
|
pollTimer = setTimeout(async () => {
|
|
4423
4630
|
pollTimer = void 0;
|
|
4424
4631
|
if (disposed) return;
|
|
@@ -4430,76 +4637,21 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4430
4637
|
}
|
|
4431
4638
|
}, pollInterval);
|
|
4432
4639
|
}
|
|
4433
|
-
|
|
4434
|
-
if (
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
try {
|
|
4438
|
-
const response = await transport(`${baseUrl}/jobs/events`, {
|
|
4439
|
-
method: "GET",
|
|
4440
|
-
headers: { Accept: "text/event-stream" },
|
|
4441
|
-
signal: streamController.signal
|
|
4442
|
-
});
|
|
4443
|
-
if (!response.ok) throw new Error(`Job stream failed (${response.status})`);
|
|
4444
|
-
if (!response.body) throw new Error("Job stream response has no readable body");
|
|
4445
|
-
streamConnected = true;
|
|
4446
|
-
if (pollTimer !== void 0) {
|
|
4447
|
-
clearTimeout(pollTimer);
|
|
4448
|
-
pollTimer = void 0;
|
|
4449
|
-
}
|
|
4450
|
-
await consumeJobStream(response.body);
|
|
4451
|
-
} catch (cause) {
|
|
4452
|
-
if (!isAbortError(cause) && !disposed) {
|
|
4453
|
-
if (jobs.value.some((job) => ACTIVE_STATUSES.has(job.status))) schedulePoll();
|
|
4454
|
-
}
|
|
4455
|
-
} finally {
|
|
4456
|
-
streamConnected = false;
|
|
4457
|
-
streamConnecting = false;
|
|
4458
|
-
streamController = void 0;
|
|
4459
|
-
if (!disposed && !paused) scheduleStreamReconnect();
|
|
4460
|
-
}
|
|
4640
|
+
function clearPoll() {
|
|
4641
|
+
if (pollTimer === void 0) return;
|
|
4642
|
+
clearTimeout(pollTimer);
|
|
4643
|
+
pollTimer = void 0;
|
|
4461
4644
|
}
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
const decoder = new TextDecoder();
|
|
4465
|
-
let buffer = "";
|
|
4466
|
-
try {
|
|
4467
|
-
while (!disposed) {
|
|
4468
|
-
const { done, value } = await reader.read();
|
|
4469
|
-
buffer += decoder.decode(value, { stream: !done });
|
|
4470
|
-
const blocks = buffer.split(/\r?\n\r?\n/);
|
|
4471
|
-
buffer = blocks.pop() ?? "";
|
|
4472
|
-
for (const block of blocks) applyJobStreamEvent(block);
|
|
4473
|
-
if (done) {
|
|
4474
|
-
if (buffer.trim()) applyJobStreamEvent(buffer);
|
|
4475
|
-
return;
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
|
-
} finally {
|
|
4479
|
-
reader.releaseLock();
|
|
4480
|
-
}
|
|
4645
|
+
function schedulePollForActiveJobs() {
|
|
4646
|
+
if (jobs.value.some((job) => ACTIVE_STATUSES.has(job.status))) schedulePoll();
|
|
4481
4647
|
}
|
|
4482
|
-
function
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
4488
|
-
if (line.startsWith("data:")) data.push(line.slice(5).trimStart());
|
|
4489
|
-
}
|
|
4490
|
-
if (!data.length) return;
|
|
4491
|
-
const payload = JSON.parse(data.join("\n"));
|
|
4492
|
-
if (event === "snapshot" && "jobs" in payload) replaceJobs(payload.jobs);
|
|
4493
|
-
else if (event === "job" || event === "terminal") upsertJob(payload);
|
|
4648
|
+
function applyJobStreamMessage(message) {
|
|
4649
|
+
if (message.event === "snapshot" && "jobs" in message.data) {
|
|
4650
|
+
const payload = message.data;
|
|
4651
|
+
replaceJobs(payload.jobs);
|
|
4652
|
+
} else if (message.event === "job" || message.event === "terminal") upsertJob(message.data);
|
|
4494
4653
|
error.value = null;
|
|
4495
4654
|
}
|
|
4496
|
-
function scheduleStreamReconnect() {
|
|
4497
|
-
if (disposed || paused || streamReconnectTimer !== void 0) return;
|
|
4498
|
-
streamReconnectTimer = setTimeout(() => {
|
|
4499
|
-
streamReconnectTimer = void 0;
|
|
4500
|
-
connectJobStream();
|
|
4501
|
-
}, streamReconnectDelay);
|
|
4502
|
-
}
|
|
4503
4655
|
async function cancelJob(job) {
|
|
4504
4656
|
const jobId = typeof job === "string" ? job : job.job_id;
|
|
4505
4657
|
const revision = beginJobOperation(jobId);
|
|
@@ -4524,23 +4676,14 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4524
4676
|
if (payload.deleted > 0) await refreshJobs();
|
|
4525
4677
|
return payload.deleted;
|
|
4526
4678
|
}
|
|
4527
|
-
const jobsAdapter = {
|
|
4528
|
-
listJobs: async () => ({ jobs: await refreshJobs() }),
|
|
4529
|
-
cancelJob: async (job) => cancelJob(job),
|
|
4530
|
-
deleteJob: async (job) => deleteJob(job),
|
|
4531
|
-
clearFinishedJobs: async () => clearFinishedJobs()
|
|
4532
|
-
};
|
|
4533
4679
|
function pause() {
|
|
4534
4680
|
if (disposed || paused) return;
|
|
4535
4681
|
paused = true;
|
|
4536
4682
|
status.value = "paused";
|
|
4537
|
-
|
|
4538
|
-
if (streamReconnectTimer !== void 0) clearTimeout(streamReconnectTimer);
|
|
4539
|
-
pollTimer = void 0;
|
|
4540
|
-
streamReconnectTimer = void 0;
|
|
4683
|
+
clearPoll();
|
|
4541
4684
|
for (const jobId of resultRetryTimers.keys()) clearResultRetry(jobId);
|
|
4542
4685
|
for (const jobId of downloadRefreshTimers.keys()) clearManagedDownloadRefresh(jobId);
|
|
4543
|
-
|
|
4686
|
+
jobStream.stop();
|
|
4544
4687
|
}
|
|
4545
4688
|
function resume() {
|
|
4546
4689
|
if (disposed || !paused) return;
|
|
@@ -4548,7 +4691,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4548
4691
|
status.value = initialized ? "ready" : "idle";
|
|
4549
4692
|
if (!initialized) return;
|
|
4550
4693
|
for (const [jobId, result] of Object.entries(resultCache.value)) scheduleManagedDownloadRefresh(jobId, managedDownloadRefreshDelay(result));
|
|
4551
|
-
if (streamEnabled)
|
|
4694
|
+
if (streamEnabled) jobStream.start();
|
|
4552
4695
|
else if (jobs.value.some((job) => ACTIVE_STATUSES.has(job.status))) schedulePoll();
|
|
4553
4696
|
}
|
|
4554
4697
|
function dispose() {
|
|
@@ -4556,10 +4699,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4556
4699
|
disposed = true;
|
|
4557
4700
|
paused = true;
|
|
4558
4701
|
status.value = "disposed";
|
|
4559
|
-
|
|
4560
|
-
if (streamReconnectTimer !== void 0) clearTimeout(streamReconnectTimer);
|
|
4561
|
-
pollTimer = void 0;
|
|
4562
|
-
streamReconnectTimer = void 0;
|
|
4702
|
+
clearPoll();
|
|
4563
4703
|
for (const timer of resultRetryTimers.values()) clearTimeout(timer);
|
|
4564
4704
|
resultRetryTimers.clear();
|
|
4565
4705
|
resultRetryAttempts.clear();
|
|
@@ -4569,7 +4709,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4569
4709
|
for (const jobId of jobWaiters.keys()) rejectJobWaiters(jobId, cause);
|
|
4570
4710
|
jobRevisions.clear();
|
|
4571
4711
|
pendingJobOperations.clear();
|
|
4572
|
-
|
|
4712
|
+
jobStream.stop();
|
|
4573
4713
|
}
|
|
4574
4714
|
async function request(path, init) {
|
|
4575
4715
|
const response = await transport(`${baseUrl}${path}`, {
|
|
@@ -4616,8 +4756,7 @@ function createPluginJobs(options, manifestPath) {
|
|
|
4616
4756
|
clearFinishedJobs,
|
|
4617
4757
|
pause,
|
|
4618
4758
|
resume,
|
|
4619
|
-
dispose
|
|
4620
|
-
jobsAdapter
|
|
4759
|
+
dispose
|
|
4621
4760
|
};
|
|
4622
4761
|
}
|
|
4623
4762
|
function jobIdFromReference(job) {
|
|
@@ -4685,9 +4824,6 @@ function createGeneratedSessionStorage(candidate) {
|
|
|
4685
4824
|
}
|
|
4686
4825
|
};
|
|
4687
4826
|
}
|
|
4688
|
-
function isAbortError(error) {
|
|
4689
|
-
return error instanceof DOMException && error.name === "AbortError";
|
|
4690
|
-
}
|
|
4691
4827
|
function isSessionNotFoundError(error) {
|
|
4692
4828
|
return error instanceof GeneratedAnalysisRequestError && error.status === 404 && error.detail === SESSION_NOT_FOUND_DETAIL;
|
|
4693
4829
|
}
|
|
@@ -7713,6 +7849,6 @@ function useControlSchema(controls, options = {}) {
|
|
|
7713
7849
|
};
|
|
7714
7850
|
}
|
|
7715
7851
|
//#endregion
|
|
7716
|
-
export { resolveJobCapabilities as $, useConcentrationUnits as A,
|
|
7852
|
+
export { resolveJobCapabilities as $, useConcentrationUnits as A, BaseToggle_default as At, currentExperimentFromContext as B, controlsToViewIds as C, useTimeUtils as Ct, getTypeDefault as D, useSelectionLimit as Dt, getFieldRegistryEntry as E, useListSelection as Et, ATOMIC_WEIGHTS as F, offset as Ft, createGeneratedAnalysisTransport as G, parseExperimentId as H, useChemicalFormula as I, shift as It, isActiveJobStatus as J, useGeneratedAnalysis as K, GeneratedPathInput_default as L, BaseTextarea_default as Lt, SequenceInput_default as M, Tooltip_default as Mt, useSequenceUtils as N, computePosition as Nt, ConcentrationInput_default as O, BaseSlider_default as Ot, FormulaInput_default as P, flip as Pt, normalizeJobState as Q, ArtifactSelectorModal_default as R, controlsToTopBarSettingsConfig as S, toMinutes as St, getDefaultControlView as T, MultiSelect_default as Tt, resolveCurrentExperimentId as U, getInjectedPlatformContext as V, resolveCurrentPluginId as W, jobStatusLabel as X, isTerminalJobStatus as Y, normalizeJobPercent as Z, controlsToFormSchema as _, generateTimeSlots as _t, defineDoseCalculatorControlProps as a, NumberInput_default as at, controlsToSettingsSchema as b, rangesOverlap as bt, defineWellPlateDoseComponentBindings as c, addMinutes as ct, defineControlModel as d, findAvailableSlots as dt, useEventStream as et, resolveControlModel as f, findNearestTimeSlotIndex as ft, mergeControlWorkspaceOptions as g, fromMinutes as gt, controlValuesToComponentProps as h, formatTimeSlot as ht, useControlSchema as i, FileUploader_default as it, MoleculeInput_default as j, BaseCheckbox_default as jt, UnitInput_default as k, BaseRadioGroup_default as kt, defineWellPlateDoseControlProps as l, compareTime as lt, controlValuesToComponentBindingsById as m, formatTime as mt, defineControls as n, _plugin_vue_export_helper_default as nt, defineDoseDesignControlModel as o, TagsInput_default as ot, controlValuesToComponentBindings as p, formatDuration as pt, usePluginJobs as q, getControlDefaults as r, AlertBox_default as rt, defineWellPlateControlProps as s, TimePicker_default as st, defineControlComponentBindings as t, DateTimePicker_default as tt, useControlWorkspace as u, durationMinutes as ut, controlsToSectionFormSchema as v, isTimeInRange as vt, controlsToViewItems as w, DatePicker_default as wt, controlsToSidebarPanels as x, snapToSlot as xt, controlsToSectionFormSchemas as y, parseTime as yt, useAnalysisArtifacts as z };
|
|
7717
7853
|
|
|
7718
|
-
//# sourceMappingURL=useControlSchema-
|
|
7854
|
+
//# sourceMappingURL=useControlSchema-BDx2Olha.js.map
|