@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.
- package/README.md +95 -0
- package/dist/chunk-2WFDP7G5.js +231 -0
- package/dist/chunk-2WFDP7G5.js.map +1 -0
- package/dist/chunk-44HBZYKP.js +224 -0
- package/dist/chunk-44HBZYKP.js.map +1 -0
- package/dist/chunk-5SEVKHS5.cjs +229 -0
- package/dist/chunk-5SEVKHS5.cjs.map +1 -0
- package/dist/chunk-UIRB6L36.cjs +249 -0
- package/dist/chunk-UIRB6L36.cjs.map +1 -0
- package/dist/hooks.cjs +251 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +132 -0
- package/dist/hooks.d.ts +132 -0
- package/dist/hooks.js +237 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.cjs +2976 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +2926 -0
- package/dist/index.js.map +1 -0
- package/dist/result-chart-NFAJ4IQ5.js +398 -0
- package/dist/result-chart-NFAJ4IQ5.js.map +1 -0
- package/dist/result-chart-YLCKBNV4.cjs +400 -0
- package/dist/result-chart-YLCKBNV4.cjs.map +1 -0
- package/dist/styles.css +59 -0
- package/dist/use-dark-mode-rFxawUv1.d.cts +123 -0
- package/dist/use-dark-mode-rFxawUv1.d.ts +123 -0
- package/dist/widget.css +2 -0
- package/dist/widget.js +445 -0
- package/package.json +113 -0
- package/src/components/__tests__/tool-renderers.test.tsx +239 -0
- package/src/components/actions/action-approval-card.tsx +296 -0
- package/src/components/actions/action-status-badge.tsx +50 -0
- package/src/components/admin/change-password-dialog.tsx +128 -0
- package/src/components/atlas-chat.tsx +656 -0
- package/src/components/chart/chart-detection.ts +318 -0
- package/src/components/chart/result-chart.tsx +590 -0
- package/src/components/chat/api-key-bar.tsx +66 -0
- package/src/components/chat/copy-button.tsx +25 -0
- package/src/components/chat/data-table.tsx +104 -0
- package/src/components/chat/error-banner.tsx +32 -0
- package/src/components/chat/explore-card.tsx +41 -0
- package/src/components/chat/follow-up-chips.tsx +29 -0
- package/src/components/chat/loading-card.tsx +10 -0
- package/src/components/chat/managed-auth-card.tsx +116 -0
- package/src/components/chat/markdown.tsx +146 -0
- package/src/components/chat/python-result-card.tsx +245 -0
- package/src/components/chat/sql-block.tsx +54 -0
- package/src/components/chat/sql-result-card.tsx +163 -0
- package/src/components/chat/starter-prompts.ts +6 -0
- package/src/components/chat/tool-part.tsx +106 -0
- package/src/components/chat/typing-indicator.tsx +22 -0
- package/src/components/conversations/conversation-item.tsx +135 -0
- package/src/components/conversations/conversation-list.tsx +69 -0
- package/src/components/conversations/conversation-sidebar.tsx +113 -0
- package/src/components/conversations/delete-confirmation.tsx +27 -0
- package/src/components/schema-explorer/schema-explorer.tsx +517 -0
- package/src/components/ui/alert-dialog.tsx +196 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/scroll-area.tsx +62 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/toggle-group.tsx +83 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/context.tsx +85 -0
- package/src/env.d.ts +9 -0
- package/src/hooks/__tests__/provider.test.tsx +83 -0
- package/src/hooks/__tests__/use-atlas-auth.test.tsx +283 -0
- package/src/hooks/__tests__/use-atlas-chat.test.tsx +157 -0
- package/src/hooks/__tests__/use-atlas-conversations.test.tsx +159 -0
- package/src/hooks/__tests__/use-atlas-theme.test.tsx +56 -0
- package/src/hooks/index.ts +47 -0
- package/src/hooks/provider.tsx +77 -0
- package/src/hooks/theme-init-script.ts +17 -0
- package/src/hooks/use-atlas-auth.ts +131 -0
- package/src/hooks/use-atlas-chat.ts +102 -0
- package/src/hooks/use-atlas-conversations.ts +61 -0
- package/src/hooks/use-atlas-theme.ts +34 -0
- package/src/hooks/use-conversations.ts +189 -0
- package/src/hooks/use-dark-mode.ts +150 -0
- package/src/index.ts +36 -0
- package/src/lib/action-types.ts +11 -0
- package/src/lib/helpers.ts +198 -0
- package/src/lib/tool-renderer-types.ts +76 -0
- package/src/lib/types.ts +29 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +59 -0
- package/src/test-setup.ts +55 -0
- package/src/widget-entry.ts +20 -0
- 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
|
+
}
|