diff --git a/apps/web-tss/src/components/features/sidebars/useFavoritesSidebarPanel.tsx b/apps/web-tss/src/components/features/sidebars/useFavoritesSidebarPanel.tsx
index 280a7e257..c782bcc6a 100644
--- a/apps/web-tss/src/components/features/sidebars/useFavoritesSidebarPanel.tsx
+++ b/apps/web-tss/src/components/features/sidebars/useFavoritesSidebarPanel.tsx
@@ -9,6 +9,8 @@ import {
} from '@/api/buster_rest/users';
import type { ISidebarGroup } from '@/components/ui/sidebar';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
+import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
+import { useMount } from '../../../hooks/useMount';
import { useWhyDidYouUpdate } from '../../../hooks/useWhyDidYouUpdate';
import { assetTypeToIcon } from '../icons/assetIcons';
@@ -17,6 +19,10 @@ export const useFavoriteSidebarPanel = () => {
const { mutateAsync: updateUserFavorites } = useUpdateUserFavorites();
const { mutateAsync: deleteUserFavorite } = useDeleteUserFavorite();
+ useMount(() => {
+ assetParamsToRoute({ assetType: 'chat', assetId: '123' });
+ });
+
// const { chatId, metricId, dashboardId, collectionId, reportId } = useParams() as {
// chatId: string | undefined;
// metricId: string | undefined;
diff --git a/apps/web-tss/src/lib/assets/README.md b/apps/web-tss/src/lib/assets/README.md
new file mode 100644
index 000000000..117392461
--- /dev/null
+++ b/apps/web-tss/src/lib/assets/README.md
@@ -0,0 +1,176 @@
+# Asset Route Builder
+
+A type-safe route builder for TanStack Start/Router that dynamically generates routes based on asset types and parameters.
+
+## Overview
+
+The Asset Route Builder provides a clean, type-safe way to generate routes for different asset types (chats, metrics, dashboards, reports) with various parameter combinations. It ensures that the generated routes match the available route files in your TanStack Start application.
+
+## Features
+
+- **Type Safety**: All routes are validated against `FileRouteTypes['id']` from the generated route tree
+- **Fluent API**: Clean builder pattern for constructing routes
+- **Dynamic Route Generation**: Automatically determines the correct route based on provided parameters
+- **Parameter Extraction**: Provides params object for navigation
+
+## Available Routes
+
+The system supports the following route patterns:
+
+### Single Asset Routes
+- `/app/chats/$chatId`
+- `/app/dashboards/$dashboardId`
+- `/app/metrics/$metricId`
+- `/app/reports/$reportId`
+
+### Chat Context Routes
+- `/app/chats/$chatId/dashboard/$dashboardId`
+- `/app/chats/$chatId/metrics/$metricId`
+- `/app/chats/$chatId/report/$reportId`
+
+### Nested Context Routes
+- `/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId`
+- `/app/chats/$chatId/report/$reportId/metrics/$metricId`
+
+## Usage
+
+### Basic Usage with `assetParamsToRoute`
+
+```typescript
+import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
+
+// Navigate to a single asset
+const chatRoute = assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+});
+// Returns: '/app/chats/$chatId'
+
+// Navigate to a metric within a chat
+const metricInChatRoute = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-456',
+ chatId: 'chat-123',
+});
+// Returns: '/app/chats/$chatId/metrics/$metricId'
+
+// Navigate to a metric within a dashboard in a chat
+const complexRoute = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-789',
+ chatId: 'chat-123',
+ dashboardId: 'dash-456',
+});
+// Returns: '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+```
+
+### Using the RouteBuilder
+
+```typescript
+import { createRouteBuilder } from '@/lib/assets/assetParamsToRoute';
+
+// Build routes step by step
+const builder = createRouteBuilder()
+ .withChat('chat-123')
+ .withDashboard('dash-456')
+ .withMetric('metric-789');
+
+const route = builder.build();
+// Returns: '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+
+const params = builder.getParams();
+// Returns: { chatId: 'chat-123', dashboardId: 'dash-456', metricId: 'metric-789' }
+```
+
+### Integration with TanStack Router
+
+```typescript
+import { useNavigate } from '@tanstack/react-router';
+import { createAssetNavigation } from '@/lib/assets/assetParamsToRoute.example';
+
+function MyComponent() {
+ const navigate = useNavigate();
+
+ const handleNavigation = () => {
+ const { route, params } = createAssetNavigation({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ chatId: 'chat-456',
+ });
+
+ // Use with your navigation API
+ navigate({ to: route, params });
+ };
+
+ return ;
+}
+```
+
+## API Reference
+
+### `assetParamsToRoute(params: AssetParamsToRoute): RouteFilePaths`
+
+Main function to convert asset parameters to a route path.
+
+#### Parameters
+
+- `params`: An object containing:
+ - `assetType`: The type of asset ('chat' | 'metric' | 'dashboard' | 'report')
+ - `assetId`: The ID of the main asset
+ - Additional optional context parameters (chatId, metricId, dashboardId, reportId)
+
+#### Returns
+
+A type-safe route path matching `FileRouteTypes['id']`.
+
+### `createRouteBuilder(): RouteBuilder`
+
+Creates a new RouteBuilder instance for fluent route construction.
+
+#### RouteBuilder Methods
+
+- `withChat(chatId: string)`: Add a chat ID to the route
+- `withMetric(metricId: string)`: Add a metric ID to the route
+- `withDashboard(dashboardId: string)`: Add a dashboard ID to the route
+- `withReport(reportId: string)`: Add a report ID to the route
+- `build()`: Build the final route path
+- `getParams()`: Get the params object for navigation
+
+## Type Definitions
+
+```typescript
+type ChatParamsToRoute = {
+ assetType: 'chat';
+ assetId: string;
+ metricId?: string;
+ dashboardId?: string;
+ reportId?: string;
+};
+
+type MetricParamsToRoute = {
+ assetType: 'metric';
+ assetId: string;
+ dashboardId?: string;
+ reportId?: string;
+ chatId?: string;
+};
+
+type DashboardParamsToRoute = {
+ assetType: 'dashboard';
+ assetId: string;
+ metricId?: string;
+ reportId?: string;
+ chatId?: string;
+};
+
+type ReportParamsToRoute = {
+ assetType: 'report';
+ assetId: string;
+ metricId?: string;
+ chatId?: string;
+};
+```
+
+## Examples
+
+See `assetParamsToRoute.example.tsx` for comprehensive usage examples and `assetParamsToRoute.test.ts` for test cases.
diff --git a/apps/web-tss/src/lib/assets/assetParamsToRoute.example.tsx b/apps/web-tss/src/lib/assets/assetParamsToRoute.example.tsx
new file mode 100644
index 000000000..3992a04bd
--- /dev/null
+++ b/apps/web-tss/src/lib/assets/assetParamsToRoute.example.tsx
@@ -0,0 +1,264 @@
+import { type Navigate, UseNavigateResult, useNavigate } from '@tanstack/react-router';
+import type { FileRouteTypes } from '@/routeTree.gen';
+import { assetParamsToRoute, createRouteBuilder } from './assetParamsToRoute';
+
+/**
+ * Example usage of assetParamsToRoute
+ *
+ * Note: These examples show how to get the correct route paths.
+ * For actual navigation, you would use these routes with your router's navigation API.
+ */
+
+// Example 1: Get route for a single asset
+export function getSingleAssetRoutes() {
+ // Chat route
+ const chatRoute = assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+ });
+ console.log(chatRoute); // '/app/chats/$chatId'
+
+ // Dashboard route
+ const dashboardRoute = assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ });
+ console.log(dashboardRoute); // '/app/dashboards/$dashboardId'
+
+ // Metric route
+ const metricRoute = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ });
+ console.log(metricRoute); // '/app/metrics/$metricId'
+
+ // Report route
+ const reportRoute = assetParamsToRoute({
+ assetType: 'report',
+ assetId: 'report-123',
+ });
+ console.log(reportRoute); // '/app/reports/$reportId'
+}
+
+// Example 2: Get routes for assets within chat context
+export function getChatContextRoutes() {
+ // Metric within a chat
+ const metricInChat = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ chatId: 'chat-456',
+ });
+ console.log(metricInChat); // '/app/chats/$chatId/metrics/$metricId'
+
+ // Dashboard within a chat
+ const dashboardInChat = assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ chatId: 'chat-456',
+ });
+ console.log(dashboardInChat); // '/app/chats/$chatId/dashboard/$dashboardId'
+
+ // Report within a chat
+ const reportInChat = assetParamsToRoute({
+ assetType: 'report',
+ assetId: 'report-123',
+ chatId: 'chat-456',
+ });
+ console.log(reportInChat); // '/app/chats/$chatId/report/$reportId'
+}
+
+// Example 3: Get routes for nested contexts
+export function getNestedContextRoutes() {
+ // Metric within a dashboard within a chat
+ const metricInDashboardInChat = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ chatId: 'chat-456',
+ dashboardId: 'dash-789',
+ });
+ console.log(metricInDashboardInChat); // '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+
+ // Metric within a report within a chat
+ const metricInReportInChat = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ chatId: 'chat-456',
+ reportId: 'report-789',
+ });
+ console.log(metricInReportInChat); // '/app/chats/$chatId/report/$reportId/metrics/$metricId'
+}
+
+// Example 4: Using the RouteBuilder for more control
+export function usingRouteBuilder() {
+ // Build a route step by step
+ const route1 = createRouteBuilder().withChat('chat-123').withMetric('metric-456').build();
+ console.log(route1); // '/app/chats/$chatId/metrics/$metricId'
+
+ // Get the params for navigation
+ const builder = createRouteBuilder()
+ .withChat('chat-123')
+ .withDashboard('dash-456')
+ .withMetric('metric-789');
+
+ const route = builder.build();
+ const params = builder.getParams();
+ console.log(route); // '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+ console.log(params); // { chatId: 'chat-123', dashboardId: 'dash-456', metricId: 'metric-789' }
+}
+
+// Example 5: Type-safe route generation
+export function typeSafeRoutes() {
+ // All of these return type-safe FileRouteTypes['id'] values
+ const routes: FileRouteTypes['id'][] = [
+ assetParamsToRoute({ assetType: 'chat', assetId: '1' }),
+ assetParamsToRoute({ assetType: 'dashboard', assetId: '2' }),
+ assetParamsToRoute({ assetType: 'metric', assetId: '3' }),
+ assetParamsToRoute({ assetType: 'report', assetId: '4' }),
+ assetParamsToRoute({ assetType: 'metric', assetId: '5', chatId: '1' }),
+ assetParamsToRoute({ assetType: 'dashboard', assetId: '6', chatId: '1' }),
+ assetParamsToRoute({ assetType: 'dashboard', assetId: '7', chatId: '1', metricId: '5' }),
+ ];
+
+ return routes;
+}
+
+// Example 6: Helper function for navigation params
+export function getNavigationParams(params: Parameters[0]) {
+ const route = assetParamsToRoute(params);
+
+ // Build the params object for navigation
+ const navParams: Record = {};
+
+ // Add the main asset ID
+ switch (params.assetType) {
+ case 'chat':
+ navParams.chatId = params.assetId;
+ break;
+ case 'metric':
+ navParams.metricId = params.assetId;
+ break;
+ case 'dashboard':
+ navParams.dashboardId = params.assetId;
+ break;
+ case 'report':
+ navParams.reportId = params.assetId;
+ break;
+ }
+
+ // Add any context params
+ if ('chatId' in params && params.chatId) navParams.chatId = params.chatId;
+ if ('metricId' in params && params.metricId) navParams.metricId = params.metricId;
+ if ('dashboardId' in params && params.dashboardId) navParams.dashboardId = params.dashboardId;
+ if ('reportId' in params && params.reportId) navParams.reportId = params.reportId;
+
+ return { route, params: navParams };
+}
+
+/**
+ * Example: Creating links with proper type safety
+ */
+export function LinkExample() {
+ // All of these will be type-safe RouteFilePaths
+ const routes = {
+ chat: assetParamsToRoute({ assetType: 'chat', assetId: 'chat-123' }),
+ metric: assetParamsToRoute({ assetType: 'metric', assetId: 'metric-123' }),
+ dashboard: assetParamsToRoute({ assetType: 'dashboard', assetId: 'dash-123' }),
+ report: assetParamsToRoute({ assetType: 'report', assetId: 'report-123' }),
+
+ // Complex routes
+ chatWithMetric: assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+ metricId: 'metric-456',
+ }),
+
+ dashboardInChat: assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ chatId: 'chat-456',
+ }),
+
+ fullRoute: assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ chatId: 'chat-456',
+ metricId: 'metric-789',
+ }),
+ };
+
+ return (
+
+ );
+}
+
+/**
+ * Example: Helper for creating asset navigation data
+ *
+ * This would be used with your router's navigation API.
+ * For example, with TanStack Router:
+ * const { route, params } = createAssetNavigation({...});
+ * navigate({ to: route, params });
+ */
+export function createAssetNavigation(params: Parameters[0]) {
+ const route = assetParamsToRoute(params);
+
+ // Build navigation params based on the route
+ const navParams: Record = {};
+
+ // Always include the main asset ID with its proper param name
+ switch (params.assetType) {
+ case 'chat':
+ navParams.chatId = params.assetId;
+ break;
+ case 'metric':
+ navParams.metricId = params.assetId;
+ break;
+ case 'dashboard':
+ navParams.dashboardId = params.assetId;
+ break;
+ case 'report':
+ navParams.reportId = params.assetId;
+ break;
+ }
+
+ // Add any additional context params
+ if ('chatId' in params && params.chatId) navParams.chatId = params.chatId;
+ if ('metricId' in params && params.metricId) navParams.metricId = params.metricId;
+ if ('dashboardId' in params && params.dashboardId) navParams.dashboardId = params.dashboardId;
+ if ('reportId' in params && params.reportId) navParams.reportId = params.reportId;
+
+ return {
+ route,
+ params: navParams,
+ // Helper methods
+ assetParamsToRoute,
+ createRouteBuilder,
+ };
+}
+
+export const TestComponent = () => {
+ const navigate = useNavigate();
+
+ const test = (params: Parameters[0]) => {
+ console.log(params);
+ };
+
+ test({
+ to: '/asdf',
+ });
+
+ navigate({
+ to: '/app/chats/$chatId',
+ params: {
+ chatId: '123',
+ },
+ });
+
+ return ;
+};
diff --git a/apps/web-tss/src/lib/assets/assetParamsToRoute.test.ts b/apps/web-tss/src/lib/assets/assetParamsToRoute.test.ts
new file mode 100644
index 000000000..c131c3469
--- /dev/null
+++ b/apps/web-tss/src/lib/assets/assetParamsToRoute.test.ts
@@ -0,0 +1,221 @@
+import { describe, it, expect } from 'vitest';
+import type { FileRouteTypes } from '@/routeTree.gen';
+import { assetParamsToRoute, createRouteBuilder } from './assetParamsToRoute';
+
+type RouteFilePaths = FileRouteTypes['id'];
+
+describe('assetParamsToRoute', () => {
+ describe('RouteBuilder', () => {
+ it('should build single asset routes correctly', () => {
+ // Test single chat route
+ const chatRoute = createRouteBuilder().withChat('chat-123').build();
+ expect(chatRoute).toBe('/app/chats/$chatId');
+
+ // Test single dashboard route
+ const dashboardRoute = createRouteBuilder().withDashboard('dash-123').build();
+ expect(dashboardRoute).toBe('/app/dashboards/$dashboardId');
+
+ // Test single metric route
+ const metricRoute = createRouteBuilder().withMetric('metric-123').build();
+ expect(metricRoute).toBe('/app/metrics/$metricId');
+
+ // Test single report route
+ const reportRoute = createRouteBuilder().withReport('report-123').build();
+ expect(reportRoute).toBe('/app/reports/$reportId');
+ });
+
+ it('should build chat combination routes correctly', () => {
+ // Chat + Dashboard
+ const chatDashRoute = createRouteBuilder()
+ .withChat('chat-123')
+ .withDashboard('dash-456')
+ .build();
+ expect(chatDashRoute).toBe('/app/chats/$chatId/dashboard/$dashboardId');
+
+ // Chat + Metric
+ const chatMetricRoute = createRouteBuilder()
+ .withChat('chat-123')
+ .withMetric('metric-456')
+ .build();
+ expect(chatMetricRoute).toBe('/app/chats/$chatId/metrics/$metricId');
+
+ // Chat + Report
+ const chatReportRoute = createRouteBuilder()
+ .withChat('chat-123')
+ .withReport('report-456')
+ .build();
+ expect(chatReportRoute).toBe('/app/chats/$chatId/report/$reportId');
+
+ // Chat + Dashboard + Metric
+ const chatDashMetricRoute = createRouteBuilder()
+ .withChat('chat-123')
+ .withDashboard('dash-456')
+ .withMetric('metric-789')
+ .build();
+ expect(chatDashMetricRoute).toBe(
+ '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+ );
+
+ // Chat + Report + Metric
+ const chatReportMetricRoute = createRouteBuilder()
+ .withChat('chat-123')
+ .withReport('report-456')
+ .withMetric('metric-789')
+ .build();
+ expect(chatReportMetricRoute).toBe('/app/chats/$chatId/report/$reportId/metrics/$metricId');
+ });
+
+ it('should maintain parameter values in state', () => {
+ const builder = createRouteBuilder()
+ .withChat('chat-123')
+ .withDashboard('dash-456')
+ .withMetric('metric-789');
+
+ const params = builder.getParams();
+ expect(params).toEqual({
+ chatId: 'chat-123',
+ dashboardId: 'dash-456',
+ metricId: 'metric-789',
+ });
+ });
+ });
+
+ describe('assetParamsToRoute function', () => {
+ it('should handle chat asset type correctly', () => {
+ // Chat only
+ const chatRoute = assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+ });
+ expect(chatRoute).toBe('/app/chats/$chatId');
+
+ // Chat with dashboard
+ const chatDashRoute = assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+ dashboardId: 'dash-456',
+ });
+ expect(chatDashRoute).toBe('/app/chats/$chatId/dashboard/$dashboardId');
+
+ // Chat with metric
+ const chatMetricRoute = assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+ metricId: 'metric-456',
+ });
+ expect(chatMetricRoute).toBe('/app/chats/$chatId/metrics/$metricId');
+
+ // Chat with dashboard and metric
+ const chatDashMetricRoute = assetParamsToRoute({
+ assetType: 'chat',
+ assetId: 'chat-123',
+ dashboardId: 'dash-456',
+ metricId: 'metric-789',
+ });
+ expect(chatDashMetricRoute).toBe(
+ '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+ );
+ });
+
+ it('should handle metric asset type correctly', () => {
+ // Metric only
+ const metricRoute = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ });
+ expect(metricRoute).toBe('/app/metrics/$metricId');
+
+ // Metric with chat
+ const metricChatRoute = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ chatId: 'chat-456',
+ });
+ expect(metricChatRoute).toBe('/app/chats/$chatId/metrics/$metricId');
+
+ // Metric with chat and dashboard
+ const metricChatDashRoute = assetParamsToRoute({
+ assetType: 'metric',
+ assetId: 'metric-123',
+ chatId: 'chat-456',
+ dashboardId: 'dash-789',
+ });
+ expect(metricChatDashRoute).toBe(
+ '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+ );
+ });
+
+ it('should handle dashboard asset type correctly', () => {
+ // Dashboard only
+ const dashRoute = assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ });
+ expect(dashRoute).toBe('/app/dashboards/$dashboardId');
+
+ // Dashboard with chat
+ const dashChatRoute = assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ chatId: 'chat-456',
+ });
+ expect(dashChatRoute).toBe('/app/chats/$chatId/dashboard/$dashboardId');
+
+ // Dashboard with chat and metric
+ const dashChatMetricRoute = assetParamsToRoute({
+ assetType: 'dashboard',
+ assetId: 'dash-123',
+ chatId: 'chat-456',
+ metricId: 'metric-789',
+ });
+ expect(dashChatMetricRoute).toBe(
+ '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
+ );
+ });
+
+ it('should handle report asset type correctly', () => {
+ // Report only
+ const reportRoute = assetParamsToRoute({
+ assetType: 'report',
+ assetId: 'report-123',
+ });
+ expect(reportRoute).toBe('/app/reports/$reportId');
+
+ // Report with chat
+ const reportChatRoute = assetParamsToRoute({
+ assetType: 'report',
+ assetId: 'report-123',
+ chatId: 'chat-456',
+ });
+ expect(reportChatRoute).toBe('/app/chats/$chatId/report/$reportId');
+
+ // Report with chat and metric
+ const reportChatMetricRoute = assetParamsToRoute({
+ assetType: 'report',
+ assetId: 'report-123',
+ chatId: 'chat-456',
+ metricId: 'metric-789',
+ });
+ expect(reportChatMetricRoute).toBe('/app/chats/$chatId/report/$reportId/metrics/$metricId');
+ });
+ });
+
+ describe('Type safety tests', () => {
+ it('should enforce type safety on routes', () => {
+ // This is a compile-time test - these should all be valid RouteFilePaths
+ const routes: RouteFilePaths[] = [
+ createRouteBuilder().withChat('1').build(),
+ createRouteBuilder().withDashboard('1').build(),
+ createRouteBuilder().withMetric('1').build(),
+ createRouteBuilder().withReport('1').build(),
+ createRouteBuilder().withChat('1').withDashboard('2').build(),
+ createRouteBuilder().withChat('1').withMetric('2').build(),
+ createRouteBuilder().withChat('1').withReport('2').build(),
+ createRouteBuilder().withChat('1').withDashboard('2').withMetric('3').build(),
+ createRouteBuilder().withChat('1').withReport('2').withMetric('3').build(),
+ ];
+
+ expect(routes).toBeDefined();
+ });
+ });
+});
diff --git a/apps/web-tss/src/lib/assets/assetParamsToRoute.ts b/apps/web-tss/src/lib/assets/assetParamsToRoute.ts
new file mode 100644
index 000000000..2dede3741
--- /dev/null
+++ b/apps/web-tss/src/lib/assets/assetParamsToRoute.ts
@@ -0,0 +1,256 @@
+import type { AssetType } from '@buster/server-shared/assets';
+import type { FileRouteTypes } from '@/routeTree.gen';
+
+type RouteFilePaths = FileRouteTypes['id'];
+
+/**
+ * Type definitions for asset route parameters
+ */
+type ChatParamsToRoute = {
+ assetType: 'chat';
+ assetId: string;
+ metricId?: string;
+ dashboardId?: string;
+ reportId?: string;
+};
+
+type MetricParamsToRoute = {
+ assetType: 'metric';
+ assetId: string;
+ dashboardId?: string;
+ reportId?: string;
+ chatId?: string;
+};
+
+type DashboardParamsToRoute = {
+ assetType: 'dashboard';
+ assetId: string;
+ metricId?: string;
+ reportId?: string;
+ chatId?: string;
+};
+
+type ReportParamsToRoute = {
+ assetType: 'report';
+ assetId: string;
+ metricId?: string;
+ chatId?: string;
+};
+
+type AssetParamsToRoute =
+ | ChatParamsToRoute
+ | MetricParamsToRoute
+ | DashboardParamsToRoute
+ | ReportParamsToRoute;
+
+/**
+ * Route builder internal state type
+ */
+type RouteBuilderState = {
+ chatId?: string;
+ metricId?: string;
+ dashboardId?: string;
+ reportId?: string;
+};
+
+/**
+ * Type-safe route mapping based on parameter combinations
+ */
+type RouteMap = {
+ // Single asset routes
+ chat: '/app/chats/$chatId';
+ dashboard: '/app/dashboards/$dashboardId';
+ metric: '/app/metrics/$metricId';
+ report: '/app/reports/$reportId';
+
+ // Chat combination routes
+ 'chat+dashboard': '/app/chats/$chatId/dashboard/$dashboardId';
+ 'chat+metric': '/app/chats/$chatId/metrics/$metricId';
+ 'chat+report': '/app/chats/$chatId/report/$reportId';
+ 'chat+dashboard+metric': '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId';
+ 'chat+report+metric': '/app/chats/$chatId/report/$reportId/metrics/$metricId';
+};
+
+/**
+ * Helper type to get valid route keys based on builder state
+ */
+type GetRouteKey = T extends {
+ chatId: string;
+ dashboardId: string;
+ metricId: string;
+}
+ ? 'chat+dashboard+metric'
+ : T extends { chatId: string; reportId: string; metricId: string }
+ ? 'chat+report+metric'
+ : T extends { chatId: string; dashboardId: string }
+ ? 'chat+dashboard'
+ : T extends { chatId: string; reportId: string }
+ ? 'chat+report'
+ : T extends { chatId: string; metricId: string }
+ ? 'chat+metric'
+ : T extends { chatId: string }
+ ? 'chat'
+ : T extends { dashboardId: string }
+ ? 'dashboard'
+ : T extends { metricId: string }
+ ? 'metric'
+ : T extends { reportId: string }
+ ? 'report'
+ : never;
+
+/**
+ * Type-safe route builder with fluent API
+ */
+class RouteBuilder> {
+ private state: T;
+
+ constructor(state: T = {} as T) {
+ this.state = state;
+ }
+
+ /**
+ * Add chat ID to the route
+ */
+ withChat(chatId: U): RouteBuilder {
+ return new RouteBuilder({ ...this.state, chatId });
+ }
+
+ /**
+ * Add metric ID to the route
+ */
+ withMetric(metricId: U): RouteBuilder {
+ return new RouteBuilder({ ...this.state, metricId });
+ }
+
+ /**
+ * Add dashboard ID to the route
+ */
+ withDashboard(dashboardId: U): RouteBuilder {
+ return new RouteBuilder({ ...this.state, dashboardId });
+ }
+
+ /**
+ * Add report ID to the route
+ */
+ withReport(reportId: U): RouteBuilder {
+ return new RouteBuilder({ ...this.state, reportId });
+ }
+
+ /**
+ * Build the route path with type safety
+ */
+ build(): GetRouteKey extends keyof RouteMap ? RouteMap[GetRouteKey] : never {
+ const key = this.getRouteKey();
+ const routeMap: RouteMap = {
+ chat: '/app/chats/$chatId',
+ dashboard: '/app/dashboards/$dashboardId',
+ metric: '/app/metrics/$metricId',
+ report: '/app/reports/$reportId',
+ 'chat+dashboard': '/app/chats/$chatId/dashboard/$dashboardId',
+ 'chat+metric': '/app/chats/$chatId/metrics/$metricId',
+ 'chat+report': '/app/chats/$chatId/report/$reportId',
+ 'chat+dashboard+metric': '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId',
+ 'chat+report+metric': '/app/chats/$chatId/report/$reportId/metrics/$metricId',
+ };
+
+ return routeMap[key as keyof RouteMap] as GetRouteKey extends keyof RouteMap
+ ? RouteMap[GetRouteKey]
+ : never;
+ }
+
+ /**
+ * Get the route params object for TanStack Router navigation
+ */
+ getParams(): T {
+ return this.state;
+ }
+
+ /**
+ * Determine the route key based on current state
+ */
+ private getRouteKey(): string {
+ const { chatId, dashboardId, metricId, reportId } = this.state;
+
+ // Chat combination routes (most specific first)
+ if (chatId && dashboardId && metricId) return 'chat+dashboard+metric';
+ if (chatId && reportId && metricId) return 'chat+report+metric';
+ if (chatId && dashboardId) return 'chat+dashboard';
+ if (chatId && reportId) return 'chat+report';
+ if (chatId && metricId) return 'chat+metric';
+
+ // Single asset routes
+ if (chatId) return 'chat';
+ if (dashboardId) return 'dashboard';
+ if (metricId) return 'metric';
+ if (reportId) return 'report';
+
+ throw new Error('No valid route could be determined from the provided parameters');
+ }
+}
+
+/**
+ * Main function to convert asset params to route
+ */
+export const assetParamsToRoute = (params: AssetParamsToRoute): RouteFilePaths => {
+ const builder = new RouteBuilder();
+
+ // Build route based on asset type and additional params
+ switch (params.assetType) {
+ case 'chat': {
+ let route = builder.withChat(params.assetId);
+ if (params.dashboardId) route = route.withDashboard(params.dashboardId);
+ if (params.reportId) route = route.withReport(params.reportId);
+ if (params.metricId) route = route.withMetric(params.metricId);
+ return route.build() as RouteFilePaths;
+ }
+
+ case 'metric': {
+ let route = builder.withMetric(params.assetId);
+ if (params.chatId) {
+ route = route.withChat(params.chatId);
+ if (params.dashboardId) route = route.withDashboard(params.dashboardId);
+ if (params.reportId) route = route.withReport(params.reportId);
+ }
+ return route.build() as RouteFilePaths;
+ }
+
+ case 'dashboard': {
+ let route = builder.withDashboard(params.assetId);
+ if (params.chatId) {
+ route = route.withChat(params.chatId);
+ if (params.metricId) route = route.withMetric(params.metricId);
+ }
+ return route.build() as RouteFilePaths;
+ }
+
+ case 'report': {
+ let route = builder.withReport(params.assetId);
+ if (params.chatId) {
+ route = route.withChat(params.chatId);
+ if (params.metricId) route = route.withMetric(params.metricId);
+ }
+ return route.build() as RouteFilePaths;
+ }
+
+ default:
+ throw new Error(`Unknown asset type: ${(params as AssetParamsToRoute).assetType}`);
+ }
+};
+
+/**
+ * Create a new route builder instance
+ */
+export const createRouteBuilder = () => new RouteBuilder();
+
+// Example usage:
+// const route1 = createRouteBuilder()
+// .withChat('chat-123')
+// .withMetric('metric-456')
+// .build(); // Type: '/app/chats/$chatId/metrics/$metricId'
+//
+// const route2 = assetParamsToRoute({
+// assetType: 'dashboard',
+// assetId: 'dash-123',
+// chatId: 'chat-456',
+// metricId: 'metric-789'
+// }); // Returns: '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'