From c8230bcce0de69bb8da828a0ef858b118539b673 Mon Sep 17 00:00:00 2001 From: mschwab Date: Fri, 7 Aug 2026 15:04:15 -0700 Subject: [PATCH 1/8] refactor(studio): move shared UI components into @nemo/common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugin bundles reach Studio's shared UI through the curated barrel in common/src/plugin.ts, but seven of the components Iron Swarm needs lived in web/packages/studio and were therefore unreachable. Move them into @nemo/common and export them, so the next plugin gets them for free rather than reimplementing a table, an accordion, or a file picker. Moved with git mv so history follows: AccessibleTitle, AccordionSection, ExpandableMessage, FileTag, InputErrorText, FileUpload (+util), QuickActionsMenuRoot, and api/common/utils, along with their tests. Studio's ~100 importers now point at @nemo/common directly; no shims are left behind. common gains react-dropzone, which came with FileUpload. Also adds JOB_POLLING_INTERVAL_MS to the barrel — the one name the audit of Iron Swarm's usage found missing. Three candidates were deliberately left out of the barrel, and the measurement is why. Exporting CreateSecretModal, fetchAgentsForSelect, and getErrorMessage pulled the generated SDK client into the shared vendor bundle, taking it from 241 kB to 553 kB and dragging in axios and oidc-client-ts. That is not just weight: plugins are meant to receive the SDK on host.sdk precisely so there is one axios instance carrying Studio's OIDC interceptor, and a second copy in common.js would have issued unauthenticated requests. The bundle is back to 314 kB with axios, oidc-client-ts and zod absent; the remainder is react-dropzone, which FileUpload genuinely needs. A plugin that wants those three composes them from the primitives the barrel does export, wired to host.sdk. Signed-off-by: mschwab --- web/packages/common/package.json | 1 + .../{studio => common}/src/api/common/utils.test.ts | 2 +- .../{studio => common}/src/api/common/utils.ts | 0 .../src/components/AccessibleTitle/index.test.tsx | 2 +- .../src/components/AccessibleTitle/index.tsx | 0 .../src/components/AccordionSection/index.tsx | 0 .../src/components/ExpandableMessage/index.test.tsx | 2 +- .../src/components/ExpandableMessage/index.tsx | 0 .../src/components/FileTag/index.tsx | 0 .../src/components/FileUpload/index.tsx | 8 ++++---- .../src/components/FileUpload/util.ts | 0 .../src/components/InputErrorText/index.tsx | 0 .../QuickActionsMenuRoot/index.test.tsx | 2 +- .../QuickActionsMenu/QuickActionsMenuRoot/index.tsx | 0 web/packages/common/src/plugin.ts | 13 +++++++++++++ .../src/api/guardrail-checks/guardrailChecks.ts | 2 +- .../components/AnonymizerJobActionsMenu/index.tsx | 8 ++++---- .../studio/src/components/CancelJobButton/index.tsx | 2 +- .../src/components/CustomizationFileTag/index.tsx | 2 +- .../components/CustomizationFileUpload/index.tsx | 2 +- .../components/DataDesignerJobActionsMenu/index.tsx | 8 ++++---- .../src/components/DatasetFileUpload/index.tsx | 4 ++-- .../studio/src/components/ErrorPanel/index.tsx | 2 +- .../FilesTable/DirectoryQuickActions/index.tsx | 8 ++++---- .../FilesTable/FileQuickActions/index.tsx | 8 ++++---- .../src/components/FilesetCreateModal/index.tsx | 2 +- .../IntakeComponents/AnnotationsPanel.tsx | 2 +- .../IntakeComponents/useSpanAnnotationActions.ts | 2 +- .../components/IntakeDetail/SessionDetailView.tsx | 2 +- .../components/IntakeDetail/TraceSpanAccordions.tsx | 2 +- .../src/components/IntakeLists/IntakeSpansTable.tsx | 2 +- .../components/IntakeLists/IntakeTracesTable.tsx | 2 +- .../src/components/ModelActionsMenu/index.tsx | 2 +- .../src/components/NewCustomizationForm/index.tsx | 4 ++-- .../InContextLearningSection/index.tsx | 2 +- .../InferenceParametersSection/index.tsx | 2 +- .../PromptTemplateSection/index.tsx | 2 +- .../PromptTuningForm/ToolsSection/index.tsx | 2 +- .../QuickActionsMenuDefault/index.tsx | 2 +- .../dataViews/AgentEvaluationsDataView/index.tsx | 2 +- .../dataViews/DeploymentsDataView/index.tsx | 2 +- .../dataViews/ExperimentDataView/index.tsx | 2 +- .../dataViews/GuardrailsDataView/index.tsx | 2 +- .../dataViews/InferenceProvidersDataView/index.tsx | 2 +- .../src/components/dataViews/JobsDataView/index.tsx | 2 +- .../dataViews/SafeSynthesizerJobsDataView/index.tsx | 2 +- .../components/dataViews/SecretsDataView/index.tsx | 2 +- .../dataViews/VirtualModelsDataView/index.tsx | 2 +- .../evaluation/Jobs/form/ScoreDefinitions.tsx | 2 +- .../sidePanels/MetricRunSidePanel/index.tsx | 2 +- .../ModelPanels/ModelPanel/components.tsx | 2 +- .../components/AnonymizerBuilderForm.tsx | 2 +- .../src/routes/AnonymizerBuilderRoute/index.tsx | 2 +- .../src/routes/AnonymizerJobDetailRoute/index.tsx | 2 +- .../studio/src/routes/AnonymizerListRoute/index.tsx | 2 +- .../CustomizationJobDetailsRoute/DetailActions.tsx | 4 ++-- .../routes/CustomizationJobDetailsRoute/index.tsx | 2 +- .../src/routes/CustomizationJobListRoute/index.tsx | 2 +- .../src/routes/DashboardLandingRoute/index.tsx | 2 +- .../src/routes/DataDesignerJobBuildRoute/index.tsx | 4 ++-- .../routes/DataDesignerJobDetailsRoute/index.tsx | 2 +- .../src/routes/DataDesignerJobListRoute/index.tsx | 2 +- .../useCreateDeploymentBySource.ts | 2 +- .../src/routes/DeploymentsListRoute/index.tsx | 2 +- .../src/routes/EvaluationDetailRoute/index.tsx | 2 +- .../SessionCompareColumn.tsx | 2 +- .../TestCaseCompare.tsx | 2 +- .../src/routes/ExperimentDetailRoute/index.tsx | 2 +- .../studio/src/routes/ExperimentRoute/index.tsx | 2 +- .../studio/src/routes/FilesetDetailRoute/index.tsx | 2 +- .../studio/src/routes/FilesetListRoute/index.tsx | 2 +- .../studio/src/routes/FilesetNewRoute/helpers.ts | 2 +- .../src/routes/FilesetNewRoute/useCreateFileset.ts | 2 +- .../CreateInferenceProviderSidePanel/index.tsx | 2 +- .../routes/InferenceProvidersListRoute/index.tsx | 2 +- .../studio/src/routes/IntakeLayout/index.tsx | 2 +- .../studio/src/routes/JobDetailRoute/index.tsx | 2 +- web/packages/studio/src/routes/JobsRoute/index.tsx | 2 +- .../routes/LegacyNewDataDesignerJobRoute/index.tsx | 2 +- .../src/routes/NewDataDesignerJobRoute/index.tsx | 2 +- .../src/routes/PromptTuningFormRoute/index.tsx | 4 ++-- .../routes/SafeSynthesizerJobDetailsRoute/index.tsx | 2 +- .../routes/SafeSynthesizerJobReportRoute/index.tsx | 2 +- .../src/routes/SafeSynthesizerListRoute/index.tsx | 2 +- .../src/routes/SafeSynthesizerNewRoute/index.tsx | 2 +- .../SecretsListRoute/CreateSecretModal/index.tsx | 2 +- .../SecretsListRoute/EditSecretModal/index.tsx | 2 +- .../studio/src/routes/SecretsListRoute/index.tsx | 2 +- .../src/routes/VirtualModelsListRoute/index.tsx | 2 +- .../src/routes/WorkspaceBaseModelsRoute/index.tsx | 2 +- .../src/routes/WorkspaceDashboardRoute/index.tsx | 2 +- .../src/routes/WorkspaceMembersRoute/index.tsx | 2 +- .../src/routes/WorkspaceSettingsRoute/index.tsx | 2 +- .../src/routes/agents/AgentDetailRoute/index.tsx | 2 +- .../AgentEvaluationDetailRoute.tsx | 2 +- .../AgentEvaluationsListRoute.tsx | 2 +- .../src/routes/agents/AgentMonitorRoute/index.tsx | 2 +- .../routes/agents/AgentSuggestionsRoute/index.tsx | 2 +- .../AgentsListRoute/CloneAgentModal/index.tsx | 2 +- .../CreateExampleAgentModal/index.tsx | 2 +- .../src/routes/agents/AgentsListRoute/index.tsx | 2 +- .../src/routes/agents/CopilotChatRoute/index.tsx | 2 +- .../routes/evaluation/EvaluationLayout/index.tsx | 2 +- .../EvaluationResultDetailsRoute/index.tsx | 2 +- .../evaluation/EvaluationResultsLayout/index.tsx | 2 +- .../guardrails/CreateGuardrailModal/index.tsx | 2 +- .../GuardrailChecksTab/GuardrailTestCard.tsx | 2 +- .../GuardrailChecksTab/GuardrailTestCasesEditor.tsx | 2 +- .../GuardrailDetailRoute/GuardrailDetailActions.tsx | 8 ++++---- .../guardrails/GuardrailDetailRoute/index.tsx | 2 +- .../src/routes/guardrails/GuardrailsRoute/index.tsx | 2 +- .../routes/optimizer/InsightTracesTable/index.tsx | 2 +- .../optimizer/OptimizerInsightRoute/index.tsx | 4 ++-- .../src/routes/optimizer/OptimizerRoute/index.tsx | 4 ++-- web/pnpm-lock.yaml | 3 +++ 115 files changed, 146 insertions(+), 129 deletions(-) rename web/packages/{studio => common}/src/api/common/utils.test.ts (98%) rename web/packages/{studio => common}/src/api/common/utils.ts (100%) rename web/packages/{studio => common}/src/components/AccessibleTitle/index.test.tsx (93%) rename web/packages/{studio => common}/src/components/AccessibleTitle/index.tsx (100%) rename web/packages/{studio => common}/src/components/AccordionSection/index.tsx (100%) rename web/packages/{studio => common}/src/components/ExpandableMessage/index.test.tsx (98%) rename web/packages/{studio => common}/src/components/ExpandableMessage/index.tsx (100%) rename web/packages/{studio => common}/src/components/FileTag/index.tsx (100%) rename web/packages/{studio => common}/src/components/FileUpload/index.tsx (95%) rename web/packages/{studio => common}/src/components/FileUpload/util.ts (100%) rename web/packages/{studio => common}/src/components/InputErrorText/index.tsx (100%) rename web/packages/{studio => common}/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx (93%) rename web/packages/{studio => common}/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx (100%) diff --git a/web/packages/common/package.json b/web/packages/common/package.json index 4b1aa7d21c..f97cd95df1 100644 --- a/web/packages/common/package.json +++ b/web/packages/common/package.json @@ -53,6 +53,7 @@ "happy-dom": "catalog:", "msw": "catalog:", "p-limit": "catalog:", + "react-dropzone": "catalog:", "react-hook-form": "catalog:", "tsx": "catalog:", "typescript": "catalog:", diff --git a/web/packages/studio/src/api/common/utils.test.ts b/web/packages/common/src/api/common/utils.test.ts similarity index 98% rename from web/packages/studio/src/api/common/utils.test.ts rename to web/packages/common/src/api/common/utils.test.ts index 3c04d54feb..60179799cb 100644 --- a/web/packages/studio/src/api/common/utils.test.ts +++ b/web/packages/common/src/api/common/utils.test.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { getErrorMessage, isValidationErrorArray } from '@studio/api/common/utils'; +import { getErrorMessage, isValidationErrorArray } from '@nemo/common/src/api/common/utils'; import { AxiosError, AxiosHeaders, InternalAxiosRequestConfig } from 'axios'; describe('isValidationErrorArray', () => { diff --git a/web/packages/studio/src/api/common/utils.ts b/web/packages/common/src/api/common/utils.ts similarity index 100% rename from web/packages/studio/src/api/common/utils.ts rename to web/packages/common/src/api/common/utils.ts diff --git a/web/packages/studio/src/components/AccessibleTitle/index.test.tsx b/web/packages/common/src/components/AccessibleTitle/index.test.tsx similarity index 93% rename from web/packages/studio/src/components/AccessibleTitle/index.test.tsx rename to web/packages/common/src/components/AccessibleTitle/index.test.tsx index feb91bb83d..d62531662f 100644 --- a/web/packages/studio/src/components/AccessibleTitle/index.test.tsx +++ b/web/packages/common/src/components/AccessibleTitle/index.test.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { render, waitFor } from '@testing-library/react'; describe('AccessibleTitle', () => { diff --git a/web/packages/studio/src/components/AccessibleTitle/index.tsx b/web/packages/common/src/components/AccessibleTitle/index.tsx similarity index 100% rename from web/packages/studio/src/components/AccessibleTitle/index.tsx rename to web/packages/common/src/components/AccessibleTitle/index.tsx diff --git a/web/packages/studio/src/components/AccordionSection/index.tsx b/web/packages/common/src/components/AccordionSection/index.tsx similarity index 100% rename from web/packages/studio/src/components/AccordionSection/index.tsx rename to web/packages/common/src/components/AccordionSection/index.tsx diff --git a/web/packages/studio/src/components/ExpandableMessage/index.test.tsx b/web/packages/common/src/components/ExpandableMessage/index.test.tsx similarity index 98% rename from web/packages/studio/src/components/ExpandableMessage/index.test.tsx rename to web/packages/common/src/components/ExpandableMessage/index.test.tsx index 5c0c79bd43..9d71ec0fad 100644 --- a/web/packages/studio/src/components/ExpandableMessage/index.test.tsx +++ b/web/packages/common/src/components/ExpandableMessage/index.test.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ExpandableMessage } from '@studio/components/ExpandableMessage'; +import { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/web/packages/studio/src/components/ExpandableMessage/index.tsx b/web/packages/common/src/components/ExpandableMessage/index.tsx similarity index 100% rename from web/packages/studio/src/components/ExpandableMessage/index.tsx rename to web/packages/common/src/components/ExpandableMessage/index.tsx diff --git a/web/packages/studio/src/components/FileTag/index.tsx b/web/packages/common/src/components/FileTag/index.tsx similarity index 100% rename from web/packages/studio/src/components/FileTag/index.tsx rename to web/packages/common/src/components/FileTag/index.tsx diff --git a/web/packages/studio/src/components/FileUpload/index.tsx b/web/packages/common/src/components/FileUpload/index.tsx similarity index 95% rename from web/packages/studio/src/components/FileUpload/index.tsx rename to web/packages/common/src/components/FileUpload/index.tsx index 5735e70cc0..ee624b4417 100644 --- a/web/packages/studio/src/components/FileUpload/index.tsx +++ b/web/packages/common/src/components/FileUpload/index.tsx @@ -1,14 +1,14 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { Block, Flex, Label, Stack, Text } from '@nvidia/foundations-react-core'; -import { FileTag, FileTagStatus } from '@studio/components/FileTag'; +import { FileTag, FileTagStatus } from '@nemo/common/src/components/FileTag'; import { getAcceptedFileExtensions, hadInvalidFileTypeError, hadTooManyFilesError, -} from '@studio/components/FileUpload/util'; -import { InputErrorText } from '@studio/components/InputErrorText'; +} from '@nemo/common/src/components/FileUpload/util'; +import { InputErrorText } from '@nemo/common/src/components/InputErrorText'; +import { Block, Flex, Label, Stack, Text } from '@nvidia/foundations-react-core'; import { FilePlus, FileText } from 'lucide-react'; import { FC, MouseEvent, MouseEventHandler, ReactNode, useCallback, useState } from 'react'; import { DropEvent, DropzoneOptions, FileRejection, useDropzone } from 'react-dropzone'; diff --git a/web/packages/studio/src/components/FileUpload/util.ts b/web/packages/common/src/components/FileUpload/util.ts similarity index 100% rename from web/packages/studio/src/components/FileUpload/util.ts rename to web/packages/common/src/components/FileUpload/util.ts diff --git a/web/packages/studio/src/components/InputErrorText/index.tsx b/web/packages/common/src/components/InputErrorText/index.tsx similarity index 100% rename from web/packages/studio/src/components/InputErrorText/index.tsx rename to web/packages/common/src/components/InputErrorText/index.tsx diff --git a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx similarity index 93% rename from web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx rename to web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx index 9557eaa76c..168e7c1806 100644 --- a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx +++ b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx similarity index 100% rename from web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx rename to web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx diff --git a/web/packages/common/src/plugin.ts b/web/packages/common/src/plugin.ts index 8367f0c3bc..80eae8849c 100644 --- a/web/packages/common/src/plugin.ts +++ b/web/packages/common/src/plugin.ts @@ -4,6 +4,18 @@ // Plugin API: the surface Studio serves to plugin bundles as `@nemo/common`. // Removals are breaking. Explicit exports, not `export *`. +export { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; +export { AccordionSection } from '@nemo/common/src/components/AccordionSection'; +export type { AccordionSectionProps } from '@nemo/common/src/components/AccordionSection'; +export { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage'; +export { FileTag } from '@nemo/common/src/components/FileTag'; +export type { FileTagProps, FileTagStatus } from '@nemo/common/src/components/FileTag'; +export { FileUpload } from '@nemo/common/src/components/FileUpload'; +export type { FileUploadProps, RenderFileTagFn } from '@nemo/common/src/components/FileUpload'; +export { InputErrorText } from '@nemo/common/src/components/InputErrorText'; +export { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; +export type { QuickActionItem } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; + export { StudioDataView, StudioDataViewToolbar, @@ -46,6 +58,7 @@ export { } from '@nemo/common/src/utils/query'; export { triggerDownload } from '@nemo/common/src/utils/file'; +export { JOB_POLLING_INTERVAL_MS } from '@nemo/common/src/constants'; export { DEFAULT_PAGE, DEFAULT_PAGE_SIZE, diff --git a/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts b/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts index 0e14950c8d..145498f8fb 100644 --- a/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts +++ b/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { isVersionConflictError } from '@nemo/common/src/api/common/utils'; import { entitiesCreateEntity, entitiesDeleteEntityByName, @@ -16,7 +17,6 @@ import type { GuardrailCheckResponse, RailsConfig, } from '@nemo/sdk/generated/platform/schema'; -import { isVersionConflictError } from '@studio/api/common/utils'; import { GUARDRAIL_CHECKS_ENTITY_TYPE, type GuardrailCheckData, diff --git a/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx b/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx index 04b458462f..7c4da6ff9c 100644 --- a/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx +++ b/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx @@ -1,6 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { + type QuickActionItem, + QuickActionsMenuRoot, +} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { CJobCancellableStatuses } from '@nemo/common/src/constants/query'; import { getAnonymizerListRunJobsQueryKey, @@ -8,10 +12,6 @@ import { } from '@nemo/sdk/generated/anonymizer/api'; import type { RunJob as AnonymizerJob } from '@nemo/sdk/generated/anonymizer/schema'; import { DeleteJobModal } from '@studio/components/dataViews/AnonymizerJobsDataView/DeleteJobModal'; -import { - type QuickActionItem, - QuickActionsMenuRoot, -} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getAnonymizerJobRoute } from '@studio/routes/utils'; import { useQueryClient } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/components/CancelJobButton/index.tsx b/web/packages/studio/src/components/CancelJobButton/index.tsx index f86af9b302..db4d29f77d 100644 --- a/web/packages/studio/src/components/CancelJobButton/index.tsx +++ b/web/packages/studio/src/components/CancelJobButton/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { FormModal } from '@nemo/common/src/components/FormModal'; import { CJobCancellableStatuses } from '@nemo/common/src/constants/query'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; @@ -11,7 +12,6 @@ import { } from '@nemo/sdk/generated/platform/api'; import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useQueryClient } from '@tanstack/react-query'; import { X } from 'lucide-react'; diff --git a/web/packages/studio/src/components/CustomizationFileTag/index.tsx b/web/packages/studio/src/components/CustomizationFileTag/index.tsx index 224e2a4b95..116bdc9e3d 100644 --- a/web/packages/studio/src/components/CustomizationFileTag/index.tsx +++ b/web/packages/studio/src/components/CustomizationFileTag/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { FileTag, FileTagProps } from '@nemo/common/src/components/FileTag'; import { Stack } from '@nvidia/foundations-react-core'; -import { FileTag, FileTagProps } from '@studio/components/FileTag'; import { CUSTOMIZATION_FILESET_FILE_ICONS, CUSTOMIZATION_FILESET_FILE_LABELS, diff --git a/web/packages/studio/src/components/CustomizationFileUpload/index.tsx b/web/packages/studio/src/components/CustomizationFileUpload/index.tsx index 3aef53d739..d2daafae1a 100644 --- a/web/packages/studio/src/components/CustomizationFileUpload/index.tsx +++ b/web/packages/studio/src/components/CustomizationFileUpload/index.tsx @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { RenderFileTagFn } from '@nemo/common/src/components/FileUpload'; import { CustomizationFileTag, CustomizationFileTagFile, } from '@studio/components/CustomizationFileTag'; import { DatasetFileUpload } from '@studio/components/DatasetFileUpload'; -import { RenderFileTagFn } from '@studio/components/FileUpload'; import { CUSTOMIZATION_FILESET_FILE_PREFIXES, CustomizationFileType, diff --git a/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx b/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx index 25a502c409..fdf4ebea03 100644 --- a/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx +++ b/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx @@ -1,6 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { + type QuickActionItem, + QuickActionsMenuRoot, +} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { CJobCancellableStatuses } from '@nemo/common/src/constants/query'; import { getDataDesignerListCreateJobsQueryKey, @@ -9,10 +13,6 @@ import { import type { CreateJob as DataDesignerJob } from '@nemo/sdk/generated/data-designer/schema'; import { DeleteJobModal } from '@studio/components/dataViews/DataDesignerJobsDataView/DeleteJobModal'; import { buildClonedJobRequest } from '@studio/components/NewDataDesignerJobForm/utils'; -import { - type QuickActionItem, - QuickActionsMenuRoot, -} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getDataDesignerJobBuildRoute, getDataDesignerJobDetailsRoute } from '@studio/routes/utils'; import { useQueryClient } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/components/DatasetFileUpload/index.tsx b/web/packages/studio/src/components/DatasetFileUpload/index.tsx index 78b9f76bc4..e952c50ed6 100644 --- a/web/packages/studio/src/components/DatasetFileUpload/index.tsx +++ b/web/packages/studio/src/components/DatasetFileUpload/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { FileTag, FileTagStatus } from '@studio/components/FileTag'; -import { RenderFileTagFn, FileUpload } from '@studio/components/FileUpload'; +import { FileTag, FileTagStatus } from '@nemo/common/src/components/FileTag'; +import { RenderFileTagFn, FileUpload } from '@nemo/common/src/components/FileUpload'; import { ComponentProps, FC } from 'react'; export interface DatasetFileUploadProps extends Omit< diff --git a/web/packages/studio/src/components/ErrorPanel/index.tsx b/web/packages/studio/src/components/ErrorPanel/index.tsx index 885c0c2950..781727010a 100644 --- a/web/packages/studio/src/components/ErrorPanel/index.tsx +++ b/web/packages/studio/src/components/ErrorPanel/index.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { Flex, PageHeader, Panel, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { logger } from '@studio/util/logger'; import { FileX } from 'lucide-react'; import { ComponentProps, FC, ReactNode, useEffect } from 'react'; diff --git a/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx b/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx index e3b272af5e..1554c9f384 100644 --- a/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx +++ b/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx @@ -1,16 +1,16 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { + QuickActionsMenuRoot, + type QuickActionItem, +} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useQueryParams } from '@nemo/common/src/hooks/useQueryParams'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { useDatasetDirectoryDelete } from '@studio/api/datasets/useDatasetDirectoryDelete'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { FileSystemDirectory } from '@studio/components/FilesTable/utils'; -import { - QuickActionsMenuRoot, - type QuickActionItem, -} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId'; import { QUERY_PARAMETERS } from '@studio/routes/constants'; import { resolveDatasetFilePath } from '@studio/util/files'; diff --git a/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx b/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx index 1f4b015205..16a5513ff4 100644 --- a/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx +++ b/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx @@ -1,6 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { + type QuickActionItem, + QuickActionsMenuRoot, +} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { useDatasetFileDelete } from '@studio/api/datasets/useDatasetFileDelete'; @@ -12,10 +16,6 @@ import { CreateFileSplitsModal } from '@studio/components/FilesTable/CreateFileS import { RenameFileModal } from '@studio/components/FilesTable/RenameFileModal'; import { TransformFileModal } from '@studio/components/FilesTable/TransformFileModal'; import { FileSystemFile, FileSystemNode } from '@studio/components/FilesTable/utils'; -import { - type QuickActionItem, - QuickActionsMenuRoot, -} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId'; import { resolveDatasetFilePath } from '@studio/util/files'; import { FC, useState } from 'react'; diff --git a/web/packages/studio/src/components/FilesetCreateModal/index.tsx b/web/packages/studio/src/components/FilesetCreateModal/index.tsx index 1586a014b9..37ab50dd48 100644 --- a/web/packages/studio/src/components/FilesetCreateModal/index.tsx +++ b/web/packages/studio/src/components/FilesetCreateModal/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage as getApiErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal } from '@nemo/common/src/components/FormModal'; @@ -13,7 +14,6 @@ import { } from '@nemo/sdk/generated/platform/api'; import { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; import { SegmentedControl, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage as getApiErrorMessage } from '@studio/api/common/utils'; import { filesetCreateFormSchema, type FilesetCreateFormData, diff --git a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx index 6cde2fcbf4..f0300e32e8 100644 --- a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx +++ b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { formatAbsoluteTimestamp } from '@nemo/common/src/components/RelativeTime/util'; import { DEFAULT_PAGE_SIZE } from '@nemo/common/src/constants/api'; import { useListAnnotations } from '@nemo/sdk/generated/platform/api'; @@ -19,7 +20,6 @@ import { Text, TextArea, } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { ThumbButton } from '@studio/components/buttons/ThumbButton'; import { useSpanAnnotationActions } from '@studio/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions'; import { MessageSquarePlus, Trash2 } from 'lucide-react'; diff --git a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts index 8b29b0bad0..f2c56ba0ac 100644 --- a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts +++ b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { getListAnnotationsQueryKey, listAnnotations, @@ -13,7 +14,6 @@ import { type FeedbackAnnotationInputValue, NoteAnnotationInputKind, } from '@nemo/sdk/generated/platform/schema'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useQueryClient } from '@tanstack/react-query'; import { useCallback, useState } from 'react'; diff --git a/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx b/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx index 9b058820b4..a1ed620cff 100644 --- a/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx +++ b/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { PageHeader, Stack, StatusMessage } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { TraceDetailLayout } from '@studio/components/IntakeDetail/TraceDetailLayout'; import { TraceSpanTree } from '@studio/components/IntakeDetail/TraceDetailSpanTree'; import { SessionSummaryHeader } from '@studio/components/IntakeDetail/TraceDetailSummaryHeader'; diff --git a/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx b/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx index d8ad94e2f8..12a8ff5c2d 100644 --- a/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx +++ b/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { type ListSpansQueryError, @@ -15,7 +16,6 @@ import { type Trace, } from '@nemo/sdk/generated/platform/schema'; import { Flex, Spinner, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { IntakeErrorBanner } from '@studio/components/IntakeDetail/IntakeComponents/IntakeErrorBanner'; import { SpanListView } from '@studio/components/IntakeDetail/TraceSpanListView'; import { diff --git a/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx b/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx index de2804f814..baeb838da3 100644 --- a/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx +++ b/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter'; import * as DataView from '@nemo/common/src/components/DataView/internal'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; @@ -16,7 +17,6 @@ import { type SpanSortField, } from '@nemo/sdk/generated/platform/schema'; import { Anchor, Button, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { IntakeTelemetryStatusBadge } from '@studio/components/IntakeDetail/IntakeComponents/IntakeTelemetryStatusBadge'; import { isDefaultStartedAtFilter, diff --git a/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx b/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx index 2f89c6bae7..6a47cdf979 100644 --- a/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx +++ b/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { EditColumnsMenu } from '@nemo/common/src/components/DataView/internal'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -9,7 +10,6 @@ import { getSortParamWithWhitelist } from '@nemo/common/src/utils/query'; import { useListTraces } from '@nemo/sdk/generated/platform/api'; import type { Trace, TraceFilter, TraceSortField } from '@nemo/sdk/generated/platform/schema'; import { Button } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { isDefaultStartedAtFilter, makeDefaultStartedAtFilter, diff --git a/web/packages/studio/src/components/ModelActionsMenu/index.tsx b/web/packages/studio/src/components/ModelActionsMenu/index.tsx index 720ff45329..5f4c353415 100644 --- a/web/packages/studio/src/components/ModelActionsMenu/index.tsx +++ b/web/packages/studio/src/components/ModelActionsMenu/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { ModelEntity } from '@nemo/sdk/generated/platform/schema'; -import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { modelToFormData } from '@studio/routes/PromptTuningFormRoute/utils'; import { getPromptTuningFormRoute } from '@studio/routes/utils'; import { GitFork, Trash, FolderOpen } from 'lucide-react'; diff --git a/web/packages/studio/src/components/NewCustomizationForm/index.tsx b/web/packages/studio/src/components/NewCustomizationForm/index.tsx index 6296d06c37..ce7b753b04 100644 --- a/web/packages/studio/src/components/NewCustomizationForm/index.tsx +++ b/web/packages/studio/src/components/NewCustomizationForm/index.tsx @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { generateDefaultName } from '@nemo/common/src/utils/generateDefaultName'; import { @@ -17,8 +19,6 @@ import { Panel, Stack, } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { CustomizationFilesetSelect } from '@studio/components/customizer/CustomizationFilesetSelect'; import { BackendSelectionSection } from '@studio/components/NewCustomizationForm/BackendSelectionSection'; import { ComputeResourcesSection } from '@studio/components/NewCustomizationForm/ComputeResourcesSection'; diff --git a/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx index 0d01862983..2f3e9b9ed7 100644 --- a/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx +++ b/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { AccordionSection } from '@nemo/common/src/components/AccordionSection'; import { FileContentPreview } from '@nemo/common/src/components/FileContentPreview'; import { FileList, FileListItem } from '@nemo/common/src/components/FileList'; import { UploadModal } from '@nemo/common/src/components/UploadModal'; import { compileSystemPrompt } from '@nemo/common/src/models/utils'; import { Button, Flex, SidePanel, Stack, Text } from '@nvidia/foundations-react-core'; -import { AccordionSection } from '@studio/components/AccordionSection'; import { ImportFileContentFormFields, importFileContentSchema, diff --git a/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx index c8e294ee12..72e829e19b 100644 --- a/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx +++ b/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccordionSection } from '@nemo/common/src/components/AccordionSection'; import { InferenceParameters } from '@nemo/common/src/components/ModelSelectV2/InferenceParameters'; import type { InferenceParams } from '@nemo/sdk/generated/platform/schema'; -import { AccordionSection } from '@studio/components/AccordionSection'; import type { PromptTuningFormFields, PromptTuningFormSectionProps, diff --git a/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx index 310c7ac51f..c5cd16d216 100644 --- a/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx +++ b/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccordionSection } from '@nemo/common/src/components/AccordionSection'; import { DEFAULT_PROMPT_TEMPLATE, DEFAULT_PROMPT_TEMPLATE_COMPILED, } from '@nemo/common/src/models/constants'; import { compileSystemPrompt } from '@nemo/common/src/models/utils'; import { Button, Flex, FormField, Stack, TextArea } from '@nvidia/foundations-react-core'; -import { AccordionSection } from '@studio/components/AccordionSection'; import type { PromptTuningFormFields, PromptTuningFormSectionProps, diff --git a/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx index 78b9a5a214..9094868ecd 100644 --- a/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx +++ b/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { AccordionSection } from '@nemo/common/src/components/AccordionSection'; import { FormModal } from '@nemo/common/src/components/FormModal'; import { Anchor, Button, Flex, Stack, Switch, Text } from '@nvidia/foundations-react-core'; -import { AccordionSection } from '@studio/components/AccordionSection'; import { DetailRow } from '@studio/components/DetailRow'; import { AddToolForm } from '@studio/components/PromptTuningForm/ToolsSection/components/AddToolForm'; import { ToolMetadataPanel } from '@studio/components/PromptTuningForm/ToolsSection/components/ToolMetadataPanel'; diff --git a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx b/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx index ca8e9a4747..cf44837b12 100644 --- a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx +++ b/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx @@ -4,7 +4,7 @@ import { QuickActionItem, QuickActionsMenuRoot, -} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; +} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { Pencil, Trash, FolderOpen } from 'lucide-react'; import { FC, useMemo } from 'react'; import { useNavigate } from 'react-router'; diff --git a/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx b/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx index c47f5d562a..5b22e58f9c 100644 --- a/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx @@ -7,6 +7,7 @@ import { ROW_SELECTION_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -26,7 +27,6 @@ import { import { Button } from '@nvidia/foundations-react-core'; import { agentNameForJob, evalConfigName } from '@studio/api/evaluation/agent-evaluations'; import { BulkDeleteModal } from '@studio/components/BulkDeleteModal'; -import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { STATUS_FILTER_OPTIONS } from '@studio/constants/platformJobs'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getAgentEvaluationDetailRoute, getFilesetRoute } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx b/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx index b0caedd500..03438993da 100644 --- a/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx @@ -10,6 +10,7 @@ * its affiliates is strictly prohibited. */ +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; @@ -23,7 +24,6 @@ import { ModelDeploymentStatus, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { ErrorPanel } from '@studio/components/ErrorPanel'; import { CUSTOMIZER_ENABLED } from '@studio/constants/environment'; import { keepPreviousData } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx b/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx index 003cf1a3ef..6392616b7c 100644 --- a/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx @@ -13,6 +13,7 @@ import { StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; @@ -33,7 +34,6 @@ import { } from '@studio/components/dataViews/ExperimentDataView/useExperimentEvaluations'; import { useSortErrorRecovery } from '@studio/components/dataViews/ExperimentDataView/useSortErrorRecovery'; import { deriveEvaluatorNames } from '@studio/components/dataViews/ExperimentDataView/util'; -import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getEvaluationDetailRoute } from '@studio/routes/utils'; import { tooltipClassName } from '@studio/styles/common'; diff --git a/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx b/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx index 5824138c11..625609270c 100644 --- a/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, @@ -14,7 +15,6 @@ import type { GuardrailsListGuardrailConfigsParams, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { countRails } from '@studio/components/dataViews/GuardrailsDataView/guardrailUtils'; import { ErrorPanel } from '@studio/components/ErrorPanel'; import { keepPreviousData } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx b/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx index 2efbf52139..7fc1f2e933 100644 --- a/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx @@ -10,6 +10,7 @@ * its affiliates is strictly prohibited. */ +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; import { ROW_ACTIONS_COLUMN_SIZE, @@ -32,7 +33,6 @@ import { ModelProviderSort, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, StatusMessage, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { ErrorPanel } from '@studio/components/ErrorPanel'; import { LINK_DOCS_INFERENCE_PROVIDERS } from '@studio/constants/links'; diff --git a/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx b/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx index 4001b9790e..d4a50688be 100644 --- a/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter'; import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView'; @@ -16,7 +17,6 @@ import type { PlatformJobsListFilter, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, StatusMessage } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { CancelJobButton } from '@studio/components/CancelJobButton'; import { HIDDEN_JOB_SOURCES, diff --git a/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx b/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx index 2bf792cc50..802e0ae89d 100644 --- a/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx @@ -9,6 +9,7 @@ import { ROW_SELECTION_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { ScoreGauge } from '@nemo/common/src/components/ScoreGauge'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; @@ -31,7 +32,6 @@ import { Banner, Button, Stack } from '@nvidia/foundations-react-core'; import { BulkDeleteModal } from '@studio/components/BulkDeleteModal'; import { isCancellableJob } from '@studio/components/dataViews/SafeSynthesizerJobsDataView/utils'; import { DocumentationButton } from '@studio/components/DocumentationButton'; -import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { FilesetFilePreviewLink } from '@studio/components/SafeSynthesizerFilesetPreview/FilesetFilePreviewLink'; import { LINK_DOCS_SAFE_SYNTHESIZER } from '@studio/constants/links'; import { STATUS_FILTER_OPTIONS } from '@studio/constants/platformJobs'; diff --git a/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx b/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx index e49c4176fd..8575867b2b 100644 --- a/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx @@ -10,6 +10,7 @@ * its affiliates is strictly prohibited. */ +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, @@ -21,7 +22,6 @@ import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { useSecretsDeleteSecret, useSecretsListSecrets } from '@nemo/sdk/generated/platform/api'; import { PlatformSecretResponse } from '@nemo/sdk/generated/platform/schema'; import { Button, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; import { ErrorPanel } from '@studio/components/ErrorPanel'; diff --git a/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx b/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx index ae6d1c1941..835c50de17 100644 --- a/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter'; import { @@ -23,7 +24,6 @@ import type { VirtualModelFilter, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, StatusMessage, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { ErrorPanel } from '@studio/components/ErrorPanel'; import { BaseModelSearchFilterField } from '@studio/components/FilterFields'; diff --git a/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx b/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx index 00465def6e..8236f4754d 100644 --- a/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx +++ b/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { InputErrorText } from '@nemo/common/src/components/InputErrorText'; import { Badge, Button, Flex, Stack, Text } from '@nvidia/foundations-react-core'; import { ScoreModal, type MetricPanelFormData, type PanelScoreFormData, } from '@studio/components/evaluation/Jobs/form/ScoreModal'; -import { InputErrorText } from '@studio/components/InputErrorText'; import { Pencil, Plus, Trash } from 'lucide-react'; import { FC, useState } from 'react'; import { FieldError, useFormContext, useWatch } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx b/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx index 78c03333bb..8b42e186e2 100644 --- a/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx +++ b/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { useModelEntity } from '@nemo/common/src/api/models/useModelEntity'; import { useModelSearch } from '@nemo/common/src/api/models/useModelSearch'; import { VariableButton } from '@nemo/common/src/components/buttons/VariableButton'; @@ -23,7 +24,6 @@ import { Stack, Text, } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import type { MetricItemWithId } from '@studio/components/dataViews/EvaluationMetricsDataView/types'; import { EvalCard } from '@studio/components/evaluation/EvalCard'; import { FileValidationPanel } from '@studio/components/sidePanels/MetricRunSidePanel/FileValidationPanel'; diff --git a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx index 5d5ab9db5d..b5ff007294 100644 --- a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx +++ b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage'; import { KVPair } from '@nemo/common/src/components/KVPair'; import { parametersToString } from '@nemo/common/src/components/ModelDetailsTooltip/utils'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; @@ -29,7 +30,6 @@ import { Tooltip, } from '@nvidia/foundations-react-core'; import { CopyButton } from '@studio/components/CopyButton'; -import { ExpandableMessage } from '@studio/components/ExpandableMessage'; import { getWorkspaceCustomizationJobDetailsRoute } from '@studio/routes/utils'; import { formatFinetuningType } from '@studio/util/customizations'; import { MessagesSquare, Eye, EyeOff, Layers, AlignLeft, AppWindow } from 'lucide-react'; diff --git a/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx b/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx index f670759ff2..ad611a9999 100644 --- a/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx +++ b/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { useAnonymizerCreateRunJob, useAnonymizerListEntityLabels, @@ -15,7 +16,6 @@ import { SegmentedControl, Stack, } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { parseAnonymizerApiError } from '@studio/routes/AnonymizerBuilderRoute/apiErrors'; import { ColumnsSection } from '@studio/routes/AnonymizerBuilderRoute/components/ColumnsSection'; diff --git a/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx b/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx index dca9e3f27a..6e344912aa 100644 --- a/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx +++ b/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ANONYMIZER_ENABLED } from '@studio/constants/environment'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { AnonymizerBuilderForm } from '@studio/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm'; diff --git a/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx b/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx index 3038d416c9..6a0bc9a874 100644 --- a/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { LogViewer } from '@nemo/common/src/components/LogViewer'; import { useAnonymizerGetRunJob, @@ -8,7 +9,6 @@ import { useAnonymizerListRunJobResults, } from '@nemo/sdk/generated/anonymizer/api'; import { Banner, Grid, PageHeader, Panel, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { AnonymizerJobActionsMenu } from '@studio/components/AnonymizerJobActionsMenu'; import { ANONYMIZER_ENABLED } from '@studio/constants/environment'; import { ROUTE_PARAMS } from '@studio/constants/routes'; diff --git a/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx b/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx index 860a45daa7..af25f7da34 100644 --- a/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx +++ b/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { AnonymizerJobsDataView } from '@studio/components/dataViews/AnonymizerJobsDataView'; import { ANONYMIZER_ENABLED } from '@studio/constants/environment'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx index 3674ecea8c..5ad8f3a80b 100644 --- a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx +++ b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { CJobCancellableStatuses, CJobLaunchableStatuses } from '@nemo/common/src/constants/query'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { @@ -10,8 +12,6 @@ import { import { getJobsGetJobQueryKey } from '@nemo/sdk/generated/platform/api'; import { PlatformJobStatus, type PlatformJobResponse } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; -import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getNewCustomizationJobRoute, getNewEvaluationMetricRoute } from '@studio/routes/utils'; import { CustomizationBackend, type CustomizationJob } from '@studio/util/customizationBackend'; diff --git a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx index d32b684e2a..dd1ef7be58 100644 --- a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx +++ b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { useModelsGetModel } from '@nemo/sdk/generated/platform/api'; import { PageHeader, @@ -10,7 +11,6 @@ import { TabsRoot, TabsTrigger, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { CustomizationDetailsPanel } from '@studio/components/CustomizationDetailsPanel'; import { CustomizationFilesetDetailsPanel } from '@studio/components/CustomizationFilesetDetailsPanel'; import { Loading } from '@studio/components/Layouts/Loading'; diff --git a/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx b/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx index 8cf4229040..07bf126beb 100644 --- a/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx +++ b/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import type { Adapter, ModelEntity } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { CustomModelsDataView } from '@studio/components/dataViews/CustomModelsDataView'; import { CustomizeModelButton } from '@studio/components/dataViews/CustomModelsDataView/CustomizeModelButton'; import { ModelPanel, ModelPanelTab } from '@studio/components/sidePanels/ModelPanels/ModelPanel'; diff --git a/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx b/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx index 3496e00055..9418c9c2fa 100644 --- a/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx +++ b/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { GradientBackground } from '@nemo/common/src/components/GradientBackground'; import { Button, Flex, Text, TextArea, Tooltip } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { writeStoredActiveSessionId } from '@studio/routes/agents/CopilotChatRoute/activeSessionStorage'; diff --git a/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx b/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx index 577ec8bc27..f779382f91 100644 --- a/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx +++ b/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { DEFAULT_LARGE_PAGE_SIZE } from '@nemo/common/src/constants/api'; import { useDataDesignerCreateJob } from '@nemo/sdk/generated/data-designer/api'; import { useModelsListProviders } from '@nemo/sdk/generated/platform/api'; import { Flex, Stack } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { findTemplate } from '@studio/components/CreateFilesetStart/templates'; import { usePreview } from '@studio/components/NewDataDesignerJobForm/usePreview'; import { getCloneJobRequestFromState } from '@studio/components/NewDataDesignerJobForm/utils'; diff --git a/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx b/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx index 0a87aa5df0..1358c6a34a 100644 --- a/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx +++ b/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import { PlatformJobTerminalStatuses } from '@nemo/common/src/constants/query'; @@ -15,7 +16,6 @@ import { TabsTrigger, Text, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { DataDesignerJobActionsMenu } from '@studio/components/DataDesignerJobActionsMenu'; import { CreateFileSplitsModal } from '@studio/components/FilesTable/CreateFileSplitsModal'; import { Loading } from '@studio/components/Layouts/Loading'; diff --git a/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx b/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx index a89a363ea3..c88e8fd5f9 100644 --- a/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx +++ b/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { DataDesignerJobsDataView } from '@studio/components/dataViews/DataDesignerJobsDataView'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts b/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts index c79c944394..43c197e8a6 100644 --- a/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts +++ b/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts @@ -10,6 +10,7 @@ * its affiliates is strictly prohibited. */ +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { @@ -27,7 +28,6 @@ import { type CreateFilesetRequest, type CreateModelDeploymentConfigRequest, } from '@nemo/sdk/generated/platform/schema'; -import { getErrorMessage } from '@studio/api/common/utils'; import { additionalEnvsFormToApi, configNameFromWizardBaseName, diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx b/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx index 52b10fd881..ed9718227f 100644 --- a/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx +++ b/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx @@ -10,10 +10,10 @@ * its affiliates is strictly prohibited. */ +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { resourceRefSchema, type ResourceRef } from '@nemo/common/src/types'; import { ModelDeployment } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { DeploymentsDataView } from '@studio/components/dataViews/DeploymentsDataView'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; diff --git a/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx b/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx index db6535d0fe..75c0c90d61 100644 --- a/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { useGetEvaluation, useGetExperiment } from '@nemo/sdk/generated/platform/api'; import { Badge, Card, Flex, PageHeader, Stack, Text } from '@nvidia/foundations-react-core'; import { useOptimizerGetInsight } from '@studio/api/optimizer'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { EvaluationSessionsDataView } from '@studio/components/dataViews/EvaluationSessionsDataView'; import { OriginatingInsightLink } from '@studio/components/OriginatingInsightLink'; import { OPTIMIZER_ENABLED } from '@studio/constants/environment'; diff --git a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx index 43abdce5eb..eb70792310 100644 --- a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx +++ b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { KVPair } from '@nemo/common/src/components/KVPair'; import { useListAnnotations } from '@nemo/sdk/generated/platform/api'; @@ -10,7 +11,6 @@ import { type FeedbackAnnotationInputValue, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Spinner, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { SpanListView } from '@studio/components/IntakeDetail/TraceSpanListView'; import { type NoteRequest } from '@studio/components/IntakeDetail/traceSpanShared'; import { useSessionTrajectories } from '@studio/components/IntakeDetail/useSessionTrajectories'; diff --git a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx index 9d5effdf91..3d119707ca 100644 --- a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx +++ b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import type { EvaluationSessionResponse } from '@nemo/sdk/generated/platform/schema'; import { PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { Loading } from '@studio/components/Layouts/Loading'; import { type BreadcrumbsItemProps, diff --git a/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx b/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx index d4ebddde78..bd097eba36 100644 --- a/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { useGetExperiment } from '@nemo/sdk/generated/platform/api'; import { Button, Card, Flex, PageHeader, Stack, Text } from '@nvidia/foundations-react-core'; import { useOptimizerGetInsight } from '@studio/api/optimizer'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { ExperimentDataView } from '@studio/components/dataViews/ExperimentDataView'; import { ExperimentEditModal } from '@studio/components/ExperimentEditModal'; import { OriginatingInsightLink } from '@studio/components/OriginatingInsightLink'; diff --git a/web/packages/studio/src/routes/ExperimentRoute/index.tsx b/web/packages/studio/src/routes/ExperimentRoute/index.tsx index e43c7820b1..b1402b5c9d 100644 --- a/web/packages/studio/src/routes/ExperimentRoute/index.tsx +++ b/web/packages/studio/src/routes/ExperimentRoute/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { WithFilterOperators } from '@nemo/common/src/api/filterOperators'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { dateTimeFilter, type DatetimeFilterValue, @@ -29,7 +30,6 @@ import { StatusMessage, Text, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { ExperimentCreateModal } from '@studio/components/ExperimentCreateModal'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { Loading } from '@studio/components/Layouts/Loading'; diff --git a/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx b/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx index bfa7c41248..dcc9cbc3c4 100644 --- a/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { creatorToIcon } from '@nemo/common/src/constants/modelMetadata'; import { useQueryParams } from '@nemo/common/src/hooks/useQueryParams'; import { getEntityReference } from '@nemo/common/src/namedEntity'; @@ -19,7 +20,6 @@ import { TabsTrigger, Text, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { FilesetActionMenu } from '@studio/components/FilesetActionMenu'; import { Loading } from '@studio/components/Layouts/Loading'; import { ROUTE_PARAMS } from '@studio/constants/routes'; diff --git a/web/packages/studio/src/routes/FilesetListRoute/index.tsx b/web/packages/studio/src/routes/FilesetListRoute/index.tsx index f0253f6df5..8b22063f8d 100644 --- a/web/packages/studio/src/routes/FilesetListRoute/index.tsx +++ b/web/packages/studio/src/routes/FilesetListRoute/index.tsx @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { getEntityReference } from '@nemo/common/src/namedEntity'; import { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { DatasetsTable } from '@studio/components/DatasetsTable'; import { NewDatasetButton } from '@studio/components/NewDatasetButton'; import { NewModelFilesetButton } from '@studio/components/NewModelFilesetButton'; diff --git a/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts b/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts index 751bccfa58..7a65e3ea6d 100644 --- a/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts +++ b/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { getErrorMessage as getApiErrorMessage } from '@studio/api/common/utils'; +import { getErrorMessage as getApiErrorMessage } from '@nemo/common/src/api/common/utils'; import { isHuggingFaceUrl, isNgcUrl } from '@studio/util/storageConfigFromUrl'; export function getSampleDatasetName(workspace: string, sampleId: string): string { diff --git a/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts b/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts index 3f863060b1..fa073fa2b2 100644 --- a/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts +++ b/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage as getApiErrorMessage } from '@nemo/common/src/api/common/utils'; import { getEntityReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { @@ -15,7 +16,6 @@ import { FilesetPurpose, CreateFilesetRequest, } from '@nemo/sdk/generated/platform/schema'; -import { getErrorMessage as getApiErrorMessage } from '@studio/api/common/utils'; import { FILESET_DETAILS_ENABLED } from '@studio/constants/environment'; import { DATASET_TYPE_SAMPLE } from '@studio/routes/FilesetNewRoute/constants'; import { diff --git a/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx b/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx index 1580eff242..ae8d405a2a 100644 --- a/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx +++ b/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx @@ -11,6 +11,7 @@ */ import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; @@ -21,7 +22,6 @@ import { useModelsListProviders, } from '@nemo/sdk/generated/platform/api'; import { Button, Flex, FormField, SidePanel, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { InferenceModelProviderSelect } from '@studio/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/InferenceModelProviderSelect'; import { PRESET_CREDENTIALS, diff --git a/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx b/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx index 3b9f5bc06d..f047d33e80 100644 --- a/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx +++ b/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx @@ -10,8 +10,8 @@ * its affiliates is strictly prohibited. */ +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { InferenceProvidersDataView } from '@studio/components/dataViews/InferenceProvidersDataView'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/IntakeLayout/index.tsx b/web/packages/studio/src/routes/IntakeLayout/index.tsx index 86c4847820..73a2ba6eaf 100644 --- a/web/packages/studio/src/routes/IntakeLayout/index.tsx +++ b/web/packages/studio/src/routes/IntakeLayout/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { PageHeader, Stack, Tabs } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { Loading } from '@studio/components/Layouts/Loading'; import { ROUTES } from '@studio/constants/routes'; diff --git a/web/packages/studio/src/routes/JobDetailRoute/index.tsx b/web/packages/studio/src/routes/JobDetailRoute/index.tsx index 2e50f2a50a..954aaa83c9 100644 --- a/web/packages/studio/src/routes/JobDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/JobDetailRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { KVPair } from '@nemo/common/src/components/KVPair'; import { LogViewer } from '@nemo/common/src/components/LogViewer'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; @@ -9,7 +10,6 @@ import { useJobLogs } from '@nemo/common/src/hooks/useJobLogs'; import { getJobRefetchInterval } from '@nemo/common/src/utils/query'; import { useJobsGetJob, useJobsListJobResults } from '@nemo/sdk/generated/platform/api'; import { Flex, Grid, PageHeader, Panel, Spinner, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { CancelJobButton } from '@studio/components/CancelJobButton'; import { ROUTE_PARAMS } from '@studio/constants/routes'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/JobsRoute/index.tsx b/web/packages/studio/src/routes/JobsRoute/index.tsx index c1f4cd82e4..8dab85a09a 100644 --- a/web/packages/studio/src/routes/JobsRoute/index.tsx +++ b/web/packages/studio/src/routes/JobsRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { JobsDataView } from '@studio/components/dataViews/JobsDataView'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { FC } from 'react'; diff --git a/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx b/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx index fa993f6d3b..be6a19acb7 100644 --- a/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx +++ b/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { NewDataDesignerJobForm } from '@studio/components/NewDataDesignerJobForm'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx b/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx index bc9609f8a2..2039c38960 100644 --- a/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx +++ b/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { CreateFilesetStart } from '@studio/components/CreateFilesetStart'; import type { StartSelection } from '@studio/components/CreateFilesetStart/types'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx b/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx index 827fe61eeb..dd51ede5b1 100644 --- a/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx +++ b/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ChatEmptyState } from '@nemo/common/src/components/Chat/ChatEmptyState'; import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; @@ -25,9 +27,7 @@ import { Stack, Text, } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { queryClient } from '@studio/api/queryClient'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { Loading } from '@studio/components/Layouts/Loading'; import { ModelChat } from '@studio/components/ModelChat'; import { InContextLearningSection } from '@studio/components/PromptTuningForm/InContextLearningSection'; diff --git a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx index af185d47ae..58c7a922c2 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { useJobLogs } from '@nemo/common/src/hooks/useJobLogs'; import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema'; import { @@ -8,7 +9,6 @@ import { useSafeSynthesizerGetJobSuspense as useGetJobV1beta1SafeSynthesizerJobsJobIdGetSuspense, } from '@nemo/sdk/generated/safe-synthesizer/api'; import { Grid, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { SafeSynthesizerNavigation } from '@studio/components/SafeSynthesizerNavigation'; import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment'; import { ROUTE_PARAMS } from '@studio/constants/routes'; diff --git a/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx index 11284792f6..a90cb490cd 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { useSafeSynthesizerDownloadJobResultSummary as useDownloadJobResultSummaryV1beta1SafeSynthesizerJobsJobIdResultsSummaryDownloadGet, useSafeSynthesizerGetJobSuspense as useGetJobV1beta1SafeSynthesizerJobsJobIdGetSuspense, } from '@nemo/sdk/generated/safe-synthesizer/api'; import { Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { SafeSynthesizerNavigation } from '@studio/components/SafeSynthesizerNavigation'; import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment'; import { ROUTE_PARAMS } from '@studio/constants/routes'; diff --git a/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx index 3e14ffe8dd..b97747d5e5 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { SafeSynthesizerJobsDataView } from '@studio/components/dataViews/SafeSynthesizerJobsDataView'; import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx index 6de339e797..672071a786 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { useSafeSynthesizerCreateJob } from '@nemo/sdk/generated/safe-synthesizer/api'; import { Banner, Button, Divider, Flex, Panel, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx b/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx index dc3f00254e..f53d4135e2 100644 --- a/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx +++ b/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx @@ -11,6 +11,7 @@ */ import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; @@ -21,7 +22,6 @@ import { useSecretsCreateSecret, } from '@nemo/sdk/generated/platform/api'; import { Stack } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useQueryClient } from '@tanstack/react-query'; import { FC } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; diff --git a/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx b/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx index f173710532..6671c6f2f0 100644 --- a/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx +++ b/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx @@ -11,6 +11,7 @@ */ import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; @@ -21,7 +22,6 @@ import { } from '@nemo/sdk/generated/platform/api'; import { PlatformSecretResponse } from '@nemo/sdk/generated/platform/schema'; import { FormField, Stack, Text, TextInput } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useQueryClient } from '@tanstack/react-query'; import { FC } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; diff --git a/web/packages/studio/src/routes/SecretsListRoute/index.tsx b/web/packages/studio/src/routes/SecretsListRoute/index.tsx index bc2fdcbaeb..de9cddc8f0 100644 --- a/web/packages/studio/src/routes/SecretsListRoute/index.tsx +++ b/web/packages/studio/src/routes/SecretsListRoute/index.tsx @@ -10,8 +10,8 @@ * its affiliates is strictly prohibited. */ +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { SecretsDataView } from '@studio/components/dataViews/SecretsDataView'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx b/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx index ad5c9bcf2b..b2f8c361b5 100644 --- a/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx +++ b/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { VirtualModelsDataView } from '@studio/components/dataViews/VirtualModelsDataView'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx index 89b117628b..9dd5c34533 100644 --- a/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx @@ -15,6 +15,7 @@ import { type ModelEntityFilterInput, } from '@nemo/common/src/api/entity-store/useBaseModels'; import { usePromptTunableBaseModelIds } from '@nemo/common/src/api/entity-store/usePromptTunableBaseModelIds'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter'; import * as DataView from '@nemo/common/src/components/DataView/internal'; import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView'; @@ -35,7 +36,6 @@ import { Text, Tooltip, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { BaseModelCard } from '@studio/components/BaseModelCard'; import { CustomizeModelButton } from '@studio/components/dataViews/CustomModelsDataView/CustomizeModelButton'; import { ModelPanel, ModelPanelTab } from '@studio/components/sidePanels/ModelPanels/ModelPanel'; diff --git a/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx index be30020efe..b48a500f0b 100644 --- a/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { GradientBackground } from '@nemo/common/src/components/GradientBackground'; import ModelEvaluationIcon from '@nemo/common/src/svgs/model_evaluation.svg?react'; import ModelPromptTuningIcon from '@nemo/common/src/svgs/model_prompt_tuning.svg?react'; import SafeSynthesizerLogo from '@nemo/common/src/svgs/safe_synthesizer_logo.svg?react'; import { Grid, PageHeader, Stack, Text } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { CUSTOMIZER_ENABLED, EVALUATOR_ENABLED, diff --git a/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx index 3d5ac07f4b..125cf3e78a 100644 --- a/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { getEntitiesListWorkspaceMembersQueryKey, useEntitiesListWorkspaceMembers, @@ -11,7 +12,6 @@ import { import type { WorkspaceMember } from '@nemo/sdk/generated/platform/schema'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; import { queryClient } from '@studio/api/queryClient'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { MembersDataView } from '@studio/components/dataViews/MembersDataView'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; diff --git a/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx index 7a71a64d8c..1ecc1ce1ad 100644 --- a/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { DEFAULT_WORKSPACE } from '@nemo/common/src/models/constants'; import { Button, @@ -11,7 +12,6 @@ import { Stack, Text, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { INFERENCE_PROVIDER_ENABLED, diff --git a/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx index c2cdb66598..0136de7e29 100644 --- a/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema/AgentDeployment'; import { @@ -14,7 +15,6 @@ import { TabsTrigger, Text, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { getAgentModelNames } from '@studio/components/dataViews/AgentsDataView/utils'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { SubmitEvaluationModal } from '@studio/components/evaluation/SubmitEvaluationModal'; diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx index 6820847bf9..73faad3192 100644 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx +++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { AccordionPanel } from '@nemo/common/src/components/AccordionPanel'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { KVPair } from '@nemo/common/src/components/KVPair'; @@ -34,7 +35,6 @@ import { joinBundleByTask, parseBundleRef, } from '@studio/api/evaluation/agent-evaluations'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { AgentEvalTaskResultsPanel } from '@studio/components/evaluation/AgentEvalTaskResultsPanel'; import { EvalAggregateScoresTable } from '@studio/components/evaluation/EvalAggregateScoresTable'; import { StatusLogsContent } from '@studio/components/evaluation/Jobs/StatusLogsContent'; diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx index 880ab967cc..00a0b87810 100644 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx +++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { AgentEvaluationsDataView } from '@studio/components/dataViews/AgentEvaluationsDataView'; import { SubmitEvaluationModal } from '@studio/components/evaluation/SubmitEvaluationModal'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx index 1b300c48de..5050573ccf 100644 --- a/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { agentsListAgents } from '@nemo/sdk/generated/agents/api'; import { useFilesListFilesetFiles } from '@nemo/sdk/generated/platform/api'; import { Combobox, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { InferenceLogsTable } from '@studio/routes/agents/AgentMonitorRoute/components/InferenceLogsTable'; diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx index 37a6bdd928..ee19dfaa28 100644 --- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ColumnFilterPanel } from '@nemo/common/src/components/DataView/ColumnFilterPanel'; import { FilterPanel } from '@nemo/common/src/components/DataView/FilterPanel'; import { Root as DataView } from '@nemo/common/src/components/DataView/internal'; @@ -16,7 +17,6 @@ import { Text, TextInput, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { Loading } from '@studio/components/Layouts/Loading'; import { AgentGroupSection } from '@studio/routes/agents/AgentSuggestionsRoute/components/AgentGroupSection'; import { ApplyEvalConfigModal } from '@studio/routes/agents/AgentSuggestionsRoute/components/ApplyEvalConfigModal'; diff --git a/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx b/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx index 3ceb49cbca..e2a2654113 100644 --- a/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledSearchableSelect } from '@nemo/common/src/components/form/ControlledSearchableSelect'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal } from '@nemo/common/src/components/FormModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { getAgentsListAgentsQueryKey, useAgentsCreateAgent } from '@nemo/sdk/generated/agents/api'; import { useModelsListModels } from '@nemo/sdk/generated/platform/api'; -import { getErrorMessage } from '@studio/api/common/utils'; import { DEFAULT_LARGE_PAGE_SIZE } from '@studio/constants/constants'; import { applyModelToConfig, diff --git a/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx b/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx index 16034c8dcb..fbe7d27db8 100644 --- a/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledSearchableSelect } from '@nemo/common/src/components/form/ControlledSearchableSelect'; import { ControlledSelect } from '@nemo/common/src/components/form/ControlledSelect'; import { FormModal } from '@nemo/common/src/components/FormModal'; @@ -9,7 +10,6 @@ import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { getAgentsListAgentsQueryKey, useAgentsCreateAgent } from '@nemo/sdk/generated/agents/api'; import { useModelsListModels } from '@nemo/sdk/generated/platform/api'; import { loadSampleAgentConfig } from '@studio/api/agents/loadSampleAgentConfig'; -import { getErrorMessage } from '@studio/api/common/utils'; import { DEFAULT_LARGE_PAGE_SIZE } from '@studio/constants/constants'; import { buildSampleAgentName, diff --git a/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx index f4f5f75997..30d1bf07fa 100644 --- a/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import type { Agent } from '@nemo/sdk/generated/agents/schema/Agent'; import { Button, PageHeader, Stack, Text } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { AgentsTable, type AgentTableRow } from '@studio/components/dataViews/AgentsDataView'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx b/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx index d571dafa76..18db409ecc 100644 --- a/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx +++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Banner, Stack, Text } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { useCopilotChatContext } from '@studio/routes/agents/CopilotChatRoute/context/useCopilotChatContext'; diff --git a/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx b/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx index 37236bb6ba..89fde821d3 100644 --- a/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx +++ b/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { DocumentationButton } from '@studio/components/DocumentationButton'; import { Loading } from '@studio/components/Layouts/Loading'; import { LINK_EVAL_DOCS_BENCHMARKS_INDUSTRY } from '@studio/constants/links'; diff --git a/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx b/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx index 66e9ef02b8..020aac62ca 100644 --- a/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx +++ b/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { AccordionPanel } from '@nemo/common/src/components/AccordionPanel'; import { PlatformJobTerminalStatuses } from '@nemo/common/src/constants/query'; import { useEvaluatorGetEvaluateJob } from '@nemo/sdk/generated/evaluator/api'; @@ -15,7 +16,6 @@ import { Spinner, Stack, } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { EvalAggregateScoresTable } from '@studio/components/evaluation/EvalAggregateScoresTable'; import { DatasetEvalRowResultsPanel } from '@studio/components/evaluation/Jobs/datasetEval/DatasetEvalRowResultsPanel'; import { useDatasetEvalResults } from '@studio/components/evaluation/Jobs/datasetEval/useDatasetEvalResults'; diff --git a/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx b/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx index a4ae4221b0..dff85e77a2 100644 --- a/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx +++ b/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { Loading } from '@studio/components/Layouts/Loading'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { FC, Suspense, useEffect } from 'react'; diff --git a/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx b/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx index 5988ffdff7..ad632ce184 100644 --- a/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx +++ b/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { zodResolver } from '@hookform/resolvers/zod'; +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal } from '@nemo/common/src/components/FormModal'; import { ENTITY_NAME_HELP, entityNameSchema, toCopyName } from '@nemo/common/src/utils/entityName'; @@ -11,7 +12,6 @@ import type { GuardrailConfigInput, GuardrailConfigInputData, } from '@nemo/sdk/generated/platform/schema'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getGuardrailDetailRoute } from '@studio/routes/utils'; import { useQueryClient } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx index 613f0702eb..7e6839a0e1 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { Button, Flex, Panel, SegmentedControl, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useUpdateGuardrailCheck } from '@studio/api/guardrail-checks/hooks'; import type { GuardrailCheckEntity, diff --git a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx index 82435f815f..f44fa68432 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import type { RailsConfig } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, Tabs, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { useCreateGuardrailCheck, useRunGuardrailChecks } from '@studio/api/guardrail-checks/hooks'; import type { GuardrailCheckEntity } from '@studio/api/guardrail-checks/types'; import { GuardrailChecksDataView } from '@studio/components/dataViews/GuardrailChecksDataView'; diff --git a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx index f8b932d538..2e74f25dd5 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { useGuardrailsDeleteConfig } from '@nemo/sdk/generated/platform/api'; -import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { type QuickActionItem, QuickActionsMenuRoot, -} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot'; +} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; +import { useGuardrailsDeleteConfig } from '@nemo/sdk/generated/platform/api'; +import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema'; +import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { CreateGuardrailModal } from '@studio/routes/guardrails/CreateGuardrailModal'; import { getGuardrailsRoute } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx index 53eca27220..a55367d0fb 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { useGuardrailsGetGuardrailConfig } from '@nemo/sdk/generated/platform/api'; import { Flex, PageHeader, Stack, Tabs, Text } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { Loading } from '@studio/components/Layouts/Loading'; import { ROUTE_PARAMS, ROUTES } from '@studio/constants/routes'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx b/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx index 909fe3f4ae..77c280785a 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx @@ -10,13 +10,13 @@ * its affiliates is strictly prohibited. */ +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { getGuardrailsGetGuardrailConfigQueryKey, useGuardrailsDeleteConfig, } from '@nemo/sdk/generated/platform/api'; import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { GuardrailsDataView } from '@studio/components/dataViews/GuardrailsDataView'; import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx b/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx index be8cf509ea..b5c0b6a421 100644 --- a/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx +++ b/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; import { EditColumnsMenu } from '@nemo/common/src/components/DataView/internal'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; @@ -9,7 +10,6 @@ import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataView import { useListTraces } from '@nemo/sdk/generated/platform/api'; import type { Trace, TraceFilter } from '@nemo/sdk/generated/platform/schema'; import { Flex, Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { IntakeTelemetryDataView } from '@studio/components/IntakeLists/IntakeTelemetryDataView'; import { makeIntakeTraceColumns } from '@studio/components/IntakeLists/intakeTraceColumns'; import { getIntakeSessionTraceRoute } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx b/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx index 3a758188b7..46d59f5e00 100644 --- a/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx +++ b/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; +import { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage'; import { KVPair } from '@nemo/common/src/components/KVPair'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; @@ -23,8 +25,6 @@ import { useOptimizerUpdateInsight, type InsightStatus, } from '@studio/api/optimizer'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; -import { ExpandableMessage } from '@studio/components/ExpandableMessage'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { Loading } from '@studio/components/Layouts/Loading'; import { LINK_DOCS_STUDIO_EVALUATION } from '@studio/constants/links'; diff --git a/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx b/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx index 1836ccc573..e8ea760fa3 100644 --- a/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx +++ b/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/api/common/utils'; +import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, @@ -9,9 +11,7 @@ import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState'; import { Flex, PageHeader, Stack, Tag, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/api/common/utils'; import { type InsightListItem, useOptimizerListInsights } from '@studio/api/optimizer'; -import { AccessibleTitle } from '@studio/components/AccessibleTitle'; import { ErrorPanel } from '@studio/components/ErrorPanel'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 6048d1a4cc..e321d0ebdf 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -443,6 +443,9 @@ importers: p-limit: specifier: 'catalog:' version: 6.2.0 + react-dropzone: + specifier: 'catalog:' + version: 14.4.1(react@19.2.7) react-hook-form: specifier: 'catalog:' version: 7.71.2(react@19.2.7) From 5e3d01a10bd7159c37a702b69311135dd112cfe9 Mon Sep 17 00:00:00 2001 From: mschwab Date: Fri, 7 Aug 2026 15:12:59 -0700 Subject: [PATCH 2/8] feat(studio): add host.breadcrumbs to the plugin contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Studio's breadcrumb bar renders in GlobalNav, which sits in the layout's navbar grid area while a plugin renders in the content area several route levels below. A plugin therefore cannot render breadcrumbs itself; it can only write into the context GlobalNav consumes. That is the same shape as notifications writing to Studio's shared toaster, so expose it the same way, on the host handle. Studio clears the trail when the plugin unmounts rather than trusting each plugin to clean up, and the setter is a plain function instead of copying useBreadcrumbs' mount-only semantics — plugin detail routes need to update the trail as data arrives. Iron Swarm's routes import useBreadcrumbs directly today; this is what they move to. Signed-off-by: mschwab --- .../src/nemo_example_plugin/web/dist/index.js | 181 +++++++++--------- plugins/example-plugin/web/AGENTS.md | 6 +- .../example-plugin/web/src/SharedUiPage.tsx | 13 +- plugins/example-plugin/web/src/types.ts | 10 + .../src/plugins/PluginRenderer.test.tsx | 92 +++++++-- .../studio/src/plugins/PluginRenderer.tsx | 22 ++- web/packages/studio/src/plugins/types.ts | 16 ++ 7 files changed, 232 insertions(+), 108 deletions(-) diff --git a/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js b/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js index 28fb66c012..1c9df06b86 100644 --- a/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js +++ b/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js @@ -3,15 +3,15 @@ import { Button as e, Flex as t, Stack as n, Text as r } from "@nvidia/foundations-react-core"; import { NavLink as i, Navigate as a, Outlet as o, Route as s, Routes as c } from "react-router"; import { RelativeTime as l, StatusBadge as u, StudioDataView as d, TableEmptyState as f, useStudioDataViewState as p } from "@nemo/common"; -import { useCallback as m } from "react"; -import { jsx as h, jsxs as g } from "react/jsx-runtime"; +import { useCallback as m, useEffect as h } from "react"; +import { jsx as g, jsxs as _ } from "react/jsx-runtime"; //#region src/paths.ts -var _ = (e, t) => `/workspaces/${e}/plugin/example/${t}`, v = [ +var v = (e, t) => `/workspaces/${e}/plugin/example/${t}`, y = [ "overview", "auth", "workspace", "shared-ui" -], y = { +], b = { overview: "Overview", auth: "Auth", workspace: "Workspace", @@ -19,11 +19,18 @@ var _ = (e, t) => `/workspaces/${e}/plugin/example/${t}`, v = [ }; //#endregion //#region src/SharedUiPage.tsx -function b({ host: e }) { +function x({ host: e }) { let { data: t, isPending: i, isError: a } = e.sdk.platform.useEntitiesListWorkspaces({ page: 1, page_size: 100 - }), o = t?.data ?? [], s = p(), c = m((e) => [ + }), o = t?.data ?? [], { set: s } = e.breadcrumbs, { workspaceId: c } = e; + h(() => { + s([{ + label: "Example Plugin", + href: v(c, "overview") + }, { label: "Shared UI" }]); + }, [s, c]); + let y = p(), b = m((e) => [ e.accessor("name", { header: "Name", size: 240 @@ -32,42 +39,42 @@ function b({ host: e }) { id: "status", header: "Status", size: 120, - cell: ({ row: e }) => /* @__PURE__ */ h(u, { status: e.original.status ?? "ready" }) + cell: ({ row: e }) => /* @__PURE__ */ g(u, { status: e.original.status ?? "ready" }) }), e.display({ id: "created", header: "Created", size: 160, - cell: ({ row: e }) => e.original.created_at ? /* @__PURE__ */ h(l, { datetime: e.original.created_at }) : /* @__PURE__ */ h(r, { + cell: ({ row: e }) => e.original.created_at ? /* @__PURE__ */ g(l, { datetime: e.original.created_at }) : /* @__PURE__ */ g(r, { kind: "body/regular/sm", color: "secondary", children: "—" }) }) ], []); - return /* @__PURE__ */ g(n, { + return /* @__PURE__ */ _(n, { gap: "2", children: [ - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "label/bold/md", children: "Shared UI" }), - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "body/regular/sm", color: "secondary", children: "This table is Studio's own StudioDataView, imported from @nemo/common and resolved through Studio's import map — not a copy bundled into the plugin." }), - a ? /* @__PURE__ */ h(f, { + a ? /* @__PURE__ */ g(f, { header: "Couldn't load workspaces", emptyMessage: "The request failed. Try again." - }) : !i && o.length === 0 ? /* @__PURE__ */ h(f, { + }) : !i && o.length === 0 ? /* @__PURE__ */ g(f, { header: "No workspaces", emptyMessage: "Create a workspace to see it listed here." - }) : /* @__PURE__ */ h(n, { + }) : /* @__PURE__ */ g(n, { className: "min-h-[320px]", - children: /* @__PURE__ */ h(d, { - dataViewState: s, - makeColumns: c, + children: /* @__PURE__ */ g(d, { + dataViewState: y, + makeColumns: b, attributes: { DataViewRoot: { data: o, totalCount: o.length, @@ -80,103 +87,103 @@ function b({ host: e }) { } //#endregion //#region src/Root.tsx -function x({ host: e }) { - return /* @__PURE__ */ h(c, { children: /* @__PURE__ */ g(s, { - element: /* @__PURE__ */ h(S, { workspaceId: e.workspaceId }), +function S({ host: e }) { + return /* @__PURE__ */ g(c, { children: /* @__PURE__ */ _(s, { + element: /* @__PURE__ */ g(C, { workspaceId: e.workspaceId }), children: [ - /* @__PURE__ */ h(s, { + /* @__PURE__ */ g(s, { index: !0, - element: /* @__PURE__ */ h(a, { - to: _(e.workspaceId, "overview"), + element: /* @__PURE__ */ g(a, { + to: v(e.workspaceId, "overview"), replace: !0 }) }), - /* @__PURE__ */ h(s, { + /* @__PURE__ */ g(s, { path: "overview", - element: /* @__PURE__ */ h(w, { host: e }) + element: /* @__PURE__ */ g(T, { host: e }) }), - /* @__PURE__ */ h(s, { + /* @__PURE__ */ g(s, { path: "auth", - element: /* @__PURE__ */ h(T, { getAccessToken: e.auth.getAccessToken }) + element: /* @__PURE__ */ g(E, { getAccessToken: e.auth.getAccessToken }) }), - /* @__PURE__ */ h(s, { + /* @__PURE__ */ g(s, { path: "workspace", - element: /* @__PURE__ */ h(E, { workspaceId: e.workspaceId }) + element: /* @__PURE__ */ g(D, { workspaceId: e.workspaceId }) }), - /* @__PURE__ */ h(s, { + /* @__PURE__ */ g(s, { path: "shared-ui", - element: /* @__PURE__ */ h(b, { host: e }) + element: /* @__PURE__ */ g(x, { host: e }) }), - /* @__PURE__ */ h(s, { + /* @__PURE__ */ g(s, { path: "*", - element: /* @__PURE__ */ h(D, {}) + element: /* @__PURE__ */ g(O, {}) }) ] }) }); } -function S({ workspaceId: e }) { +function C({ workspaceId: e }) { let r = ({ isActive: e }) => `px-3 py-1 rounded text-sm font-medium transition-colors ${e ? "text-primary bg-surface-hover" : "text-subtle hover:text-primary"}`; - return /* @__PURE__ */ g(n, { + return /* @__PURE__ */ _(n, { gap: "4", className: "h-full p-4", - children: [/* @__PURE__ */ h(t, { + children: [/* @__PURE__ */ g(t, { gap: "2", className: "border-b border-subtle pb-2", - children: v.map((t) => /* @__PURE__ */ h(i, { - to: _(e, t), + children: y.map((t) => /* @__PURE__ */ g(i, { + to: v(e, t), className: r, - children: y[t] + children: b[t] }, t)) - }), /* @__PURE__ */ h("div", { + }), /* @__PURE__ */ g("div", { className: "flex-1", - children: /* @__PURE__ */ h(o, {}) + children: /* @__PURE__ */ g(o, {}) })] }); } -function C({ children: e }) { - return /* @__PURE__ */ h("pre", { +function w({ children: e }) { + return /* @__PURE__ */ g("pre", { className: "bg-surface-sunken text-subtle rounded p-3 text-xs overflow-x-auto font-mono", children: e }); } -function w({ host: i }) { +function T({ host: i }) { let { data: a, isPending: o, isError: s } = i.sdk.platform.useEntitiesListWorkspaces({ page: 1, page_size: 100 }, { query: { staleTime: 5e3 } }), c = a?.data ?? []; - return /* @__PURE__ */ g(n, { + return /* @__PURE__ */ _(n, { gap: "2", children: [ - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "label/bold/lg", children: "Example Plugin" }), - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "body/regular/sm", color: "secondary", children: "This is an example Studio plugin. Use the tabs above or the Studio side nav to explore what information is available to a plugin at runtime." }), - /* @__PURE__ */ g(n, { + /* @__PURE__ */ _(n, { gap: "1", children: [ - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "label/bold/sm", children: "Shared SDK" }), - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "body/regular/xs", color: "secondary", children: "Listed via Studio's sdk.platform.useEntitiesListWorkspaces() — the platform's typed hook, running on Studio's authenticated axios and shared QueryClient rather than a plugin copy." }), - o ? /* @__PURE__ */ h(r, { + o ? /* @__PURE__ */ g(r, { kind: "body/regular/xs", color: "secondary", children: "Loading…" - }) : s ? /* @__PURE__ */ h(r, { + }) : s ? /* @__PURE__ */ g(r, { kind: "body/regular/xs", color: "danger", children: "Request failed." - }) : /* @__PURE__ */ g(r, { + }) : /* @__PURE__ */ _(r, { kind: "body/regular/sm", children: [ c.length, @@ -187,27 +194,27 @@ function w({ host: i }) { }) ] }), - /* @__PURE__ */ g(n, { + /* @__PURE__ */ _(n, { gap: "1", children: [ - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "label/bold/sm", children: "Host capabilities" }), - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "body/regular/xs", color: "secondary", children: "Studio's notifications, telemetry, and navigation, all off the host handle — no plugin-side setup." }), - /* @__PURE__ */ g(t, { + /* @__PURE__ */ _(t, { gap: "2", - children: [/* @__PURE__ */ h(e, { + children: [/* @__PURE__ */ g(e, { kind: "secondary", onClick: () => { i.notifications.notify("Toast from the example plugin", "success"), i.telemetry.event("overview_notify_clicked"); }, children: "Notify" - }), /* @__PURE__ */ h(e, { + }), /* @__PURE__ */ g(e, { kind: "secondary", onClick: () => i.navigation.navigate(`/workspaces/${i.workspaceId}/base-models`), children: "Go to Base Models" @@ -218,37 +225,37 @@ function w({ host: i }) { ] }); } -function T({ getAccessToken: e }) { +function E({ getAccessToken: e }) { let t = e(), i = null; try { let e = t.split(".")[1]; e && (i = JSON.parse(atob(e.replace(/-/g, "+").replace(/_/g, "/")))); } catch {} - return /* @__PURE__ */ g(n, { + return /* @__PURE__ */ _(n, { gap: "3", children: [ - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "label/bold/md", children: "Auth" }), - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "body/regular/sm", color: "secondary", children: "Studio passes an OIDC access token to every plugin via the plugin's auth prop. Call getAccessToken() per request — it returns the current token after silent renewal — and use it as a Bearer token." }), - /* @__PURE__ */ g(n, { + /* @__PURE__ */ _(n, { gap: "1", - children: [/* @__PURE__ */ h(r, { + children: [/* @__PURE__ */ g(r, { kind: "label/bold/sm", children: "Example API call" - }), /* @__PURE__ */ h(C, { children: "fetch('/apis/v1/workspaces', {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })] + }), /* @__PURE__ */ g(w, { children: "fetch('/apis/v1/workspaces', {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })] }), - /* @__PURE__ */ g(n, { + /* @__PURE__ */ _(n, { gap: "1", - children: [/* @__PURE__ */ h(r, { + children: [/* @__PURE__ */ g(r, { kind: "label/bold/sm", children: "Token claims (decoded, not verified)" - }), i ? /* @__PURE__ */ h(C, { children: JSON.stringify(i, null, 2) }) : /* @__PURE__ */ h(r, { + }), i ? /* @__PURE__ */ g(w, { children: JSON.stringify(i, null, 2) }) : /* @__PURE__ */ g(r, { kind: "body/regular/xs", color: "secondary", children: t ? "Could not decode token." : "No token provided." @@ -257,38 +264,38 @@ function T({ getAccessToken: e }) { ] }); } -function E({ workspaceId: e }) { - return /* @__PURE__ */ g(n, { +function D({ workspaceId: e }) { + return /* @__PURE__ */ _(n, { gap: "3", children: [ - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "label/bold/md", children: "Workspace" }), - /* @__PURE__ */ h(r, { + /* @__PURE__ */ g(r, { kind: "body/regular/sm", color: "secondary", children: "Studio passes the current workspace ID to every plugin via the plugin's workspaceId prop." }), - /* @__PURE__ */ g(n, { + /* @__PURE__ */ _(n, { gap: "1", - children: [/* @__PURE__ */ h(r, { + children: [/* @__PURE__ */ g(r, { kind: "label/bold/sm", children: "Current workspace" - }), /* @__PURE__ */ h(C, { children: e })] + }), /* @__PURE__ */ g(w, { children: e })] }), - /* @__PURE__ */ g(n, { + /* @__PURE__ */ _(n, { gap: "1", - children: [/* @__PURE__ */ h(r, { + children: [/* @__PURE__ */ g(r, { kind: "label/bold/sm", children: "Example API call scoped to this workspace" - }), /* @__PURE__ */ h(C, { children: "fetch(`/apis/v1/workspaces/${workspaceId}/models`, {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })] + }), /* @__PURE__ */ g(w, { children: "fetch(`/apis/v1/workspaces/${workspaceId}/models`, {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })] }) ] }); } -function D() { - return /* @__PURE__ */ h(r, { +function O() { + return /* @__PURE__ */ g(r, { kind: "body/regular/sm", color: "secondary", children: "Page not found." @@ -296,34 +303,34 @@ function D() { } //#endregion //#region src/Nav.tsx -var O = (e) => [{ +var k = (e) => [{ group: "Example Plugin", items: [ { id: "example-overview", iconName: "flask-conical", label: "Overview", - href: _(e, "overview") + href: v(e, "overview") }, { id: "example-auth", iconName: "key-round", label: "Auth", - href: _(e, "auth") + href: v(e, "auth") }, { id: "example-workspace", iconName: "building-2", label: "Workspace", - href: _(e, "workspace") + href: v(e, "workspace") }, { id: "example-shared-ui", iconName: "table", label: "Shared UI", - href: _(e, "shared-ui") + href: v(e, "shared-ui") } ] }]; //#endregion -export { x as Root, O as navItems }; +export { S as Root, k as navItems }; diff --git a/plugins/example-plugin/web/AGENTS.md b/plugins/example-plugin/web/AGENTS.md index f401cf2f31..1a814a5198 100644 --- a/plugins/example-plugin/web/AGENTS.md +++ b/plugins/example-plugin/web/AGENTS.md @@ -35,7 +35,10 @@ Studio injects everything a plugin needs through a **single `host` prop** handle without changing `Root`'s signature. Destructure what you use. All are backed by Studio's own singletons: `notifications` fires into Studio's shared toaster, `telemetry` logs to Studio's OTEL pipeline (auto-scoped to the plugin), -`navigation` drives Studio's shared router. +`navigation` drives Studio's shared router, and `breadcrumbs` writes Studio's +breadcrumb bar — which lives in GlobalNav, *outside* the plugin's subtree, so a +plugin cannot render it itself. Studio clears the trail when the plugin +unmounts, so don't hand-roll cleanup. See `src/SharedUiPage.tsx`. `@tanstack/react-query` **is** shared — call `useQuery`/`useMutation` and it reads Studio's `QueryClientProvider` (one cache across Studio and every plugin). Put the @@ -94,6 +97,7 @@ import { StudioDataView, useStudioDataViewState } from '@nemo/common'; navigation: { navigate: (to: string) => void; back: () => void }; notifications: { notify: (message: string, type?: 'success'|'error'|'info'|'warning') => void }; telemetry: { info; warn; error: (m, cause?) => void; event: (name, attrs?) => void }; + breadcrumbs: { set: (trail: { label: string; href?: string }[]) => void }; }; } ``` diff --git a/plugins/example-plugin/web/src/SharedUiPage.tsx b/plugins/example-plugin/web/src/SharedUiPage.tsx index ea71cb18e3..4a03fcd982 100644 --- a/plugins/example-plugin/web/src/SharedUiPage.tsx +++ b/plugins/example-plugin/web/src/SharedUiPage.tsx @@ -9,7 +9,8 @@ import { useStudioDataViewState, } from "@nemo/common"; import { Stack, Text } from "@nvidia/foundations-react-core"; -import { useCallback, type ComponentProps } from "react"; +import { useCallback, useEffect, type ComponentProps } from "react"; +import { pluginPath } from "./paths"; import type { PluginHost, Workspace } from "./types"; // `@nemo/common` is external like react — this is Studio's own StudioDataView, @@ -22,6 +23,16 @@ export function SharedUiPage({ host }: { host: PluginHost }) { }); const workspaces = data?.data ?? []; + // Renders in Studio's chrome, outside this subtree. Studio clears it on unmount. + const { set: setBreadcrumbs } = host.breadcrumbs; + const { workspaceId } = host; + useEffect(() => { + setBreadcrumbs([ + { label: "Example Plugin", href: pluginPath(workspaceId, "overview") }, + { label: "Shared UI" }, + ]); + }, [setBreadcrumbs, workspaceId]); + // Syncs to URL search params — one DataView per route or they fight. const dataViewState = useStudioDataViewState(); diff --git a/plugins/example-plugin/web/src/types.ts b/plugins/example-plugin/web/src/types.ts index 2efe153219..d45ac94a74 100644 --- a/plugins/example-plugin/web/src/types.ts +++ b/plugins/example-plugin/web/src/types.ts @@ -39,6 +39,15 @@ export interface PluginNotifications { notify: (message: string, type?: NotificationType) => void; } +export interface PluginBreadcrumb { + label: string; + href?: string; +} + +export interface PluginBreadcrumbs { + set: (trail: PluginBreadcrumb[]) => void; +} + export interface PluginTelemetry { info: (message: string, cause?: unknown) => void; warn: (message: string, cause?: unknown) => void; @@ -56,6 +65,7 @@ export interface PluginHost { navigation: PluginNavigation; notifications: PluginNotifications; telemetry: PluginTelemetry; + breadcrumbs: PluginBreadcrumbs; } export interface PluginRootProps { diff --git a/web/packages/studio/src/plugins/PluginRenderer.test.tsx b/web/packages/studio/src/plugins/PluginRenderer.test.tsx index 8b41a2b492..721d55e6d0 100644 --- a/web/packages/studio/src/plugins/PluginRenderer.test.tsx +++ b/web/packages/studio/src/plugins/PluginRenderer.test.tsx @@ -4,7 +4,12 @@ import { usePlugins, usePluginsLoaded } from '@studio/plugins/PluginContext'; import { PluginRenderer } from '@studio/plugins/PluginRenderer'; import type { LoadedPlugin, PluginRootProps } from '@studio/plugins/types'; -import { render, screen } from '@testing-library/react'; +import { BreadcrumbsProvider } from '@studio/providers/breadcrumbs/BreadcrumbsProvider'; +import { + useBreadcrumbs, + type BreadcrumbsItemProps, +} from '@studio/providers/breadcrumbs/useBreadcrumbs'; +import { act, render, screen } from '@testing-library/react'; import { useEffect } from 'react'; import { MemoryRouter, Route, Routes } from 'react-router'; @@ -52,11 +57,13 @@ vi.mock('@studio/hooks/useWorkspaceFromPath', () => ({ function renderPlugin(pluginName = 'test-plugin') { return render( - - - } /> - - + + + + } /> + + + ); } @@ -73,11 +80,16 @@ describe('PluginRenderer', () => { vi.mocked(usePlugins).mockReturnValue([]); render( - - - } /> - - + + + + } + /> + + + ); expect(screen.getByText(/loading/i)).toBeInTheDocument(); @@ -106,11 +118,16 @@ describe('PluginRenderer', () => { authState.accessToken = 'renewed-token'; rerender( - - - } /> - - + + + + } + /> + + + ); expect(mountSpy).toHaveBeenCalledTimes(1); @@ -142,4 +159,47 @@ describe('PluginRenderer', () => { consoleError.mockRestore(); }); + + it('writes a plugin breadcrumb trail into Studio and clears it on unmount', () => { + const seen: BreadcrumbsItemProps[][] = []; + function BreadcrumbSpy() { + seen.push(useBreadcrumbs().breadcrumbs); + return null; + } + vi.mocked(usePlugins).mockReturnValue([makePlugin('test-plugin')]); + + // The provider and spy outlive the plugin subtree, so the cleared trail is + // still observable after the plugin itself unmounts. + const tree = (mounted: boolean) => ( + + + {mounted ? ( + + + } + /> + + + ) : null} + + ); + const { rerender } = render(tree(true)); + + act(() => + capturedProps?.host.breadcrumbs.set([ + { label: 'Runs', href: '/workspaces/ws1/plugin/test-plugin/runs' }, + { label: 'Run 7' }, + ]) + ); + expect(seen.at(-1)).toEqual([ + { slotLabel: 'Runs', href: '/workspaces/ws1/plugin/test-plugin/runs' }, + { slotLabel: 'Run 7', href: undefined }, + ]); + + rerender(tree(false)); + // Studio clears the trail itself; a plugin that forgets cannot leave one behind. + expect(seen.at(-1)).toEqual([]); + }); }); diff --git a/web/packages/studio/src/plugins/PluginRenderer.tsx b/web/packages/studio/src/plugins/PluginRenderer.tsx index 8d23e9084c..95621f974f 100644 --- a/web/packages/studio/src/plugins/PluginRenderer.tsx +++ b/web/packages/studio/src/plugins/PluginRenderer.tsx @@ -6,9 +6,15 @@ import * as platformSdk from '@nemo/sdk/generated/platform/api'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { usePlugins, usePluginsLoaded } from '@studio/plugins/PluginContext'; import { PluginErrorBoundary } from '@studio/plugins/PluginErrorBoundary'; -import type { PluginHost, PluginSdk, PluginTelemetry } from '@studio/plugins/types'; +import type { + PluginBreadcrumb, + PluginHost, + PluginSdk, + PluginTelemetry, +} from '@studio/plugins/types'; +import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { logger } from '@studio/util/logger'; -import { useCallback, useMemo, useRef, type ReactElement } from 'react'; +import { useCallback, useEffect, useMemo, useRef, type ReactElement } from 'react'; import { useAuth } from 'react-oidc-context'; import { useNavigate, useParams } from 'react-router'; @@ -41,6 +47,15 @@ export const PluginRenderer = (): ReactElement => { accessTokenRef.current = accessToken; const getAccessToken = useCallback(() => accessTokenRef.current, []); + const { setBreadcrumbs } = useBreadcrumbs(); + const setPluginBreadcrumbs = useCallback( + (trail: PluginBreadcrumb[]) => + setBreadcrumbs(trail.map(({ label, href }) => ({ slotLabel: label, href }))), + [setBreadcrumbs] + ); + // Studio owns the cleanup so a plugin can't leave a stale trail behind. + useEffect(() => () => setBreadcrumbs([]), [setBreadcrumbs]); + const host = useMemo( () => ({ workspaceId: workspace, @@ -49,8 +64,9 @@ export const PluginRenderer = (): ReactElement => { navigation: { navigate: (to) => navigate(to), back: () => navigate(-1) }, notifications: { notify: (message, type = 'info') => toast[type](message) }, telemetry: makeTelemetry(pluginName ?? 'unknown'), + breadcrumbs: { set: setPluginBreadcrumbs }, }), - [workspace, accessToken, getAccessToken, navigate, toast, pluginName] + [workspace, accessToken, getAccessToken, navigate, toast, pluginName, setPluginBreadcrumbs] ); if (!isLoaded) { diff --git a/web/packages/studio/src/plugins/types.ts b/web/packages/studio/src/plugins/types.ts index 9cc4014b26..6ba016e6f1 100644 --- a/web/packages/studio/src/plugins/types.ts +++ b/web/packages/studio/src/plugins/types.ts @@ -25,6 +25,21 @@ export interface PluginNotifications { notify: (message: string, type?: NotificationType) => void; } +export interface PluginBreadcrumb { + label: string; + /** Absolute Studio path; omit for the trailing (current) crumb. */ + href?: string; +} + +/** + * Write into Studio's breadcrumb bar, which renders in GlobalNav — outside the + * plugin's own subtree, so a plugin cannot render it itself. Studio clears the + * trail when the plugin unmounts. + */ +export interface PluginBreadcrumbs { + set: (trail: PluginBreadcrumb[]) => void; +} + /** Structured logging to Studio's OTEL pipeline, auto-scoped to the plugin. */ export interface PluginTelemetry { info: (message: string, cause?: unknown) => void; @@ -45,6 +60,7 @@ export interface PluginHost { navigation: PluginNavigation; notifications: PluginNotifications; telemetry: PluginTelemetry; + breadcrumbs: PluginBreadcrumbs; } export interface PluginRootProps { From 7e4b23aefee24d9403b7f0778596ed192959aaf7 Mon Sep 17 00:00:00 2001 From: mschwab Date: Fri, 7 Aug 2026 15:45:37 -0700 Subject: [PATCH 3/8] refactor(studio): move ConfirmationModal, ErrorPanel, CancelJobButton and the logger to @nemo/common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the shared-UI promotion. These four were blocked on Studio's OTEL logger, which imports constants/environment and pulls in the whole feature-flag chain, so move the logger too — but sever it from that config rather than dragging it along. The service name arrives via configureLogger(), which App.tsx calls at bootstrap; logVersion stays in Studio because it needs VERSION_SHA. CancelJobButton read the workspace off the URL via useWorkspaceFromPath. It now takes it as a prop, which is what let it move and is better regardless — a component that reads the router is harder to reuse. Both call sites already had the workspace in scope. Also relocates getErrorMessage out of NewDataDesignerJobForm/utils. It is seven dependency-free lines with nothing to do with Data Designer, and importing it was what dragged that 206-line module — and the logger chain behind it — into anything that wanted a formatted error string. Two of the four are deliberately not exported to plugins. CancelJobButton calls the jobs API through the generated SDK, and ErrorPanel formats errors through the axios-aware helper; exporting either put axios and oidc-client-ts back into the shared vendor bundle, which is the same correctness problem as before — plugins get their SDK on host.sdk so there is exactly one authenticated client. Both still live in common for Studio's own use. The bundle lands at 339 kB with axios absent. Signed-off-by: mschwab --- web/packages/common/package.json | 1 + .../src/components/CancelJobButton/index.tsx | 10 +++++--- .../components}/ConfirmationModal/index.tsx | 4 ++-- .../DeleteConfirmationModal/index.tsx | 2 +- .../src/components/ErrorPanel/index.tsx | 2 +- web/packages/common/src/plugin.ts | 6 +++++ web/packages/common/src/utils/error.ts | 9 ++++++++ .../src/utils}/forms/error.test.ts | 6 ++--- .../util => common/src/utils}/forms/error.ts | 2 +- .../src/util => common/src/utils}/logger.ts | 23 ++++++++----------- web/packages/studio/src/App.tsx | 5 +++- .../src/api/datasets/useDatasetFileContent.ts | 2 +- .../src/components/BulkDeleteModal/index.tsx | 2 +- .../CreateFilesetStart/useDescribeWithAi.ts | 2 +- .../CustomizationFilesetCreateModal/index.tsx | 2 +- .../components/DatasetCreateModal/index.tsx | 2 +- .../src/components/DatasetInputFile/index.tsx | 2 +- .../components/DatasetsTable/index.test.tsx | 2 +- .../src/components/DatasetsTable/index.tsx | 2 +- .../ExperimentCreateModal/index.tsx | 2 +- .../CreateFileSplitsModal/index.tsx | 2 +- .../DirectoryQuickActions/index.tsx | 2 +- .../FilesTable/FileQuickActions/index.tsx | 2 +- .../FilesTable/RenameFileModal/index.tsx | 2 +- .../FilesTable/TransformFileModal/index.tsx | 2 +- .../components/FilesetActionMenu/index.tsx | 4 ++-- .../components/FilesetCreateModal/index.tsx | 2 +- .../components/FileActions/index.tsx | 2 +- .../studio/src/components/ModelChat/index.tsx | 2 +- .../useModelComparePrompts.ts | 2 +- .../JobRequestGenerator.tsx | 2 +- .../NewDataDesignerJobForm/usePreview.ts | 2 +- .../NewDataDesignerJobForm/utils.test.ts | 2 +- .../NewDataDesignerJobForm/utils.ts | 7 ------ .../ImportFromDatasetModal/index.tsx | 2 +- .../PromptTuningForm/ToolsSection/index.tsx | 2 +- .../components/WorkspaceCreateModal/index.tsx | 2 +- .../components/WorkspaceDropdown/index.tsx | 2 +- .../src/components/common/SearchBar/index.tsx | 2 +- .../dataViews/AgentsDataView/index.tsx | 2 +- .../dataViews/CustomModelsDataView/index.tsx | 2 +- .../dataViews/DeploymentsDataView/index.tsx | 2 +- .../dataViews/GuardrailsDataView/index.tsx | 2 +- .../InferenceProvidersDataView/index.tsx | 4 ++-- .../dataViews/JobsDataView/index.tsx | 11 ++++++--- .../dataViews/SecretsDataView/index.tsx | 4 ++-- .../dataViews/VirtualModelsDataView/index.tsx | 4 ++-- .../form/InputFile/useInputFile.ts | 2 +- .../components/evaluation/Jobs/ActionMenu.tsx | 2 +- .../evaluation/Jobs/DetailsPanel.tsx | 2 +- .../Jobs/EvaluationJobBulkDeleteModal.tsx | 2 +- .../filesets/AddToFolderModal/index.tsx | 2 +- .../DatasetFileDropzone/index.tsx | 2 +- .../NewDirectoryModal/index.tsx | 2 +- .../filesets/hooks/useBulkDownload.ts | 2 +- .../filesets/hooks/useBulkDuplicate.ts | 2 +- .../CancelJobButton.test.tsx} | 4 ++-- .../DeleteConfirmationModal.test.tsx} | 2 +- .../ErrorPanel.test.tsx} | 2 +- .../sidePanels/MetricRunSidePanel/index.tsx | 2 +- .../ModelPanels/ModelPanel/index.tsx | 2 +- web/packages/studio/src/main.tsx | 2 +- .../src/plugins/PluginErrorBoundary.tsx | 2 +- .../studio/src/plugins/PluginRenderer.tsx | 2 +- web/packages/studio/src/plugins/utils.ts | 2 +- .../src/routes/AuthSuccessRoute/index.tsx | 2 +- .../src/routes/DeploymentsListRoute/index.tsx | 2 +- .../useDeleteDeploymentAndConfig.ts | 2 +- .../ActionMenu/index.test.tsx | 2 +- .../FilesetListRoute/ActionMenu/index.tsx | 4 ++-- .../src/routes/FilesetNewRoute/index.tsx | 2 +- .../src/routes/JobDetailRoute/index.tsx | 4 ++-- .../SafeSynthesizerNewRoute/index.test.tsx | 6 ++--- .../routes/SafeSynthesizerNewRoute/index.tsx | 2 +- .../WorkspaceLayout/WorkspaceSideNav.tsx | 2 +- .../WorkspaceMemberModal.tsx | 2 +- .../routes/WorkspaceMembersRoute/index.tsx | 2 +- .../DeleteWorkspaceModal/index.tsx | 2 +- .../EditDescriptionModal/index.tsx | 2 +- .../AgentDetailRoute/DeploymentLogsView.tsx | 2 +- .../routes/agents/AgentDetailRoute/index.tsx | 2 +- .../agents/AgentSuggestionsRoute/api.ts | 2 +- .../useOptimizerSuggestions.ts | 2 +- .../agents/AgentSuggestionsRoute/utils.ts | 2 +- .../ChatThreadErrorBoundary.tsx | 2 +- .../historyPanel/HistoryPanelContents.tsx | 2 +- .../agents/CopilotChatRoute/stream.test.ts | 2 +- .../routes/agents/CopilotChatRoute/stream.ts | 2 +- .../studio/src/routes/groups/agentRoutes.tsx | 2 +- .../src/routes/groups/anonymizerRoutes.tsx | 2 +- .../src/routes/groups/customizationRoutes.tsx | 2 +- .../src/routes/groups/dashboardRoutes.tsx | 2 +- .../src/routes/groups/dataDesignerRoutes.tsx | 2 +- .../src/routes/groups/deploymentRoutes.tsx | 2 +- .../src/routes/groups/evaluationRoutes.tsx | 2 +- .../src/routes/groups/experimentRoutes.tsx | 2 +- .../src/routes/groups/filesetRoutes.tsx | 2 +- .../src/routes/groups/guardrailsRoutes.tsx | 2 +- .../routes/groups/inferenceProviderRoutes.tsx | 2 +- .../studio/src/routes/groups/intakeRoutes.tsx | 2 +- .../studio/src/routes/groups/jobRoutes.tsx | 2 +- .../studio/src/routes/groups/memberRoutes.tsx | 2 +- .../src/routes/groups/modelCompareRoutes.tsx | 2 +- .../src/routes/groups/optimizerRoutes.tsx | 2 +- .../routes/groups/safeSynthesizerRoutes.tsx | 2 +- .../src/routes/groups/secretsRoutes.tsx | 2 +- .../src/routes/groups/settingsRoutes.tsx | 2 +- .../src/routes/groups/virtualModelsRoutes.tsx | 2 +- .../GuardrailDetailActions.tsx | 2 +- .../guardrails/GuardrailsRoute/index.tsx | 2 +- web/packages/studio/src/routes/index.tsx | 2 +- .../routes/optimizer/OptimizerRoute/index.tsx | 2 +- web/packages/studio/src/util/files.ts | 2 +- web/packages/studio/src/util/llm.ts | 2 +- web/packages/studio/src/util/logVersion.ts | 11 +++++++++ web/packages/studio/src/util/logger.test.ts | 3 ++- web/packages/studio/src/util/strings.ts | 2 +- .../studio/src/workers/LargeFileWorker.ts | 2 +- web/pnpm-lock.yaml | 3 +++ 119 files changed, 181 insertions(+), 148 deletions(-) rename web/packages/{studio => common}/src/components/CancelJobButton/index.tsx (93%) rename web/packages/{studio/src/components/modals => common/src/components}/ConfirmationModal/index.tsx (96%) rename web/packages/{studio => common}/src/components/DeleteConfirmationModal/index.tsx (94%) rename web/packages/{studio => common}/src/components/ErrorPanel/index.tsx (98%) create mode 100644 web/packages/common/src/utils/error.ts rename web/packages/{studio/src/util => common/src/utils}/forms/error.test.ts (94%) rename web/packages/{studio/src/util => common/src/utils}/forms/error.ts (94%) rename web/packages/{studio/src/util => common/src/utils}/logger.ts (83%) rename web/packages/studio/src/components/{CancelJobButton/index.test.tsx => promoted/CancelJobButton.test.tsx} (95%) rename web/packages/studio/src/components/{DeleteConfirmationModal/index.test.tsx => promoted/DeleteConfirmationModal.test.tsx} (96%) rename web/packages/studio/src/components/{ErrorPanel/index.test.tsx => promoted/ErrorPanel.test.tsx} (98%) create mode 100644 web/packages/studio/src/util/logVersion.ts diff --git a/web/packages/common/package.json b/web/packages/common/package.json index f97cd95df1..bfa768d37b 100644 --- a/web/packages/common/package.json +++ b/web/packages/common/package.json @@ -78,6 +78,7 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@opentelemetry/api-logs": "^0.219.0", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-table": "8.20.5", "@tanstack/react-virtual": "3.13.6", diff --git a/web/packages/studio/src/components/CancelJobButton/index.tsx b/web/packages/common/src/components/CancelJobButton/index.tsx similarity index 93% rename from web/packages/studio/src/components/CancelJobButton/index.tsx rename to web/packages/common/src/components/CancelJobButton/index.tsx index db4d29f77d..881a5ee86c 100644 --- a/web/packages/studio/src/components/CancelJobButton/index.tsx +++ b/web/packages/common/src/components/CancelJobButton/index.tsx @@ -12,22 +12,26 @@ import { } from '@nemo/sdk/generated/platform/api'; import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Text } from '@nvidia/foundations-react-core'; -import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useQueryClient } from '@tanstack/react-query'; import { X } from 'lucide-react'; import { FC, MouseEvent, useState } from 'react'; interface CancelJobButtonProps { + workspace: string; jobName: string; jobStatus?: PlatformJobStatus; compact?: boolean; } -export const CancelJobButton: FC = ({ jobName, jobStatus, compact }) => { +export const CancelJobButton: FC = ({ + workspace, + jobName, + jobStatus, + compact, +}) => { const [isModalOpen, setIsModalOpen] = useState(false); const toast = useToast(); const queryClient = useQueryClient(); - const workspace = useWorkspaceFromPath(); const { mutateAsync, isPending } = useJobsCancelJob({ mutation: { diff --git a/web/packages/studio/src/components/modals/ConfirmationModal/index.tsx b/web/packages/common/src/components/ConfirmationModal/index.tsx similarity index 96% rename from web/packages/studio/src/components/modals/ConfirmationModal/index.tsx rename to web/packages/common/src/components/ConfirmationModal/index.tsx index 41cc0c491d..f64bd539c3 100644 --- a/web/packages/studio/src/components/modals/ConfirmationModal/index.tsx +++ b/web/packages/common/src/components/ConfirmationModal/index.tsx @@ -5,9 +5,9 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/Controlled import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { getErrorMessage } from '@nemo/common/src/utils/error'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { Stack, Text } from '@nvidia/foundations-react-core'; -import { getErrorMessage } from '@studio/components/NewDataDesignerJobForm/utils'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { type ComponentProps, type FC, useState } from 'react'; import { useForm } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/DeleteConfirmationModal/index.tsx b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx similarity index 94% rename from web/packages/studio/src/components/DeleteConfirmationModal/index.tsx rename to web/packages/common/src/components/DeleteConfirmationModal/index.tsx index f4411770b1..71007c0966 100644 --- a/web/packages/studio/src/components/DeleteConfirmationModal/index.tsx +++ b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal'; import { FormModalProps } from '@nemo/common/src/components/FormModal'; -import { ConfirmationModal } from '@studio/components/modals/ConfirmationModal'; import { FC } from 'react'; interface DeleteModalProps extends Pick { diff --git a/web/packages/studio/src/components/ErrorPanel/index.tsx b/web/packages/common/src/components/ErrorPanel/index.tsx similarity index 98% rename from web/packages/studio/src/components/ErrorPanel/index.tsx rename to web/packages/common/src/components/ErrorPanel/index.tsx index 781727010a..2691b78ed7 100644 --- a/web/packages/studio/src/components/ErrorPanel/index.tsx +++ b/web/packages/common/src/components/ErrorPanel/index.tsx @@ -3,8 +3,8 @@ import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; +import { logger } from '@nemo/common/src/utils/logger'; import { Flex, PageHeader, Panel, Stack, Text } from '@nvidia/foundations-react-core'; -import { logger } from '@studio/util/logger'; import { FileX } from 'lucide-react'; import { ComponentProps, FC, ReactNode, useEffect } from 'react'; import { useRouteError, isRouteErrorResponse } from 'react-router'; diff --git a/web/packages/common/src/plugin.ts b/web/packages/common/src/plugin.ts index 80eae8849c..c9f1ee4e13 100644 --- a/web/packages/common/src/plugin.ts +++ b/web/packages/common/src/plugin.ts @@ -6,6 +6,8 @@ export { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; export { AccordionSection } from '@nemo/common/src/components/AccordionSection'; +export { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal'; +export { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; export type { AccordionSectionProps } from '@nemo/common/src/components/AccordionSection'; export { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage'; export { FileTag } from '@nemo/common/src/components/FileTag'; @@ -58,6 +60,10 @@ export { } from '@nemo/common/src/utils/query'; export { triggerDownload } from '@nemo/common/src/utils/file'; +export { getErrorMessage } from '@nemo/common/src/utils/error'; +export { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; +export { logger, toError } from '@nemo/common/src/utils/logger'; + export { JOB_POLLING_INTERVAL_MS } from '@nemo/common/src/constants'; export { DEFAULT_PAGE, diff --git a/web/packages/common/src/utils/error.ts b/web/packages/common/src/utils/error.ts new file mode 100644 index 0000000000..f0a843780e --- /dev/null +++ b/web/packages/common/src/utils/error.ts @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** Display message from an unknown error; falls back when it is not an Error. */ +export function getErrorMessage(error: unknown, defaultMessage?: string): string { + return error instanceof Error + ? error.message + : (defaultMessage ?? 'Something went wrong. Please try again.'); +} diff --git a/web/packages/studio/src/util/forms/error.test.ts b/web/packages/common/src/utils/forms/error.test.ts similarity index 94% rename from web/packages/studio/src/util/forms/error.test.ts rename to web/packages/common/src/utils/forms/error.test.ts index 128e9cc50c..90cbfd4ee7 100644 --- a/web/packages/studio/src/util/forms/error.test.ts +++ b/web/packages/common/src/utils/forms/error.test.ts @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; -import { logger } from '@studio/util/logger'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; +import { logger } from '@nemo/common/src/utils/logger'; import { FieldErrors } from 'react-hook-form'; // Mock the websiteLogger -vi.mock('@studio/util/logger', () => ({ +vi.mock('@nemo/common/src/utils/logger', () => ({ logger: { error: vi.fn(), }, diff --git a/web/packages/studio/src/util/forms/error.ts b/web/packages/common/src/utils/forms/error.ts similarity index 94% rename from web/packages/studio/src/util/forms/error.ts rename to web/packages/common/src/utils/forms/error.ts index fd9502e4e2..49580c2e9e 100644 --- a/web/packages/studio/src/util/forms/error.ts +++ b/web/packages/common/src/utils/forms/error.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { logger } from '@studio/util/logger'; +import { logger } from '@nemo/common/src/utils/logger'; import { FieldErrors } from 'react-hook-form'; /** diff --git a/web/packages/studio/src/util/logger.ts b/web/packages/common/src/utils/logger.ts similarity index 83% rename from web/packages/studio/src/util/logger.ts rename to web/packages/common/src/utils/logger.ts index 519a8f6749..714c7b6d48 100644 --- a/web/packages/studio/src/util/logger.ts +++ b/web/packages/common/src/utils/logger.ts @@ -3,9 +3,15 @@ /* eslint-disable no-console */ import { Logger, logs, SeverityNumber } from '@opentelemetry/api-logs'; -import { OTEL_SERVICE_NAME, VERSION_SHA } from '@studio/constants/environment'; -const otelLogger: Logger = logs.getLogger(OTEL_SERVICE_NAME); +// The service name lives in Studio's env config, which a shared library must not +// import. Studio calls configureLogger() at bootstrap; until then, logs still go +// to the console and OTEL receives them under a placeholder name. +let otelLogger: Logger = logs.getLogger('nemo-studio'); + +export const configureLogger = (serviceName: string): void => { + otelLogger = logs.getLogger(serviceName); +}; /** * Wrapper class around the global OpenTelemetry Logger. Logs will be transported to: @@ -18,8 +24,8 @@ const otelLogger: Logger = logs.getLogger(OTEL_SERVICE_NAME); * Example usage: * * ``` - * import { websiteLogger } from '@studio/util/logger'; - * websiteLogger.info('Some message to log'); + * import { logger } from '@nemo/common/src/utils/logger'; + * logger.info('Some message to log'); * ``` */ class WebsiteLogger { @@ -82,12 +88,3 @@ export const handleGenericError = (error: Error | string) => { logger.error(error); } }; - -/** - * Logs the version of the app to the website logger. - */ -export const logVersion = async () => { - if (VERSION_SHA) { - logger.info(`Version: ${VERSION_SHA}`); - } -}; diff --git a/web/packages/studio/src/App.tsx b/web/packages/studio/src/App.tsx index 0876070c24..b8a87f1a6b 100644 --- a/web/packages/studio/src/App.tsx +++ b/web/packages/studio/src/App.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { configureLogger } from '@nemo/common/src/utils/logger'; import { ThemeProvider as KaizenThemeProvider, Theme, @@ -13,12 +14,13 @@ import { AUTH_SCOPE_PREFIX, AUTH_SCOPES, BASE_URL, + OTEL_SERVICE_NAME, } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { routes } from '@studio/routes'; import { useLocalStorage } from '@studio/util/hooks/useLocalStorage'; import { UI_THEME } from '@studio/util/localStorage'; -import { logVersion } from '@studio/util/logger'; +import { logVersion } from '@studio/util/logVersion'; import { QueryClientProvider } from '@tanstack/react-query'; import { WebStorageStateStore } from 'oidc-client-ts'; import { StrictMode } from 'react'; @@ -55,6 +57,7 @@ if (import.meta.hot) { } const router = createBrowserRouter(routes, { basename: BASE_URL ?? '/' }); +configureLogger(OTEL_SERVICE_NAME); logVersion(); const effectiveScope = expandScopes(AUTH_SCOPES || 'openid profile email', AUTH_SCOPE_PREFIX); diff --git a/web/packages/studio/src/api/datasets/useDatasetFileContent.ts b/web/packages/studio/src/api/datasets/useDatasetFileContent.ts index 6564437eb8..804c7b4243 100644 --- a/web/packages/studio/src/api/datasets/useDatasetFileContent.ts +++ b/web/packages/studio/src/api/datasets/useDatasetFileContent.ts @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { customFetch } from '@nemo/sdk/generated/fetchers/platform'; import { filesDownloadFile, getFilesDownloadFileQueryKey } from '@nemo/sdk/generated/platform/api'; import type { EntityIdentifier } from '@studio/api/common/types'; import { getDatasetFileContentQueryKey } from '@studio/api/datasets/invalidateDatasetCaches'; import { PLATFORM_BASE_URL } from '@studio/constants/environment'; import { isBinaryExtension } from '@studio/util/binaryFile'; -import { logger } from '@studio/util/logger'; import { queryOptions, useQuery, UseQueryOptions, useSuspenseQuery } from '@tanstack/react-query'; import axios from 'axios'; import { parquetRead } from 'hyparquet'; diff --git a/web/packages/studio/src/components/BulkDeleteModal/index.tsx b/web/packages/studio/src/components/BulkDeleteModal/index.tsx index 4108f37908..7c1f6f4639 100644 --- a/web/packages/studio/src/components/BulkDeleteModal/index.tsx +++ b/web/packages/studio/src/components/BulkDeleteModal/index.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { useState } from 'react'; export interface BulkDeleteModalProps { diff --git a/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts b/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts index 89e9a3ba48..8c1411450c 100644 --- a/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts +++ b/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts @@ -4,12 +4,12 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { type ModelWorkspaceGroup } from '@nemo/common/src/api/models/useModels'; import { useChatCompletion } from '@nemo/common/src/hooks/useChatCompletion'; +import { getErrorMessage } from '@nemo/common/src/utils/error'; import type { CreateJobRequest as DataDesignerJobRequest } from '@nemo/sdk/generated/data-designer/schema'; import { buildFixMessages } from '@studio/components/CreateFilesetStart/fixRequest'; import { DATA_DESIGNER_JOB_GENERATOR_SYSTEM_PROMPT } from '@studio/components/NewDataDesignerJobForm/constants'; import { generateDataDesignerJobRequestTool } from '@studio/components/NewDataDesignerJobForm/tools'; import { - getErrorMessage, getWorkspaceAndModel, parseToolResponseToJobRequest, sanitizeJobRequestName, diff --git a/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx b/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx index 24451a5feb..baebe52d63 100644 --- a/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx +++ b/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx @@ -6,6 +6,7 @@ import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal import { FilesetFileUploadError } from '@nemo/common/src/datasets/constants'; import { getURNFromNamedEntityRef } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; import { TextInput, TextArea, FormField, Stack } from '@nvidia/foundations-react-core'; import { useDatasetCreate } from '@studio/api/datasets/useDatasetCreate'; @@ -25,7 +26,6 @@ import { datasetSchema } from '@studio/constants/zod'; import { useCustomizationFilesPreview } from '@studio/hooks/useCustomizationFiles'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { renameFile } from '@studio/util/files'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC, useState } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { z } from 'zod'; diff --git a/web/packages/studio/src/components/DatasetCreateModal/index.tsx b/web/packages/studio/src/components/DatasetCreateModal/index.tsx index 6f0ccab2d9..e270e86465 100644 --- a/web/packages/studio/src/components/DatasetCreateModal/index.tsx +++ b/web/packages/studio/src/components/DatasetCreateModal/index.tsx @@ -9,6 +9,7 @@ import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal import { FilesetFileUploadError } from '@nemo/common/src/datasets/constants'; import { getEntityReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { useFilesUpdateFilesetMetadata } from '@nemo/sdk/generated/platform/api'; import { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; import { Select, Stack } from '@nvidia/foundations-react-core'; @@ -26,7 +27,6 @@ import { CUSTOMIZATION_FILESET_FILE_PREFIXES } from '@studio/constants/customiza import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getFilesetDetailsRoute } from '@studio/routes/utils'; import { renameFile } from '@studio/util/files'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC, useState } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; diff --git a/web/packages/studio/src/components/DatasetInputFile/index.tsx b/web/packages/studio/src/components/DatasetInputFile/index.tsx index 8db93d767e..99ca9ada1a 100644 --- a/web/packages/studio/src/components/DatasetInputFile/index.tsx +++ b/web/packages/studio/src/components/DatasetInputFile/index.tsx @@ -8,6 +8,7 @@ import { SubmitUploadType } from '@nemo/common/src/components/UploadModal/types' import { InputFileSchemaType } from '@nemo/common/src/types'; import { extractUserFriendlyKeysFromRow } from '@nemo/common/src/utils/file'; import { validateFileFormat, detectFileStructure } from '@nemo/common/src/utils/fileValidation'; +import { logger } from '@nemo/common/src/utils/logger'; import { Banner, Button, @@ -27,7 +28,6 @@ import type { import { useDatasetInputFileReducer } from '@studio/components/DatasetInputFile/useDatasetInputFileReducer'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getDatasetDisplayNameFromFilesUrl } from '@studio/util/files'; -import { logger } from '@studio/util/logger'; import { useQueryClient } from '@tanstack/react-query'; import { Plus, CircleCheck, CircleHelp, Eye, File as FileIcon, Trash2 } from 'lucide-react'; import { FC, useCallback, useEffect, useRef } from 'react'; diff --git a/web/packages/studio/src/components/DatasetsTable/index.test.tsx b/web/packages/studio/src/components/DatasetsTable/index.test.tsx index afc2f0dc4d..17edaede54 100644 --- a/web/packages/studio/src/components/DatasetsTable/index.test.tsx +++ b/web/packages/studio/src/components/DatasetsTable/index.test.tsx @@ -52,7 +52,7 @@ vi.mock('@studio/components/BulkDeleteModal', () => ({ ), })); -vi.mock('@studio/components/DeleteConfirmationModal', () => ({ +vi.mock('@nemo/common/src/components/DeleteConfirmationModal', () => ({ DeleteConfirmationModal: vi.fn( ({ open, diff --git a/web/packages/studio/src/components/DatasetsTable/index.tsx b/web/packages/studio/src/components/DatasetsTable/index.tsx index d75df25bd7..c0c84c065d 100644 --- a/web/packages/studio/src/components/DatasetsTable/index.tsx +++ b/web/packages/studio/src/components/DatasetsTable/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; import { getEntityReference } from '@nemo/common/src/namedEntity'; @@ -11,7 +12,6 @@ import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/co import { makeDatasetsTableColumns } from '@studio/components/DatasetsTable/columns'; import { type DatasetsTableProps } from '@studio/components/DatasetsTable/types'; import { useDatasetsTable } from '@studio/components/DatasetsTable/useDatasetsTable'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; import { Loading } from '@studio/components/Layouts/Loading'; import { NewDatasetButton } from '@studio/components/NewDatasetButton'; diff --git a/web/packages/studio/src/components/ExperimentCreateModal/index.tsx b/web/packages/studio/src/components/ExperimentCreateModal/index.tsx index 73237f1bdd..7c72ac1ffe 100644 --- a/web/packages/studio/src/components/ExperimentCreateModal/index.tsx +++ b/web/packages/studio/src/components/ExperimentCreateModal/index.tsx @@ -13,6 +13,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { FormModal, type FormModalProps } from '@nemo/common/src/components/FormModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { getListExperimentsQueryKey, useCreateExperiment } from '@nemo/sdk/generated/platform/api'; import { CodeSnippet, @@ -32,7 +33,6 @@ import { experimentCreateSchema, type ExperimentCreateFormFields, } from '@studio/components/ExperimentCreateModal/constants'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { AxiosError } from 'axios'; import { useState, type FC } from 'react'; import { useForm, type SubmitHandler } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx b/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx index 82df2da4ac..2501ddcf98 100644 --- a/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx +++ b/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx @@ -7,6 +7,7 @@ import { ControlledSelect } from '@nemo/common/src/components/form/ControlledSel import { FormModal } from '@nemo/common/src/components/FormModal'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { Banner, Divider, Flex, Label, Spinner, Stack, Text } from '@nvidia/foundations-react-core'; import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent'; import { useSplitDatasetFile } from '@studio/api/datasets/useSplitDatasetFile'; @@ -25,7 +26,6 @@ import { ValueWithLabel } from '@studio/components/ValueWithLabel'; import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId'; import { tooltipClassName } from '@studio/styles/common'; import { getContentSchema } from '@studio/util/files'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { getTextWithCount } from '@studio/util/strings'; import { Split } from 'lucide-react'; import { ComponentProps, FC, useMemo } from 'react'; diff --git a/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx b/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx index 1554c9f384..06631ec5e2 100644 --- a/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx +++ b/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { QuickActionsMenuRoot, type QuickActionItem, @@ -9,7 +10,6 @@ import { useQueryParams } from '@nemo/common/src/hooks/useQueryParams'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { useDatasetDirectoryDelete } from '@studio/api/datasets/useDatasetDirectoryDelete'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { FileSystemDirectory } from '@studio/components/FilesTable/utils'; import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId'; import { QUERY_PARAMETERS } from '@studio/routes/constants'; diff --git a/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx b/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx index 16a5513ff4..4b92af34f7 100644 --- a/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx +++ b/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { type QuickActionItem, QuickActionsMenuRoot, @@ -8,7 +9,6 @@ import { import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { useDatasetFileDelete } from '@studio/api/datasets/useDatasetFileDelete'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { AddToFolderModal } from '@studio/components/filesets/AddToFolderModal'; import { useBulkDownload } from '@studio/components/filesets/hooks/useBulkDownload'; import { useBulkDuplicate } from '@studio/components/filesets/hooks/useBulkDuplicate'; diff --git a/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx b/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx index 79b7489891..1c8eb6b4a9 100644 --- a/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx +++ b/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx @@ -5,9 +5,9 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/Controlled import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { useDatasetFileRename } from '@studio/api/datasets/useDatasetFileRename'; import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx b/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx index 267cec2d72..161eae907b 100644 --- a/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx +++ b/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx @@ -7,6 +7,7 @@ import { FormModal } from '@nemo/common/src/components/FormModal'; import { ModelSelect } from '@nemo/common/src/components/ModelSelect'; import { getEntityReference, getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { useModelsListModels } from '@nemo/sdk/generated/platform/api'; import { Divider, Flex, Label, Spinner, Stack, Text } from '@nvidia/foundations-react-core'; import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent'; @@ -20,7 +21,6 @@ import { ValueWithLabel } from '@studio/components/ValueWithLabel'; import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getContentSchema } from '@studio/util/files'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { GitBranch } from 'lucide-react'; import { useMemo, type ComponentProps, type FC } from 'react'; import { useForm } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/FilesetActionMenu/index.tsx b/web/packages/studio/src/components/FilesetActionMenu/index.tsx index 47b120d96b..ba93ac5b19 100644 --- a/web/packages/studio/src/components/FilesetActionMenu/index.tsx +++ b/web/packages/studio/src/components/FilesetActionMenu/index.tsx @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { getEntityReference } from '@nemo/common/src/namedEntity'; +import { logger } from '@nemo/common/src/utils/logger'; import { useFilesDeleteFileset } from '@nemo/sdk/generated/platform/api'; import type { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; import { @@ -14,8 +16,6 @@ import { import { invalidateDatasetCaches } from '@studio/api/datasets/invalidateDatasetCaches'; import { DatasetCreateModal } from '@studio/components/DatasetCreateModal'; import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/constants'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; -import { logger } from '@studio/util/logger'; import { EllipsisVertical } from 'lucide-react'; import { type FC, useState } from 'react'; diff --git a/web/packages/studio/src/components/FilesetCreateModal/index.tsx b/web/packages/studio/src/components/FilesetCreateModal/index.tsx index 37ab50dd48..c0a59a6932 100644 --- a/web/packages/studio/src/components/FilesetCreateModal/index.tsx +++ b/web/packages/studio/src/components/FilesetCreateModal/index.tsx @@ -8,6 +8,7 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/Controlled import { FormModal } from '@nemo/common/src/components/FormModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { toValidFilesetName } from '@nemo/common/src/utils/filesetName'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { getFilesListFilesetsQueryKey, useFilesCreateFileset, @@ -26,7 +27,6 @@ import { FilesetDetailTab } from '@studio/routes/FilesetDetailRoute/constants'; import { CreateSecretModal } from '@studio/routes/SecretsListRoute/CreateSecretModal'; import { SecretSearchableSelect } from '@studio/routes/SecretsListRoute/SecretSearchableSelect'; import { getFilesetDetailRoute } from '@studio/routes/utils'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { isHuggingFaceUrl, isNgcUrl, diff --git a/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx b/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx index 22ac0fa163..a16e6c6dc9 100644 --- a/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx +++ b/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { triggerDownload } from '@nemo/common/src/utils/file'; import { @@ -12,7 +13,6 @@ import { Flex, } from '@nvidia/foundations-react-core'; import { useDatasetFileDelete } from '@studio/api/datasets/useDatasetFileDelete'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { CreateFileSplitsModal } from '@studio/components/FilesTable/CreateFileSplitsModal'; import { RenameFileModal } from '@studio/components/FilesTable/RenameFileModal'; import { FileSystemFile, FileSystemNode } from '@studio/components/FilesTable/utils'; diff --git a/web/packages/studio/src/components/ModelChat/index.tsx b/web/packages/studio/src/components/ModelChat/index.tsx index 3865e67ab6..2b37ebba3f 100644 --- a/web/packages/studio/src/components/ModelChat/index.tsx +++ b/web/packages/studio/src/components/ModelChat/index.tsx @@ -7,12 +7,12 @@ import { type AssistantChatProps, } from '@nemo/common/src/components/AssistantChat'; import type { AssistantMessageCompletion } from '@nemo/common/src/components/AssistantChat/types'; +import { handleGenericError } from '@nemo/common/src/utils/logger'; import type { ModelChatStatus } from '@nemo/common/src/utils/models'; import { DEFAULT_SEED_QUESTIONS } from '@studio/components/chat/defaultSeedQuestions'; import { SeedQuestions } from '@studio/components/chat/SeedQuestions'; import { StatsBadge, type ChatMetrics } from '@studio/components/chat/StatsBadge'; import type { ComposerSeed } from '@studio/routes/ModelCompareRoute/types'; -import { handleGenericError } from '@studio/util/logger'; import { type ReactNode, useEffect, useRef, useState, type FC } from 'react'; interface ModelChatProps extends Pick< diff --git a/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts b/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts index d531f9b7dd..251e6438ae 100644 --- a/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts +++ b/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts @@ -4,6 +4,7 @@ import type { SubmitUploadType } from '@nemo/common/src/components/UploadModal/types'; import { useChatCompletion } from '@nemo/common/src/hooks/useChatCompletion'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; +import { logger } from '@nemo/common/src/utils/logger'; import { type FileSampleMethod } from '@nemo/common/src/utils/sampleTextLines'; import { filesDownloadFile } from '@nemo/sdk/generated/platform/api'; import { SAMPLE_DATASETS } from '@studio/components/chat/sampleDatasets'; @@ -25,7 +26,6 @@ import type { ResponseResult, ResponseStats, } from '@studio/components/ModelComparePrompts/types'; -import { logger } from '@studio/util/logger'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; type UseModelComparePromptsArgs = Pick< diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx b/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx index 4ebac32948..0f562343d4 100644 --- a/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx +++ b/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx @@ -10,13 +10,13 @@ import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; import { useChatCompletion } from '@nemo/common/src/hooks/useChatCompletion'; +import { getErrorMessage } from '@nemo/common/src/utils/error'; import type { CreateJobRequest as DataDesignerJobRequest } from '@nemo/sdk/generated/data-designer/schema'; import { Flex, Stack, Text } from '@nvidia/foundations-react-core'; import { DATA_DESIGNER_JOB_GENERATOR_SYSTEM_PROMPT } from '@studio/components/NewDataDesignerJobForm/constants'; import { generateDataDesignerJobRequestTool } from '@studio/components/NewDataDesignerJobForm/tools'; import { applyFormModelToJobRequest, - getErrorMessage, getWorkspaceAndModel, parseJsonContentToJobRequest, parseToolResponseToJobRequest, diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts b/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts index 03d78a2e70..54cd0d2f57 100644 --- a/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts +++ b/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { getErrorMessage } from '@nemo/common/src/utils/error'; import type { DataDesignerConfig } from '@nemo/sdk/generated/data-designer/schema'; import { isAbortError, streamPreview } from '@studio/components/NewDataDesignerJobForm/previewApi'; -import { getErrorMessage } from '@studio/components/NewDataDesignerJobForm/utils'; import { useCallback, useRef, useState } from 'react'; const PREVIEW_PATH_TEMPLATE = '/apis/data-designer/v2/workspaces/:workspace/preview'; diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts index 28b1c5a00f..6917310cb3 100644 --- a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts +++ b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { COPY_NAME_SUFFIX } from '@nemo/common/src/utils/entityName'; +import { getErrorMessage } from '@nemo/common/src/utils/error'; import type { CreateJob as DataDesignerJob, CreateJobRequest as DataDesignerJobRequest, @@ -10,7 +11,6 @@ import { applyFormModelToJobRequest, buildClonedJobRequest, getCloneJobRequestFromState, - getErrorMessage, getWorkspaceAndModel, modelsFromProviders, PARSE_ERROR_INVALID_JSON, diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts index 64a66aee8b..d1e6208cea 100644 --- a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts +++ b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts @@ -12,13 +12,6 @@ import type { ModelEntity, ModelProvider } from '@nemo/sdk/generated/platform/sc /** Model option for the form: entity plus display name used in job requests */ export type DataDesignerModelOption = ModelEntity & { served_model_name: string }; -/** Get a display message from an unknown error; use default when not an Error instance */ -export function getErrorMessage(error: unknown, defaultMessage?: string): string { - return error instanceof Error - ? error.message - : (defaultMessage ?? 'Something went wrong. Please try again.'); -} - /** * Resolve workspace and model name for chat/completion calls. * If modelRef contains "/", parses as "workspace/name"; otherwise uses fallbackWorkspace and modelRef as name. diff --git a/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx b/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx index cca9c3510e..9d80abbb5f 100644 --- a/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx +++ b/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx @@ -3,11 +3,11 @@ import { FormModal } from '@nemo/common/src/components/FormModal'; import { getPartsFromReference } from '@nemo/common/src/namedEntity'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { Banner, Stack } from '@nvidia/foundations-react-core'; import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent'; import { DatasetFileSelect } from '@studio/components/DatasetFileSelect'; import { DatasetSelect } from '@studio/components/DatasetSelect'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC } from 'react'; import { Controller, FormProvider, useForm, useWatch } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx index 9094868ecd..505fa48d82 100644 --- a/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx +++ b/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx @@ -4,6 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { AccordionSection } from '@nemo/common/src/components/AccordionSection'; import { FormModal } from '@nemo/common/src/components/FormModal'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { Anchor, Button, Flex, Stack, Switch, Text } from '@nvidia/foundations-react-core'; import { DetailRow } from '@studio/components/DetailRow'; import { AddToolForm } from '@studio/components/PromptTuningForm/ToolsSection/components/AddToolForm'; @@ -15,7 +16,6 @@ import { import { TOOL_JSON_EXAMPLE } from '@studio/components/PromptTuningForm/ToolsSection/constants'; import { useSubmitSingleTool } from '@studio/components/PromptTuningForm/ToolsSection/hooks/useSubmitSingleTool'; import { PromptTuningFormFields } from '@studio/routes/PromptTuningFormRoute/utils'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { Plus, ExternalLink, Toolbox } from 'lucide-react'; import { FC, useState } from 'react'; import { FormProvider, useForm, useFormContext, useWatch } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx b/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx index 8e14ebe344..b576932038 100644 --- a/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx +++ b/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx @@ -13,6 +13,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { getEntitiesListWorkspacesQueryKey, useEntitiesCreateWorkspace, @@ -21,7 +22,6 @@ import { FormField, Stack, TextArea, TextInput } from '@nvidia/foundations-react import { queryClient } from '@studio/api/queryClient'; import { workspaceCreateSchema } from '@studio/constants/zod'; import { getWorkspaceDetailsDefaultRoute } from '@studio/routes/utils'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { AxiosError } from 'axios'; import { FC } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/WorkspaceDropdown/index.tsx b/web/packages/studio/src/components/WorkspaceDropdown/index.tsx index 078a66b9eb..b2ede06b9a 100644 --- a/web/packages/studio/src/components/WorkspaceDropdown/index.tsx +++ b/web/packages/studio/src/components/WorkspaceDropdown/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { useEntitiesListWorkspaces } from '@nemo/sdk/generated/platform/api'; import { Workspace } from '@nemo/sdk/generated/platform/schema'; import { @@ -23,7 +24,6 @@ import { useRecentWorkspaces } from '@studio/components/WorkspaceDropdown/useRec import { DEFAULT_LARGE_PAGE_SIZE } from '@studio/constants/constants'; import { getWorkspaceDetailsDefaultRoute } from '@studio/routes/utils'; import { useBoolean } from '@studio/util/hooks/useBoolean'; -import { logger } from '@studio/util/logger'; import cn from 'classnames'; import { Plus, Filter } from 'lucide-react'; import { ChangeEvent, FC, lazy, useMemo, useState } from 'react'; diff --git a/web/packages/studio/src/components/common/SearchBar/index.tsx b/web/packages/studio/src/components/common/SearchBar/index.tsx index b1bbb0bb74..ab8d6b97b4 100644 --- a/web/packages/studio/src/components/common/SearchBar/index.tsx +++ b/web/packages/studio/src/components/common/SearchBar/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { FormField, TextInput } from '@nvidia/foundations-react-core'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { Search } from 'lucide-react'; import { ComponentProps } from 'react'; import { useForm, type SubmitHandler } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx b/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx index ca2e5a1d85..6520c5f38d 100644 --- a/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx @@ -7,6 +7,7 @@ import { ROW_SELECTION_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -27,7 +28,6 @@ import type { Agent } from '@nemo/sdk/generated/agents/schema/Agent'; import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema/AgentDeployment'; import { Button, Text } from '@nvidia/foundations-react-core'; import { getAgentModelNames } from '@studio/components/dataViews/AgentsDataView/utils'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; import { MODEL_COMPARE_ENABLED } from '@studio/constants/environment'; import { LINK_DOCS_STUDIO } from '@studio/constants/links'; diff --git a/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx b/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx index 2c8e50ed55..22147f7b2f 100644 --- a/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx @@ -8,6 +8,7 @@ import { ROW_SELECTION_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -33,7 +34,6 @@ import { FINETUNING_TYPE_FILTER_OPTIONS } from '@studio/components/dataViews/Cus import { CustomizeModelButton } from '@studio/components/dataViews/CustomModelsDataView/CustomizeModelButton'; import { DeploymentIndicator } from '@studio/components/dataViews/CustomModelsDataView/DeploymentIndicator'; import { KindTag } from '@studio/components/dataViews/CustomModelsDataView/KindTag'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; import { BaseModelSearchFilterField } from '@studio/components/FilterFields'; import type { ModelPanelTab } from '@studio/components/sidePanels/ModelPanels/ModelPanel'; diff --git a/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx b/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx index 03438993da..04620f07da 100644 --- a/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx @@ -13,6 +13,7 @@ import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -24,7 +25,6 @@ import { ModelDeploymentStatus, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, Text } from '@nvidia/foundations-react-core'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { CUSTOMIZER_ENABLED } from '@studio/constants/environment'; import { keepPreviousData } from '@tanstack/react-query'; import { Rocket, Trash2 } from 'lucide-react'; diff --git a/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx b/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx index 625609270c..cea8380227 100644 --- a/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx @@ -6,6 +6,7 @@ import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState'; @@ -16,7 +17,6 @@ import type { } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Text } from '@nvidia/foundations-react-core'; import { countRails } from '@studio/components/dataViews/GuardrailsDataView/guardrailUtils'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { keepPreviousData } from '@tanstack/react-query'; import { Copy, ShieldCheck, Trash } from 'lucide-react'; import { type ComponentProps, type FC, useCallback } from 'react'; diff --git a/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx b/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx index 7fc1f2e933..3bd52a3b05 100644 --- a/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx @@ -16,6 +16,8 @@ import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -33,8 +35,6 @@ import { ModelProviderSort, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, StatusMessage, Text } from '@nvidia/foundations-react-core'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { LINK_DOCS_INFERENCE_PROVIDERS } from '@studio/constants/links'; import { EditInferenceProviderModal } from '@studio/routes/InferenceProvidersListRoute/EditInferenceProviderModal'; import { InferenceProviderDetailsSidePanel } from '@studio/routes/InferenceProvidersListRoute/InferenceProviderDetailsSidePanel'; diff --git a/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx b/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx index d4a50688be..1ce408cea6 100644 --- a/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx @@ -3,8 +3,10 @@ import { getErrorMessage } from '@nemo/common/src/api/common/utils'; import { withOperators } from '@nemo/common/src/api/filterOperators'; +import { CancelJobButton } from '@nemo/common/src/components/CancelJobButton'; import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter'; import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; @@ -17,14 +19,12 @@ import type { PlatformJobsListFilter, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, StatusMessage } from '@nvidia/foundations-react-core'; -import { CancelJobButton } from '@studio/components/CancelJobButton'; import { HIDDEN_JOB_SOURCES, JOB_SOURCE, SOURCE_OPTIONS, } from '@studio/components/dataViews/JobsDataView/constants'; import { getJobDetailRoute } from '@studio/components/dataViews/JobsDataView/utils'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { CUSTOMIZER_ENABLED } from '@studio/constants/environment'; import { LINK_DOCS_JOBS } from '@studio/constants/links'; import { STATUS_FILTER_OPTIONS } from '@studio/constants/platformJobs'; @@ -195,7 +195,12 @@ export const JobsDataView = () => { enableSorting: false, cell: ({ row }) => ( - + ), }), diff --git a/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx b/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx index 8575867b2b..2709d5e509 100644 --- a/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx @@ -15,6 +15,8 @@ import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState'; @@ -22,9 +24,7 @@ import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { useSecretsDeleteSecret, useSecretsListSecrets } from '@nemo/sdk/generated/platform/api'; import { PlatformSecretResponse } from '@nemo/sdk/generated/platform/schema'; import { Button, Stack, Text } from '@nvidia/foundations-react-core'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { LINK_DOCS_SECRETS } from '@studio/constants/links'; import { EditSecretModal } from '@studio/routes/SecretsListRoute/EditSecretModal'; import { keepPreviousData } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx b/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx index 835c50de17..a4eb248827 100644 --- a/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx @@ -8,6 +8,8 @@ import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; import { useDeferredUnmount } from '@nemo/common/src/hooks/useDeferredUnmount'; @@ -24,8 +26,6 @@ import type { VirtualModelFilter, } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, Stack, StatusMessage, Text } from '@nvidia/foundations-react-core'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { BaseModelSearchFilterField } from '@studio/components/FilterFields'; import { VirtualModelDetailsSidePanel } from '@studio/routes/VirtualModelsListRoute/VirtualModelDetailsSidePanel'; import { keepPreviousData, useQueryClient } from '@tanstack/react-query'; diff --git a/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts b/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts index 3921680d24..b1f8d707d0 100644 --- a/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts +++ b/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts @@ -9,6 +9,7 @@ import { FileValidationResult, FileFormatDetectionResult, } from '@nemo/common/src/utils/fileValidation'; +import { logger } from '@nemo/common/src/utils/logger'; import { datasetFileContentQueryOptions } from '@studio/api/datasets/useDatasetFileContent'; import { buildTemplatePreview } from '@studio/components/evaluation/Configurations/form/InputFile/helpers'; import { @@ -18,7 +19,6 @@ import { } from '@studio/hooks/evaluation/useCreateConfigurationForm'; import { useFileValidation } from '@studio/hooks/evaluation/useFileValidation'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; -import { logger } from '@studio/util/logger'; import { useQueryClient } from '@tanstack/react-query'; import { useState, useCallback, useEffect, useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx b/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx index b45d8cda96..71656d3dcc 100644 --- a/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx +++ b/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { useEvaluatorDeleteEvaluateJob } from '@nemo/sdk/generated/evaluator/api'; import type { EvaluateJob } from '@nemo/sdk/generated/evaluator/schema'; import { @@ -17,7 +18,6 @@ import { } from '@nvidia/foundations-react-core'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getEvaluationJobName } from '@studio/selectors/evaluationJob'; -import { logger } from '@studio/util/logger'; import { ArrowRight, EllipsisVertical, Trash } from 'lucide-react'; import { FC, useState } from 'react'; diff --git a/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx b/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx index 07f06747f6..966ceaf292 100644 --- a/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx +++ b/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx @@ -14,6 +14,7 @@ import { getDifferenceInMilliseconds, utcToLocalDate, } from '@nemo/common/src/utils/date'; +import { logger } from '@nemo/common/src/utils/logger'; import { getEvaluatorGetEvaluateJobQueryKey, useEvaluatorCancelEvaluateJob, @@ -23,7 +24,6 @@ import { Banner, Button, Flex, Modal, Panel, Stack, Text } from '@nvidia/foundat import { ButtonLaunchEvaluation } from '@studio/components/evaluation/ButtonLaunchEvaluation'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getFilesetRoute } from '@studio/routes/utils'; -import { logger } from '@studio/util/logger'; import { useQueryClient } from '@tanstack/react-query'; import { ChartBar, CircleX } from 'lucide-react'; import { useState } from 'react'; diff --git a/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx b/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx index e10d0f96f2..1942dca878 100644 --- a/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx +++ b/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { useEvaluatorDeleteEvaluateJob } from '@nemo/sdk/generated/evaluator/api'; import { Button, Flex, Modal } from '@nvidia/foundations-react-core'; import { useMutateMany } from '@studio/api/common/useMutateMany'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; -import { logger } from '@studio/util/logger'; import { Trash } from 'lucide-react'; import { FC, useState } from 'react'; diff --git a/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx b/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx index 9e9c366c1c..6b45c6e7f2 100644 --- a/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx +++ b/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx @@ -5,6 +5,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { useFilesListFilesetFiles } from '@nemo/sdk/generated/platform/api'; import { Button, @@ -28,7 +29,6 @@ import { FileSystemNode } from '@studio/components/FilesTable/utils'; import { useDatasetNavigator } from '@studio/hooks/useDatasetNavigator'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { getFilesetDetailsRoute } from '@studio/routes/utils'; -import { logger } from '@studio/util/logger'; import { FolderOpen, Info } from 'lucide-react'; import { type FC, useMemo } from 'react'; import { useForm, useWatch } from 'react-hook-form'; diff --git a/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx b/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx index c1b83bb85e..1bb94bd314 100644 --- a/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx +++ b/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { Block, Flex, Text } from '@nvidia/foundations-react-core'; -import { logger } from '@studio/util/logger'; import { FC, useCallback, ReactNode } from 'react'; import { useDropzone, FileRejection } from 'react-dropzone'; diff --git a/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx b/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx index 1f14c1a720..94fb303784 100644 --- a/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx +++ b/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx @@ -5,10 +5,10 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { useCreateDirectory } from '@studio/api/datasets/useCreateDirectory'; import { getSiblingFolders } from '@studio/components/filesets/AddToFolderModal/utils'; import { FileSystemNode } from '@studio/components/FilesTable/utils'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { z } from 'zod'; diff --git a/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts b/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts index 0e555ce47e..242afa577d 100644 --- a/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts +++ b/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts @@ -3,10 +3,10 @@ import { useToast } from '@nemo/common/src/providers/toast/useToast'; import { triggerDownload } from '@nemo/common/src/utils/file'; +import { logger } from '@nemo/common/src/utils/logger'; import { useDownloadFileAsArrayBuffer } from '@studio/components/filesets/hooks/useDownloadFileAsArrayBuffer'; import { FileSystemFile } from '@studio/components/FilesTable/utils'; import { getFileNameFromPath } from '@studio/util/files'; -import { logger } from '@studio/util/logger'; import { useCallback, useState } from 'react'; export interface UseBulkDownloadOptions { diff --git a/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts b/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts index a97449354f..e4b0176894 100644 --- a/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts +++ b/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { filesListFilesetFiles } from '@nemo/sdk/generated/platform/api'; import { useDatasetFilesUpload } from '@studio/api/datasets/useDatasetFilesUpload'; import { useDownloadFileAsArrayBuffer } from '@studio/components/filesets/hooks/useDownloadFileAsArrayBuffer'; import { FileSystemFile } from '@studio/components/FilesTable/utils'; -import { logger } from '@studio/util/logger'; import { useCallback, useState } from 'react'; export interface UseBulkDuplicateOptions { diff --git a/web/packages/studio/src/components/CancelJobButton/index.test.tsx b/web/packages/studio/src/components/promoted/CancelJobButton.test.tsx similarity index 95% rename from web/packages/studio/src/components/CancelJobButton/index.test.tsx rename to web/packages/studio/src/components/promoted/CancelJobButton.test.tsx index 18c164ce0b..c25666a513 100644 --- a/web/packages/studio/src/components/CancelJobButton/index.test.tsx +++ b/web/packages/studio/src/components/promoted/CancelJobButton.test.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { CancelJobButton } from '@nemo/common/src/components/CancelJobButton'; import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema'; -import { CancelJobButton } from '@studio/components/CancelJobButton'; import { PLATFORM_BASE_URL } from '@studio/constants/environment'; import { ROUTE_PARAMS } from '@studio/constants/routes'; import { workspace1 } from '@studio/mocks/entity-store/projects'; @@ -18,7 +18,7 @@ const JOB_NAME = 'test-job-abc123'; const renderButton = (status?: PlatformJobStatus) => render( - + ); diff --git a/web/packages/studio/src/components/DeleteConfirmationModal/index.test.tsx b/web/packages/studio/src/components/promoted/DeleteConfirmationModal.test.tsx similarity index 96% rename from web/packages/studio/src/components/DeleteConfirmationModal/index.test.tsx rename to web/packages/studio/src/components/promoted/DeleteConfirmationModal.test.tsx index df9b4f3002..97fba36921 100644 --- a/web/packages/studio/src/components/DeleteConfirmationModal/index.test.tsx +++ b/web/packages/studio/src/components/promoted/DeleteConfirmationModal.test.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { dataset } from '@studio/mocks/datasets'; import { render, screen } from '@studio/tests/util/render'; import { waitFor } from '@testing-library/react'; diff --git a/web/packages/studio/src/components/ErrorPanel/index.test.tsx b/web/packages/studio/src/components/promoted/ErrorPanel.test.tsx similarity index 98% rename from web/packages/studio/src/components/ErrorPanel/index.test.tsx rename to web/packages/studio/src/components/promoted/ErrorPanel.test.tsx index 1b32991af6..e86cb4e49c 100644 --- a/web/packages/studio/src/components/ErrorPanel/index.test.tsx +++ b/web/packages/studio/src/components/promoted/ErrorPanel.test.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { ErrorPanel, ErrorPanelProps } from '@nemo/common/src/components/ErrorPanel'; import { suppressConsoleError } from '@nemo/testing/utils/suppress-console'; -import { ErrorPanel, ErrorPanelProps } from '@studio/components/ErrorPanel'; import { mockUseNavigate } from '@studio/tests/util/mockUseParams'; import { render, screen, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx b/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx index 8b42e186e2..0cad82a3b0 100644 --- a/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx +++ b/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx @@ -13,6 +13,7 @@ import { ControlledDatasetFileSelect } from '@nemo/common/src/components/Dataset import type { VariableDef } from '@nemo/common/src/components/form/VariableTextArea'; import { ModelSelectV2 } from '@nemo/common/src/components/ModelSelectV2'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { useEvaluatorCreateEvaluateJob } from '@nemo/sdk/generated/evaluator/api'; import type { EvaluateJobRequest, MetricInline, Model } from '@nemo/sdk/generated/evaluator/schema'; import { @@ -43,7 +44,6 @@ import { DEFAULT_INFERENCE_PARAMS_FORM_VALUES } from '@studio/hooks/evaluation/u import { QUERY_PARAMETERS } from '@studio/routes/constants'; import { getEvaluationResultDetailsRoute } from '@studio/routes/utils'; import { buildModelPayload } from '@studio/util/evaluations'; -import { logger } from '@studio/util/logger'; import { Plus } from 'lucide-react'; import { type FC, useCallback, useEffect, useMemo, useState } from 'react'; import { diff --git a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx index 50328cb4fb..93f01ad042 100644 --- a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx +++ b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { getModelsListModelsQueryKey, getProviderProxyGetQueryKey, @@ -19,7 +20,6 @@ import { SidePanel, Stack, } from '@nvidia/foundations-react-core'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { Empty } from '@studio/components/Empty'; import { Loading } from '@studio/components/Layouts/Loading'; import { ModelChat } from '@studio/components/ModelChat'; diff --git a/web/packages/studio/src/main.tsx b/web/packages/studio/src/main.tsx index a5cb3a91b2..fa4a7672f9 100644 --- a/web/packages/studio/src/main.tsx +++ b/web/packages/studio/src/main.tsx @@ -3,9 +3,9 @@ import '@studio/index.css'; +import { logger } from '@nemo/common/src/utils/logger'; import { App } from '@studio/App'; import { UI_THEME } from '@studio/util/localStorage'; -import { logger } from '@studio/util/logger'; import ReactDOM from 'react-dom/client'; // OpenTelemetry patches fetch/XHR globally, so this must settle before React diff --git a/web/packages/studio/src/plugins/PluginErrorBoundary.tsx b/web/packages/studio/src/plugins/PluginErrorBoundary.tsx index 6f078f06c6..18b60752d5 100644 --- a/web/packages/studio/src/plugins/PluginErrorBoundary.tsx +++ b/web/packages/studio/src/plugins/PluginErrorBoundary.tsx @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; +import { logger } from '@nemo/common/src/utils/logger'; import { Button, PageHeader, Panel, Stack } from '@nvidia/foundations-react-core'; -import { logger } from '@studio/util/logger'; import { Component, type ErrorInfo, type ReactNode } from 'react'; interface PluginErrorBoundaryProps { diff --git a/web/packages/studio/src/plugins/PluginRenderer.tsx b/web/packages/studio/src/plugins/PluginRenderer.tsx index 95621f974f..2c1e804906 100644 --- a/web/packages/studio/src/plugins/PluginRenderer.tsx +++ b/web/packages/studio/src/plugins/PluginRenderer.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import * as platformSdk from '@nemo/sdk/generated/platform/api'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { usePlugins, usePluginsLoaded } from '@studio/plugins/PluginContext'; @@ -13,7 +14,6 @@ import type { PluginTelemetry, } from '@studio/plugins/types'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; -import { logger } from '@studio/util/logger'; import { useCallback, useEffect, useMemo, useRef, type ReactElement } from 'react'; import { useAuth } from 'react-oidc-context'; import { useNavigate, useParams } from 'react-router'; diff --git a/web/packages/studio/src/plugins/utils.ts b/web/packages/studio/src/plugins/utils.ts index f6c08fcf2c..0d5c1463bd 100644 --- a/web/packages/studio/src/plugins/utils.ts +++ b/web/packages/studio/src/plugins/utils.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { PLATFORM_BASE_URL } from '@studio/constants/environment'; import { PLUGINS_MANIFEST_ENDPOINT } from '@studio/plugins/consts'; import { isTrustedBundleUrl } from '@studio/plugins/security'; @@ -10,7 +11,6 @@ import type { PluginModule, PluginQueryData, } from '@studio/plugins/types'; -import { logger } from '@studio/util/logger'; export function isValidPluginManifest(obj: unknown): obj is PluginManifest { if (typeof obj !== 'object' || obj === null) return false; diff --git a/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx b/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx index 3862c26d82..5356be4001 100644 --- a/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx +++ b/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { entitiesCreateWorkspace, entitiesGetWorkspace } from '@nemo/sdk/generated/platform/api'; import { Loading } from '@studio/components/Layouts/Loading'; import { useAuthProfile } from '@studio/providers/auth'; -import { logger } from '@studio/util/logger'; import { isAxiosError } from 'axios'; import { FC, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router'; diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx b/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx index ed9718227f..bf2247d850 100644 --- a/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx +++ b/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx @@ -11,11 +11,11 @@ */ import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { resourceRefSchema, type ResourceRef } from '@nemo/common/src/types'; import { ModelDeployment } from '@nemo/sdk/generated/platform/schema'; import { Button, Flex, PageHeader, Stack } from '@nvidia/foundations-react-core'; import { DeploymentsDataView } from '@studio/components/dataViews/DeploymentsDataView'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { DocumentationButton } from '@studio/components/DocumentationButton'; import { LINK_DOCS_DEPLOYMENTS } from '@studio/constants/links'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts b/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts index dca77329a2..7f800913bd 100644 --- a/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts +++ b/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts @@ -12,6 +12,7 @@ import { getPartsFromReference } from '@nemo/common/src/namedEntity'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; import { filesDeleteFileset, getFilesListFilesetsQueryKey, @@ -37,7 +38,6 @@ import { HUGGING_FACE_DEPLOYMENT_SOURCE_VALUE, huggingFaceSourceFilesetName, } from '@studio/routes/DeploymentsListRoute/huggingFaceDeploymentArtifacts'; -import { logger } from '@studio/util/logger'; import { type QueryClient, useMutation, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; diff --git a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx index ffeddcb698..85b2e64579 100644 --- a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx +++ b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx @@ -32,7 +32,7 @@ vi.mock('@studio/components/DatasetCreateModal', () => ({ }), })); -vi.mock('@studio/components/DeleteConfirmationModal', () => ({ +vi.mock('@nemo/common/src/components/DeleteConfirmationModal', () => ({ DeleteConfirmationModal: vi.fn(({ open, onClose, onDelete, title }) => { if (!open) return null; return ( diff --git a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx index e2899fc286..292b60468c 100644 --- a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx +++ b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { getEntityReference } from '@nemo/common/src/namedEntity'; +import { logger } from '@nemo/common/src/utils/logger'; import { useFilesDeleteFileset } from '@nemo/sdk/generated/platform/api'; import { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; import { @@ -14,8 +16,6 @@ import { import { invalidateDatasetCaches } from '@studio/api/datasets/invalidateDatasetCaches'; import { DatasetCreateModal } from '@studio/components/DatasetCreateModal'; import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/constants'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; -import { logger } from '@studio/util/logger'; import { EllipsisVertical } from 'lucide-react'; import { FC, useState } from 'react'; diff --git a/web/packages/studio/src/routes/FilesetNewRoute/index.tsx b/web/packages/studio/src/routes/FilesetNewRoute/index.tsx index 3822a6586d..64737e2d2b 100644 --- a/web/packages/studio/src/routes/FilesetNewRoute/index.tsx +++ b/web/packages/studio/src/routes/FilesetNewRoute/index.tsx @@ -6,6 +6,7 @@ import { checkDatasetQuality, type DatasetQualityReport, } from '@nemo/common/src/utils/datasetQuality'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { FilesetPurpose } from '@nemo/sdk/generated/platform/schema'; import { Button, SegmentedControl, SidePanel, Stack } from '@nvidia/foundations-react-core'; import { useSampleDatasetFiles } from '@studio/api/datasets/useSampleDatasetFiles'; @@ -23,7 +24,6 @@ import { import { useCreateFileset } from '@studio/routes/FilesetNewRoute/useCreateFileset'; import { CreateSecretModal } from '@studio/routes/SecretsListRoute/CreateSecretModal'; import { getWorkspaceFilesetsRoute } from '@studio/routes/utils'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { isHuggingFaceUrl, isNgcUrl } from '@studio/util/storageConfigFromUrl'; import { QueryObserverResult } from '@tanstack/react-query'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; diff --git a/web/packages/studio/src/routes/JobDetailRoute/index.tsx b/web/packages/studio/src/routes/JobDetailRoute/index.tsx index 954aaa83c9..4461e70768 100644 --- a/web/packages/studio/src/routes/JobDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/JobDetailRoute/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; +import { CancelJobButton } from '@nemo/common/src/components/CancelJobButton'; import { KVPair } from '@nemo/common/src/components/KVPair'; import { LogViewer } from '@nemo/common/src/components/LogViewer'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; @@ -10,7 +11,6 @@ import { useJobLogs } from '@nemo/common/src/hooks/useJobLogs'; import { getJobRefetchInterval } from '@nemo/common/src/utils/query'; import { useJobsGetJob, useJobsListJobResults } from '@nemo/sdk/generated/platform/api'; import { Flex, Grid, PageHeader, Panel, Spinner, Stack } from '@nvidia/foundations-react-core'; -import { CancelJobButton } from '@studio/components/CancelJobButton'; import { ROUTE_PARAMS } from '@studio/constants/routes'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; @@ -62,7 +62,7 @@ export const JobDetailRoute: FC = () => { slotHeading={jobName} slotActions={ - + } /> diff --git a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx index 160cc32092..b41a42d321 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx @@ -127,7 +127,7 @@ describe('SafeSynthesizerNewRoute', () => { SAFE_SYNTHESIZER_ENABLED: true, OTEL_SERVICE_NAME: 'test-service', })); - vi.doMock('@studio/util/logger', () => ({ + vi.doMock('@nemo/common/src/utils/logger', () => ({ logger: { debug: vi.fn(), error: vi.fn(), info: vi.fn(), warn: vi.fn() }, })); @@ -141,7 +141,7 @@ describe('SafeSynthesizerNewRoute', () => { SAFE_SYNTHESIZER_ENABLED: false, OTEL_SERVICE_NAME: 'test-service', })); - vi.doMock('@studio/util/logger', () => ({ + vi.doMock('@nemo/common/src/utils/logger', () => ({ logger: { debug: vi.fn(), error: vi.fn(), info: vi.fn(), warn: vi.fn() }, })); @@ -795,7 +795,7 @@ describe('SafeSynthesizerNewRoute', () => { it('should have form validation error handling configured', async () => { // Remove any lingering vi.doMock for the logger (left by Feature flag tests) so // the component gets the real logger and we can assert via console.error. - vi.doUnmock('@studio/util/logger'); + vi.doUnmock('@nemo/common/src/utils/logger'); vi.doMock('@studio/constants/environment', () => ({ SAFE_SYNTHESIZER_ENABLED: true, OTEL_SERVICE_NAME: 'test-service', diff --git a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx index 672071a786..3aec570dc4 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx @@ -3,6 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { getErrorMessage } from '@nemo/common/src/api/common/utils'; +import { logger } from '@nemo/common/src/utils/logger'; import { useSafeSynthesizerCreateJob } from '@nemo/sdk/generated/safe-synthesizer/api'; import { Banner, Button, Divider, Flex, Panel, Stack, Text } from '@nvidia/foundations-react-core'; import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment'; @@ -18,7 +19,6 @@ import { getSafeSynthesizerFormDefaults, } from '@studio/routes/SafeSynthesizerNewRoute/schema'; import { getSafeSynthesizerJobRoute, getSafeSynthesizerRoute } from '@studio/routes/utils'; -import { logger } from '@studio/util/logger'; import { FC, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx index 833b977703..9a7d6455ab 100644 --- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx +++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { NavigationDrawer } from '@studio/components/Layouts/NavigationDrawer'; import type { NavInputItem, @@ -56,7 +57,6 @@ import { getWorkspaceSettingsRoute, getWorkspaceVirtualModelsRoute, } from '@studio/routes/utils'; -import { logger } from '@studio/util/logger'; import { Bot, Boxes, diff --git a/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx b/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx index 42934b5ab9..2234eba267 100644 --- a/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx +++ b/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx @@ -8,6 +8,7 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/Controlled import { FormModal } from '@nemo/common/src/components/FormModal'; import { RadioCard } from '@nemo/common/src/components/RadioCard'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { getEntitiesListWorkspaceMembersQueryKey, useEntitiesAddWorkspaceMember, @@ -22,7 +23,6 @@ import { WORKSPACE_ROLE_DESCRIPTIONS, type WorkspaceMemberRole, } from '@studio/routes/WorkspaceMembersRoute/workspaceMemberRoles'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC, useEffect, useMemo } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { z } from 'zod'; diff --git a/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx index 125cf3e78a..cf40dfe0f2 100644 --- a/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx @@ -4,6 +4,7 @@ */ import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { getEntitiesListWorkspaceMembersQueryKey, useEntitiesListWorkspaceMembers, @@ -13,7 +14,6 @@ import type { WorkspaceMember } from '@nemo/sdk/generated/platform/schema'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; import { queryClient } from '@studio/api/queryClient'; import { MembersDataView } from '@studio/components/dataViews/MembersDataView'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx b/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx index 3422166e8e..5ded8de630 100644 --- a/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { DEFAULT_WORKSPACE } from '@nemo/common/src/models/constants'; import { getEntitiesListWorkspacesQueryKey, @@ -8,7 +9,6 @@ import { } from '@nemo/sdk/generated/platform/api'; import { WorkspacesPage } from '@nemo/sdk/generated/platform/schema'; import { queryClient } from '@studio/api/queryClient'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { useRecentWorkspaces } from '@studio/components/WorkspaceDropdown/useRecentWorkspaces'; import { getWorkspaceDetailsDefaultRoute } from '@studio/routes/utils'; import { FC } from 'react'; diff --git a/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx b/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx index ad91728446..8872a18448 100644 --- a/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx +++ b/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx @@ -6,6 +6,7 @@ import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledT import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { getEntitiesGetWorkspaceQueryKey, getEntitiesListWorkspacesQueryKey, @@ -15,7 +16,6 @@ import { import { EntitiesUpdateWorkspaceBody } from '@nemo/sdk/generated/platform/zod/entity-store'; import { Stack, Text } from '@nvidia/foundations-react-core'; import { queryClient } from '@studio/api/queryClient'; -import { handleFormErrorsGeneric } from '@studio/util/forms/error'; import { FC, useEffect } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { z } from 'zod'; diff --git a/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx b/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx index 0d418e4451..5b996e98a2 100644 --- a/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx +++ b/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { LogViewer } from '@nemo/common/src/components/LogViewer'; +import { logger } from '@nemo/common/src/utils/logger'; import { getAgentsStreamDeploymentLogsQueryKey, useAgentsGetDeploymentLogs, @@ -10,7 +11,6 @@ import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema'; import type { PlatformJobLog } from '@nemo/sdk/generated/platform/schema'; import { Block, Select, Stack, Text } from '@nvidia/foundations-react-core'; import { PLATFORM_BASE_URL } from '@studio/constants/environment'; -import { logger } from '@studio/util/logger'; import { streamSse } from '@studio/util/sseStream'; import { type FC, useEffect, useMemo, useState } from 'react'; import { useAuth } from 'react-oidc-context'; diff --git a/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx index 0136de7e29..958a419701 100644 --- a/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx +++ b/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { StatusBadge } from '@nemo/common/src/components/StatusBadge'; import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema/AgentDeployment'; import { @@ -16,7 +17,6 @@ import { Text, } from '@nvidia/foundations-react-core'; import { getAgentModelNames } from '@studio/components/dataViews/AgentsDataView/utils'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { SubmitEvaluationModal } from '@studio/components/evaluation/SubmitEvaluationModal'; import { ROUTE_PARAMS } from '@studio/constants/routes'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts index a24036af6d..d763eb8c02 100644 --- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts +++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { toError } from '@nemo/common/src/utils/logger'; import { customFetch } from '@nemo/sdk/generated/fetchers/platform'; import { filesCreateFileset, @@ -29,7 +30,6 @@ import { serializeSuggestions, suggestionIdentity, } from '@studio/routes/agents/AgentSuggestionsRoute/utils'; -import { toError } from '@studio/util/logger'; export const TELEMETRY_FILESET = 'nemo-agent-telemetry'; export const OPTIMIZER_FILESET = 'nemo-agent-optimizer'; diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts index 863ce26c0d..94892fe236 100644 --- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts +++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { toError } from '@nemo/common/src/utils/logger'; import { ensureEvalConfigFileset } from '@studio/api/evaluation/eval-config-fileset'; import { applySuggestion, @@ -37,7 +38,6 @@ import { suggestionIdentity, } from '@studio/routes/agents/AgentSuggestionsRoute/utils'; import { getAgentEvaluationDetailRoute } from '@studio/routes/utils'; -import { toError } from '@studio/util/logger'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback, useEffect, useRef, useState } from 'react'; diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts index 4f64e01f9c..2c33a0975f 100644 --- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts +++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import type { ModelEntity } from '@nemo/sdk/generated/platform/schema/ModelEntity'; import { SAMPLE_EVAL_CONFIG_PATH } from '@studio/api/evaluation/eval-config-fileset'; import type { AgentConfig } from '@studio/components/dataViews/AgentsDataView'; @@ -11,7 +12,6 @@ import type { SnapshotShape, SuggestionApplySpec, } from '@studio/routes/agents/AgentSuggestionsRoute/types'; -import { logger } from '@studio/util/logger'; /** Fileset name for an agent's eval bundle. */ export const evalFilesetForAgent = (agentName: string): string => `${agentName}-eval`; diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx b/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx index c627d90525..718aced993 100644 --- a/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx +++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; +import { logger } from '@nemo/common/src/utils/logger'; import { Button } from '@nvidia/foundations-react-core'; -import { logger } from '@studio/util/logger'; import { Component, type ErrorInfo, type ReactNode } from 'react'; interface ChatThreadErrorBoundaryProps { diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx b/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx index f42e564f7b..d05370ab06 100644 --- a/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx +++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { Banner, Button, Flex, Text, Tooltip } from '@nvidia/foundations-react-core'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { Empty } from '@studio/components/Empty'; import { deleteCopilotSessionHistory, diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts index 0679bc9816..aa2bc7630a 100644 --- a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts +++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { COPILOT_JOB_PROGRESS_MCP_TOOL_NAME } from '@studio/routes/agents/CopilotChatRoute/jobProgressConsts'; import { getAssistantPartsFromCopilotEvent, @@ -9,7 +10,6 @@ import { parseSseChunk, } from '@studio/routes/agents/CopilotChatRoute/stream'; import { COPILOT_SUBTLE_TOOL_GROUP_NAME } from '@studio/routes/agents/CopilotChatRoute/toolParts'; -import { logger } from '@studio/util/logger'; describe('Copilot stream utilities', () => { it('parses SSE events and preserves incomplete trailing data', () => { diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts index e897804080..6d20f869b5 100644 --- a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts +++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import type { ThreadAssistantMessagePart } from '@assistant-ui/react'; +import { logger } from '@nemo/common/src/utils/logger'; import { createCopilotToolCallPart, groupConsecutiveCopilotSubtleToolCalls, } from '@studio/routes/agents/CopilotChatRoute/toolParts'; -import { logger } from '@studio/util/logger'; interface ServerSentEvent { event?: string; diff --git a/web/packages/studio/src/routes/groups/agentRoutes.tsx b/web/packages/studio/src/routes/groups/agentRoutes.tsx index b07163f8bb..1131d96b6b 100644 --- a/web/packages/studio/src/routes/groups/agentRoutes.tsx +++ b/web/packages/studio/src/routes/groups/agentRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { AGENTS_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { iconColorClass } from '@studio/routes/constants'; diff --git a/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx b/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx index f94032a1a8..fb1c799aaf 100644 --- a/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx +++ b/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ANONYMIZER_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateAnonymizerRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/customizationRoutes.tsx b/web/packages/studio/src/routes/groups/customizationRoutes.tsx index d150cf5221..6166e18f7d 100644 --- a/web/packages/studio/src/routes/groups/customizationRoutes.tsx +++ b/web/packages/studio/src/routes/groups/customizationRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateCustomizationRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/dashboardRoutes.tsx b/web/packages/studio/src/routes/groups/dashboardRoutes.tsx index 381f7b096e..b2e8476e49 100644 --- a/web/packages/studio/src/routes/groups/dashboardRoutes.tsx +++ b/web/packages/studio/src/routes/groups/dashboardRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { COPILOT_STUDIO_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateCopilotStudioRoutes, gateDashboardRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx b/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx index 173de5646f..1d3cb71102 100644 --- a/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx +++ b/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { DATA_DESIGNER_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateDataDesignerRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/deploymentRoutes.tsx b/web/packages/studio/src/routes/groups/deploymentRoutes.tsx index 30e6ce63ff..381e56a5f2 100644 --- a/web/packages/studio/src/routes/groups/deploymentRoutes.tsx +++ b/web/packages/studio/src/routes/groups/deploymentRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { DEPLOYMENTS_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateDeploymentsRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/evaluationRoutes.tsx b/web/packages/studio/src/routes/groups/evaluationRoutes.tsx index da001535e1..909bd54880 100644 --- a/web/packages/studio/src/routes/groups/evaluationRoutes.tsx +++ b/web/packages/studio/src/routes/groups/evaluationRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateEvaluationBenchmarksRoutes, gateEvaluationRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/experimentRoutes.tsx b/web/packages/studio/src/routes/groups/experimentRoutes.tsx index a34beba13e..73186a3fdc 100644 --- a/web/packages/studio/src/routes/groups/experimentRoutes.tsx +++ b/web/packages/studio/src/routes/groups/experimentRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateExperimentRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/filesetRoutes.tsx b/web/packages/studio/src/routes/groups/filesetRoutes.tsx index fe65bc377e..1dfdb8c832 100644 --- a/web/packages/studio/src/routes/groups/filesetRoutes.tsx +++ b/web/packages/studio/src/routes/groups/filesetRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateDatasetsRoutes, gateFilesetDetailsRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx b/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx index 135f12b6a0..a939766930 100644 --- a/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx +++ b/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { GUARDRAIL_CHECKS_DEFAULT_SUB_TAB } from '@studio/routes/guardrails/GuardrailChecksTab/constants'; import { gateGuardrailsRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx b/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx index 32f92d189f..1e8fedfe9a 100644 --- a/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx +++ b/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateInferenceProviderRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/intakeRoutes.tsx b/web/packages/studio/src/routes/groups/intakeRoutes.tsx index 3cf04d2f4e..b1399e6c09 100644 --- a/web/packages/studio/src/routes/groups/intakeRoutes.tsx +++ b/web/packages/studio/src/routes/groups/intakeRoutes.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { Stack } from '@nvidia/foundations-react-core'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { INTAKE_FILTER_ACTION_TARGET_ID } from '@studio/routes/IntakeLayout'; import { gateIntakeRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/jobRoutes.tsx b/web/packages/studio/src/routes/groups/jobRoutes.tsx index ed05e9de2c..60f4f07693 100644 --- a/web/packages/studio/src/routes/groups/jobRoutes.tsx +++ b/web/packages/studio/src/routes/groups/jobRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateJobsRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/memberRoutes.tsx b/web/packages/studio/src/routes/groups/memberRoutes.tsx index 66e40bd0c3..a0492267a8 100644 --- a/web/packages/studio/src/routes/groups/memberRoutes.tsx +++ b/web/packages/studio/src/routes/groups/memberRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateMembersRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx b/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx index cf7b5114a6..610d23a4f3 100644 --- a/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx +++ b/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { MODEL_COMPARE_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateModelCompareRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/optimizerRoutes.tsx b/web/packages/studio/src/routes/groups/optimizerRoutes.tsx index 455addcab4..84be5c5126 100644 --- a/web/packages/studio/src/routes/groups/optimizerRoutes.tsx +++ b/web/packages/studio/src/routes/groups/optimizerRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { OPTIMIZER_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateOptimizerRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx b/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx index 37948d7ff0..f9195be52e 100644 --- a/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx +++ b/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { gateSafeSynthesizerRoutes } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/groups/secretsRoutes.tsx b/web/packages/studio/src/routes/groups/secretsRoutes.tsx index 82201652ba..3b22f67ceb 100644 --- a/web/packages/studio/src/routes/groups/secretsRoutes.tsx +++ b/web/packages/studio/src/routes/groups/secretsRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateSecretsRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/settingsRoutes.tsx b/web/packages/studio/src/routes/groups/settingsRoutes.tsx index 6c70030c87..f1f5c0097a 100644 --- a/web/packages/studio/src/routes/groups/settingsRoutes.tsx +++ b/web/packages/studio/src/routes/groups/settingsRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { gateSettingsRoutes } from '@studio/routes/utils'; import { lazy } from 'react'; diff --git a/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx b/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx index 303cc67c09..781e187999 100644 --- a/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx +++ b/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { ROUTES } from '@studio/constants/routes'; import { lazy } from 'react'; import type { RouteObject } from 'react-router'; diff --git a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx index 2e74f25dd5..4d4c175dc8 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { type QuickActionItem, QuickActionsMenuRoot, } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { useGuardrailsDeleteConfig } from '@nemo/sdk/generated/platform/api'; import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { CreateGuardrailModal } from '@studio/routes/guardrails/CreateGuardrailModal'; import { getGuardrailsRoute } from '@studio/routes/utils'; diff --git a/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx b/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx index 77c280785a..46fdf80c2d 100644 --- a/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx +++ b/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx @@ -11,6 +11,7 @@ */ import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; +import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal'; import { getGuardrailsGetGuardrailConfigQueryKey, useGuardrailsDeleteConfig, @@ -18,7 +19,6 @@ import { import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema'; import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; import { GuardrailsDataView } from '@studio/components/dataViews/GuardrailsDataView'; -import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { CreateGuardrailModal } from '@studio/routes/guardrails/CreateGuardrailModal'; diff --git a/web/packages/studio/src/routes/index.tsx b/web/packages/studio/src/routes/index.tsx index b54d11d538..c6d2b6d267 100644 --- a/web/packages/studio/src/routes/index.tsx +++ b/web/packages/studio/src/routes/index.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { Loading } from '@studio/components/Layouts/Loading'; import { ROUTES } from '@studio/constants/routes'; import { PluginProvider } from '@studio/plugins/PluginProvider'; diff --git a/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx b/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx index e8ea760fa3..44c57d5f69 100644 --- a/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx +++ b/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx @@ -7,12 +7,12 @@ import { ROW_ACTIONS_COLUMN_SIZE, StudioDataView, } from '@nemo/common/src/components/DataView/StudioDataView'; +import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { RelativeTime } from '@nemo/common/src/components/RelativeTime'; import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState'; import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState'; import { Flex, PageHeader, Stack, Tag, Text } from '@nvidia/foundations-react-core'; import { type InsightListItem, useOptimizerListInsights } from '@studio/api/optimizer'; -import { ErrorPanel } from '@studio/components/ErrorPanel'; import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; diff --git a/web/packages/studio/src/util/files.ts b/web/packages/studio/src/util/files.ts index d34c6f60b7..f2b0d930cd 100644 --- a/web/packages/studio/src/util/files.ts +++ b/web/packages/studio/src/util/files.ts @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import { ContentType } from '@nemo/common/src/components/CodeEditor/constants'; +import { logger } from '@nemo/common/src/utils/logger'; import { FileSystemDirectory, FileSystemNode } from '@studio/components/FilesTable/utils'; import { parseCSVTable } from '@studio/components/SafeSynthesizerFilesetPreview/util'; -import { logger } from '@studio/util/logger'; import { getTextWithCount, parseCSV } from '@studio/util/strings'; /** diff --git a/web/packages/studio/src/util/llm.ts b/web/packages/studio/src/util/llm.ts index c81ce0ecc4..13c9b99102 100644 --- a/web/packages/studio/src/util/llm.ts +++ b/web/packages/studio/src/util/llm.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import type { ExcludedChatCompletionMessageParam } from '@nemo/common/src/types/chat'; +import { logger } from '@nemo/common/src/utils/logger'; import { Row } from '@studio/util/files'; -import { logger } from '@studio/util/logger'; import Handlebars from 'handlebars'; export const extractUserMessage = (props: { row: Row; template?: string }): string => { diff --git a/web/packages/studio/src/util/logVersion.ts b/web/packages/studio/src/util/logVersion.ts new file mode 100644 index 0000000000..7993faf4c2 --- /dev/null +++ b/web/packages/studio/src/util/logVersion.ts @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { logger } from '@nemo/common/src/utils/logger'; +import { VERSION_SHA } from '@studio/constants/environment'; + +export const logVersion = async () => { + if (VERSION_SHA) { + logger.info(`Version: ${VERSION_SHA}`); + } +}; diff --git a/web/packages/studio/src/util/logger.test.ts b/web/packages/studio/src/util/logger.test.ts index 6c95f19fa2..cc11e406a1 100644 --- a/web/packages/studio/src/util/logger.test.ts +++ b/web/packages/studio/src/util/logger.test.ts @@ -31,8 +31,9 @@ vi.mock('@studio/constants/environment', async (importOriginal) => { }; }); +import { handleGenericError, logger } from '@nemo/common/src/utils/logger'; import { SeverityNumber } from '@opentelemetry/api-logs'; -import { handleGenericError, logVersion, logger } from '@studio/util/logger'; +import { logVersion } from '@studio/util/logVersion'; describe('WebsiteLogger', () => { beforeEach(() => { diff --git a/web/packages/studio/src/util/strings.ts b/web/packages/studio/src/util/strings.ts index 6863c17fbb..ed869784ff 100644 --- a/web/packages/studio/src/util/strings.ts +++ b/web/packages/studio/src/util/strings.ts @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { logger } from '@nemo/common/src/utils/logger'; import { Row } from '@studio/util/files'; -import { logger } from '@studio/util/logger'; import Papa from 'papaparse'; export { getTextWithCount } from '@nemo/common/src/utils/formatters'; diff --git a/web/packages/studio/src/workers/LargeFileWorker.ts b/web/packages/studio/src/workers/LargeFileWorker.ts index 9857c566a0..ecb0c74a25 100644 --- a/web/packages/studio/src/workers/LargeFileWorker.ts +++ b/web/packages/studio/src/workers/LargeFileWorker.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { DEFAULT_WORKSPACE } from '@nemo/common/src/models/constants'; +import { toError } from '@nemo/common/src/utils/logger'; import { filesDownloadFile } from '@nemo/sdk/generated/platform/api'; -import { toError } from '@studio/util/logger'; import axios from 'axios'; export interface LargeFileWorkerMessage { diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e321d0ebdf..9149347d16 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -319,6 +319,9 @@ importers: '@nvidia/foundations-react-core': specifier: 'catalog:' version: 1.7.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@opentelemetry/api-logs': + specifier: ^0.219.0 + version: 0.219.0 '@tanstack/match-sorter-utils': specifier: ^8.19.4 version: 8.19.4 From ca4e8166d2a92865acc5427a9be1f48c015377ff Mon Sep 17 00:00:00 2001 From: mschwab Date: Fri, 7 Aug 2026 16:29:37 -0700 Subject: [PATCH 4/8] fix(studio): clear plugin breadcrumbs on page and plugin switches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review on #1192 caught two gaps in the breadcrumbs wiring. PluginRenderer cleared the trail only on unmount, but the router reuses the component across a :pluginName change, so the outgoing plugin's breadcrumbs stayed visible until the incoming one set its own. Key the cleanup on pluginName. The new test fails without that dep. SharedUiPage set a trail and never cleared it. Studio only clears when the whole plugin unmounts, so navigating between pages inside a plugin left the previous page's crumb behind. The example is the template other plugins copy, and AGENTS.md told authors not to hand-roll cleanup — true for plugin unmount, wrong for page unmount. Fixed both, and listed breadcrumbs in the host capability enumeration it was missing from. Signed-off-by: mschwab --- .../src/nemo_example_plugin/web/dist/index.js | 10 +++--- plugins/example-plugin/web/AGENTS.md | 5 +-- .../example-plugin/web/src/SharedUiPage.tsx | 4 ++- .../src/plugins/PluginRenderer.test.tsx | 32 +++++++++++++++++++ .../studio/src/plugins/PluginRenderer.tsx | 6 ++-- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js b/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js index 1c9df06b86..c240f91e88 100644 --- a/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js +++ b/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js @@ -24,12 +24,10 @@ function x({ host: e }) { page: 1, page_size: 100 }), o = t?.data ?? [], { set: s } = e.breadcrumbs, { workspaceId: c } = e; - h(() => { - s([{ - label: "Example Plugin", - href: v(c, "overview") - }, { label: "Shared UI" }]); - }, [s, c]); + h(() => (s([{ + label: "Example Plugin", + href: v(c, "overview") + }, { label: "Shared UI" }]), () => s([])), [s, c]); let y = p(), b = m((e) => [ e.accessor("name", { header: "Name", diff --git a/plugins/example-plugin/web/AGENTS.md b/plugins/example-plugin/web/AGENTS.md index 1a814a5198..95c5ca7545 100644 --- a/plugins/example-plugin/web/AGENTS.md +++ b/plugins/example-plugin/web/AGENTS.md @@ -31,14 +31,15 @@ Runtime contract: `../../../web/packages/studio/src/plugins/types.ts`. Studio injects everything a plugin needs through a **single `host` prop** (`host.workspaceId`, `host.auth`, `host.sdk`, `host.navigation`, -`host.notifications`, `host.telemetry`) — grouped so new capabilities extend the +`host.notifications`, `host.telemetry`, `host.breadcrumbs`) — grouped so new capabilities extend the handle without changing `Root`'s signature. Destructure what you use. All are backed by Studio's own singletons: `notifications` fires into Studio's shared toaster, `telemetry` logs to Studio's OTEL pipeline (auto-scoped to the plugin), `navigation` drives Studio's shared router, and `breadcrumbs` writes Studio's breadcrumb bar — which lives in GlobalNav, *outside* the plugin's subtree, so a plugin cannot render it itself. Studio clears the trail when the plugin -unmounts, so don't hand-roll cleanup. See `src/SharedUiPage.tsx`. +unmounts, but **not between pages within the plugin**: return a cleanup that +clears it, or the trail follows you to the next page. See `src/SharedUiPage.tsx`. `@tanstack/react-query` **is** shared — call `useQuery`/`useMutation` and it reads Studio's `QueryClientProvider` (one cache across Studio and every plugin). Put the diff --git a/plugins/example-plugin/web/src/SharedUiPage.tsx b/plugins/example-plugin/web/src/SharedUiPage.tsx index 4a03fcd982..3907e5420d 100644 --- a/plugins/example-plugin/web/src/SharedUiPage.tsx +++ b/plugins/example-plugin/web/src/SharedUiPage.tsx @@ -23,7 +23,8 @@ export function SharedUiPage({ host }: { host: PluginHost }) { }); const workspaces = data?.data ?? []; - // Renders in Studio's chrome, outside this subtree. Studio clears it on unmount. + // Renders in Studio's chrome, outside this subtree. Studio clears the trail + // when the plugin unmounts, but not between pages — so clear it here too. const { set: setBreadcrumbs } = host.breadcrumbs; const { workspaceId } = host; useEffect(() => { @@ -31,6 +32,7 @@ export function SharedUiPage({ host }: { host: PluginHost }) { { label: "Example Plugin", href: pluginPath(workspaceId, "overview") }, { label: "Shared UI" }, ]); + return () => setBreadcrumbs([]); }, [setBreadcrumbs, workspaceId]); // Syncs to URL search params — one DataView per route or they fight. diff --git a/web/packages/studio/src/plugins/PluginRenderer.test.tsx b/web/packages/studio/src/plugins/PluginRenderer.test.tsx index 721d55e6d0..77d3b19a22 100644 --- a/web/packages/studio/src/plugins/PluginRenderer.test.tsx +++ b/web/packages/studio/src/plugins/PluginRenderer.test.tsx @@ -202,4 +202,36 @@ describe('PluginRenderer', () => { // Studio clears the trail itself; a plugin that forgets cannot leave one behind. expect(seen.at(-1)).toEqual([]); }); + + it('clears the trail when the router swaps one plugin for another', () => { + const seen: BreadcrumbsItemProps[][] = []; + function BreadcrumbSpy() { + seen.push(useBreadcrumbs().breadcrumbs); + return null; + } + vi.mocked(usePlugins).mockReturnValue([makePlugin('plugin-a'), makePlugin('plugin-b')]); + + render( + + + + + } + /> + + + + ); + + act(() => capturedProps?.host.breadcrumbs.set([{ label: 'From plugin A' }])); + expect(seen.at(-1)).toEqual([{ slotLabel: 'From plugin A', href: undefined }]); + + // Navigate for real: MemoryRouter only reads initialEntries once, and the + // point is that PluginRenderer stays mounted while :pluginName changes, so + // only the effect's deps can force the reset. + act(() => capturedProps?.host.navigation.navigate('/workspaces/ws1/plugin/plugin-b/')); + expect(seen.at(-1)).toEqual([]); + }); }); diff --git a/web/packages/studio/src/plugins/PluginRenderer.tsx b/web/packages/studio/src/plugins/PluginRenderer.tsx index 2c1e804906..49637a0722 100644 --- a/web/packages/studio/src/plugins/PluginRenderer.tsx +++ b/web/packages/studio/src/plugins/PluginRenderer.tsx @@ -53,8 +53,10 @@ export const PluginRenderer = (): ReactElement => { setBreadcrumbs(trail.map(({ label, href }) => ({ slotLabel: label, href }))), [setBreadcrumbs] ); - // Studio owns the cleanup so a plugin can't leave a stale trail behind. - useEffect(() => () => setBreadcrumbs([]), [setBreadcrumbs]); + // Studio owns the cleanup so a plugin can't leave a stale trail behind. Keyed + // on pluginName too: the router reuses this component across plugins, and the + // outgoing plugin's trail would otherwise persist until the next one sets its own. + useEffect(() => () => setBreadcrumbs([]), [setBreadcrumbs, pluginName]); const host = useMemo( () => ({ From f24b9921a35af64f48db3fb216be17740b8b2205 Mon Sep 17 00:00:00 2001 From: mschwab Date: Mon, 10 Aug 2026 09:52:10 -0700 Subject: [PATCH 5/8] fix(studio): give plugin-facing components an onNotify sink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ToastProvider does not cross the plugin boundary. Studio mounts it by deep import, so it lands in Studio's own module graph, while a plugin resolves the bare `@nemo/common` specifier to public/vendor/common.js — a second copy with its own ToastContext. ConfirmationModal, DeleteConfirmationModal and LogViewer are all plugin API and all called useToast internally, so rendering any of them from a plugin threw "useToast must be used within a ToastProvider". Mounting the provider from the shared copy is not reachable: Studio's tsconfig maps only `@nemo/common/*`, and `@nemo/common` is in VENDOR_IMPORT_MAP but not VENDOR_EXTERNALS, so Studio never emits a bare import to externalize. Adding it would split Studio against itself — the provider in vendor, every deep-import useToast caller still bundled. Instead the three components take an optional onNotify. A new useNotify hook resolves the sink: onNotify first, then the surrounding ToastProvider, then a logger.warn and drop. It reads ToastContext with useContext rather than useToast, so an absent context is a supported state instead of a throw. PluginNotifications.notify also gains the toast options argument so durationMs survives the boundary rather than being silently dropped at it. Signed-off-by: mschwab --- plugins/example-plugin/web/AGENTS.md | 18 +++- plugins/example-plugin/web/src/types.ts | 6 +- .../components/ConfirmationModal/index.tsx | 14 ++- .../DeleteConfirmationModal/index.tsx | 3 + .../common/src/components/LogViewer/index.tsx | 10 +- web/packages/common/src/plugin.ts | 2 + .../common/src/providers/toast/types.ts | 11 ++- .../src/providers/toast/useNotify.test.tsx | 95 +++++++++++++++++++ .../common/src/providers/toast/useNotify.ts | 32 +++++++ .../studio/src/plugins/PluginRenderer.tsx | 2 +- web/packages/studio/src/plugins/types.ts | 7 +- 11 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 web/packages/common/src/providers/toast/useNotify.test.tsx create mode 100644 web/packages/common/src/providers/toast/useNotify.ts diff --git a/plugins/example-plugin/web/AGENTS.md b/plugins/example-plugin/web/AGENTS.md index 95c5ca7545..24c75a6078 100644 --- a/plugins/example-plugin/web/AGENTS.md +++ b/plugins/example-plugin/web/AGENTS.md @@ -85,6 +85,16 @@ import { StudioDataView, useStudioDataViewState } from '@nemo/common'; Studio bundles the same files through its own graph. A plugin adds no CSS. - **`useStudioDataViewState` syncs to URL search params** on Studio's shared router — two DataViews on one route will fight over them. +- **Toasts need `onNotify`.** `ToastProvider` is *not* shared: Studio mounts it + by deep import, so this bundle carries its own `ToastContext` with nothing in + it. `ConfirmationModal`, `DeleteConfirmationModal` and `LogViewer` therefore + take an `onNotify` prop — pass `host.notifications.notify` and the message + lands in Studio's toaster. Omit it and the message is dropped with a + `logger.warn`; nothing throws, so the miss is silent in the UI. + + ```tsx + + ``` ## Contract @@ -96,7 +106,13 @@ import { StudioDataView, useStudioDataViewState } from '@nemo/common'; auth: { accessToken: string; getAccessToken: () => string }; sdk: { platform: /* @nemo/sdk platform hooks */ }; navigation: { navigate: (to: string) => void; back: () => void }; - notifications: { notify: (message: string, type?: 'success'|'error'|'info'|'warning') => void }; + notifications: { + notify: ( + message: string, + type?: 'success'|'error'|'info'|'warning', + options?: { durationMs?: number | false }, + ) => void; + }; telemetry: { info; warn; error: (m, cause?) => void; event: (name, attrs?) => void }; breadcrumbs: { set: (trail: { label: string; href?: string }[]) => void }; }; diff --git a/plugins/example-plugin/web/src/types.ts b/plugins/example-plugin/web/src/types.ts index d45ac94a74..f6955147e8 100644 --- a/plugins/example-plugin/web/src/types.ts +++ b/plugins/example-plugin/web/src/types.ts @@ -35,8 +35,12 @@ export interface PluginNavigation { export type NotificationType = 'success' | 'error' | 'info' | 'warning'; +export interface NotificationOptions { + durationMs?: number | false; +} + export interface PluginNotifications { - notify: (message: string, type?: NotificationType) => void; + notify: (message: string, type?: NotificationType, options?: NotificationOptions) => void; } export interface PluginBreadcrumb { diff --git a/web/packages/common/src/components/ConfirmationModal/index.tsx b/web/packages/common/src/components/ConfirmationModal/index.tsx index f64bd539c3..0c1961b91a 100644 --- a/web/packages/common/src/components/ConfirmationModal/index.tsx +++ b/web/packages/common/src/components/ConfirmationModal/index.tsx @@ -4,7 +4,8 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; -import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import { useNotify } from '@nemo/common/src/providers/toast/useNotify'; import { getErrorMessage } from '@nemo/common/src/utils/error'; import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; import { Stack, Text } from '@nvidia/foundations-react-core'; @@ -34,6 +35,8 @@ export interface ConfirmationModalProps extends Pick = ({ @@ -49,6 +52,7 @@ export const ConfirmationModal: FC = ({ submitButtonText = 'Confirm', submitButtonColor, suppressResultToasts = false, + onNotify, }) => { const { reset, @@ -60,7 +64,7 @@ export const ConfirmationModal: FC = ({ defaultValues: { confirmText: '' }, }); const [isPending, setIsPending] = useState(false); - const toast = useToast(); + const notify = useNotify(onNotify); const resetAndClose = () => { reset(); @@ -73,13 +77,13 @@ export const ConfirmationModal: FC = ({ const ok = await onConfirm(); if (!suppressResultToasts) { if (ok) { - toast.success(successText); + notify(successText, 'success'); } else { - toast.error(errorText); + notify(errorText, 'error'); } } } catch (error: unknown) { - toast.error(getErrorMessage(error)); + notify(getErrorMessage(error), 'error'); } finally { resetAndClose(); setIsPending(false); diff --git a/web/packages/common/src/components/DeleteConfirmationModal/index.tsx b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx index 71007c0966..eedf0267c7 100644 --- a/web/packages/common/src/components/DeleteConfirmationModal/index.tsx +++ b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx @@ -3,6 +3,7 @@ import { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal'; import { FormModalProps } from '@nemo/common/src/components/FormModal'; +import { NotifyFn } from '@nemo/common/src/providers/toast/types'; import { FC } from 'react'; interface DeleteModalProps extends Pick { @@ -14,6 +15,8 @@ interface DeleteModalProps extends Pick { successText?: string; errorText?: string; suppressResultToasts?: boolean; + /** Where result messages go. Defaults to the surrounding ToastProvider; plugins pass `host.notifications.notify`. */ + onNotify?: NotifyFn; } export const DeleteConfirmationModal: FC = ({ diff --git a/web/packages/common/src/components/LogViewer/index.tsx b/web/packages/common/src/components/LogViewer/index.tsx index f0f87a380d..18ecfe65c5 100644 --- a/web/packages/common/src/components/LogViewer/index.tsx +++ b/web/packages/common/src/components/LogViewer/index.tsx @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { useStickToBottom } from '@nemo/common/src/hooks/useStickToBottom'; -import { useToast } from '@nemo/common/src/providers/toast/useToast'; +import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import { useNotify } from '@nemo/common/src/providers/toast/useNotify'; import { triggerDownload } from '@nemo/common/src/utils/file'; import { formatLogs } from '@nemo/common/src/utils/logs'; import type { PlatformJobLog } from '@nemo/sdk/generated/platform/schema'; @@ -27,6 +28,8 @@ interface LogViewerProps { downloadFilename?: string; rows?: number; emptyMessage?: string; + /** Where the copy confirmation goes. Defaults to the surrounding ToastProvider; plugins pass `host.notifications.notify`. */ + onNotify?: NotifyFn; } export const LogViewer: FC = ({ @@ -35,6 +38,7 @@ export const LogViewer: FC = ({ downloadFilename, rows = DEFAULT_ROW_COUNT, emptyMessage = 'No logs available yet', + onNotify, }) => { const [showAllLogs, setShowAllLogs] = useState(false); const [wrapLines, setWrapLines] = useState(false); @@ -45,7 +49,7 @@ export const LogViewer: FC = ({ const isShowingLogs = useMemo(() => logs.length > 0 && !isLoading, [logs.length, isLoading]); - const { success } = useToast(); + const notify = useNotify(onNotify); const { ref: codeScrollRef, scrollToBottom } = useStickToBottom({ enabled: isShowingLogs, @@ -87,7 +91,7 @@ export const LogViewer: FC = ({ kind="block" collapsible={false} rows={rows} - onCopySuccess={() => success('Copied to clipboard!', { durationMs: 3000 })} + onCopySuccess={() => notify('Copied to clipboard!', 'success', { durationMs: 3000 })} className="min-h-auto h-full" attributes={{ CodeSnippetCode: { diff --git a/web/packages/common/src/plugin.ts b/web/packages/common/src/plugin.ts index c9f1ee4e13..c0a2a4c864 100644 --- a/web/packages/common/src/plugin.ts +++ b/web/packages/common/src/plugin.ts @@ -64,6 +64,8 @@ export { getErrorMessage } from '@nemo/common/src/utils/error'; export { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; export { logger, toError } from '@nemo/common/src/utils/logger'; +export type { NotifyFn, NotifyType } from '@nemo/common/src/providers/toast/types'; + export { JOB_POLLING_INTERVAL_MS } from '@nemo/common/src/constants'; export { DEFAULT_PAGE, diff --git a/web/packages/common/src/providers/toast/types.ts b/web/packages/common/src/providers/toast/types.ts index 8143683135..362f679296 100644 --- a/web/packages/common/src/providers/toast/types.ts +++ b/web/packages/common/src/providers/toast/types.ts @@ -4,7 +4,7 @@ import { Toast } from '@nvidia/foundations-react-core'; import { ComponentProps, ReactNode } from 'react'; -interface MessageFnOptions { +export interface MessageFnOptions { durationMs?: number | false; // How long should the toast appear for? Pass false to prevent auto-dismiss. } interface AddToastFnOptions extends MessageFnOptions { @@ -27,6 +27,15 @@ export interface ToastObject { dismissToast: DismissToastFn; } +export type NotifyType = 'success' | 'error' | 'info' | 'warning'; + +/** + * Provider-independent notification sink. Components exposed across the plugin + * boundary take this as a prop so a plugin can route them at Studio's toaster + * without sharing a ToastContext module instance. + */ +export type NotifyFn = (message: string, type?: NotifyType, options?: MessageFnOptions) => void; + export interface ToastContextValue { addToast: AddToastFn; dismissToast: DismissToastFn; diff --git a/web/packages/common/src/providers/toast/useNotify.test.tsx b/web/packages/common/src/providers/toast/useNotify.test.tsx new file mode 100644 index 0000000000..fa0bc33bdc --- /dev/null +++ b/web/packages/common/src/providers/toast/useNotify.test.tsx @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { ToastProvider } from '@nemo/common/src/providers/toast/ToastProvider'; +import { MessageFnOptions, NotifyFn } from '@nemo/common/src/providers/toast/types'; +import { useNotify } from '@nemo/common/src/providers/toast/useNotify'; +import { logger } from '@nemo/common/src/utils/logger'; +import { act, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { FC } from 'react'; + +const Notifier: FC<{ onNotify?: NotifyFn; options?: MessageFnOptions }> = ({ + onNotify, + options, +}) => { + const notify = useNotify(onNotify); + return ; +}; + +describe('useNotify', () => { + const user = userEvent.setup(); + + it('sends to the surrounding ToastProvider when no onNotify is given', async () => { + render( + + + + ); + + await user.click(screen.getByRole('button', { name: 'Notify' })); + + expect(await screen.findByText('All done')).toBeInTheDocument(); + }); + + it('prefers onNotify over the provider', async () => { + const onNotify = vi.fn(); + render( + + + + ); + + await user.click(screen.getByRole('button', { name: 'Notify' })); + + expect(onNotify).toHaveBeenCalledWith('All done', 'success', undefined); + expect(screen.queryByText('All done')).not.toBeInTheDocument(); + }); + + it('works without a provider, which is the plugin case', async () => { + const onNotify = vi.fn(); + render(); + + await user.click(screen.getByRole('button', { name: 'Notify' })); + + expect(onNotify).toHaveBeenCalledWith('All done', 'success', undefined); + }); + + it('forwards toast options to onNotify', async () => { + const onNotify = vi.fn(); + render(); + + await user.click(screen.getByRole('button', { name: 'Notify' })); + + expect(onNotify).toHaveBeenCalledWith('All done', 'success', { durationMs: false }); + }); + + it('honours durationMs on the provider path', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + render( + + + + ); + + await user.click(screen.getByRole('button', { name: 'Notify' })); + expect(await screen.findByText('All done')).toBeInTheDocument(); + + await act(async () => { + vi.advanceTimersByTime(10_000); + }); + expect(screen.getByText('All done')).toBeInTheDocument(); + + vi.useRealTimers(); + }); + + it('drops the message with a warning when there is no sink at all', async () => { + const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {}); + render(); + + await user.click(screen.getByRole('button', { name: 'Notify' })); + + expect(warn).toHaveBeenCalledWith(expect.stringContaining('All done')); + warn.mockRestore(); + }); +}); diff --git a/web/packages/common/src/providers/toast/useNotify.ts b/web/packages/common/src/providers/toast/useNotify.ts new file mode 100644 index 0000000000..f673842fb1 --- /dev/null +++ b/web/packages/common/src/providers/toast/useNotify.ts @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import { ToastContext } from '@nemo/common/src/providers/toast/useToast'; +import { logger } from '@nemo/common/src/utils/logger'; +import { useCallback, useContext } from 'react'; + +/** + * Resolves a notification sink: an explicit `onNotify` first, then the + * ToastProvider above this component. Unlike `useToast` this never throws — + * a plugin renders against a different `ToastContext` instance than the one + * Studio mounts, so the context is legitimately absent there. + */ +export const useNotify = (onNotify?: NotifyFn): NotifyFn => { + const toast = useContext(ToastContext)?.toast; + + return useCallback( + (message, type = 'info', options) => { + if (onNotify) { + onNotify(message, type, options); + return; + } + if (toast) { + toast[type](message, options); + return; + } + logger.warn(`[toast] dropped notification, no ToastProvider and no onNotify: ${message}`); + }, + [onNotify, toast] + ); +}; diff --git a/web/packages/studio/src/plugins/PluginRenderer.tsx b/web/packages/studio/src/plugins/PluginRenderer.tsx index 49637a0722..10f888b511 100644 --- a/web/packages/studio/src/plugins/PluginRenderer.tsx +++ b/web/packages/studio/src/plugins/PluginRenderer.tsx @@ -64,7 +64,7 @@ export const PluginRenderer = (): ReactElement => { auth: { accessToken, getAccessToken }, sdk: STUDIO_SDK, navigation: { navigate: (to) => navigate(to), back: () => navigate(-1) }, - notifications: { notify: (message, type = 'info') => toast[type](message) }, + notifications: { notify: (message, type = 'info', options) => toast[type](message, options) }, telemetry: makeTelemetry(pluginName ?? 'unknown'), breadcrumbs: { set: setPluginBreadcrumbs }, }), diff --git a/web/packages/studio/src/plugins/types.ts b/web/packages/studio/src/plugins/types.ts index 6ba016e6f1..c3e6f07cf9 100644 --- a/web/packages/studio/src/plugins/types.ts +++ b/web/packages/studio/src/plugins/types.ts @@ -20,9 +20,14 @@ export interface PluginNavigation { export type NotificationType = 'success' | 'error' | 'info' | 'warning'; +export interface NotificationOptions { + /** ms before auto-dismiss; `false` keeps the toast until dismissed. Defaults per type. */ + durationMs?: number | false; +} + /** Fire a toast into Studio's shared toaster; defaults to `info`. */ export interface PluginNotifications { - notify: (message: string, type?: NotificationType) => void; + notify: (message: string, type?: NotificationType, options?: NotificationOptions) => void; } export interface PluginBreadcrumb { From 3ee3c629546447b49e24fa8833efe9810ec60d15 Mon Sep 17 00:00:00 2001 From: mschwab Date: Mon, 10 Aug 2026 09:52:36 -0700 Subject: [PATCH 6/8] fix(studio): merge plugin nav groups into core groups of the same name WorkspaceSideNav concatenated plugin groups onto the core list, so a plugin whose navItems() returned group: 'Safety' rendered a second "Safety" header beneath Studio's rather than joining it. Plugin groups now accumulate by name and fold into the matching core group, using the existing isGroup guard so ungrouped top-level items are left alone. Groups matching nothing append in plugin order, and two plugins naming the same group merge into one. Signed-off-by: mschwab --- .../WorkspaceLayout/WorkspaceSideNav.test.tsx | 96 +++++++++++++++++++ .../WorkspaceLayout/WorkspaceSideNav.tsx | 26 ++++- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx index 7db8d2c1a2..a548c67959 100644 --- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx +++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx @@ -1,9 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { PluginContext } from '@studio/plugins/PluginContext'; +import type { LoadedPlugin, PluginNavGroup } from '@studio/plugins/types'; import { WorkspaceSideNav } from '@studio/routes/WorkspaceLayout/WorkspaceSideNav'; import { renderRoute, screen } from '@studio/tests/util/render'; import userEvent from '@testing-library/user-event'; +import type { ReactElement } from 'react'; vi.hoisted(() => { vi.stubEnv('VITE_FF_OPTIMIZER_ENABLED', 'false'); @@ -24,6 +27,32 @@ const renderSideNav = (history = '/workspaces/test-workspace/dashboard') => ], }); +const makePlugin = (name: string, groups: PluginNavGroup[]): LoadedPlugin => ({ + name, + Root: () => null, + navItems: () => groups, +}); + +const renderWithPlugins = (plugins: LoadedPlugin[]) => { + const element: ReactElement = ( + p.name)), + isLoaded: true, + isError: false, + }} + > + + + ); + + return renderRoute(element, { + history: '/workspaces/test-workspace/dashboard', + routes: [{ path: '/workspaces/:workspace/*', element }], + }); +}; + describe('WorkspaceSideNav', () => { it('links to the traces view by default', () => { renderSideNav(); @@ -125,4 +154,71 @@ describe('WorkspaceSideNav', () => { await user.click(screen.getByRole('link', { name: 'Agents' })); expect(disclosure('Agents')).toHaveAttribute('aria-expanded', 'true'); }); + + it('folds a plugin group into the core group of the same name', () => { + renderWithPlugins([ + makePlugin('red-team', [ + { + group: 'Governance', + items: [ + { + id: 'red-team', + iconName: 'shield', + label: 'Red Team', + href: '/workspaces/test-workspace/red-team', + }, + ], + }, + ]), + ]); + + expect(screen.getAllByText('Governance')).toHaveLength(1); + expect(screen.getByRole('link', { name: 'Red Team' })).toBeInTheDocument(); + }); + + it('appends a plugin group that matches no core group', () => { + renderWithPlugins([ + makePlugin('red-team', [ + { + group: 'Red Team', + items: [ + { + id: 'probes', + iconName: 'shield', + label: 'Probes', + href: '/workspaces/test-workspace/probes', + }, + ], + }, + ]), + ]); + + expect(screen.getAllByText('Red Team')).toHaveLength(1); + expect(screen.getByRole('link', { name: 'Probes' })).toBeInTheDocument(); + }); + + it('merges groups of the same name across two plugins', () => { + renderWithPlugins([ + makePlugin('one', [ + { + group: 'Governance', + items: [ + { id: 'one', iconName: 'shield', label: 'One', href: '/workspaces/test-workspace/one' }, + ], + }, + ]), + makePlugin('two', [ + { + group: 'Governance', + items: [ + { id: 'two', iconName: 'shield', label: 'Two', href: '/workspaces/test-workspace/two' }, + ], + }, + ]), + ]); + + expect(screen.getAllByText('Governance')).toHaveLength(1); + expect(screen.getByRole('link', { name: 'One' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Two' })).toBeInTheDocument(); + }); }); diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx index 9a7d6455ab..91b944b1c1 100644 --- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx +++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx @@ -399,11 +399,27 @@ export const WorkspaceSideNav = ({ collapsed }: { collapsed?: boolean }) => { [plugins, workspace] ); - // A fresh array literal here would re-run NavigationDrawer's own memos on every render. - const allItems = useMemo( - () => [...items, ...pluginNavGroups, ...systemNavGroup], - [items, pluginNavGroups, systemNavGroup] - ); + // A plugin naming an existing group joins it rather than rendering a second + // header with the same label. Unmatched groups append in plugin order. + const allItems = useMemo(() => { + const pending = new Map(); + for (const { group, items: groupItems } of pluginNavGroups) { + pending.set(group, [...(pending.get(group) ?? []), ...groupItems]); + } + + const merged: NavInputItem[] = items.map((entry) => { + if (!isGroup(entry) || entry.group === undefined) return entry; + const extra = pending.get(entry.group); + if (!extra) return entry; + pending.delete(entry.group); + return { ...entry, items: [...entry.items, ...extra] }; + }); + + for (const [group, groupItems] of pending) { + merged.push({ group, items: groupItems }); + } + return [...merged, ...systemNavGroup]; + }, [items, pluginNavGroups, systemNavGroup]); return ; }; From 32a29a94b28627a1b4b616c17951be5f0a800cd6 Mon Sep 17 00:00:00 2001 From: mschwab Date: Mon, 10 Aug 2026 10:12:34 -0700 Subject: [PATCH 7/8] fix(studio): namespace plugin nav item ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NavigationDrawer keys items by item.id and keys accordion state by it too. Merging plugin groups into core groups of the same name puts both sets of items in one array, so a plugin reusing a core id (say 'virtual-models') now produces duplicate React keys where it previously only collided in accordion state. Plugin ids are prefixed with the plugin name. Nothing else reads them — active state matches on href — so the prefix is confined to the two key sites. Also applies the review's import-type and explicit-props-interface nits on the lines this branch introduced. Signed-off-by: mschwab --- .../components/ConfirmationModal/index.tsx | 2 +- .../DeleteConfirmationModal/index.tsx | 2 +- .../common/src/components/LogViewer/index.tsx | 2 +- .../src/providers/toast/useNotify.test.tsx | 14 +++++---- .../common/src/providers/toast/useNotify.ts | 2 +- .../WorkspaceLayout/WorkspaceSideNav.test.tsx | 29 +++++++++++++++++++ .../WorkspaceLayout/WorkspaceSideNav.tsx | 4 ++- 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/web/packages/common/src/components/ConfirmationModal/index.tsx b/web/packages/common/src/components/ConfirmationModal/index.tsx index 0c1961b91a..252756199d 100644 --- a/web/packages/common/src/components/ConfirmationModal/index.tsx +++ b/web/packages/common/src/components/ConfirmationModal/index.tsx @@ -4,7 +4,7 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput'; import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal'; import { LoadingButton } from '@nemo/common/src/components/LoadingButton'; -import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import type { NotifyFn } from '@nemo/common/src/providers/toast/types'; import { useNotify } from '@nemo/common/src/providers/toast/useNotify'; import { getErrorMessage } from '@nemo/common/src/utils/error'; import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error'; diff --git a/web/packages/common/src/components/DeleteConfirmationModal/index.tsx b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx index eedf0267c7..b23541f732 100644 --- a/web/packages/common/src/components/DeleteConfirmationModal/index.tsx +++ b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx @@ -3,7 +3,7 @@ import { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal'; import { FormModalProps } from '@nemo/common/src/components/FormModal'; -import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import type { NotifyFn } from '@nemo/common/src/providers/toast/types'; import { FC } from 'react'; interface DeleteModalProps extends Pick { diff --git a/web/packages/common/src/components/LogViewer/index.tsx b/web/packages/common/src/components/LogViewer/index.tsx index 18ecfe65c5..3fa157d2cf 100644 --- a/web/packages/common/src/components/LogViewer/index.tsx +++ b/web/packages/common/src/components/LogViewer/index.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useStickToBottom } from '@nemo/common/src/hooks/useStickToBottom'; -import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import type { NotifyFn } from '@nemo/common/src/providers/toast/types'; import { useNotify } from '@nemo/common/src/providers/toast/useNotify'; import { triggerDownload } from '@nemo/common/src/utils/file'; import { formatLogs } from '@nemo/common/src/utils/logs'; diff --git a/web/packages/common/src/providers/toast/useNotify.test.tsx b/web/packages/common/src/providers/toast/useNotify.test.tsx index fa0bc33bdc..93b6ce1b71 100644 --- a/web/packages/common/src/providers/toast/useNotify.test.tsx +++ b/web/packages/common/src/providers/toast/useNotify.test.tsx @@ -2,17 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 import { ToastProvider } from '@nemo/common/src/providers/toast/ToastProvider'; -import { MessageFnOptions, NotifyFn } from '@nemo/common/src/providers/toast/types'; +import type { MessageFnOptions, NotifyFn } from '@nemo/common/src/providers/toast/types'; import { useNotify } from '@nemo/common/src/providers/toast/useNotify'; import { logger } from '@nemo/common/src/utils/logger'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { FC } from 'react'; +import type { FC } from 'react'; -const Notifier: FC<{ onNotify?: NotifyFn; options?: MessageFnOptions }> = ({ - onNotify, - options, -}) => { +interface NotifierProps { + onNotify?: NotifyFn; + options?: MessageFnOptions; +} + +const Notifier: FC = ({ onNotify, options }) => { const notify = useNotify(onNotify); return ; }; diff --git a/web/packages/common/src/providers/toast/useNotify.ts b/web/packages/common/src/providers/toast/useNotify.ts index f673842fb1..af55a9bc0d 100644 --- a/web/packages/common/src/providers/toast/useNotify.ts +++ b/web/packages/common/src/providers/toast/useNotify.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { NotifyFn } from '@nemo/common/src/providers/toast/types'; +import type { NotifyFn } from '@nemo/common/src/providers/toast/types'; import { ToastContext } from '@nemo/common/src/providers/toast/useToast'; import { logger } from '@nemo/common/src/utils/logger'; import { useCallback, useContext } from 'react'; diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx index a548c67959..8f77fffe91 100644 --- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx +++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx @@ -197,6 +197,35 @@ describe('WorkspaceSideNav', () => { expect(screen.getByRole('link', { name: 'Probes' })).toBeInTheDocument(); }); + it('keeps a plugin item whose id matches a core item in the merged group', () => { + const duplicateKeyWarning = vi.spyOn(console, 'error').mockImplementation(() => {}); + + renderWithPlugins([ + makePlugin('red-team', [ + { + group: 'Governance', + items: [ + { + id: 'guardrails', + iconName: 'shield', + label: 'Red Team Models', + href: '/workspaces/test-workspace/red-team-models', + }, + ], + }, + ]), + ]); + + expect(screen.getByRole('link', { name: 'Guardrails' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Red Team Models' })).toBeInTheDocument(); + const keyWarnings = duplicateKeyWarning.mock.calls + .map((args) => args.map(String).join(' ')) + .filter((message) => message.includes('same key')); + expect(keyWarnings).toEqual([]); + + duplicateKeyWarning.mockRestore(); + }); + it('merges groups of the same name across two plugins', () => { renderWithPlugins([ makePlugin('one', [ diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx index 91b944b1c1..dc4469dbf0 100644 --- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx +++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx @@ -384,7 +384,9 @@ export const WorkspaceSideNav = ({ collapsed }: { collapsed?: boolean }) => { items: group.items.map((item) => { const Icon = getPluginIcon(item.iconName); return { - id: item.id, + // Namespaced: ids are React keys and accordion-state keys, and + // merging puts plugin items in the same array as core ones. + id: `${plugin.name}:${item.id}`, slotIcon: Icon ? : undefined, slotLabel: item.label, href: item.href, From 441b2f83b9fec245bb9c14a59528dbcb7dd8ef4e Mon Sep 17 00:00:00 2001 From: mschwab Date: Tue, 11 Aug 2026 09:22:41 -0700 Subject: [PATCH 8/8] test(studio): drop stale useToast module mock in ProgressSection LogViewer now resolves its toast sink through useNotify, which imports ToastContext from the same module the test replaced wholesale. The mock returned only useToast, so every render in the file threw. The mocked success fn was never asserted, so the mock can go: with no provider, useNotify falls back to a logged warning instead of throwing. Signed-off-by: mschwab --- .../components/ProgressSection.test.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx index e3e38b7b1d..5d4d4d2d1b 100644 --- a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx +++ b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx @@ -8,17 +8,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -// Mock the toast hook -const mockToastSuccess = vi.fn(); -vi.mock('@nemo/common/src/providers/toast/useToast', () => ({ - useToast: () => ({ - success: mockToastSuccess, - error: vi.fn(), - info: vi.fn(), - warning: vi.fn(), - }), -})); - vi.mock('lucide-react', async () => { return (await import('@nemo/testing/mocks/lucide')).mockLucideReact(await import('react')); });