@burdenoff/microfe-crewfoundry 2026.707.1 → 2026.710.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/crewfoundry/components/viz/BarChart.js.map +1 -1
- package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -1
- package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -1
- package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -1
- package/dist/crewfoundry/components/viz/LineChart.js.map +1 -1
- package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -1
- package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -1
- package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -1
- package/dist/crewfoundry/components/viz/StatCard.js.map +1 -1
- package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -1
- package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -1
- package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -1
- package/dist/crewfoundry/index.js +1 -0
- package/dist/crewfoundry/lib/analytics.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +2 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +4 -4
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +3 -3
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +8 -4
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +4 -4
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js +3 -3
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +3 -3
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/dist/crewfoundry/utils/courseProgress.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/services/assistantApi.js +273 -0
- package/dist/services/assistantApi.js.map +1 -0
- package/dist/services/createSandboxAssistantTransport.js +120 -0
- package/dist/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/utils/pageContext.js +52 -0
- package/dist/utils/pageContext.js.map +1 -0
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamsTeamDetailPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/TeamsTeamDetailPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n ResponsiveTable,\n Spinner,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { BarChart3, Calendar, FolderKanban, Settings, Star, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useCrewFoundryStore, type Employee } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, getInitials, type Person } from '../components/viz';\n\nfunction InitialsAvatar({ name, src, size = 32 }: { name: string; src?: string; size?: number }) {\n if (src) {\n return (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n loading=\"lazy\"\n className=\"shrink-0 rounded-full object-cover\"\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function TeamsTeamDetailPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { teams, employees, projects, initialized } = useCrewFoundryStore();\n const teamId = searchParams.get('id');\n const team = teams.find((t) => t.id === teamId) ?? teams[0];\n if (!initialized && !team) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!team)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Team not found\"\n description=\"This team is no longer available.\"\n />\n </div>\n );\n const teamMembers = employees.filter((e) => team.members.includes(e.id));\n const teamProjects = projects.filter((p) => p.teamId === team.id);\n const activeProjectCount = teamProjects.filter((p) => p.status === 'active').length;\n const lead = employees.find((e) => e.id === team.leadId);\n const memberPeople: Person[] = teamMembers.map((e) => ({\n name: `${e.firstName} ${e.lastName}`.trim(),\n avatarUrl: e.avatar,\n role: e.position,\n }));\n\n const memberColumns: ResponsiveTableColumn<Employee>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (emp) => (\n <span className=\"flex items-center gap-2.5\">\n <InitialsAvatar name={`${emp.firstName} ${emp.lastName}`} src={emp.avatar} size={30} />\n <span className=\"font-medium text-text-primary\">\n {emp.firstName} {emp.lastName}\n </span>\n </span>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n priority: 'secondary',\n cell: (emp) => <span className=\"text-text-secondary\">{emp.position}</span>,\n },\n {\n key: 'performance',\n header: 'Performance',\n priority: 'secondary',\n cell: (emp) => (\n <span className=\"flex items-center gap-1 md:justify-center\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {emp.performanceScore}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'primary',\n cell: (emp) => (\n <Badge variant={emp.status === 'active' ? 'default' : 'secondary'} className=\"text-[10px]\">\n {formatEnumLabel(emp.status)}\n </Badge>\n ),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{team.name}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{team.description}</p>\n <PagePurpose className=\"mt-2\">\n A complete view of one team — who's on it, what they're working on, and how\n they're performing — so you can support the group and keep its projects on track.\n </PagePurpose>\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-2\">\n <span className=\"flex items-center gap-2 text-xs text-text-secondary\">\n Led by\n <InitialsAvatar name={lead ? `${lead.firstName} ${lead.lastName}` : team.leadName} src={lead?.avatar} size={22} />\n <span className=\"font-medium text-text-primary\">\n {lead ? `${lead.firstName} ${lead.lastName}` : team.leadName || '—'}\n </span>\n </span>\n {memberPeople.length > 0 ? <AvatarStack people={memberPeople} max={6} size={26} /> : null}\n </div>\n </div>\n <Badge variant={team.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(team.status)}\n </Badge>\n </div>\n\n <NextSteps\n storageKey=\"crewfoundry-team-details\"\n steps={[\n {\n id: 'analytics',\n label: 'View team analytics',\n description: 'See productivity and workload trends for this team.',\n icon: <BarChart3 className=\"h-4 w-4\" />,\n onClick: () => navigate('../teams/team-analytics-page'),\n },\n {\n id: 'settings',\n label: 'Adjust team settings',\n description: 'Manage membership, permissions, and preferences.',\n icon: <Settings className=\"h-4 w-4\" />,\n onClick: () =>\n navigate(`../teams/team-settings-page?id=${encodeURIComponent(team.id)}`),\n },\n {\n id: 'directory',\n label: 'Browse all teams',\n description: 'Explore how the rest of the organization is structured.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('../teams/team-directory-page'),\n },\n ]}\n />\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Users className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{team.memberCount}</p>\n <p className=\"text-xs text-text-secondary\">Members</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <FolderKanban className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">\n {activeProjectCount}\n <span className=\"text-sm font-normal text-text-secondary\">/{teamProjects.length}</span>\n </p>\n <p className=\"text-xs text-text-secondary\">Active Projects</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Star className=\"h-5 w-5 mx-auto text-status-warning-text mb-1\" />\n <p className=\"text-lg font-bold\">{team.leadName}</p>\n <p className=\"text-xs text-text-secondary\">Lead</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Calendar className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{team.createdAt}</p>\n <p className=\"text-xs text-text-secondary\">Created</p>\n </CardContent>\n </Card>\n </div>\n <EmphasisPanel>\n <div className=\"mb-2 text-lg font-semibold\">Team Members</div>\n {teamMembers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No members yet\"\n description=\"Add people to this team to start collaborating.\"\n />\n ) : (\n <ResponsiveTable columns={memberColumns} data={teamMembers} rowKey={(emp) => emp.id} />\n )}\n </EmphasisPanel>\n {teamProjects.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Team Projects</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {teamProjects.map((p) => (\n <div\n key={p.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${p.name} project detail`}\n onClick={() =>\n navigate(`../projects/project-details-page?id=${encodeURIComponent(p.id)}`)\n }\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`../projects/project-details-page?id=${encodeURIComponent(p.id)}`);\n }\n }}\n className=\"flex items-center justify-between rounded border p-3 cursor-pointer hover:bg-bg-sunken/50 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <div>\n <p className=\"text-sm font-medium\">{p.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {p.startDate} — {p.endDate}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"w-24 h-2 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-2 bg-action-primary-bg rounded\"\n style={{ width: `${p.progress}%` }}\n />\n </div>\n <span className=\"text-xs\">{p.progress}%</span>\n <Badge\n variant={p.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(p.status)}\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TeamsTeamDetailPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/TeamsTeamDetailPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n ResponsiveTable,\n Spinner,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { BarChart3, Calendar, FolderKanban, Settings, Star, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useCrewFoundryStore, type Employee } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, getInitials, type Person } from '../components/viz';\n\nfunction InitialsAvatar({ name, src, size = 32 }: { name: string; src?: string; size?: number }) {\n if (src) {\n return (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n loading=\"lazy\"\n className=\"shrink-0 rounded-full object-cover\"\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function TeamsTeamDetailPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { teams, employees, projects, initialized } = useCrewFoundryStore();\n const teamId = searchParams.get('id');\n const team = teams.find((t) => t.id === teamId) ?? teams[0];\n if (!initialized && !team) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!team)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Team not found\"\n description=\"This team is no longer available.\"\n />\n </div>\n );\n const teamMembers = employees.filter((e) => team.members.includes(e.id));\n const teamProjects = projects.filter((p) => p.teamId === team.id);\n const activeProjectCount = teamProjects.filter((p) => p.status === 'active').length;\n const lead = employees.find((e) => e.id === team.leadId);\n const memberPeople: Person[] = teamMembers.map((e) => ({\n name: `${e.firstName} ${e.lastName}`.trim(),\n avatarUrl: e.avatar,\n role: e.position,\n }));\n\n const memberColumns: ResponsiveTableColumn<Employee>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (emp) => (\n <span className=\"flex items-center gap-2.5\">\n <InitialsAvatar name={`${emp.firstName} ${emp.lastName}`} src={emp.avatar} size={30} />\n <span className=\"font-medium text-text-primary\">\n {emp.firstName} {emp.lastName}\n </span>\n </span>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n priority: 'secondary',\n cell: (emp) => <span className=\"text-text-secondary\">{emp.position}</span>,\n },\n {\n key: 'performance',\n header: 'Performance',\n priority: 'secondary',\n cell: (emp) => (\n <span className=\"flex items-center gap-1 md:justify-center\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {emp.performanceScore}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'primary',\n cell: (emp) => (\n <Badge variant={emp.status === 'active' ? 'default' : 'secondary'} className=\"text-[10px]\">\n {formatEnumLabel(emp.status)}\n </Badge>\n ),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{team.name}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{team.description}</p>\n <PagePurpose className=\"mt-2\">\n A complete view of one team — who's on it, what they're working on, and how\n they're performing — so you can support the group and keep its projects on track.\n </PagePurpose>\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-2\">\n <span className=\"flex items-center gap-2 text-xs text-text-secondary\">\n Led by\n <InitialsAvatar\n name={lead ? `${lead.firstName} ${lead.lastName}` : team.leadName}\n src={lead?.avatar}\n size={22}\n />\n <span className=\"font-medium text-text-primary\">\n {lead ? `${lead.firstName} ${lead.lastName}` : team.leadName || '—'}\n </span>\n </span>\n {memberPeople.length > 0 ? (\n <AvatarStack people={memberPeople} max={6} size={26} />\n ) : null}\n </div>\n </div>\n <Badge variant={team.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(team.status)}\n </Badge>\n </div>\n\n <NextSteps\n storageKey=\"crewfoundry-team-details\"\n steps={[\n {\n id: 'analytics',\n label: 'View team analytics',\n description: 'See productivity and workload trends for this team.',\n icon: <BarChart3 className=\"h-4 w-4\" />,\n onClick: () => navigate('../teams/team-analytics-page'),\n },\n {\n id: 'settings',\n label: 'Adjust team settings',\n description: 'Manage membership, permissions, and preferences.',\n icon: <Settings className=\"h-4 w-4\" />,\n onClick: () =>\n navigate(`../teams/team-settings-page?id=${encodeURIComponent(team.id)}`),\n },\n {\n id: 'directory',\n label: 'Browse all teams',\n description: 'Explore how the rest of the organization is structured.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('../teams/team-directory-page'),\n },\n ]}\n />\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Users className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{team.memberCount}</p>\n <p className=\"text-xs text-text-secondary\">Members</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <FolderKanban className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">\n {activeProjectCount}\n <span className=\"text-sm font-normal text-text-secondary\">\n /{teamProjects.length}\n </span>\n </p>\n <p className=\"text-xs text-text-secondary\">Active Projects</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Star className=\"h-5 w-5 mx-auto text-status-warning-text mb-1\" />\n <p className=\"text-lg font-bold\">{team.leadName}</p>\n <p className=\"text-xs text-text-secondary\">Lead</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Calendar className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{team.createdAt}</p>\n <p className=\"text-xs text-text-secondary\">Created</p>\n </CardContent>\n </Card>\n </div>\n <EmphasisPanel>\n <div className=\"mb-2 text-lg font-semibold\">Team Members</div>\n {teamMembers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No members yet\"\n description=\"Add people to this team to start collaborating.\"\n />\n ) : (\n <ResponsiveTable columns={memberColumns} data={teamMembers} rowKey={(emp) => emp.id} />\n )}\n </EmphasisPanel>\n {teamProjects.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Team Projects</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {teamProjects.map((p) => (\n <div\n key={p.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${p.name} project detail`}\n onClick={() =>\n navigate(`../projects/project-details-page?id=${encodeURIComponent(p.id)}`)\n }\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`../projects/project-details-page?id=${encodeURIComponent(p.id)}`);\n }\n }}\n className=\"flex items-center justify-between rounded border p-3 cursor-pointer hover:bg-bg-sunken/50 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <div>\n <p className=\"text-sm font-medium\">{p.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {p.startDate} — {p.endDate}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"w-24 h-2 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-2 bg-action-primary-bg rounded\"\n style={{ width: `${p.progress}%` }}\n />\n </div>\n <span className=\"text-xs\">{p.progress}%</span>\n <Badge\n variant={p.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(p.status)}\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamsTeamsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/TeamsTeamsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { ArrowRight, FolderKanban, Plus, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useMemo, useState } from 'react';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport {\n useCrewFoundryStore,\n type Employee,\n type Team,\n} from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, getInitials, type Person } from '../components/viz';\n\nconst TEAM_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Team Name', type: 'text', required: true },\n { name: 'department', label: 'Department', type: 'text', placeholder: 'e.g. Engineering' },\n { name: 'leadName', label: 'Team Lead', type: 'text', placeholder: 'Lead full name' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Forming', value: 'forming' },\n ],\n },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n];\n\nfunction InitialsAvatar({ name, src, size = 32 }: { name: string; src?: string; size?: number }) {\n if (src) {\n return (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n loading=\"lazy\"\n className=\"shrink-0 rounded-full object-cover\"\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n >\n {getInitials(name)}\n </span>\n );\n}\n\nfunction employeeName(employee: Employee): string {\n return `${employee.firstName} ${employee.lastName}`.trim();\n}\n\nexport function TeamsTeamsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { teams, employees, projects } = useCrewFoundryStore();\n const addTeam = useCrewFoundryStore((state) => state.addTeam);\n const createModal = useFormModal();\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const employeeById = useMemo(\n () => new Map(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const activeProjectsByTeam = useMemo(() => {\n const map = new Map<string, number>();\n for (const project of projects) {\n if (project.status === 'active') {\n map.set(project.teamId, (map.get(project.teamId) ?? 0) + 1);\n }\n }\n return map;\n }, [projects]);\n\n const membersOf = (team: Team): Employee[] =>\n team.members\n .map((memberId) => employeeById.get(memberId))\n .filter((employee): employee is Employee => employee !== undefined);\n\n const peopleOf = (team: Team): Person[] =>\n membersOf(team).map((employee) => ({\n name: employeeName(employee),\n avatarUrl: employee.avatar,\n role: employee.position,\n }));\n\n const pageTeams = teams.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n const selectedTeam =\n teams.find((team) => team.id === selectedId) ?? pageTeams[0] ?? teams[0] ?? null;\n const selectedMembers = selectedTeam ? membersOf(selectedTeam) : [];\n const selectedActiveProjects = selectedTeam\n ? projects.filter((project) => project.teamId === selectedTeam.id && project.status === 'active')\n : [];\n\n const openTeam = (team: Team) =>\n navigate(`../teams/team-detail-page?id=${encodeURIComponent(team.id)}`);\n\n const handleCreate = async (values: FormValues) => {\n // CreateCrewTeamInput accepts only this field subset; the store spreads the\n // object straight into `input`, so we send exactly the accepted shape and\n // let the server response replace the optimistic row.\n const payload: Partial<Team> = {\n name: String(values.name ?? '').trim(),\n description: String(values.description ?? '').trim(),\n department: String(values.department ?? '').trim() || 'operations',\n leadName: String(values.leadName ?? '').trim(),\n status: (String(values.status ?? 'forming') as Team['status']) || 'forming',\n members: [],\n };\n await addTeam(payload as Team);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.teams.title', 'Teams')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {teams.length} {t('crewfoundry.teams.summary.count', 'teams across the organization')}\n </p>\n <PagePurpose className=\"mt-1\">\n Manage your teams — create groups, assign members, and organize people around the work\n they do together.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createModal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.teams.actions.createTeam', 'Create Team')}\n </Button>\n </div>\n\n {teams.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No teams yet\"\n description=\"Create a team to organize people around their work.\"\n />\n ) : (\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Team cards */}\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pageTeams.map((team) => {\n const lead = team.leadId ? employeeById.get(team.leadId) : undefined;\n const activeCount = activeProjectsByTeam.get(team.id) ?? 0;\n const selected = selectedTeam?.id === team.id;\n return (\n <GlassCard\n key={team.id}\n treatment={selected ? 'glass' : 'plain'}\n glow={selected}\n role=\"button\"\n tabIndex={0}\n aria-pressed={selected}\n aria-label={`Select ${team.name} team`}\n onClick={() => setSelectedId(team.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSelectedId(team.id);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:shadow-[var(--shadow-focus)] focus-visible:outline-none\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-base\">{team.name}</CardTitle>\n <Badge\n variant={team.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(team.status)}\n </Badge>\n </div>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span>{t('crewfoundry.teams.card.ledBy', 'Led by')}</span>\n <InitialsAvatar\n name={lead ? employeeName(lead) : team.leadName}\n src={lead?.avatar}\n size={20}\n />\n <span className=\"font-medium text-text-primary\">\n {lead ? employeeName(lead) : team.leadName || '—'}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <AvatarStack people={peopleOf(team)} max={5} size={30} />\n <div className=\"flex items-center gap-4 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" />\n {team.memberCount} {t('crewfoundry.teams.card.members', 'Members')}\n </span>\n <span className=\"flex items-center gap-1\">\n <FolderKanban className=\"h-3.5 w-3.5\" />\n {activeCount}{' '}\n {t('crewfoundry.teams.card.activeProjects', 'Active Projects')}\n </span>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {teams.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={teams.length}\n onPageChange={setPage}\n />\n )}\n </div>\n\n {/* Selected-team detail panel */}\n <div className=\"lg:col-span-1\">\n {selectedTeam ? (\n <GlassCard\n treatment=\"glass\"\n className=\"border-border-seam lg:sticky lg:top-6\"\n aria-live=\"polite\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg\">{selectedTeam.name}</CardTitle>\n <Badge\n variant={selectedTeam.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(selectedTeam.status)}\n </Badge>\n </div>\n <CardDescription>{selectedTeam.description}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {t('crewfoundry.teams.panel.members', 'Members')} ({selectedMembers.length})\n </h3>\n {selectedMembers.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No members have been added to this team yet.\n </p>\n ) : (\n <ul className=\"space-y-2 max-h-72 overflow-y-auto pr-1\">\n {selectedMembers.map((member) => (\n <li key={member.id} className=\"flex items-center gap-2.5\">\n <InitialsAvatar\n name={employeeName(member)}\n src={member.avatar}\n size={32}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {employeeName(member)}\n {member.id === selectedTeam.leadId ? (\n <span className=\"ml-1.5 text-[10px] font-medium text-action-primary-text\">\n · Lead\n </span>\n ) : null}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {member.position}\n </p>\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {t('crewfoundry.teams.panel.activeProjects', 'Active Projects')} (\n {selectedActiveProjects.length})\n </h3>\n {selectedActiveProjects.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No active projects for this team right now.\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {selectedActiveProjects.map((project) => (\n <li key={project.id}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-xs\">\n <span className=\"truncate font-medium text-text-primary\">\n {project.name}\n </span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {project.progress}%\n </span>\n </div>\n <div className=\"h-1.5 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-1.5 rounded-full bg-action-primary-bg\"\n style={{ width: `${project.progress}%` }}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => openTeam(selectedTeam)}\n >\n {t('crewfoundry.teams.panel.openDetail', 'Open full team detail')}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </CardContent>\n </GlassCard>\n ) : null}\n </div>\n </div>\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title={t('crewfoundry.teams.actions.createTeam', 'Create Team')}\n description=\"Organize people around the work they do together.\"\n fields={TEAM_FIELDS}\n submitLabel=\"Create Team\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TeamsTeamsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/TeamsTeamsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { ArrowRight, FolderKanban, Plus, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useMemo, useState } from 'react';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Employee, type Team } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, getInitials, type Person } from '../components/viz';\n\nconst TEAM_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Team Name', type: 'text', required: true },\n { name: 'department', label: 'Department', type: 'text', placeholder: 'e.g. Engineering' },\n { name: 'leadName', label: 'Team Lead', type: 'text', placeholder: 'Lead full name' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Forming', value: 'forming' },\n ],\n },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n];\n\nfunction InitialsAvatar({ name, src, size = 32 }: { name: string; src?: string; size?: number }) {\n if (src) {\n return (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n loading=\"lazy\"\n className=\"shrink-0 rounded-full object-cover\"\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n >\n {getInitials(name)}\n </span>\n );\n}\n\nfunction employeeName(employee: Employee): string {\n return `${employee.firstName} ${employee.lastName}`.trim();\n}\n\nexport function TeamsTeamsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { teams, employees, projects } = useCrewFoundryStore();\n const addTeam = useCrewFoundryStore((state) => state.addTeam);\n const createModal = useFormModal();\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const employeeById = useMemo(\n () => new Map(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const activeProjectsByTeam = useMemo(() => {\n const map = new Map<string, number>();\n for (const project of projects) {\n if (project.status === 'active') {\n map.set(project.teamId, (map.get(project.teamId) ?? 0) + 1);\n }\n }\n return map;\n }, [projects]);\n\n const membersOf = (team: Team): Employee[] =>\n team.members\n .map((memberId) => employeeById.get(memberId))\n .filter((employee): employee is Employee => employee !== undefined);\n\n const peopleOf = (team: Team): Person[] =>\n membersOf(team).map((employee) => ({\n name: employeeName(employee),\n avatarUrl: employee.avatar,\n role: employee.position,\n }));\n\n const pageTeams = teams.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n const selectedTeam =\n teams.find((team) => team.id === selectedId) ?? pageTeams[0] ?? teams[0] ?? null;\n const selectedMembers = selectedTeam ? membersOf(selectedTeam) : [];\n const selectedActiveProjects = selectedTeam\n ? projects.filter(\n (project) => project.teamId === selectedTeam.id && project.status === 'active'\n )\n : [];\n\n const openTeam = (team: Team) =>\n navigate(`../teams/team-detail-page?id=${encodeURIComponent(team.id)}`);\n\n const handleCreate = async (values: FormValues) => {\n // CreateCrewTeamInput accepts only this field subset; the store spreads the\n // object straight into `input`, so we send exactly the accepted shape and\n // let the server response replace the optimistic row.\n const payload: Partial<Team> = {\n name: String(values.name ?? '').trim(),\n description: String(values.description ?? '').trim(),\n department: String(values.department ?? '').trim() || 'operations',\n leadName: String(values.leadName ?? '').trim(),\n status: (String(values.status ?? 'forming') as Team['status']) || 'forming',\n members: [],\n };\n await addTeam(payload as Team);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.teams.title', 'Teams')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {teams.length} {t('crewfoundry.teams.summary.count', 'teams across the organization')}\n </p>\n <PagePurpose className=\"mt-1\">\n Manage your teams — create groups, assign members, and organize people around the work\n they do together.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createModal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.teams.actions.createTeam', 'Create Team')}\n </Button>\n </div>\n\n {teams.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No teams yet\"\n description=\"Create a team to organize people around their work.\"\n />\n ) : (\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Team cards */}\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pageTeams.map((team) => {\n const lead = team.leadId ? employeeById.get(team.leadId) : undefined;\n const activeCount = activeProjectsByTeam.get(team.id) ?? 0;\n const selected = selectedTeam?.id === team.id;\n return (\n <GlassCard\n key={team.id}\n treatment={selected ? 'glass' : 'plain'}\n glow={selected}\n role=\"button\"\n tabIndex={0}\n aria-pressed={selected}\n aria-label={`Select ${team.name} team`}\n onClick={() => setSelectedId(team.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSelectedId(team.id);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:shadow-[var(--shadow-focus)] focus-visible:outline-none\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-base\">{team.name}</CardTitle>\n <Badge\n variant={team.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(team.status)}\n </Badge>\n </div>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span>{t('crewfoundry.teams.card.ledBy', 'Led by')}</span>\n <InitialsAvatar\n name={lead ? employeeName(lead) : team.leadName}\n src={lead?.avatar}\n size={20}\n />\n <span className=\"font-medium text-text-primary\">\n {lead ? employeeName(lead) : team.leadName || '—'}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <AvatarStack people={peopleOf(team)} max={5} size={30} />\n <div className=\"flex items-center gap-4 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" />\n {team.memberCount} {t('crewfoundry.teams.card.members', 'Members')}\n </span>\n <span className=\"flex items-center gap-1\">\n <FolderKanban className=\"h-3.5 w-3.5\" />\n {activeCount}{' '}\n {t('crewfoundry.teams.card.activeProjects', 'Active Projects')}\n </span>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {teams.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={teams.length}\n onPageChange={setPage}\n />\n )}\n </div>\n\n {/* Selected-team detail panel */}\n <div className=\"lg:col-span-1\">\n {selectedTeam ? (\n <GlassCard\n treatment=\"glass\"\n className=\"border-border-seam lg:sticky lg:top-6\"\n aria-live=\"polite\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg\">{selectedTeam.name}</CardTitle>\n <Badge\n variant={selectedTeam.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(selectedTeam.status)}\n </Badge>\n </div>\n <CardDescription>{selectedTeam.description}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {t('crewfoundry.teams.panel.members', 'Members')} ({selectedMembers.length})\n </h3>\n {selectedMembers.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No members have been added to this team yet.\n </p>\n ) : (\n <ul className=\"space-y-2 max-h-72 overflow-y-auto pr-1\">\n {selectedMembers.map((member) => (\n <li key={member.id} className=\"flex items-center gap-2.5\">\n <InitialsAvatar\n name={employeeName(member)}\n src={member.avatar}\n size={32}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {employeeName(member)}\n {member.id === selectedTeam.leadId ? (\n <span className=\"ml-1.5 text-[10px] font-medium text-action-primary-text\">\n · Lead\n </span>\n ) : null}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {member.position}\n </p>\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {t('crewfoundry.teams.panel.activeProjects', 'Active Projects')} (\n {selectedActiveProjects.length})\n </h3>\n {selectedActiveProjects.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No active projects for this team right now.\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {selectedActiveProjects.map((project) => (\n <li key={project.id}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-xs\">\n <span className=\"truncate font-medium text-text-primary\">\n {project.name}\n </span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {project.progress}%\n </span>\n </div>\n <div className=\"h-1.5 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-1.5 rounded-full bg-action-primary-bg\"\n style={{ width: `${project.progress}%` }}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => openTeam(selectedTeam)}\n >\n {t('crewfoundry.teams.panel.openDetail', 'Open full team detail')}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </CardContent>\n </GlassCard>\n ) : null}\n </div>\n </div>\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title={t('crewfoundry.teams.actions.createTeam', 'Create Team')}\n description=\"Organize people around the work they do together.\"\n fields={TEAM_FIELDS}\n submitLabel=\"Create Team\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"courseProgress.js","names":[],"sources":["../../../src/crewfoundry/utils/courseProgress.ts"],"sourcesContent":["/**\n * Per-user course progress, persisted client-side.\n *\n * The CrewFoundry backend models a course only as an aggregate (enrolled /\n * completed counts + a `modules` integer) — there is no per-user, per-module\n * completion record and no mark-complete mutation. Rather than fabricate a\n * progress number, this stores the learner's OWN real interactions (the modules\n * they mark complete in the course player) in localStorage, so progress, hours\n * and certificates reflect genuine user activity and persist across reloads.\n *\n * When a backend progress entity is added, these helpers become the single\n * place to swap the storage layer for the real mutation/query.\n */\n\nconst PROGRESS_KEY = 'crewfoundry:course-progress:v1';\n\nexport interface CourseProgress {\n /** Zero-based indexes of modules the learner has marked complete. */\n completed: number[];\n /** ISO timestamp of when the course first reached 100%. */\n completedAt?: string;\n}\n\ntype ProgressMap = Record<string, CourseProgress>;\n\nfunction readMap(): ProgressMap {\n if (typeof window === 'undefined') {\n return {};\n }\n try {\n const raw = window.localStorage.getItem(PROGRESS_KEY);\n if (!raw) {\n return {};\n }\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') {\n return {};\n }\n return parsed as ProgressMap;\n } catch {\n return {};\n }\n}\n\nfunction writeMap(map: ProgressMap): void {\n if (typeof window === 'undefined') {\n return;\n }\n try {\n window.localStorage.setItem(PROGRESS_KEY, JSON.stringify(map));\n } catch {\n /* storage unavailable / quota exceeded — progress is best-effort */\n }\n}\n\nfunction sanitize(entry: CourseProgress | undefined): CourseProgress {\n if (!entry || !Array.isArray(entry.completed)) {\n return { completed: [] };\n }\n const completed = [...new Set(entry.completed.filter((n) => typeof n === 'number' && n >= 0))].sort(
|
|
1
|
+
{"version":3,"file":"courseProgress.js","names":[],"sources":["../../../src/crewfoundry/utils/courseProgress.ts"],"sourcesContent":["/**\n * Per-user course progress, persisted client-side.\n *\n * The CrewFoundry backend models a course only as an aggregate (enrolled /\n * completed counts + a `modules` integer) — there is no per-user, per-module\n * completion record and no mark-complete mutation. Rather than fabricate a\n * progress number, this stores the learner's OWN real interactions (the modules\n * they mark complete in the course player) in localStorage, so progress, hours\n * and certificates reflect genuine user activity and persist across reloads.\n *\n * When a backend progress entity is added, these helpers become the single\n * place to swap the storage layer for the real mutation/query.\n */\n\nconst PROGRESS_KEY = 'crewfoundry:course-progress:v1';\n\nexport interface CourseProgress {\n /** Zero-based indexes of modules the learner has marked complete. */\n completed: number[];\n /** ISO timestamp of when the course first reached 100%. */\n completedAt?: string;\n}\n\ntype ProgressMap = Record<string, CourseProgress>;\n\nfunction readMap(): ProgressMap {\n if (typeof window === 'undefined') {\n return {};\n }\n try {\n const raw = window.localStorage.getItem(PROGRESS_KEY);\n if (!raw) {\n return {};\n }\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') {\n return {};\n }\n return parsed as ProgressMap;\n } catch {\n return {};\n }\n}\n\nfunction writeMap(map: ProgressMap): void {\n if (typeof window === 'undefined') {\n return;\n }\n try {\n window.localStorage.setItem(PROGRESS_KEY, JSON.stringify(map));\n } catch {\n /* storage unavailable / quota exceeded — progress is best-effort */\n }\n}\n\nfunction sanitize(entry: CourseProgress | undefined): CourseProgress {\n if (!entry || !Array.isArray(entry.completed)) {\n return { completed: [] };\n }\n const completed = [\n ...new Set(entry.completed.filter((n) => typeof n === 'number' && n >= 0)),\n ].sort((a, b) => a - b);\n return {\n completed,\n completedAt: typeof entry.completedAt === 'string' ? entry.completedAt : undefined,\n };\n}\n\n/** Read a single course's progress (never throws; empty when absent). */\nexport function readCourseProgress(courseId: string): CourseProgress {\n return sanitize(readMap()[courseId]);\n}\n\n/** Read every course's progress as a map keyed by course id. */\nexport function readAllCourseProgress(): ProgressMap {\n const map = readMap();\n const out: ProgressMap = {};\n for (const [id, entry] of Object.entries(map)) {\n out[id] = sanitize(entry);\n }\n return out;\n}\n\n/**\n * Persist the set of completed module indexes for a course and return the saved\n * progress. Stamps `completedAt` the first time the course reaches 100%.\n */\nexport function writeCourseProgress(\n courseId: string,\n completed: number[],\n totalModules: number\n): CourseProgress {\n const map = readMap();\n const previous = sanitize(map[courseId]);\n const nextCompleted = [...new Set(completed.filter((n) => typeof n === 'number' && n >= 0))].sort(\n (a, b) => a - b\n );\n const reached100 = totalModules > 0 && nextCompleted.length >= totalModules;\n const entry: CourseProgress = {\n completed: nextCompleted,\n completedAt: reached100 ? (previous.completedAt ?? new Date().toISOString()) : undefined,\n };\n map[courseId] = entry;\n writeMap(map);\n return entry;\n}\n\n/** Completion percentage (0–100) for a course given its module count. */\nexport function progressPercent(progress: CourseProgress, totalModules: number): number {\n if (totalModules <= 0) {\n return 0;\n }\n return Math.min(100, Math.round((progress.completed.length / totalModules) * 100));\n}\n"],"mappings":";AAcA,IAAM,IAAe;AAWrB,SAAS,IAAuB;AAC9B,KAAI,OAAO,SAAW,IACpB,QAAO,EAAE;AAEX,KAAI;EACF,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAa;AACrD,MAAI,CAAC,EACH,QAAO,EAAE;EAEX,IAAM,IAAkB,KAAK,MAAM,EAAI;AAIvC,SAHI,CAAC,KAAU,OAAO,KAAW,WACxB,EAAE,GAEJ;SACD;AACN,SAAO,EAAE;;;AAIb,SAAS,EAAS,GAAwB;AACpC,cAAO,SAAW,KAGtB,KAAI;AACF,SAAO,aAAa,QAAQ,GAAc,KAAK,UAAU,EAAI,CAAC;SACxD;;AAKV,SAAS,EAAS,GAAmD;AAOnE,QANI,CAAC,KAAS,CAAC,MAAM,QAAQ,EAAM,UAAU,GACpC,EAAE,WAAW,EAAE,EAAE,GAKnB;EACL,WAJgB,CAChB,GAAG,IAAI,IAAI,EAAM,UAAU,QAAQ,MAAM,OAAO,KAAM,YAAY,KAAK,EAAE,CAAC,CAC3E,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;EAGrB,aAAa,OAAO,EAAM,eAAgB,WAAW,EAAM,cAAc,KAAA;EAC1E;;AAIH,SAAgB,EAAmB,GAAkC;AACnE,QAAO,EAAS,GAAS,CAAC,GAAU;;AAItC,SAAgB,IAAqC;CACnD,IAAM,IAAM,GAAS,EACf,IAAmB,EAAE;AAC3B,MAAK,IAAM,CAAC,GAAI,MAAU,OAAO,QAAQ,EAAI,CAC3C,GAAI,KAAM,EAAS,EAAM;AAE3B,QAAO;;AAOT,SAAgB,EACd,GACA,GACA,GACgB;CAChB,IAAM,IAAM,GAAS,EACf,IAAW,EAAS,EAAI,GAAU,EAClC,IAAgB,CAAC,GAAG,IAAI,IAAI,EAAU,QAAQ,MAAM,OAAO,KAAM,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC,MAC1F,GAAG,MAAM,IAAI,EACf,EAEK,IAAwB;EAC5B,WAAW;EACX,aAHiB,IAAe,KAAK,EAAc,UAAU,IAGlC,EAAS,gCAAe,IAAI,MAAM,EAAC,aAAa,GAAI,KAAA;EAChF;AAGD,QAFA,EAAI,KAAY,GAChB,EAAS,EAAI,EACN;;AAIT,SAAgB,EAAgB,GAA0B,GAA8B;AAItF,QAHI,KAAgB,IACX,IAEF,KAAK,IAAI,KAAK,KAAK,MAAO,EAAS,UAAU,SAAS,IAAgB,IAAI,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCrewFoundry as e } from "./crewfoundry/providers/CrewFoundryProvider.js";
|
|
2
2
|
import { useCrewFoundryStore as t } from "./crewfoundry/store/crewfoundryStore.js";
|
|
3
3
|
import { CrewFoundryRoot as n } from "./crewfoundry/CrewFoundryRoot.js";
|
|
4
|
+
import { useSandboxAssistantTransport as r } from "./services/createSandboxAssistantTransport.js";
|
|
4
5
|
import "./crewfoundry/index.js";
|
|
5
|
-
export { n as CrewFoundryRoot, e as useCrewFoundry, t as useCrewFoundryStore };
|
|
6
|
+
export { n as CrewFoundryRoot, e as useCrewFoundry, t as useCrewFoundryStore, r as useSandboxAssistantTransport };
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { print as e } from "graphql";
|
|
2
|
+
import { gql as t } from "@apollo/client";
|
|
3
|
+
//#region src/services/assistantApi.ts
|
|
4
|
+
var n = "burdenoffregistry.azurecr.io", r = "alpha", i = "alpha-delegated-auth-v11", a = "sandbox-app-client-headers-v1";
|
|
5
|
+
function o(e) {
|
|
6
|
+
switch (e) {
|
|
7
|
+
case "general": return r;
|
|
8
|
+
case "assistant":
|
|
9
|
+
case "api-calls": return i;
|
|
10
|
+
default: return i;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function s(e) {
|
|
14
|
+
return `${n}/wspace/ai-assistant-${e === "assistant" ? "api-calls" : e}:${o(e)}`;
|
|
15
|
+
}
|
|
16
|
+
function c(e) {
|
|
17
|
+
switch (e) {
|
|
18
|
+
case "general": return {
|
|
19
|
+
cpuRequest: "100m",
|
|
20
|
+
cpuLimit: "500m",
|
|
21
|
+
memoryRequest: "256Mi",
|
|
22
|
+
memoryLimit: "1Gi"
|
|
23
|
+
};
|
|
24
|
+
case "assistant":
|
|
25
|
+
case "api-calls": return {
|
|
26
|
+
cpuRequest: "150m",
|
|
27
|
+
cpuLimit: "750m",
|
|
28
|
+
memoryRequest: "384Mi",
|
|
29
|
+
memoryLimit: "1536Mi"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function l(e) {
|
|
34
|
+
let t = e.metadata?.assistantMode;
|
|
35
|
+
if (t === "assistant" || t === "general" || t === "api-calls") return t;
|
|
36
|
+
let n = e.name ?? "";
|
|
37
|
+
return n.startsWith("ai-assistant-api-calls-") ? "api-calls" : n.startsWith("ai-assistant-general-") ? "general" : n.startsWith("ai-assistant-assistant-") ? "assistant" : null;
|
|
38
|
+
}
|
|
39
|
+
var u = t`
|
|
40
|
+
mutation CreateSandbox($input: CreateSandboxInput!) {
|
|
41
|
+
createSandbox(input: $input) {
|
|
42
|
+
id
|
|
43
|
+
name
|
|
44
|
+
status
|
|
45
|
+
network {
|
|
46
|
+
internalUrl
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`, d = t`
|
|
51
|
+
query GetSandbox($id: ID!, $context: ContextInput!) {
|
|
52
|
+
getSandbox(id: $id, context: $context) {
|
|
53
|
+
id
|
|
54
|
+
status
|
|
55
|
+
errorMessage
|
|
56
|
+
network {
|
|
57
|
+
internalUrl
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`, f = t`
|
|
62
|
+
query GetActiveSandboxes($context: ContextInput!) {
|
|
63
|
+
getActiveSandboxes(context: $context) {
|
|
64
|
+
id
|
|
65
|
+
name
|
|
66
|
+
status
|
|
67
|
+
metadata
|
|
68
|
+
template
|
|
69
|
+
createdBy
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`, p = t`
|
|
73
|
+
mutation ProxySandboxRequest($input: ProxySandboxRequestInput!) {
|
|
74
|
+
proxySandboxRequest(input: $input) {
|
|
75
|
+
status
|
|
76
|
+
body
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`, m = t`
|
|
80
|
+
mutation ExtendSandboxTtl($id: ID!, $additionalSeconds: Int!, $context: ContextInput!) {
|
|
81
|
+
extendSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {
|
|
82
|
+
id
|
|
83
|
+
expiresAt
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
t`
|
|
88
|
+
mutation CheckAiCredits {
|
|
89
|
+
checkAiCredits
|
|
90
|
+
}
|
|
91
|
+
`, t`
|
|
92
|
+
mutation ReportAiTokenUsage($input: ReportAiTokenUsageInput!) {
|
|
93
|
+
reportAiTokenUsage(input: $input)
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
function h(e, t) {
|
|
97
|
+
let n = {};
|
|
98
|
+
return t?.accessToken && (n.authorization = `Bearer ${t.accessToken}`), t?.workspaceToken && (n["x-workspace-authorization"] = `Bearer ${t.workspaceToken}`), e && (n["x-workspace-id"] = e), t?.userId && (n["x-actor-id"] = t.userId, n["x-actor-type"] = "user"), t?.organizationId && (n["x-org-id"] = t.organizationId), Object.keys(n).length > 0 ? { headers: n } : void 0;
|
|
99
|
+
}
|
|
100
|
+
function g(e, t) {
|
|
101
|
+
return {
|
|
102
|
+
workspaceId: e,
|
|
103
|
+
...t?.organizationId ? { custom: { organizationId: t.organizationId } } : {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function _(t) {
|
|
107
|
+
let n = await fetch("/workspaces/graphql", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
...h(t.workspaceId, t.authContext)?.headers ?? {}
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
query: e(t.document),
|
|
115
|
+
variables: t.variables
|
|
116
|
+
})
|
|
117
|
+
}), r = await n.json().catch(() => null);
|
|
118
|
+
if (!n.ok) {
|
|
119
|
+
let e = r?.errors?.map((e) => e.message).join(", "), t = Error(e || `Sandbox GraphQL HTTP ${n.status}`);
|
|
120
|
+
throw t.statusCode = n.status, t.result = r ?? void 0, t;
|
|
121
|
+
}
|
|
122
|
+
if (!r) throw Error("Sandbox GraphQL returned empty response");
|
|
123
|
+
if (r.errors?.length) throw Error(r.errors.map((e) => e.message).join(", "));
|
|
124
|
+
if (!r.data) throw Error("Sandbox GraphQL returned no data");
|
|
125
|
+
return r.data;
|
|
126
|
+
}
|
|
127
|
+
async function v(e, t, n, r) {
|
|
128
|
+
return ((await _({
|
|
129
|
+
document: f,
|
|
130
|
+
variables: { context: g(t, r) },
|
|
131
|
+
workspaceId: t,
|
|
132
|
+
authContext: r
|
|
133
|
+
})).getActiveSandboxes ?? []).find((e) => (e.status === "RUNNING" || e.status === "PROVISIONING" || e.status === "PENDING") && e.template === "AI_BUILDER" && l(e) === n && (!r?.userId || e.createdBy === r.userId) && e.metadata?.authStrategy === a && e.metadata?.imageTag === o(n))?.id ?? null;
|
|
134
|
+
}
|
|
135
|
+
async function y(e, t, n, r) {
|
|
136
|
+
let i = c(t), l = [
|
|
137
|
+
{
|
|
138
|
+
name: "AI_ASSISTANT_MODE",
|
|
139
|
+
value: t
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "HOSTNAME",
|
|
143
|
+
value: "0.0.0.0"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "WORKSPACE_ID",
|
|
147
|
+
value: n
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "AI_ASSISTANT_PRODUCT",
|
|
151
|
+
value: "crewfoundry"
|
|
152
|
+
}
|
|
153
|
+
], d = await _({
|
|
154
|
+
document: u,
|
|
155
|
+
variables: { input: {
|
|
156
|
+
name: `ai-assistant-${t}-${Date.now()}`,
|
|
157
|
+
description: `CrewFoundry AI Assistant sandbox (${t} mode)`,
|
|
158
|
+
template: "AI_BUILDER",
|
|
159
|
+
mode: "DEPLOYMENT",
|
|
160
|
+
containers: [{
|
|
161
|
+
name: "ai-assistant",
|
|
162
|
+
image: s(t),
|
|
163
|
+
ports: [{
|
|
164
|
+
containerPort: 8080,
|
|
165
|
+
name: "http"
|
|
166
|
+
}],
|
|
167
|
+
env: l
|
|
168
|
+
}],
|
|
169
|
+
resources: {
|
|
170
|
+
cpuRequest: i.cpuRequest,
|
|
171
|
+
cpuLimit: i.cpuLimit,
|
|
172
|
+
memoryRequest: i.memoryRequest,
|
|
173
|
+
memoryLimit: i.memoryLimit
|
|
174
|
+
},
|
|
175
|
+
ttlSeconds: 600,
|
|
176
|
+
metadata: {
|
|
177
|
+
assistantMode: t,
|
|
178
|
+
assistantOwnerUserId: r?.userId ?? null,
|
|
179
|
+
assistantOrganizationId: r?.organizationId ?? null,
|
|
180
|
+
authStrategy: a,
|
|
181
|
+
imageTag: o(t)
|
|
182
|
+
},
|
|
183
|
+
context: g(n, r)
|
|
184
|
+
} },
|
|
185
|
+
workspaceId: n,
|
|
186
|
+
authContext: r
|
|
187
|
+
});
|
|
188
|
+
if (!d?.createSandbox?.id) throw Error("createSandbox returned empty response");
|
|
189
|
+
return d.createSandbox.id;
|
|
190
|
+
}
|
|
191
|
+
async function b(e, t, n, r, i = 3e5, a) {
|
|
192
|
+
let o = Date.now();
|
|
193
|
+
for (; Date.now() - o < i;) {
|
|
194
|
+
let e = await _({
|
|
195
|
+
document: d,
|
|
196
|
+
variables: {
|
|
197
|
+
id: t,
|
|
198
|
+
context: g(n, r)
|
|
199
|
+
},
|
|
200
|
+
workspaceId: n,
|
|
201
|
+
authContext: r
|
|
202
|
+
}), i = e?.getSandbox?.status, s = e?.getSandbox?.errorMessage;
|
|
203
|
+
if (i === "RUNNING") return;
|
|
204
|
+
if (i === "FAILED" || i === "STOPPED" || i === "EXPIRED") throw Error(s ? `Sandbox ${i.toLowerCase()}: ${s}` : `Sandbox ${i.toLowerCase()}`);
|
|
205
|
+
let c = Math.max(1, Math.round((Date.now() - o) / 1e3));
|
|
206
|
+
a?.(`Starting assistant environment... (${c}s)`), await new Promise((e) => setTimeout(e, 2e3));
|
|
207
|
+
}
|
|
208
|
+
throw Error("Sandbox startup timed out");
|
|
209
|
+
}
|
|
210
|
+
async function x(e, t, n, r, i, a, o) {
|
|
211
|
+
let s = (await _({
|
|
212
|
+
document: p,
|
|
213
|
+
variables: { input: {
|
|
214
|
+
sandboxId: t,
|
|
215
|
+
path: r,
|
|
216
|
+
method: i,
|
|
217
|
+
body: a,
|
|
218
|
+
context: g(n, o)
|
|
219
|
+
} },
|
|
220
|
+
workspaceId: n,
|
|
221
|
+
authContext: o
|
|
222
|
+
})).proxySandboxRequest;
|
|
223
|
+
if (!s || s.status >= 400) {
|
|
224
|
+
let e = s?.body;
|
|
225
|
+
throw Error(e?.error ?? `Proxy error: ${s?.status ?? "unknown"}`);
|
|
226
|
+
}
|
|
227
|
+
return s.body;
|
|
228
|
+
}
|
|
229
|
+
async function S(e, t, n, r, i = 9e4, a) {
|
|
230
|
+
let o = Date.now();
|
|
231
|
+
for (; Date.now() - o < i;) {
|
|
232
|
+
try {
|
|
233
|
+
await x(e, t, n, "/health", "GET", void 0, r);
|
|
234
|
+
return;
|
|
235
|
+
} catch (e) {
|
|
236
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
237
|
+
if (!/proxy error: 502|proxy error: 503|proxy error: 404|service not available/i.test(t)) throw e;
|
|
238
|
+
}
|
|
239
|
+
let i = Math.max(1, Math.round((Date.now() - o) / 1e3));
|
|
240
|
+
a?.(`Connecting to assistant... (${i}s)`), await new Promise((e) => setTimeout(e, 2e3));
|
|
241
|
+
}
|
|
242
|
+
throw Error("Assistant service did not become healthy before timeout");
|
|
243
|
+
}
|
|
244
|
+
async function C(e, t, n, r, i) {
|
|
245
|
+
return { sessionId: (await x(e, t, n, "/sessions", "POST", { mode: r }, i)).session.id };
|
|
246
|
+
}
|
|
247
|
+
async function w(e, t, n, r, i, a, o, s) {
|
|
248
|
+
let c = {
|
|
249
|
+
mode: a,
|
|
250
|
+
prompt: i,
|
|
251
|
+
pageContext: o ?? void 0
|
|
252
|
+
};
|
|
253
|
+
await x(e, t, n, `/sessions/${r}/prompt-async`, "POST", c, s);
|
|
254
|
+
}
|
|
255
|
+
async function T(e, t, n, r, i, a = 50) {
|
|
256
|
+
return (await x(e, t, n, `/sessions/${r}/messages?limit=${a}`, "GET", void 0, i)).messages ?? [];
|
|
257
|
+
}
|
|
258
|
+
async function E(e, t, n, r = 600, i) {
|
|
259
|
+
await _({
|
|
260
|
+
document: m,
|
|
261
|
+
variables: {
|
|
262
|
+
id: t,
|
|
263
|
+
additionalSeconds: r,
|
|
264
|
+
context: g(n, i)
|
|
265
|
+
},
|
|
266
|
+
workspaceId: n,
|
|
267
|
+
authContext: i
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
export { y as createAssistantSandbox, C as createAssistantSession, E as extendAssistantSandboxTTL, v as findExistingSandbox, T as getAssistantMessages, w as sendAssistantPromptAsync, S as waitForAssistantServiceReady, b as waitForSandboxReady };
|
|
272
|
+
|
|
273
|
+
//# sourceMappingURL=assistantApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API service for CrewFoundry.\n *\n * All requests go through the wspace-public-gateway (GraphQL Federation):\n * Frontend → wspace-public-gateway → wspace-sandbox-svc → ai-assistant\n *\n * Uses the host app's Apollo Client which already points to the gateway.\n */\n\nimport { type ApolloClient } from '@apollo/client';\nimport { print } from 'graphql';\nimport { gql } from '@apollo/client';\nimport type { AssistantArtifact, AssistantMode } from '@/types/assistant';\nimport type { PageContext } from '@/utils/pageContext';\n\n// ── Image registry ─────────────────────────────────────────────────\n\nconst HARBOR_REGISTRY = 'burdenoffregistry.azurecr.io';\nconst DEFAULT_IMAGE_TAG = import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha';\nconst API_CALLS_IMAGE_TAG =\n import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-delegated-auth-v11';\nconst ASSISTANT_API_AUTH_STRATEGY = 'sandbox-app-client-headers-v1';\n\nfunction getImageTagForMode(mode: AssistantMode): string {\n switch (mode) {\n case 'general':\n return DEFAULT_IMAGE_TAG;\n case 'assistant':\n case 'api-calls':\n return API_CALLS_IMAGE_TAG;\n default:\n return API_CALLS_IMAGE_TAG;\n }\n}\n\nfunction getImageForMode(mode: AssistantMode): string {\n const imageMode = mode === 'assistant' ? 'api-calls' : mode;\n return `${HARBOR_REGISTRY}/wspace/ai-assistant-${imageMode}:${getImageTagForMode(mode)}`;\n}\n\nfunction getResourcesForMode(mode: AssistantMode): {\n cpuRequest: string;\n cpuLimit: string;\n memoryRequest: string;\n memoryLimit: string;\n} {\n switch (mode) {\n case 'general':\n return {\n cpuRequest: '100m',\n cpuLimit: '500m',\n memoryRequest: '256Mi',\n memoryLimit: '1Gi',\n };\n case 'assistant':\n case 'api-calls':\n return {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n };\n }\n}\n\nfunction resolveSandboxAssistantMode(sandbox: {\n metadata: Record<string, unknown> | null;\n name?: string | null;\n}): AssistantMode | null {\n const metadataMode = sandbox.metadata?.assistantMode;\n if (metadataMode === 'assistant' || metadataMode === 'general' || metadataMode === 'api-calls') {\n return metadataMode;\n }\n\n const name = sandbox.name ?? '';\n if (name.startsWith('ai-assistant-api-calls-')) return 'api-calls';\n if (name.startsWith('ai-assistant-general-')) return 'general';\n if (name.startsWith('ai-assistant-assistant-')) return 'assistant';\n return null;\n}\n\n// ── GraphQL operations (inline so the MFE does not depend on generated files) ──\n\nconst CreateSandboxDocument = gql`\n mutation CreateSandbox($input: CreateSandboxInput!) {\n createSandbox(input: $input) {\n id\n name\n status\n network {\n internalUrl\n }\n }\n }\n`;\n\nconst GetSandboxDocument = gql`\n query GetSandbox($id: ID!, $context: ContextInput!) {\n getSandbox(id: $id, context: $context) {\n id\n status\n errorMessage\n network {\n internalUrl\n }\n }\n }\n`;\n\nconst GetActiveSandboxesDocument = gql`\n query GetActiveSandboxes($context: ContextInput!) {\n getActiveSandboxes(context: $context) {\n id\n name\n status\n metadata\n template\n createdBy\n }\n }\n`;\n\nconst ProxySandboxRequestDocument = gql`\n mutation ProxySandboxRequest($input: ProxySandboxRequestInput!) {\n proxySandboxRequest(input: $input) {\n status\n body\n }\n }\n`;\n\nconst ExtendSandboxTtlDocument = gql`\n mutation ExtendSandboxTtl($id: ID!, $additionalSeconds: Int!, $context: ContextInput!) {\n extendSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n`;\n\nconst CheckAiCreditsDocument = gql`\n mutation CheckAiCredits {\n checkAiCredits\n }\n`;\n\nconst ReportAiTokenUsageDocument = gql`\n mutation ReportAiTokenUsage($input: ReportAiTokenUsageInput!) {\n reportAiTokenUsage(input: $input)\n }\n`;\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface AssistantPromptResult {\n response: string;\n tokens?: { input: number; output: number };\n artifacts?: AssistantArtifact[];\n}\n\nexport interface AssistantRawMessagePart {\n type?: string;\n text?: string;\n tool?: string;\n state?: {\n status?: string;\n input?: unknown;\n output?: unknown;\n raw?: unknown;\n };\n}\n\nexport interface AssistantRawMessage {\n info?: {\n id?: string;\n role?: string;\n providerID?: string;\n modelID?: string;\n tokens?: { input: number; output: number };\n time?: {\n created?: number;\n completed?: number;\n };\n };\n parts?: AssistantRawMessagePart[];\n}\n\nexport interface AssistantSandboxAuthContext {\n accessToken?: string | null;\n workspaceToken?: string | null;\n userId?: string | null;\n organizationId?: string | null;\n}\n\nexport interface AssistantSessionInfo {\n id: string;\n mode: string;\n}\n\ninterface GraphQLResponse<TData> {\n data?: TData;\n errors?: Array<{ message: string }>;\n}\n\nexport interface DownloadedAssistantArtifact {\n filename: string;\n mimeType: string;\n contentBase64: string;\n}\n\ninterface ProxyResponse {\n proxySandboxRequest: {\n status: number;\n body: Record<string, unknown>;\n };\n}\n\nfunction getGatewayRequestContext(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): { headers?: Record<string, string> } | undefined {\n const headers: Record<string, string> = {};\n\n if (authContext?.accessToken) {\n headers.authorization = `Bearer ${authContext.accessToken}`;\n }\n\n if (authContext?.workspaceToken) {\n headers['x-workspace-authorization'] = `Bearer ${authContext.workspaceToken}`;\n }\n\n if (workspaceId) {\n headers['x-workspace-id'] = workspaceId;\n }\n\n if (authContext?.userId) {\n headers['x-actor-id'] = authContext.userId;\n headers['x-actor-type'] = 'user';\n }\n\n if (authContext?.organizationId) {\n headers['x-org-id'] = authContext.organizationId;\n }\n\n return Object.keys(headers).length > 0 ? { headers } : undefined;\n}\n\nfunction buildSandboxContextInput(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Record<string, unknown> {\n return {\n workspaceId,\n ...(authContext?.organizationId\n ? {\n custom: {\n organizationId: authContext.organizationId,\n },\n }\n : {}),\n };\n}\n\nasync function executeSandboxGraphQL<TData>(params: {\n document: ReturnType<typeof gql>;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const response = await fetch('/workspaces/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(getGatewayRequestContext(params.workspaceId, params.authContext)?.headers ?? {}),\n },\n body: JSON.stringify({\n query: print(params.document),\n variables: params.variables,\n }),\n });\n\n const result = (await response.json().catch(() => null)) as GraphQLResponse<TData> | null;\n\n if (!response.ok) {\n const messages = result?.errors?.map((error: { message: string }) => error.message).join(', ');\n const httpError = new Error(messages || `Sandbox GraphQL HTTP ${response.status}`) as Error & {\n statusCode?: number;\n result?: unknown;\n };\n httpError.statusCode = response.status;\n httpError.result = result ?? undefined;\n throw httpError;\n }\n\n if (!result) {\n throw new Error('Sandbox GraphQL returned empty response');\n }\n\n if (result.errors?.length) {\n throw new Error(result.errors.map((error: { message: string }) => error.message).join(', '));\n }\n\n if (!result.data) {\n throw new Error('Sandbox GraphQL returned no data');\n }\n\n return result.data;\n}\n\n// ── Find existing sandbox for a mode ───────────────────────────────\n\nexport async function findExistingSandbox(\n _apollo: ApolloClient,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<string | null> {\n const data = await executeSandboxGraphQL<{\n getActiveSandboxes: Array<{\n id: string;\n name: string;\n status: string;\n metadata: Record<string, unknown> | null;\n template: string;\n createdBy: string;\n }>;\n }>({\n document: GetActiveSandboxesDocument,\n variables: { context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const sandboxes = data.getActiveSandboxes ?? [];\n\n return (\n sandboxes.find(\n (s) =>\n (s.status === 'RUNNING' || s.status === 'PROVISIONING' || s.status === 'PENDING') &&\n s.template === 'AI_BUILDER' &&\n resolveSandboxAssistantMode(s) === mode &&\n (!authContext?.userId || s.createdBy === authContext.userId) &&\n s.metadata?.authStrategy === ASSISTANT_API_AUTH_STRATEGY &&\n s.metadata?.imageTag === getImageTagForMode(mode)\n )?.id ?? null\n );\n}\n\n// ── Sandbox lifecycle ──────────────────────────────────────────────\n\nexport async function createAssistantSandbox(\n _apollo: ApolloClient,\n mode: AssistantMode,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<string> {\n const resources = getResourcesForMode(mode);\n const env = [\n { name: 'AI_ASSISTANT_MODE', value: mode },\n { name: 'HOSTNAME', value: '0.0.0.0' },\n { name: 'WORKSPACE_ID', value: workspaceId },\n { name: 'AI_ASSISTANT_PRODUCT', value: 'crewfoundry' },\n ];\n\n const data = await executeSandboxGraphQL<{ createSandbox: { id: string } }>({\n document: CreateSandboxDocument,\n variables: {\n input: {\n name: `ai-assistant-${mode}-${Date.now()}`,\n description: `CrewFoundry AI Assistant sandbox (${mode} mode)`,\n template: 'AI_BUILDER',\n mode: 'DEPLOYMENT',\n containers: [\n {\n name: 'ai-assistant',\n image: getImageForMode(mode),\n ports: [{ containerPort: 8080, name: 'http' }],\n env,\n },\n ],\n resources: {\n cpuRequest: resources.cpuRequest,\n cpuLimit: resources.cpuLimit,\n memoryRequest: resources.memoryRequest,\n memoryLimit: resources.memoryLimit,\n },\n ttlSeconds: 600,\n metadata: {\n assistantMode: mode,\n assistantOwnerUserId: authContext?.userId ?? null,\n assistantOrganizationId: authContext?.organizationId ?? null,\n authStrategy: ASSISTANT_API_AUTH_STRATEGY,\n imageTag: getImageTagForMode(mode),\n },\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n if (!data?.createSandbox?.id) {\n throw new Error('createSandbox returned empty response');\n }\n return data.createSandbox.id;\n}\n\nexport async function waitForSandboxReady(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 300_000,\n onProgress?: (message: string) => void\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < maxWaitMs) {\n const data = await executeSandboxGraphQL<{\n getSandbox: { status?: string; errorMessage?: string } | null;\n }>({\n document: GetSandboxDocument,\n variables: { id: sandboxId, context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const status = data?.getSandbox?.status;\n const errorMessage = data?.getSandbox?.errorMessage as string | undefined;\n if (status === 'RUNNING') return;\n if (status === 'FAILED' || status === 'STOPPED' || status === 'EXPIRED') {\n throw new Error(\n errorMessage\n ? `Sandbox ${status.toLowerCase()}: ${errorMessage}`\n : `Sandbox ${status.toLowerCase()}`\n );\n }\n const elapsedSeconds = Math.max(1, Math.round((Date.now() - start) / 1000));\n onProgress?.(`Starting assistant environment... (${elapsedSeconds}s)`);\n await new Promise((r) => setTimeout(r, 2000));\n }\n throw new Error('Sandbox startup timed out');\n}\n\n// ── Proxy calls to ai-assistant (via gateway → sandbox-svc) ───────\n\nasync function proxyToSandbox(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n path: string,\n method: string,\n body?: unknown,\n authContext?: AssistantSandboxAuthContext\n): Promise<Record<string, unknown>> {\n const result = await executeSandboxGraphQL<ProxyResponse>({\n document: ProxySandboxRequestDocument,\n variables: {\n input: {\n sandboxId,\n path,\n method,\n body,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n const proxyResult = result.proxySandboxRequest;\n if (!proxyResult || proxyResult.status >= 400) {\n const errBody = proxyResult?.body as Record<string, unknown> | undefined;\n throw new Error(\n (errBody?.error as string) ?? `Proxy error: ${proxyResult?.status ?? 'unknown'}`\n );\n }\n\n return proxyResult.body;\n}\n\nexport async function waitForAssistantServiceReady(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 90_000,\n onProgress?: (message: string) => void\n): Promise<void> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < maxWaitMs) {\n try {\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n '/health',\n 'GET',\n undefined,\n authContext\n );\n return;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (\n !/proxy error: 502|proxy error: 503|proxy error: 404|service not available/i.test(message)\n ) {\n throw error;\n }\n }\n\n const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));\n onProgress?.(`Connecting to assistant... (${elapsedSeconds}s)`);\n\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n throw new Error('Assistant service did not become healthy before timeout');\n}\n\n// ── Session management ─────────────────────────────────────────────\n\nexport async function createAssistantSession(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<{ sessionId: string }> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n '/sessions',\n 'POST',\n { mode },\n authContext\n );\n const session = body.session as Record<string, unknown>;\n return { sessionId: session.id as string };\n}\n\nexport async function getAssistantSession(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantSessionInfo> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}`,\n 'GET',\n undefined,\n authContext\n );\n const session = body.session as Record<string, unknown> | undefined;\n\n return {\n id: (session?.id as string | undefined) ?? sessionId,\n mode: (body.mode as string | undefined) ?? 'unknown',\n };\n}\n\n// ── Send prompt ────────────────────────────────────────────────────\n\nexport async function sendAssistantPrompt(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantPromptResult> {\n const requestBody = {\n mode,\n prompt,\n pageContext: pageContext\n ? {\n currentUrl: pageContext.currentUrl,\n currentPage: pageContext.currentPage,\n entityType: pageContext.entityType,\n entityId: pageContext.entityId,\n platform: pageContext.platform,\n version: pageContext.version,\n viewportSize: pageContext.viewportSize,\n }\n : undefined,\n };\n\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt`,\n 'POST',\n requestBody,\n authContext\n );\n\n const message = body.message as Record<string, unknown> | undefined;\n const tokens = message?.tokens as { input: number; output: number } | undefined;\n\n return { response: body.text as string, tokens };\n}\n\nexport async function sendAssistantPromptAsync(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n const requestBody = {\n mode,\n prompt,\n pageContext: pageContext ?? undefined,\n };\n\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt-async`,\n 'POST',\n requestBody,\n authContext\n );\n}\n\nexport async function getAssistantMessages(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext,\n limit = 50\n): Promise<AssistantRawMessage[]> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/messages?limit=${limit}`,\n 'GET',\n undefined,\n authContext\n );\n\n return (body.messages as AssistantRawMessage[] | undefined) ?? [];\n}\n\n// ── AI credits (per-token metering) ────────────────────────────────\n\nexport async function checkAssistantAiCredits(\n _apollo: ApolloClient,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<boolean> {\n const data = await executeSandboxGraphQL<{ checkAiCredits: boolean }>({\n document: CheckAiCreditsDocument,\n variables: {},\n workspaceId,\n authContext,\n });\n return data.checkAiCredits;\n}\n\nexport async function reportAssistantAiTokenUsage(\n _apollo: ApolloClient,\n workspaceId: string,\n sessionId: string,\n messageId: string,\n model: string | undefined,\n tokens: { input: number; output: number },\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n const inputTokens = Math.max(0, Math.round(tokens.input ?? 0));\n const outputTokens = Math.max(0, Math.round(tokens.output ?? 0));\n if (inputTokens + outputTokens === 0) return;\n if (!model) return;\n\n await executeSandboxGraphQL<{ reportAiTokenUsage: boolean }>({\n document: ReportAiTokenUsageDocument,\n variables: {\n input: { sessionId, messageId, model, inputTokens, outputTokens },\n },\n workspaceId,\n authContext,\n });\n}\n\nexport async function getAssistantArtifacts(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantArtifact[]> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/artifacts`,\n 'GET',\n undefined,\n authContext\n );\n\n return ((body.artifacts as AssistantArtifact[] | undefined) ?? []).filter(\n (artifact) => !artifact.name.startsWith('.')\n );\n}\n\nexport async function downloadAssistantArtifact(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n artifactPath: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<DownloadedAssistantArtifact> {\n const encodedPath = encodeURIComponent(artifactPath);\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/artifacts/download?path=${encodedPath}`,\n 'GET',\n undefined,\n authContext\n );\n\n const artifact = body.artifact as Record<string, unknown> | undefined;\n if (!artifact) {\n throw new Error('Artifact download returned an empty response');\n }\n\n return {\n filename: artifact.filename as string,\n mimeType: artifact.mimeType as string,\n contentBase64: artifact.contentBase64 as string,\n };\n}\n\nexport async function extendAssistantSandboxTTL(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n additionalSeconds = 600,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await executeSandboxGraphQL<{ extendSandboxTTL: { id: string } }>({\n document: ExtendSandboxTtlDocument,\n variables: {\n id: sandboxId,\n additionalSeconds,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n workspaceId,\n authContext,\n });\n}\n"],"mappings":";;;AAiBA,IAAM,IAAkB,gCAClB,IAAuE,SACvE,IAC+C,4BAC/C,IAA8B;AAEpC,SAAS,EAAmB,GAA6B;AACvD,SAAQ,GAAR;EACE,KAAK,UACH,QAAO;EACT,KAAK;EACL,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,EAAgB,GAA6B;AAEpD,QAAO,GAAG,EAAgB,uBADR,MAAS,cAAc,cAAc,EACI,GAAG,EAAmB,EAAK;;AAGxF,SAAS,EAAoB,GAK3B;AACA,SAAQ,GAAR;EACE,KAAK,UACH,QAAO;GACL,YAAY;GACZ,UAAU;GACV,eAAe;GACf,aAAa;GACd;EACH,KAAK;EACL,KAAK,YACH,QAAO;GACL,YAAY;GACZ,UAAU;GACV,eAAe;GACf,aAAa;GACd;;;AAIP,SAAS,EAA4B,GAGZ;CACvB,IAAM,IAAe,EAAQ,UAAU;AACvC,KAAI,MAAiB,eAAe,MAAiB,aAAa,MAAiB,YACjF,QAAO;CAGT,IAAM,IAAO,EAAQ,QAAQ;AAI7B,QAHI,EAAK,WAAW,0BAA0B,GAAS,cACnD,EAAK,WAAW,wBAAwB,GAAS,YACjD,EAAK,WAAW,0BAA0B,GAAS,cAChD;;AAKT,IAAM,IAAwB,CAAG;;;;;;;;;;;GAa3B,IAAqB,CAAG;;;;;;;;;;;GAaxB,IAA6B,CAAG;;;;;;;;;;;GAahC,IAA8B,CAAG;;;;;;;GASjC,IAA2B,CAAG;;;;;;;;AASL,CAAG;;;;GAMC,CAAG;;;;;AAuEtC,SAAS,EACP,GACA,GACkD;CAClD,IAAM,IAAkC,EAAE;AAuB1C,QArBI,GAAa,gBACf,EAAQ,gBAAgB,UAAU,EAAY,gBAG5C,GAAa,mBACf,EAAQ,+BAA+B,UAAU,EAAY,mBAG3D,MACF,EAAQ,oBAAoB,IAG1B,GAAa,WACf,EAAQ,gBAAgB,EAAY,QACpC,EAAQ,kBAAkB,SAGxB,GAAa,mBACf,EAAQ,cAAc,EAAY,iBAG7B,OAAO,KAAK,EAAQ,CAAC,SAAS,IAAI,EAAE,YAAS,GAAG,KAAA;;AAGzD,SAAS,EACP,GACA,GACyB;AACzB,QAAO;EACL;EACA,GAAI,GAAa,iBACb,EACE,QAAQ,EACN,gBAAgB,EAAY,gBAC7B,EACF,GACD,EAAE;EACP;;AAGH,eAAe,EAA6B,GAKzB;CACjB,IAAM,IAAW,MAAM,MAAM,uBAAuB;EAClD,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,GAAI,EAAyB,EAAO,aAAa,EAAO,YAAY,EAAE,WAAW,EAAE;GACpF;EACD,MAAM,KAAK,UAAU;GACnB,OAAO,EAAM,EAAO,SAAS;GAC7B,WAAW,EAAO;GACnB,CAAC;EACH,CAAC,EAEI,IAAU,MAAM,EAAS,MAAM,CAAC,YAAY,KAAK;AAEvD,KAAI,CAAC,EAAS,IAAI;EAChB,IAAM,IAAW,GAAQ,QAAQ,KAAK,MAA+B,EAAM,QAAQ,CAAC,KAAK,KAAK,EACxF,IAAgB,MAAM,KAAY,wBAAwB,EAAS,SAAS;AAMlF,QAFA,EAAU,aAAa,EAAS,QAChC,EAAU,SAAS,KAAU,KAAA,GACvB;;AAGR,KAAI,CAAC,EACH,OAAU,MAAM,0CAA0C;AAG5D,KAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,KAAK,MAA+B,EAAM,QAAQ,CAAC,KAAK,KAAK,CAAC;AAG9F,KAAI,CAAC,EAAO,KACV,OAAU,MAAM,mCAAmC;AAGrD,QAAO,EAAO;;AAKhB,eAAsB,EACpB,GACA,GACA,GACA,GACwB;AAkBxB,UAjBa,MAAM,EAShB;EACD,UAAU;EACV,WAAW,EAAE,SAAS,EAAyB,GAAa,EAAY,EAAE;EAC1E;EACA;EACD,CAAC,EACqB,sBAAsB,EAAE,EAGnC,MACP,OACE,EAAE,WAAW,aAAa,EAAE,WAAW,kBAAkB,EAAE,WAAW,cACvE,EAAE,aAAa,gBACf,EAA4B,EAAE,KAAK,MAClC,CAAC,GAAa,UAAU,EAAE,cAAc,EAAY,WACrD,EAAE,UAAU,iBAAiB,KAC7B,EAAE,UAAU,aAAa,EAAmB,EAAK,CACpD,EAAE,MAAM;;AAMb,eAAsB,EACpB,GACA,GACA,GACA,GACiB;CACjB,IAAM,IAAY,EAAoB,EAAK,EACrC,IAAM;EACV;GAAE,MAAM;GAAqB,OAAO;GAAM;EAC1C;GAAE,MAAM;GAAY,OAAO;GAAW;EACtC;GAAE,MAAM;GAAgB,OAAO;GAAa;EAC5C;GAAE,MAAM;GAAwB,OAAO;GAAe;EACvD,EAEK,IAAO,MAAM,EAAyD;EAC1E,UAAU;EACV,WAAW,EACT,OAAO;GACL,MAAM,gBAAgB,EAAK,GAAG,KAAK,KAAK;GACxC,aAAa,qCAAqC,EAAK;GACvD,UAAU;GACV,MAAM;GACN,YAAY,CACV;IACE,MAAM;IACN,OAAO,EAAgB,EAAK;IAC5B,OAAO,CAAC;KAAE,eAAe;KAAM,MAAM;KAAQ,CAAC;IAC9C;IACD,CACF;GACD,WAAW;IACT,YAAY,EAAU;IACtB,UAAU,EAAU;IACpB,eAAe,EAAU;IACzB,aAAa,EAAU;IACxB;GACD,YAAY;GACZ,UAAU;IACR,eAAe;IACf,sBAAsB,GAAa,UAAU;IAC7C,yBAAyB,GAAa,kBAAkB;IACxD,cAAc;IACd,UAAU,EAAmB,EAAK;IACnC;GACD,SAAS,EAAyB,GAAa,EAAY;GAC5D,EACF;EACD;EACA;EACD,CAAC;AAEF,KAAI,CAAC,GAAM,eAAe,GACxB,OAAU,MAAM,wCAAwC;AAE1D,QAAO,EAAK,cAAc;;AAG5B,eAAsB,EACpB,GACA,GACA,GACA,GACA,IAAY,KACZ,GACe;CACf,IAAM,IAAQ,KAAK,KAAK;AACxB,QAAO,KAAK,KAAK,GAAG,IAAQ,IAAW;EACrC,IAAM,IAAO,MAAM,EAEhB;GACD,UAAU;GACV,WAAW;IAAE,IAAI;IAAW,SAAS,EAAyB,GAAa,EAAY;IAAE;GACzF;GACA;GACD,CAAC,EACI,IAAS,GAAM,YAAY,QAC3B,IAAe,GAAM,YAAY;AACvC,MAAI,MAAW,UAAW;AAC1B,MAAI,MAAW,YAAY,MAAW,aAAa,MAAW,UAC5D,OAAU,MACR,IACI,WAAW,EAAO,aAAa,CAAC,IAAI,MACpC,WAAW,EAAO,aAAa,GACpC;EAEH,IAAM,IAAiB,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,KAAK,GAAG,KAAS,IAAK,CAAC;AAE3E,EADA,IAAa,sCAAsC,EAAe,IAAI,EACtE,MAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;;AAE/C,OAAU,MAAM,4BAA4B;;AAK9C,eAAe,EACb,GACA,GACA,GACA,GACA,GACA,GACA,GACkC;CAgBlC,IAAM,KAfS,MAAM,EAAqC;EACxD,UAAU;EACV,WAAW,EACT,OAAO;GACL;GACA;GACA;GACA;GACA,SAAS,EAAyB,GAAa,EAAY;GAC5D,EACF;EACD;EACA;EACD,CAAC,EAEyB;AAC3B,KAAI,CAAC,KAAe,EAAY,UAAU,KAAK;EAC7C,IAAM,IAAU,GAAa;AAC7B,QAAU,MACP,GAAS,SAAoB,gBAAgB,GAAa,UAAU,YACtE;;AAGH,QAAO,EAAY;;AAGrB,eAAsB,EACpB,GACA,GACA,GACA,GACA,IAAY,KACZ,GACe;CACf,IAAM,IAAY,KAAK,KAAK;AAE5B,QAAO,KAAK,KAAK,GAAG,IAAY,IAAW;AACzC,MAAI;AACF,SAAM,EACJ,GACA,GACA,GACA,WACA,OACA,KAAA,GACA,EACD;AACD;WACO,GAAO;GACd,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;AACtE,OACE,CAAC,4EAA4E,KAAK,EAAQ,CAE1F,OAAM;;EAIV,IAAM,IAAiB,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,KAAK,GAAG,KAAa,IAAK,CAAC;AAG/E,EAFA,IAAa,+BAA+B,EAAe,IAAI,EAE/D,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,IAAK,CAAC;;AAG3D,OAAU,MAAM,0DAA0D;;AAK5E,eAAsB,EACpB,GACA,GACA,GACA,GACA,GACgC;AAWhC,QAAO,EAAE,YAVI,MAAM,EACjB,GACA,GACA,GACA,aACA,QACA,EAAE,SAAM,EACR,EACD,EACoB,QACO,IAAc;;AAuE5C,eAAsB,EACpB,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACe;CACf,IAAM,IAAc;EAClB;EACA;EACA,aAAa,KAAe,KAAA;EAC7B;AAED,OAAM,EACJ,GACA,GACA,GACA,aAAa,EAAU,gBACvB,QACA,GACA,EACD;;AAGH,eAAsB,EACpB,GACA,GACA,GACA,GACA,GACA,IAAQ,IACwB;AAWhC,SAVa,MAAM,EACjB,GACA,GACA,GACA,aAAa,EAAU,kBAAkB,KACzC,OACA,KAAA,GACA,EACD,EAEY,YAAkD,EAAE;;AAgGnE,eAAsB,EACpB,GACA,GACA,GACA,IAAoB,KACpB,GACe;AACf,OAAM,EAA4D;EAChE,UAAU;EACV,WAAW;GACT,IAAI;GACJ;GACA,SAAS,EAAyB,GAAa,EAAY;GAC5D;EACD;EACA;EACD,CAAC"}
|