@useatlas/create 0.0.2 → 0.0.4
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/README.md +4 -18
- package/index.ts +191 -31
- package/package.json +1 -1
- package/templates/docker/.env.example +3 -3
- package/templates/docker/Dockerfile.sidecar +28 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +9 -9
- package/templates/docker/bin/atlas.ts +108 -44
- package/templates/docker/data/demo-semantic/catalog.yml +51 -27
- package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
- package/templates/docker/data/demo-semantic/glossary.yml +104 -8
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/docker/docker-compose.yml +1 -1
- package/templates/docker/docs/deploy.md +2 -39
- package/templates/docker/package.json +17 -1
- package/templates/docker/semantic/catalog.yml +62 -3
- package/templates/docker/semantic/entities/accounts.yml +162 -0
- package/templates/docker/semantic/entities/companies.yml +143 -0
- package/templates/docker/semantic/entities/people.yml +132 -0
- package/templates/docker/semantic/glossary.yml +116 -4
- package/templates/docker/semantic/metrics/accounts.yml +77 -0
- package/templates/docker/semantic/metrics/companies.yml +63 -0
- package/templates/docker/sidecar/Dockerfile +5 -6
- package/templates/docker/sidecar/railway.json +1 -2
- package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/docker/src/api/__tests__/health.test.ts +30 -8
- package/templates/docker/src/api/routes/admin.ts +549 -8
- package/templates/docker/src/api/routes/chat.ts +5 -20
- package/templates/docker/src/api/routes/health.ts +39 -27
- package/templates/docker/src/api/routes/openapi.ts +1329 -74
- package/templates/docker/src/api/routes/query.ts +2 -1
- package/templates/docker/src/api/server.ts +27 -0
- package/templates/docker/src/app/api/[...route]/route.ts +2 -2
- package/templates/docker/src/app/globals.css +13 -12
- package/templates/docker/src/app/layout.tsx +9 -2
- package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/docker/src/components/ui/badge.tsx +48 -0
- package/templates/docker/src/components/ui/button.tsx +64 -0
- package/templates/docker/src/components/ui/card.tsx +92 -0
- package/templates/docker/src/components/ui/collapsible.tsx +33 -0
- package/templates/docker/src/components/ui/command.tsx +184 -0
- package/templates/docker/src/components/ui/dialog.tsx +158 -0
- package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/docker/src/components/ui/input.tsx +21 -0
- package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
- package/templates/docker/src/components/ui/select.tsx +190 -0
- package/templates/docker/src/components/ui/separator.tsx +28 -0
- package/templates/docker/src/components/ui/sheet.tsx +143 -0
- package/templates/docker/src/components/ui/sidebar.tsx +726 -0
- package/templates/docker/src/components/ui/skeleton.tsx +13 -0
- package/templates/docker/src/components/ui/table.tsx +116 -0
- package/templates/docker/src/components/ui/tabs.tsx +91 -0
- package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
- package/templates/docker/src/components/ui/toggle.tsx +47 -0
- package/templates/docker/src/components/ui/tooltip.tsx +57 -0
- package/templates/docker/src/hooks/use-mobile.ts +19 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
- package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/docker/src/lib/agent-query.ts +5 -23
- package/templates/docker/src/lib/agent.ts +32 -112
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/docker/src/lib/auth/middleware.ts +30 -4
- package/templates/docker/src/lib/auth/migrate.ts +97 -0
- package/templates/docker/src/lib/auth/server.ts +12 -1
- package/templates/docker/src/lib/config.ts +37 -39
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/docker/src/lib/db/connection.ts +87 -265
- package/templates/docker/src/lib/db/internal.ts +6 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/docker/src/lib/plugins/index.ts +4 -1
- package/templates/docker/src/lib/plugins/migrate.ts +103 -0
- package/templates/docker/src/lib/plugins/registry.ts +12 -6
- package/templates/docker/src/lib/plugins/wiring.ts +113 -4
- package/templates/docker/src/lib/providers.ts +6 -1
- package/templates/docker/src/lib/security.ts +24 -0
- package/templates/docker/src/lib/semantic.ts +2 -0
- package/templates/docker/src/lib/sidecar-types.ts +12 -1
- package/templates/docker/src/lib/startup.ts +71 -101
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/docker/src/lib/tools/explore.ts +28 -32
- package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/docker/src/lib/tools/python.ts +367 -0
- package/templates/docker/src/lib/tools/registry.ts +49 -22
- package/templates/docker/src/lib/tools/sql.ts +88 -88
- package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/docker/src/ui/context.tsx +1 -1
- package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/docker/tsconfig.json +2 -2
- package/templates/nextjs-standalone/.env.example +1 -1
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +9 -9
- package/templates/nextjs-standalone/bin/atlas.ts +108 -44
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/nextjs-standalone/docs/deploy.md +2 -39
- package/templates/nextjs-standalone/package.json +11 -2
- package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
- package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
- package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
- package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
- package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
- package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
- package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
- package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
- package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
- package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
- package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
- package/templates/nextjs-standalone/src/api/server.ts +27 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
- package/templates/nextjs-standalone/src/app/globals.css +13 -12
- package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
- package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
- package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
- package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
- package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
- package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
- package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
- package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
- package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
- package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
- package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
- package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
- package/templates/nextjs-standalone/src/lib/config.ts +37 -39
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
- package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +103 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +12 -6
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
- package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
- package/templates/nextjs-standalone/src/lib/security.ts +24 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
- package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
- package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/nextjs-standalone/tsconfig.json +0 -1
- package/templates/nextjs-standalone/vercel.json +4 -1
- package/templates/docker/render.yaml +0 -34
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/docker/src/lib/db/duckdb.ts +0 -122
- package/templates/docker/src/lib/db/salesforce.ts +0 -342
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/docker/src/lib/tools/salesforce.ts +0 -138
- package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { ChevronRight, File, Folder } from "lucide-react";
|
|
5
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
6
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
export type SemanticSelection =
|
|
10
|
+
| { type: "catalog" }
|
|
11
|
+
| { type: "glossary" }
|
|
12
|
+
| { type: "entity"; name: string }
|
|
13
|
+
| { type: "metrics"; file?: string }
|
|
14
|
+
| null;
|
|
15
|
+
|
|
16
|
+
interface SemanticFileTreeProps {
|
|
17
|
+
entityNames: string[];
|
|
18
|
+
metricFileNames: string[];
|
|
19
|
+
hasCatalog: boolean;
|
|
20
|
+
hasGlossary: boolean;
|
|
21
|
+
selection: SemanticSelection;
|
|
22
|
+
onSelect: (selection: SemanticSelection) => void;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isSelected(selection: SemanticSelection, target: SemanticSelection): boolean {
|
|
27
|
+
if (!selection || !target) return false;
|
|
28
|
+
if (selection.type !== target.type) return false;
|
|
29
|
+
if (selection.type === "entity" && target.type === "entity") return selection.name === target.name;
|
|
30
|
+
if (selection.type === "metrics" && target.type === "metrics") return selection.file === target.file;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function FileItem({
|
|
35
|
+
name,
|
|
36
|
+
selected,
|
|
37
|
+
onClick,
|
|
38
|
+
indent = 0,
|
|
39
|
+
}: {
|
|
40
|
+
name: string;
|
|
41
|
+
selected: boolean;
|
|
42
|
+
onClick: () => void;
|
|
43
|
+
indent?: number;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<button
|
|
47
|
+
onClick={onClick}
|
|
48
|
+
className={cn(
|
|
49
|
+
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors",
|
|
50
|
+
selected ? "bg-accent text-accent-foreground" : "hover:bg-muted text-muted-foreground hover:text-foreground",
|
|
51
|
+
)}
|
|
52
|
+
style={{ paddingLeft: `${8 + indent * 16}px` }}
|
|
53
|
+
>
|
|
54
|
+
<File className="size-4 shrink-0 opacity-60" />
|
|
55
|
+
<span className="truncate">{name}</span>
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function FolderSection({
|
|
61
|
+
name,
|
|
62
|
+
children,
|
|
63
|
+
defaultOpen = true,
|
|
64
|
+
indent = 0,
|
|
65
|
+
}: {
|
|
66
|
+
name: string;
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
defaultOpen?: boolean;
|
|
69
|
+
indent?: number;
|
|
70
|
+
}) {
|
|
71
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Collapsible open={open} onOpenChange={setOpen}>
|
|
75
|
+
<CollapsibleTrigger
|
|
76
|
+
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm font-medium hover:bg-muted transition-colors"
|
|
77
|
+
style={{ paddingLeft: `${8 + indent * 16}px` }}
|
|
78
|
+
>
|
|
79
|
+
<ChevronRight
|
|
80
|
+
className={cn("size-3.5 shrink-0 transition-transform", open && "rotate-90")}
|
|
81
|
+
/>
|
|
82
|
+
<Folder className="size-4 shrink-0 opacity-60" />
|
|
83
|
+
<span className="truncate">{name}</span>
|
|
84
|
+
</CollapsibleTrigger>
|
|
85
|
+
<CollapsibleContent>{children}</CollapsibleContent>
|
|
86
|
+
</Collapsible>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function SemanticFileTree({
|
|
91
|
+
entityNames,
|
|
92
|
+
metricFileNames,
|
|
93
|
+
hasCatalog,
|
|
94
|
+
hasGlossary,
|
|
95
|
+
selection,
|
|
96
|
+
onSelect,
|
|
97
|
+
className,
|
|
98
|
+
}: SemanticFileTreeProps) {
|
|
99
|
+
return (
|
|
100
|
+
<div className={cn("flex flex-col", className)}>
|
|
101
|
+
<div className="border-b px-4 py-3">
|
|
102
|
+
<div className="flex items-center gap-2">
|
|
103
|
+
<Folder className="size-4 text-muted-foreground" />
|
|
104
|
+
<span className="text-sm font-semibold">semantic/</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<ScrollArea className="flex-1">
|
|
108
|
+
<div className="space-y-0.5 p-2">
|
|
109
|
+
{hasCatalog && (
|
|
110
|
+
<FileItem
|
|
111
|
+
name="catalog.yml"
|
|
112
|
+
selected={isSelected(selection, { type: "catalog" })}
|
|
113
|
+
onClick={() => onSelect({ type: "catalog" })}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
{hasGlossary && (
|
|
117
|
+
<FileItem
|
|
118
|
+
name="glossary.yml"
|
|
119
|
+
selected={isSelected(selection, { type: "glossary" })}
|
|
120
|
+
onClick={() => onSelect({ type: "glossary" })}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
<FolderSection name="entities">
|
|
125
|
+
{entityNames.length === 0 ? (
|
|
126
|
+
<p className="py-2 text-xs text-muted-foreground" style={{ paddingLeft: "40px" }}>
|
|
127
|
+
No entities
|
|
128
|
+
</p>
|
|
129
|
+
) : (
|
|
130
|
+
entityNames.map((name) => (
|
|
131
|
+
<FileItem
|
|
132
|
+
key={name}
|
|
133
|
+
name={`${name}.yml`}
|
|
134
|
+
selected={isSelected(selection, { type: "entity", name })}
|
|
135
|
+
onClick={() => onSelect({ type: "entity", name })}
|
|
136
|
+
indent={1}
|
|
137
|
+
/>
|
|
138
|
+
))
|
|
139
|
+
)}
|
|
140
|
+
</FolderSection>
|
|
141
|
+
|
|
142
|
+
{metricFileNames.length > 0 && (
|
|
143
|
+
<FolderSection name="metrics">
|
|
144
|
+
{metricFileNames.map((file) => (
|
|
145
|
+
<FileItem
|
|
146
|
+
key={file}
|
|
147
|
+
name={`${file}.yml`}
|
|
148
|
+
selected={isSelected(selection, { type: "metrics", file })}
|
|
149
|
+
onClick={() => onSelect({ type: "metrics", file })}
|
|
150
|
+
indent={1}
|
|
151
|
+
/>
|
|
152
|
+
))}
|
|
153
|
+
</FolderSection>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</ScrollArea>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -16,22 +16,39 @@ import { ToolPart } from "./chat/tool-part";
|
|
|
16
16
|
import { Markdown } from "./chat/markdown";
|
|
17
17
|
import { STARTER_PROMPTS } from "./chat/starter-prompts";
|
|
18
18
|
import { ConversationSidebar } from "./conversations/conversation-sidebar";
|
|
19
|
+
import { ChangePasswordDialog } from "./admin/change-password-dialog";
|
|
19
20
|
|
|
20
21
|
const API_KEY_STORAGE_KEY = "atlas-api-key";
|
|
21
22
|
|
|
23
|
+
/* Static SVG icons — hoisted to avoid recreation on every render */
|
|
24
|
+
const MenuIcon = (
|
|
25
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-5 w-5">
|
|
26
|
+
<path fillRule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" clipRule="evenodd" />
|
|
27
|
+
</svg>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const AtlasLogo = (
|
|
31
|
+
<svg viewBox="0 0 256 256" fill="none" className="h-7 w-7 shrink-0 text-primary" aria-hidden="true">
|
|
32
|
+
<path d="M128 24 L232 208 L24 208 Z" stroke="currentColor" strokeWidth="14" fill="none" strokeLinejoin="round"/>
|
|
33
|
+
<circle cx="128" cy="28" r="16" fill="currentColor"/>
|
|
34
|
+
</svg>
|
|
35
|
+
);
|
|
36
|
+
|
|
22
37
|
export function AtlasChat() {
|
|
23
38
|
const { apiUrl, isCrossOrigin, authClient } = useAtlasConfig();
|
|
24
39
|
const dark = useDarkMode();
|
|
25
40
|
const [input, setInput] = useState("");
|
|
26
|
-
const [authMode, setAuthMode] = useState<AuthMode>(
|
|
41
|
+
const [authMode, setAuthMode] = useState<AuthMode | null>(null);
|
|
27
42
|
const [healthWarning, setHealthWarning] = useState("");
|
|
28
43
|
const [apiKey, setApiKey] = useState("");
|
|
29
44
|
const [conversationId, setConversationId] = useState<string | null>(null);
|
|
30
45
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
31
46
|
const [loadingConversation, setLoadingConversation] = useState(false);
|
|
47
|
+
const [passwordChangeRequired, setPasswordChangeRequired] = useState(false);
|
|
32
48
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
33
49
|
|
|
34
50
|
const managedSession = authClient.useSession();
|
|
51
|
+
const authResolved = authMode !== null;
|
|
35
52
|
const isManaged = authMode === "managed";
|
|
36
53
|
const isSignedIn = isManaged && !!managedSession.data?.user;
|
|
37
54
|
|
|
@@ -55,6 +72,9 @@ export function AtlasChat() {
|
|
|
55
72
|
const refreshConvosRef = useRef(convos.refresh);
|
|
56
73
|
refreshConvosRef.current = convos.refresh;
|
|
57
74
|
|
|
75
|
+
const conversationIdRef = useRef(conversationId);
|
|
76
|
+
conversationIdRef.current = conversationId;
|
|
77
|
+
|
|
58
78
|
// Load API key from sessionStorage on mount + fetch auth mode + conversations
|
|
59
79
|
useEffect(() => {
|
|
60
80
|
try {
|
|
@@ -76,6 +96,7 @@ export function AtlasChat() {
|
|
|
76
96
|
return fetchHealth(attempt + 1);
|
|
77
97
|
}
|
|
78
98
|
setHealthWarning("Health check failed — check server logs. Try refreshing the page.");
|
|
99
|
+
setAuthMode("none");
|
|
79
100
|
return;
|
|
80
101
|
}
|
|
81
102
|
const data = await res.json();
|
|
@@ -90,6 +111,7 @@ export function AtlasChat() {
|
|
|
90
111
|
return fetchHealth(attempt + 1);
|
|
91
112
|
}
|
|
92
113
|
setHealthWarning("Unable to reach the API server. Try refreshing the page.");
|
|
114
|
+
setAuthMode("none");
|
|
93
115
|
}
|
|
94
116
|
}
|
|
95
117
|
fetchHealth(1);
|
|
@@ -100,6 +122,25 @@ export function AtlasChat() {
|
|
|
100
122
|
convos.fetchList();
|
|
101
123
|
}, [authMode, convos.fetchList]);
|
|
102
124
|
|
|
125
|
+
// Check if managed auth user needs to change their default password
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!isManaged || !managedSession.data?.user) return;
|
|
128
|
+
|
|
129
|
+
async function checkPasswordStatus() {
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`${apiUrl}/api/v1/admin/me/password-status`, {
|
|
132
|
+
credentials: isCrossOrigin ? "include" : "same-origin",
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) return;
|
|
135
|
+
const data = await res.json();
|
|
136
|
+
if (data.passwordChangeRequired) setPasswordChangeRequired(true);
|
|
137
|
+
} catch {
|
|
138
|
+
// Non-critical — skip silently
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
checkPasswordStatus();
|
|
142
|
+
}, [isManaged, managedSession.data?.user, apiUrl, isCrossOrigin]);
|
|
143
|
+
|
|
103
144
|
const handleSaveApiKey = useCallback((key: string) => {
|
|
104
145
|
setApiKey(key);
|
|
105
146
|
try {
|
|
@@ -109,7 +150,9 @@ export function AtlasChat() {
|
|
|
109
150
|
}
|
|
110
151
|
}, []);
|
|
111
152
|
|
|
112
|
-
// Dynamic transport — captures x-conversation-id from response
|
|
153
|
+
// Dynamic transport — captures x-conversation-id from response.
|
|
154
|
+
// conversationId is accessed via ref to avoid recreating the transport mid-stream
|
|
155
|
+
// (which causes an infinite re-render loop in useChat).
|
|
113
156
|
const transport = useMemo(() => {
|
|
114
157
|
const headers: Record<string, string> = {};
|
|
115
158
|
if (apiKey) {
|
|
@@ -119,11 +162,11 @@ export function AtlasChat() {
|
|
|
119
162
|
api: `${apiUrl}/api/chat`,
|
|
120
163
|
headers,
|
|
121
164
|
credentials: isCrossOrigin ? "include" : undefined,
|
|
122
|
-
body:
|
|
165
|
+
body: () => (conversationIdRef.current ? { conversationId: conversationIdRef.current } : {}),
|
|
123
166
|
fetch: (async (input, init) => {
|
|
124
167
|
const response = await globalThis.fetch(input, init);
|
|
125
168
|
const convId = response.headers.get("x-conversation-id");
|
|
126
|
-
if (convId && convId !==
|
|
169
|
+
if (convId && convId !== conversationIdRef.current) {
|
|
127
170
|
setConversationId(convId);
|
|
128
171
|
setTimeout(() => {
|
|
129
172
|
refreshConvosRef.current().catch((err) => {
|
|
@@ -134,7 +177,7 @@ export function AtlasChat() {
|
|
|
134
177
|
return response;
|
|
135
178
|
}) as typeof fetch,
|
|
136
179
|
});
|
|
137
|
-
}, [apiKey, authMode, apiUrl, isCrossOrigin
|
|
180
|
+
}, [apiKey, authMode, apiUrl, isCrossOrigin]);
|
|
138
181
|
|
|
139
182
|
const { messages, setMessages, sendMessage, status, error } = useChat({ transport });
|
|
140
183
|
|
|
@@ -186,6 +229,16 @@ export function AtlasChat() {
|
|
|
186
229
|
setMobileMenuOpen(false);
|
|
187
230
|
}
|
|
188
231
|
|
|
232
|
+
// Wait for auth mode detection before rendering — prevents flash of chat UI
|
|
233
|
+
// when managed auth is active but session hasn't been checked yet.
|
|
234
|
+
if (!authResolved || (isManaged && managedSession.isPending)) {
|
|
235
|
+
return (
|
|
236
|
+
<DarkModeContext.Provider value={dark}>
|
|
237
|
+
<div className="flex h-dvh items-center justify-center bg-white dark:bg-zinc-950" />
|
|
238
|
+
</DarkModeContext.Provider>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
189
242
|
return (
|
|
190
243
|
<DarkModeContext.Provider value={dark}>
|
|
191
244
|
<div className="flex h-dvh">
|
|
@@ -214,16 +267,11 @@ export function AtlasChat() {
|
|
|
214
267
|
className="rounded p-1 text-zinc-400 hover:text-zinc-700 md:hidden dark:hover:text-zinc-200"
|
|
215
268
|
aria-label="Open conversation history"
|
|
216
269
|
>
|
|
217
|
-
|
|
218
|
-
<path fillRule="evenodd" d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" clipRule="evenodd" />
|
|
219
|
-
</svg>
|
|
270
|
+
{MenuIcon}
|
|
220
271
|
</button>
|
|
221
272
|
)}
|
|
222
273
|
<div className="flex items-center gap-2.5">
|
|
223
|
-
|
|
224
|
-
<path d="M128 24 L232 208 L24 208 Z" stroke="#23CE9E" strokeWidth="14" fill="none" strokeLinejoin="round"/>
|
|
225
|
-
<circle cx="128" cy="28" r="16" fill="#23CE9E"/>
|
|
226
|
-
</svg>
|
|
274
|
+
{AtlasLogo}
|
|
227
275
|
<div>
|
|
228
276
|
<h1 className="text-xl font-semibold tracking-tight">Atlas</h1>
|
|
229
277
|
<p className="text-sm text-zinc-500">Ask your data anything</p>
|
|
@@ -365,6 +413,10 @@ export function AtlasChat() {
|
|
|
365
413
|
</div>
|
|
366
414
|
</main>
|
|
367
415
|
</div>
|
|
416
|
+
<ChangePasswordDialog
|
|
417
|
+
open={passwordChangeRequired}
|
|
418
|
+
onComplete={() => setPasswordChangeRequired(false)}
|
|
419
|
+
/>
|
|
368
420
|
</DarkModeContext.Provider>
|
|
369
421
|
);
|
|
370
422
|
}
|
|
@@ -100,6 +100,26 @@ function truncateLabel(label: unknown, maxLen = 12): string {
|
|
|
100
100
|
/* Tooltip */
|
|
101
101
|
/* ------------------------------------------------------------------ */
|
|
102
102
|
|
|
103
|
+
const TOOLTIP_LABEL_STYLE = { fontWeight: 600, marginBottom: 4 } as const;
|
|
104
|
+
|
|
105
|
+
const tooltipStyleCache = new Map<boolean, React.CSSProperties>();
|
|
106
|
+
function getTooltipStyle(dark: boolean): React.CSSProperties {
|
|
107
|
+
let style = tooltipStyleCache.get(dark);
|
|
108
|
+
if (!style) {
|
|
109
|
+
const t = themeTokens(dark);
|
|
110
|
+
style = {
|
|
111
|
+
background: t.tooltipBg,
|
|
112
|
+
border: `1px solid ${t.tooltipBorder}`,
|
|
113
|
+
borderRadius: 6,
|
|
114
|
+
padding: "8px 12px",
|
|
115
|
+
fontSize: 12,
|
|
116
|
+
color: t.tooltipText,
|
|
117
|
+
};
|
|
118
|
+
tooltipStyleCache.set(dark, style);
|
|
119
|
+
}
|
|
120
|
+
return style;
|
|
121
|
+
}
|
|
122
|
+
|
|
103
123
|
function ChartTooltip({ active, payload, label, dark }: {
|
|
104
124
|
active?: boolean;
|
|
105
125
|
payload?: Array<{ name: string; value: number; color: string }>;
|
|
@@ -107,19 +127,9 @@ function ChartTooltip({ active, payload, label, dark }: {
|
|
|
107
127
|
dark: boolean;
|
|
108
128
|
}) {
|
|
109
129
|
if (!active || !payload?.length) return null;
|
|
110
|
-
const t = themeTokens(dark);
|
|
111
130
|
return (
|
|
112
|
-
<div
|
|
113
|
-
style={{
|
|
114
|
-
background: t.tooltipBg,
|
|
115
|
-
border: `1px solid ${t.tooltipBorder}`,
|
|
116
|
-
borderRadius: 6,
|
|
117
|
-
padding: "8px 12px",
|
|
118
|
-
fontSize: 12,
|
|
119
|
-
color: t.tooltipText,
|
|
120
|
-
}}
|
|
121
|
-
>
|
|
122
|
-
<p style={{ fontWeight: 600, marginBottom: 4 }}>{label}</p>
|
|
131
|
+
<div style={getTooltipStyle(dark)}>
|
|
132
|
+
<p style={TOOLTIP_LABEL_STYLE}>{label}</p>
|
|
123
133
|
{payload.map((entry, i) => (
|
|
124
134
|
<p key={i} style={{ color: entry.color }}>
|
|
125
135
|
{entry.name}: {typeof entry.value === "number" ? formatNumber(entry.value) : entry.value}
|
|
@@ -365,9 +375,9 @@ export function ResultChart({
|
|
|
365
375
|
</div>
|
|
366
376
|
<ChartErrorBoundary key={currentType}>
|
|
367
377
|
<div className="p-2">
|
|
368
|
-
{currentType === "bar"
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
{currentType === "bar" ? <BarChartView data={chartData} rec={currentRec} dark={dark} />
|
|
379
|
+
: currentType === "line" ? <LineChartView data={chartData} rec={currentRec} dark={dark} />
|
|
380
|
+
: <PieChartView data={chartData} rec={currentRec} dark={dark} />}
|
|
371
381
|
</div>
|
|
372
382
|
</ChartErrorBoundary>
|
|
373
383
|
</div>
|
|
@@ -1,58 +1,104 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useContext } from "react";
|
|
3
|
+
import { memo, useContext, useState, useEffect, type ReactNode } from "react";
|
|
4
4
|
import ReactMarkdown from "react-markdown";
|
|
5
|
-
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
6
|
-
import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
7
5
|
import { DarkModeContext } from "../../hooks/use-dark-mode";
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
/* Lazy-loaded syntax highlighter (~300KB) */
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
|
|
11
|
+
type SyntaxHighlighterModule = typeof import("react-syntax-highlighter");
|
|
12
|
+
type StyleModule = typeof import("react-syntax-highlighter/dist/esm/styles/prism");
|
|
13
|
+
|
|
14
|
+
let _highlighterCache: { Prism: SyntaxHighlighterModule["Prism"]; oneDark: StyleModule["oneDark"]; oneLight: StyleModule["oneLight"] } | null = null;
|
|
15
|
+
|
|
16
|
+
function LazyCodeBlock({ language, dark, children }: { language: string; dark: boolean; children: string }) {
|
|
17
|
+
const [mod, setMod] = useState(_highlighterCache);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (_highlighterCache) return;
|
|
21
|
+
Promise.all([
|
|
22
|
+
import("react-syntax-highlighter"),
|
|
23
|
+
import("react-syntax-highlighter/dist/esm/styles/prism"),
|
|
24
|
+
]).then(([sh, styles]) => {
|
|
25
|
+
_highlighterCache = { Prism: sh.Prism, oneDark: styles.oneDark, oneLight: styles.oneLight };
|
|
26
|
+
setMod(_highlighterCache);
|
|
27
|
+
});
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
if (!mod) {
|
|
31
|
+
return (
|
|
32
|
+
<pre className="my-2 overflow-x-auto rounded-lg bg-zinc-100 p-3 text-xs dark:bg-zinc-800">
|
|
33
|
+
<code>{children}</code>
|
|
34
|
+
</pre>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<mod.Prism
|
|
40
|
+
language={language}
|
|
41
|
+
style={dark ? mod.oneDark : mod.oneLight}
|
|
42
|
+
customStyle={CODE_BLOCK_STYLE}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</mod.Prism>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const CODE_BLOCK_STYLE = {
|
|
50
|
+
margin: "0.5rem 0",
|
|
51
|
+
borderRadius: "0.5rem",
|
|
52
|
+
fontSize: "0.75rem",
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
/* ------------------------------------------------------------------ */
|
|
56
|
+
/* Static markdown renderers — hoisted outside component */
|
|
57
|
+
/* ------------------------------------------------------------------ */
|
|
58
|
+
|
|
59
|
+
const mdComponents = {
|
|
60
|
+
p: ({ children }: { children?: ReactNode }) => (
|
|
61
|
+
<p className="mb-3 leading-relaxed last:mb-0">{children}</p>
|
|
62
|
+
),
|
|
63
|
+
h1: ({ children }: { children?: ReactNode }) => (
|
|
64
|
+
<h1 className="mb-2 mt-4 text-lg font-bold first:mt-0">{children}</h1>
|
|
65
|
+
),
|
|
66
|
+
h2: ({ children }: { children?: ReactNode }) => (
|
|
67
|
+
<h2 className="mb-2 mt-3 text-base font-semibold first:mt-0">{children}</h2>
|
|
68
|
+
),
|
|
69
|
+
h3: ({ children }: { children?: ReactNode }) => (
|
|
70
|
+
<h3 className="mb-1 mt-2 font-semibold first:mt-0">{children}</h3>
|
|
71
|
+
),
|
|
72
|
+
ul: ({ children }: { children?: ReactNode }) => (
|
|
73
|
+
<ul className="mb-3 list-disc space-y-1 pl-4">{children}</ul>
|
|
74
|
+
),
|
|
75
|
+
ol: ({ children }: { children?: ReactNode }) => (
|
|
76
|
+
<ol className="mb-3 list-decimal space-y-1 pl-4">{children}</ol>
|
|
77
|
+
),
|
|
78
|
+
strong: ({ children }: { children?: ReactNode }) => (
|
|
79
|
+
<strong className="font-semibold text-zinc-900 dark:text-zinc-50">{children}</strong>
|
|
80
|
+
),
|
|
81
|
+
blockquote: ({ children }: { children?: ReactNode }) => (
|
|
82
|
+
<blockquote className="my-2 border-l-2 border-zinc-300 pl-3 text-zinc-500 dark:border-zinc-600 dark:text-zinc-400">
|
|
83
|
+
{children}
|
|
84
|
+
</blockquote>
|
|
85
|
+
),
|
|
86
|
+
pre: ({ children }: { children?: ReactNode }) => <>{children}</>,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const Markdown = memo(function Markdown({ content }: { content: string }) {
|
|
10
90
|
const dark = useContext(DarkModeContext);
|
|
11
91
|
return (
|
|
12
92
|
<ReactMarkdown
|
|
13
93
|
components={{
|
|
14
|
-
|
|
15
|
-
<p className="mb-3 leading-relaxed last:mb-0">{children}</p>
|
|
16
|
-
),
|
|
17
|
-
h1: ({ children }) => (
|
|
18
|
-
<h1 className="mb-2 mt-4 text-lg font-bold first:mt-0">{children}</h1>
|
|
19
|
-
),
|
|
20
|
-
h2: ({ children }) => (
|
|
21
|
-
<h2 className="mb-2 mt-3 text-base font-semibold first:mt-0">{children}</h2>
|
|
22
|
-
),
|
|
23
|
-
h3: ({ children }) => (
|
|
24
|
-
<h3 className="mb-1 mt-2 font-semibold first:mt-0">{children}</h3>
|
|
25
|
-
),
|
|
26
|
-
ul: ({ children }) => (
|
|
27
|
-
<ul className="mb-3 list-disc space-y-1 pl-4">{children}</ul>
|
|
28
|
-
),
|
|
29
|
-
ol: ({ children }) => (
|
|
30
|
-
<ol className="mb-3 list-decimal space-y-1 pl-4">{children}</ol>
|
|
31
|
-
),
|
|
32
|
-
strong: ({ children }) => (
|
|
33
|
-
<strong className="font-semibold text-zinc-900 dark:text-zinc-50">{children}</strong>
|
|
34
|
-
),
|
|
35
|
-
blockquote: ({ children }) => (
|
|
36
|
-
<blockquote className="my-2 border-l-2 border-zinc-300 pl-3 text-zinc-500 dark:border-zinc-600 dark:text-zinc-400">
|
|
37
|
-
{children}
|
|
38
|
-
</blockquote>
|
|
39
|
-
),
|
|
40
|
-
pre: ({ children }) => <>{children}</>,
|
|
94
|
+
...mdComponents,
|
|
41
95
|
code({ className, children, ...props }) {
|
|
42
96
|
const match = /language-(\w+)/.exec(className || "");
|
|
43
97
|
if (match) {
|
|
44
98
|
return (
|
|
45
|
-
<
|
|
46
|
-
language={match[1]}
|
|
47
|
-
style={dark ? oneDark : oneLight}
|
|
48
|
-
customStyle={{
|
|
49
|
-
margin: "0.5rem 0",
|
|
50
|
-
borderRadius: "0.5rem",
|
|
51
|
-
fontSize: "0.75rem",
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
99
|
+
<LazyCodeBlock language={match[1]} dark={dark}>
|
|
54
100
|
{String(children).replace(/\n$/, "")}
|
|
55
|
-
</
|
|
101
|
+
</LazyCodeBlock>
|
|
56
102
|
);
|
|
57
103
|
}
|
|
58
104
|
return (
|
|
@@ -69,4 +115,4 @@ export function Markdown({ content }: { content: string }) {
|
|
|
69
115
|
{content}
|
|
70
116
|
</ReactMarkdown>
|
|
71
117
|
);
|
|
72
|
-
}
|
|
118
|
+
});
|