Compare commits

...

1 Commits

Author SHA1 Message Date
Gaurav Tewari
75da4a59ea feat(llm-attribute-mapping): tabbed settings page foundation
Mirror the LLM pricing settings structure for attribute mapping: a
Tabs-based Configuration page with the mapping content in a tab panel,
plus route/permission/page wiring.

- Settings/AttributeMapping: Tabs container + AttributeMappingTabPanel
  (Discard/Save action shell + empty state)
- route LLM_OBSERVABILITY_ATTRIBUTE_MAPPING, permission, page wrapper,
  DateTimeSelectionV2 skip-list
2026-06-30 21:34:01 +05:30
11 changed files with 159 additions and 0 deletions

View File

@@ -323,6 +323,13 @@ export const AIAssistantPage = Loadable(
),
);
export const LLMObservabilityAttributeMappingPage = Loadable(
() =>
import(
/* webpackChunkName: "LLM Observability Attribute Mapping Page" */ 'pages/LLMObservabilityAttributeMapping'
),
);
export const LLMObservabilityPage = Loadable(
() =>
import(

View File

@@ -23,6 +23,7 @@ import {
IntegrationsDetailsPage,
LicensePage,
ListAllALertsPage,
LLMObservabilityAttributeMappingPage,
LLMObservabilityPage,
LLMObservabilityModelPricingPage,
LiveLogs,
@@ -514,6 +515,13 @@ const routes: AppRoutes[] = [
key: 'AI_ASSISTANT',
isPrivate: true,
},
{
path: ROUTES.LLM_OBSERVABILITY_ATTRIBUTE_MAPPING,
exact: true,
component: LLMObservabilityAttributeMappingPage,
key: 'LLM_OBSERVABILITY_ATTRIBUTE_MAPPING',
isPrivate: true,
},
{
path: ROUTES.LLM_OBSERVABILITY_BASE,
exact: true,

View File

@@ -89,6 +89,8 @@ const ROUTES = {
AI_ASSISTANT_BASE: '/ai-assistant',
AI_ASSISTANT_ICON_PREVIEW: '/ai-assistant-icon-preview',
MCP_SERVER: '/settings/mcp-server',
LLM_OBSERVABILITY_ATTRIBUTE_MAPPING:
'/llm-observability/settings/attribute-mapping',
LLM_OBSERVABILITY_BASE: '/llm-observability',
LLM_OBSERVABILITY_MODEL_PRICING: '/llm-observability/settings/model-pricing',
} as const;

View File

@@ -0,0 +1,16 @@
.actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--spacing-4);
margin-bottom: var(--spacing-6);
}
.tableEmpty {
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-16);
border: 1px dashed var(--bg-slate-400);
border-radius: var(--radius-2);
}

View File

@@ -0,0 +1,57 @@
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import styles from './AttributeMappingTabPanel.module.scss';
const noop = (): void => undefined;
function AttributeMappingTabPanel(): JSX.Element {
// Draft state, mapping-group listing and the save/discard wiring land in the
// next PRs. For now this is a read-only shell with disabled actions.
const isDirty = false;
const isSaving = false;
return (
<>
<div className={styles.actions}>
{isDirty && (
<Typography.Text
color="muted"
size="small"
as="span"
testId="unsaved-changes"
>
Unsaved changes
</Typography.Text>
)}
<Button
variant="outlined"
color="secondary"
onClick={noop}
disabled={!isDirty || isSaving}
testId="discard-changes-btn"
>
Discard
</Button>
<Button
variant="solid"
color="primary"
onClick={noop}
loading={isSaving}
disabled={!isDirty || isSaving}
testId="save-changes-btn"
>
{isSaving ? 'Saving…' : 'Save changes'}
</Button>
</div>
<div className={styles.tableEmpty} data-testid="attribute-mapping-empty">
<Typography.Text color="muted">
No mapping groups configured yet.
</Typography.Text>
</div>
</>
);
}
export default AttributeMappingTabPanel;

View File

@@ -0,0 +1 @@
export { default } from './AttributeMappingTabPanel';

View File

@@ -0,0 +1,19 @@
.llmObservabilityAttributeMapping {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--spacing-12) var(--spacing-16);
}
.pageHeader {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--spacing-8);
}
.pageHeaderTitle {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
}

View File

@@ -0,0 +1,40 @@
import { Tabs } from '@signozhq/ui/tabs';
import { Typography } from '@signozhq/ui/typography';
import AttributeMappingTabPanel from './AttributeMappingTabPanel';
import styles from './LLMObservabilityAttributeMapping.module.scss';
function LLMObservabilityAttributeMapping(): JSX.Element {
return (
<div
className={styles.llmObservabilityAttributeMapping}
data-testid="llm-observability-attribute-mapping-page"
>
<header className={styles.pageHeader}>
<div className={styles.pageHeaderTitle}>
<Typography.Text as="h1" size="large" weight="semibold">
Configuration
</Typography.Text>
<Typography.Text color="muted">
Map source LLM trace attributes to SigNoz semantic conventions
</Typography.Text>
</div>
</header>
<Tabs
// Attribute mapping is the only tab for now. As more grouping/mapping
// views land, this can become a URL-backed param like model pricing.
defaultValue="attribute-mapping"
items={[
{
key: 'attribute-mapping',
label: 'Attribute mapping',
children: <AttributeMappingTabPanel />,
},
]}
/>
</div>
);
}
export default LLMObservabilityAttributeMapping;

View File

@@ -203,6 +203,7 @@ export const routesToSkip = [
ROUTES.METER_EXPLORER_VIEWS,
ROUTES.METRICS_EXPLORER_VOLUME_CONTROL,
ROUTES.SOMETHING_WENT_WRONG,
ROUTES.LLM_OBSERVABILITY_ATTRIBUTE_MAPPING,
ROUTES.LLM_OBSERVABILITY_BASE,
ROUTES.LLM_OBSERVABILITY_MODEL_PRICING,
];

View File

@@ -0,0 +1,7 @@
import LLMObservabilityAttributeMapping from 'container/LLMObservability/Settings/AttributeMapping/LLMObservabilityAttributeMapping';
function LLMObservabilityAttributeMappingPage(): JSX.Element {
return <LLMObservabilityAttributeMapping />;
}
export default LLMObservabilityAttributeMappingPage;

View File

@@ -134,6 +134,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
AI_ASSISTANT_ICON_PREVIEW: ['ADMIN', 'EDITOR', 'VIEWER'],
MCP_SERVER: ['ADMIN', 'EDITOR', 'VIEWER'],
AI_ASSISTANT_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
LLM_OBSERVABILITY_ATTRIBUTE_MAPPING: ['ADMIN', 'EDITOR', 'VIEWER'],
LLM_OBSERVABILITY_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
LLM_OBSERVABILITY_MODEL_PRICING: ['ADMIN', 'EDITOR', 'VIEWER'],
};