* fix: increase assistant chat input height from 1 to 3 rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Anthropic prompt caching to assistant chat Cache the static system prompt and conversation history prefix across turns, reducing input token costs by ~80% on multi-turn conversations. RAG context is intentionally uncached since it changes per query. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Microsoft Learn MCP integration + refine assistant system prompt - Integrate Microsoft Learn MCP server via Anthropic's MCP connector for real-time documentation lookups (docs search, fetch, code samples) - Refine system prompt: clear persona, structured answer guidelines, when to use RAG flows vs Microsoft Learn, guardrails against fabrication - Add ENABLE_MCP_MICROSOFT_LEARN config toggle (default: True) - Fix bugs from prior edit: wrong MCP URL, broken indentation, undefined usage/token variables, NOT_GIVEN for disabled MCP params - Log MCP tool usage and cache performance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: AI chat session conclusion + survey completion & management AI Assistant - Conclude Session: - 3-step modal: select outcome (resolved/escalated/paused), add notes, AI-generated summary - AI generates structured ticket notes from conversation transcript (PSA-ready format) - Copy to clipboard for pasting into ticketing systems - "Resume in New Chat" for paused sessions (pre-loads context into new chat) - Backend: POST /chats/{id}/conclude endpoint, conclusion_summary/outcome/concluded_at fields - Migration 048: add conclusion fields to assistant_chats Survey Completion Flow: - Email-to-self option after submission (branded HTML email with formatted responses) - Finish button navigates to /survey/thank-you page - Thank you page with close-window message and feedback email callout - Already-submitted state updated with same messaging - Backend: POST /survey/email-copy public endpoint Survey Admin Management: - Read/unread indicators (cyan dot, bold name, auto-mark on expand) - Unread count stat card - Per-row context menu: mark read/unread, archive/unarchive, delete - Bulk actions bar: select all, mark read/unread, archive, delete - Show Archived toggle to filter archived responses - Backend: 7 new admin endpoints (read, unread, archive, unarchive, delete, bulk) - Migration 049: add is_read, archived_at to survey_responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: initialize VerifyEmailPage state from token to avoid setState in effect Moves the no-token error case from useEffect into initial state to satisfy the react-hooks/set-state-in-effect ESLint rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
487 lines
13 KiB
TypeScript
487 lines
13 KiB
TypeScript
import { createBrowserRouter } from 'react-router-dom'
|
|
import { lazy, Suspense } from 'react'
|
|
import { AppLayout, ProtectedRoute } from '@/components/layout'
|
|
import { RouteError } from '@/components/common/RouteError'
|
|
import { PageLoader } from '@/components/common/PageLoader'
|
|
import {
|
|
LoginPage,
|
|
RegisterPage,
|
|
} from '@/pages'
|
|
|
|
// Public pages
|
|
const SharedSessionPage = lazy(() => import('@/pages/SharedSessionPage'))
|
|
const SurveyPage = lazy(() => import('@/pages/SurveyPage'))
|
|
const SurveyThankYouPage = lazy(() => import('@/pages/SurveyThankYouPage'))
|
|
|
|
// Standalone auth pages
|
|
const VerifyEmailPage = lazy(() => import('@/pages/VerifyEmailPage'))
|
|
const ChangePasswordPage = lazy(() => import('@/pages/ChangePasswordPage'))
|
|
const ForgotPasswordPage = lazy(() => import('@/pages/ForgotPasswordPage'))
|
|
const ResetPasswordPage = lazy(() => import('@/pages/ResetPasswordPage'))
|
|
|
|
// Lazy load heavy pages for code splitting
|
|
const QuickStartPage = lazy(() => import('@/pages/QuickStartPage'))
|
|
const TreeLibraryPage = lazy(() => import('@/pages/TreeLibraryPage'))
|
|
const MyTreesPage = lazy(() => import('@/pages/MyTreesPage'))
|
|
const TreeNavigationPage = lazy(() => import('@/pages/TreeNavigationPage'))
|
|
const TreeEditorPage = lazy(() => import('@/pages/TreeEditorPage'))
|
|
const ProceduralEditorPage = lazy(() => import('@/pages/ProceduralEditorPage'))
|
|
const ProceduralNavigationPage = lazy(() => import('@/pages/ProceduralNavigationPage'))
|
|
const MaintenanceFlowDetailPage = lazy(() => import('@/pages/MaintenanceFlowDetailPage'))
|
|
const BatchStatusPage = lazy(() => import('@/pages/BatchStatusPage'))
|
|
const SessionHistoryPage = lazy(() => import('@/pages/SessionHistoryPage'))
|
|
const SessionDetailPage = lazy(() => import('@/pages/SessionDetailPage'))
|
|
const MySharesPage = lazy(() => import('@/pages/MySharesPage'))
|
|
const TeamAnalyticsPage = lazy(() => import('@/pages/TeamAnalyticsPage'))
|
|
const MyAnalyticsPage = lazy(() => import('@/pages/MyAnalyticsPage'))
|
|
const FeedbackPage = lazy(() => import('@/pages/FeedbackPage'))
|
|
const StepLibraryPage = lazy(() => import('@/pages/StepLibraryPage'))
|
|
const AIChatBuilderPage = lazy(() => import('@/pages/AIChatBuilderPage'))
|
|
const AssistantChatPage = lazy(() => import('@/pages/AssistantChatPage'))
|
|
const GuidesHubPage = lazy(() => import('@/pages/GuidesHubPage'))
|
|
const GuideDetailPage = lazy(() => import('@/pages/GuideDetailPage'))
|
|
const AccountSettingsPage = lazy(() => import('@/pages/AccountSettingsPage'))
|
|
// Admin pages
|
|
const AdminLayout = lazy(() => import('@/components/admin/AdminLayout'))
|
|
const AdminDashboardPage = lazy(() => import('@/pages/admin/DashboardPage'))
|
|
const AdminUsersPage = lazy(() => import('@/pages/admin/UsersPage'))
|
|
const AdminUserDetailPage = lazy(() => import('@/pages/admin/UserDetailPage'))
|
|
const AdminInviteCodesPage = lazy(() => import('@/pages/admin/InviteCodesPage'))
|
|
const AdminAuditLogsPage = lazy(() => import('@/pages/admin/AuditLogsPage'))
|
|
const AdminPlanLimitsPage = lazy(() => import('@/pages/admin/PlanLimitsPage'))
|
|
const AdminFeatureFlagsPage = lazy(() => import('@/pages/admin/FeatureFlagsPage'))
|
|
const AdminSettingsPage = lazy(() => import('@/pages/admin/SettingsPage'))
|
|
const AdminGlobalCategoriesPage = lazy(() => import('@/pages/admin/GlobalCategoriesPage'))
|
|
const AdminSurveyInvitesPage = lazy(() => import('@/pages/admin/SurveyInvitesPage'))
|
|
const AdminSurveyResponsesPage = lazy(() => import('@/pages/admin/SurveyResponsesPage'))
|
|
|
|
// Account pages
|
|
const AccountLayout = lazy(() => import('@/components/account/AccountLayout'))
|
|
const ProfileSettingsPage = lazy(() => import('@/pages/account/ProfileSettingsPage'))
|
|
const TeamCategoriesPage = lazy(() => import('@/pages/account/TeamCategoriesPage'))
|
|
const TargetListsPage = lazy(() => import('@/pages/account/TargetListsPage'))
|
|
const ChatRetentionSettingsPage = lazy(() => import('@/pages/account/ChatRetentionSettingsPage'))
|
|
|
|
export const router = createBrowserRouter([
|
|
{
|
|
path: '/login',
|
|
element: <LoginPage />,
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/register',
|
|
element: <RegisterPage />,
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/forgot-password',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ForgotPasswordPage />
|
|
</Suspense>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/reset-password',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ResetPasswordPage />
|
|
</Suspense>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/verify-email',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<VerifyEmailPage />
|
|
</Suspense>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/survey',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<SurveyPage />
|
|
</Suspense>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/survey/thank-you',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<SurveyThankYouPage />
|
|
</Suspense>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/share/:shareToken',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<SharedSessionPage />
|
|
</Suspense>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/change-password',
|
|
element: (
|
|
<ProtectedRoute>
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ChangePasswordPage />
|
|
</Suspense>
|
|
</ProtectedRoute>
|
|
),
|
|
errorElement: <RouteError />,
|
|
},
|
|
{
|
|
path: '/',
|
|
element: (
|
|
<ProtectedRoute>
|
|
<AppLayout />
|
|
</ProtectedRoute>
|
|
),
|
|
errorElement: <RouteError />,
|
|
children: [
|
|
{
|
|
index: true,
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<QuickStartPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'trees',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<TreeLibraryPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'my-trees',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<MyTreesPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'trees/new',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<TreeEditorPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'trees/:id/edit',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<TreeEditorPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'flows/new',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProceduralEditorPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'flows/:id/edit',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProceduralEditorPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'flows/:id/navigate',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProceduralNavigationPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'flows/:id/maintenance',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<MaintenanceFlowDetailPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'flows/:id/batches/:batchId',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<BatchStatusPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'trees/:id/navigate',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<TreeNavigationPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'sessions',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<SessionHistoryPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'sessions/:id',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<SessionDetailPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'shares',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<MySharesPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'analytics',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<TeamAnalyticsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'analytics/me',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<MyAnalyticsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'feedback',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<FeedbackPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'step-library',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<StepLibraryPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'ai/chat',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AIChatBuilderPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'assistant',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AssistantChatPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'guides',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<GuidesHubPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'guides/:slug',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<GuideDetailPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
// Admin routes
|
|
{
|
|
path: 'admin',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProtectedRoute requiredRole="super_admin">
|
|
<AdminLayout />
|
|
</ProtectedRoute>
|
|
</Suspense>
|
|
),
|
|
children: [
|
|
{
|
|
index: true,
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminDashboardPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'users',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminUsersPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'users/:userId',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminUserDetailPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'invite-codes',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminInviteCodesPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'audit-logs',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminAuditLogsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'plan-limits',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminPlanLimitsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'feature-flags',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminFeatureFlagsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'settings',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminSettingsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'categories',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminGlobalCategoriesPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'survey-invites',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminSurveyInvitesPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'survey-responses',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AdminSurveyResponsesPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
],
|
|
},
|
|
// Account routes
|
|
{
|
|
path: 'account',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AccountLayout />
|
|
</Suspense>
|
|
),
|
|
children: [
|
|
{
|
|
index: true,
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<AccountSettingsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'profile',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProfileSettingsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'categories',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProtectedRoute requiredRole="owner">
|
|
<TeamCategoriesPage />
|
|
</ProtectedRoute>
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'chat-retention',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<ProtectedRoute requiredRole="owner">
|
|
<ChatRetentionSettingsPage />
|
|
</ProtectedRoute>
|
|
</Suspense>
|
|
),
|
|
},
|
|
{
|
|
path: 'target-lists',
|
|
element: (
|
|
<Suspense fallback={<PageLoader />}>
|
|
<TargetListsPage />
|
|
</Suspense>
|
|
),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
])
|
|
|
|
export default router
|