@useatlas/react 0.0.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.
Files changed (98) hide show
  1. package/README.md +95 -0
  2. package/dist/chunk-2WFDP7G5.js +231 -0
  3. package/dist/chunk-2WFDP7G5.js.map +1 -0
  4. package/dist/chunk-44HBZYKP.js +224 -0
  5. package/dist/chunk-44HBZYKP.js.map +1 -0
  6. package/dist/chunk-5SEVKHS5.cjs +229 -0
  7. package/dist/chunk-5SEVKHS5.cjs.map +1 -0
  8. package/dist/chunk-UIRB6L36.cjs +249 -0
  9. package/dist/chunk-UIRB6L36.cjs.map +1 -0
  10. package/dist/hooks.cjs +251 -0
  11. package/dist/hooks.cjs.map +1 -0
  12. package/dist/hooks.d.cts +132 -0
  13. package/dist/hooks.d.ts +132 -0
  14. package/dist/hooks.js +237 -0
  15. package/dist/hooks.js.map +1 -0
  16. package/dist/index.cjs +2976 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.cts +69 -0
  19. package/dist/index.d.ts +69 -0
  20. package/dist/index.js +2926 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/result-chart-NFAJ4IQ5.js +398 -0
  23. package/dist/result-chart-NFAJ4IQ5.js.map +1 -0
  24. package/dist/result-chart-YLCKBNV4.cjs +400 -0
  25. package/dist/result-chart-YLCKBNV4.cjs.map +1 -0
  26. package/dist/styles.css +59 -0
  27. package/dist/use-dark-mode-rFxawUv1.d.cts +123 -0
  28. package/dist/use-dark-mode-rFxawUv1.d.ts +123 -0
  29. package/dist/widget.css +2 -0
  30. package/dist/widget.js +445 -0
  31. package/package.json +113 -0
  32. package/src/components/__tests__/tool-renderers.test.tsx +239 -0
  33. package/src/components/actions/action-approval-card.tsx +296 -0
  34. package/src/components/actions/action-status-badge.tsx +50 -0
  35. package/src/components/admin/change-password-dialog.tsx +128 -0
  36. package/src/components/atlas-chat.tsx +656 -0
  37. package/src/components/chart/chart-detection.ts +318 -0
  38. package/src/components/chart/result-chart.tsx +590 -0
  39. package/src/components/chat/api-key-bar.tsx +66 -0
  40. package/src/components/chat/copy-button.tsx +25 -0
  41. package/src/components/chat/data-table.tsx +104 -0
  42. package/src/components/chat/error-banner.tsx +32 -0
  43. package/src/components/chat/explore-card.tsx +41 -0
  44. package/src/components/chat/follow-up-chips.tsx +29 -0
  45. package/src/components/chat/loading-card.tsx +10 -0
  46. package/src/components/chat/managed-auth-card.tsx +116 -0
  47. package/src/components/chat/markdown.tsx +146 -0
  48. package/src/components/chat/python-result-card.tsx +245 -0
  49. package/src/components/chat/sql-block.tsx +54 -0
  50. package/src/components/chat/sql-result-card.tsx +163 -0
  51. package/src/components/chat/starter-prompts.ts +6 -0
  52. package/src/components/chat/tool-part.tsx +106 -0
  53. package/src/components/chat/typing-indicator.tsx +22 -0
  54. package/src/components/conversations/conversation-item.tsx +135 -0
  55. package/src/components/conversations/conversation-list.tsx +69 -0
  56. package/src/components/conversations/conversation-sidebar.tsx +113 -0
  57. package/src/components/conversations/delete-confirmation.tsx +27 -0
  58. package/src/components/schema-explorer/schema-explorer.tsx +517 -0
  59. package/src/components/ui/alert-dialog.tsx +196 -0
  60. package/src/components/ui/badge.tsx +48 -0
  61. package/src/components/ui/button.tsx +64 -0
  62. package/src/components/ui/card.tsx +92 -0
  63. package/src/components/ui/dialog.tsx +158 -0
  64. package/src/components/ui/dropdown-menu.tsx +257 -0
  65. package/src/components/ui/input.tsx +21 -0
  66. package/src/components/ui/label.tsx +24 -0
  67. package/src/components/ui/scroll-area.tsx +62 -0
  68. package/src/components/ui/separator.tsx +28 -0
  69. package/src/components/ui/sheet.tsx +143 -0
  70. package/src/components/ui/table.tsx +116 -0
  71. package/src/components/ui/toggle-group.tsx +83 -0
  72. package/src/components/ui/toggle.tsx +47 -0
  73. package/src/context.tsx +85 -0
  74. package/src/env.d.ts +9 -0
  75. package/src/hooks/__tests__/provider.test.tsx +83 -0
  76. package/src/hooks/__tests__/use-atlas-auth.test.tsx +283 -0
  77. package/src/hooks/__tests__/use-atlas-chat.test.tsx +157 -0
  78. package/src/hooks/__tests__/use-atlas-conversations.test.tsx +159 -0
  79. package/src/hooks/__tests__/use-atlas-theme.test.tsx +56 -0
  80. package/src/hooks/index.ts +47 -0
  81. package/src/hooks/provider.tsx +77 -0
  82. package/src/hooks/theme-init-script.ts +17 -0
  83. package/src/hooks/use-atlas-auth.ts +131 -0
  84. package/src/hooks/use-atlas-chat.ts +102 -0
  85. package/src/hooks/use-atlas-conversations.ts +61 -0
  86. package/src/hooks/use-atlas-theme.ts +34 -0
  87. package/src/hooks/use-conversations.ts +189 -0
  88. package/src/hooks/use-dark-mode.ts +150 -0
  89. package/src/index.ts +36 -0
  90. package/src/lib/action-types.ts +11 -0
  91. package/src/lib/helpers.ts +198 -0
  92. package/src/lib/tool-renderer-types.ts +76 -0
  93. package/src/lib/types.ts +29 -0
  94. package/src/lib/utils.ts +6 -0
  95. package/src/styles.css +59 -0
  96. package/src/test-setup.ts +55 -0
  97. package/src/widget-entry.ts +20 -0
  98. package/src/widget.css +12 -0
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Star, Trash2 } from "lucide-react";
5
+ import { Button } from "../ui/button";
6
+ import type { Conversation } from "../../lib/types";
7
+ import { DeleteConfirmation } from "./delete-confirmation";
8
+
9
+ function relativeTime(dateStr: string): string {
10
+ const then = new Date(dateStr).getTime();
11
+ if (isNaN(then)) return "";
12
+ const now = Date.now();
13
+ const diff = now - then;
14
+
15
+ const minutes = Math.floor(diff / 60_000);
16
+ if (minutes < 1) return "just now";
17
+ if (minutes < 60) return `${minutes}m ago`;
18
+
19
+ const hours = Math.floor(minutes / 60);
20
+ if (hours < 24) return `${hours}h ago`;
21
+
22
+ const days = Math.floor(hours / 24);
23
+ if (days < 7) return `${days}d ago`;
24
+
25
+ return new Date(dateStr).toLocaleDateString(undefined, { month: "short", day: "numeric" });
26
+ }
27
+
28
+ export function ConversationItem({
29
+ conversation,
30
+ isActive,
31
+ onSelect,
32
+ onDelete,
33
+ onStar,
34
+ }: {
35
+ conversation: Conversation;
36
+ isActive: boolean;
37
+ onSelect: () => void;
38
+ onDelete: () => Promise<boolean>;
39
+ onStar: (starred: boolean) => Promise<boolean>;
40
+ }) {
41
+ const [confirmDelete, setConfirmDelete] = useState(false);
42
+ const [deleting, setDeleting] = useState(false);
43
+ const [starPending, setStarPending] = useState(false);
44
+
45
+ if (confirmDelete) {
46
+ return (
47
+ <div className="rounded-lg border border-red-200 bg-red-50/50 dark:border-red-900/30 dark:bg-red-950/10">
48
+ <DeleteConfirmation
49
+ onCancel={() => setConfirmDelete(false)}
50
+ onConfirm={async () => {
51
+ setDeleting(true);
52
+ try {
53
+ const success = await onDelete();
54
+ if (success) {
55
+ setConfirmDelete(false);
56
+ }
57
+ } catch (err) {
58
+ console.warn("Failed to delete conversation:", err);
59
+ } finally {
60
+ setDeleting(false);
61
+ }
62
+ }}
63
+ />
64
+ </div>
65
+ );
66
+ }
67
+
68
+ return (
69
+ <div
70
+ role="button"
71
+ tabIndex={0}
72
+ onClick={onSelect}
73
+ onKeyDown={(e) => {
74
+ if (e.key === "Enter" || e.key === " ") {
75
+ e.preventDefault();
76
+ onSelect();
77
+ }
78
+ }}
79
+ className={`group flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left text-sm transition-colors ${
80
+ isActive
81
+ ? "bg-blue-50 text-blue-700 dark:bg-blue-600/10 dark:text-blue-400"
82
+ : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
83
+ }`}
84
+ >
85
+ <div className="min-w-0 flex-1">
86
+ <p className="truncate text-sm font-medium">
87
+ {conversation.title || "New conversation"}
88
+ </p>
89
+ <p className="text-xs text-zinc-400 dark:text-zinc-500">
90
+ {relativeTime(conversation.updatedAt)}
91
+ </p>
92
+ </div>
93
+ <div className="flex shrink-0 items-center gap-0.5">
94
+ <Button
95
+ variant="ghost"
96
+ size="icon"
97
+ onClick={async (e) => {
98
+ e.stopPropagation();
99
+ if (starPending) return;
100
+ setStarPending(true);
101
+ try {
102
+ await onStar(!conversation.starred);
103
+ } catch (err) {
104
+ console.warn("Failed to update star:", err);
105
+ } finally {
106
+ setStarPending(false);
107
+ }
108
+ }}
109
+ disabled={starPending}
110
+ className={`size-8 transition-all ${
111
+ conversation.starred
112
+ ? "text-amber-400 opacity-100 hover:text-amber-500 dark:text-amber-400 dark:hover:text-amber-300"
113
+ : "text-zinc-400 opacity-0 hover:text-amber-400 group-hover:opacity-100 dark:hover:text-amber-400"
114
+ } ${starPending ? "opacity-50" : ""}`}
115
+ aria-label={conversation.starred ? "Unstar conversation" : "Star conversation"}
116
+ >
117
+ <Star className="h-3.5 w-3.5" fill={conversation.starred ? "currentColor" : "none"} />
118
+ </Button>
119
+ <Button
120
+ variant="ghost"
121
+ size="icon"
122
+ onClick={(e) => {
123
+ e.stopPropagation();
124
+ setConfirmDelete(true);
125
+ }}
126
+ disabled={deleting}
127
+ className="size-8 shrink-0 text-zinc-400 opacity-0 transition-all hover:bg-red-50 hover:text-red-500 group-hover:opacity-100 dark:hover:bg-red-950/20 dark:hover:text-red-400"
128
+ aria-label="Delete conversation"
129
+ >
130
+ <Trash2 className="h-3.5 w-3.5" />
131
+ </Button>
132
+ </div>
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import type { Conversation } from "../../lib/types";
4
+ import { ConversationItem } from "./conversation-item";
5
+
6
+ export function ConversationList({
7
+ conversations,
8
+ selectedId,
9
+ onSelect,
10
+ onDelete,
11
+ onStar,
12
+ showSections = true,
13
+ emptyMessage,
14
+ }: {
15
+ conversations: Conversation[];
16
+ selectedId: string | null;
17
+ onSelect: (id: string) => void;
18
+ onDelete: (id: string) => Promise<boolean>;
19
+ onStar: (id: string, starred: boolean) => Promise<boolean>;
20
+ showSections?: boolean;
21
+ emptyMessage?: string;
22
+ }) {
23
+ if (conversations.length === 0) {
24
+ return (
25
+ <div className="px-3 py-6 text-center text-xs text-zinc-400 dark:text-zinc-500">
26
+ {emptyMessage ?? "No conversations yet"}
27
+ </div>
28
+ );
29
+ }
30
+
31
+ function renderItems(items: Conversation[]) {
32
+ return items.map((c) => (
33
+ <ConversationItem
34
+ key={c.id}
35
+ conversation={c}
36
+ isActive={c.id === selectedId}
37
+ onSelect={() => onSelect(c.id)}
38
+ onDelete={() => onDelete(c.id)}
39
+ onStar={(s) => onStar(c.id, s)}
40
+ />
41
+ ));
42
+ }
43
+
44
+ if (!showSections) {
45
+ return <div className="space-y-1">{renderItems(conversations)}</div>;
46
+ }
47
+
48
+ const starred = conversations.filter((c) => c.starred);
49
+ const unstarred = conversations.filter((c) => !c.starred);
50
+
51
+ return (
52
+ <div className="space-y-1">
53
+ {starred.length > 0 && (
54
+ <>
55
+ <div className="px-3 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500">
56
+ Starred
57
+ </div>
58
+ {renderItems(starred)}
59
+ {unstarred.length > 0 && (
60
+ <div className="px-3 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500">
61
+ Recent
62
+ </div>
63
+ )}
64
+ </>
65
+ )}
66
+ {renderItems(unstarred)}
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Star } from "lucide-react";
5
+ import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
6
+ import { Badge } from "../ui/badge";
7
+ import type { Conversation } from "../../lib/types";
8
+ import { ConversationList } from "./conversation-list";
9
+
10
+ type SidebarFilter = "all" | "saved";
11
+
12
+ export function ConversationSidebar({
13
+ conversations,
14
+ selectedId,
15
+ loading,
16
+ onSelect,
17
+ onDelete,
18
+ onStar,
19
+ onNewChat,
20
+ mobileOpen,
21
+ onMobileClose,
22
+ }: {
23
+ conversations: Conversation[];
24
+ selectedId: string | null;
25
+ loading: boolean;
26
+ onSelect: (id: string) => void;
27
+ onDelete: (id: string) => Promise<boolean>;
28
+ onStar: (id: string, starred: boolean) => Promise<boolean>;
29
+ onNewChat: () => void;
30
+ mobileOpen: boolean;
31
+ onMobileClose: () => void;
32
+ }) {
33
+ const [filter, setFilter] = useState<SidebarFilter>("all");
34
+ const starredConversations = conversations.filter((c) => c.starred);
35
+ const filteredConversations = filter === "saved" ? starredConversations : conversations;
36
+
37
+ const sidebar = (
38
+ <div className="flex h-full flex-col border-r border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950">
39
+ <div className="flex items-center justify-between border-b border-zinc-200 px-3 py-3 dark:border-zinc-800">
40
+ <span className="text-sm font-medium text-zinc-700 dark:text-zinc-300">History</span>
41
+ <button
42
+ onClick={onNewChat}
43
+ className="rounded-lg border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-600 transition-colors hover:border-zinc-400 hover:text-zinc-900 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200"
44
+ >
45
+ + New
46
+ </button>
47
+ </div>
48
+
49
+ <div className="border-b border-zinc-200 px-3 py-2 dark:border-zinc-800">
50
+ <ToggleGroup
51
+ type="single"
52
+ size="sm"
53
+ value={filter}
54
+ onValueChange={(val) => { if (val) setFilter(val as SidebarFilter); }}
55
+ className="gap-1"
56
+ >
57
+ <ToggleGroupItem value="all" className="px-2.5 text-xs">
58
+ All
59
+ </ToggleGroupItem>
60
+ <ToggleGroupItem value="saved" className="gap-1.5 px-2.5 text-xs">
61
+ <Star className="h-3 w-3" fill={filter === "saved" ? "currentColor" : "none"} />
62
+ Saved
63
+ {starredConversations.length > 0 && (
64
+ <Badge variant="secondary" className="h-4 px-1.5 text-[10px] font-semibold">
65
+ {starredConversations.length}
66
+ </Badge>
67
+ )}
68
+ </ToggleGroupItem>
69
+ </ToggleGroup>
70
+ </div>
71
+
72
+ <div className="flex-1 overflow-y-auto p-2">
73
+ {loading && conversations.length === 0 ? (
74
+ <div className="flex items-center justify-center py-8">
75
+ <span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-zinc-300 border-t-zinc-600 dark:border-zinc-600 dark:border-t-zinc-300" />
76
+ </div>
77
+ ) : (
78
+ <ConversationList
79
+ conversations={filteredConversations}
80
+ selectedId={selectedId}
81
+ onSelect={onSelect}
82
+ onDelete={onDelete}
83
+ onStar={onStar}
84
+ showSections={filter === "all"}
85
+ emptyMessage={filter === "saved" ? "Star conversations to save them here" : undefined}
86
+ />
87
+ )}
88
+ </div>
89
+ </div>
90
+ );
91
+
92
+ return (
93
+ <>
94
+ {/* Desktop sidebar */}
95
+ <div className="hidden w-[280px] shrink-0 md:block">
96
+ {sidebar}
97
+ </div>
98
+
99
+ {/* Mobile overlay */}
100
+ {mobileOpen && (
101
+ <>
102
+ <div
103
+ className="fixed inset-0 z-40 bg-black/30 md:hidden"
104
+ onClick={onMobileClose}
105
+ />
106
+ <div className="fixed inset-y-0 left-0 z-50 w-[280px] md:hidden">
107
+ {sidebar}
108
+ </div>
109
+ </>
110
+ )}
111
+ </>
112
+ );
113
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ export function DeleteConfirmation({
4
+ onConfirm,
5
+ onCancel,
6
+ }: {
7
+ onConfirm: () => void;
8
+ onCancel: () => void;
9
+ }) {
10
+ return (
11
+ <div className="flex items-center gap-2 px-3 py-2 text-xs">
12
+ <span className="text-zinc-500 dark:text-zinc-400">Delete?</span>
13
+ <button
14
+ onClick={onCancel}
15
+ className="rounded px-2 py-0.5 text-zinc-500 transition-colors hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
16
+ >
17
+ Cancel
18
+ </button>
19
+ <button
20
+ onClick={onConfirm}
21
+ className="rounded bg-red-600 px-2 py-0.5 text-white transition-colors hover:bg-red-500"
22
+ >
23
+ Delete
24
+ </button>
25
+ </div>
26
+ );
27
+ }