@vllnt/ui 0.2.1-canary.9c473e0 → 0.2.1-canary.ab2c69a
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/components/checklist/checklist.js +7 -0
- package/dist/components/checklist/index.js +3 -1
- package/dist/components/conversation-thread/conversation-thread.js +348 -0
- package/dist/components/conversation-thread/index.js +20 -0
- package/dist/components/curriculum/curriculum.js +349 -0
- package/dist/components/curriculum/index.js +10 -0
- package/dist/components/form/form.js +220 -51
- package/dist/components/form/index.js +6 -2
- package/dist/components/index.js +73 -1
- package/dist/components/infinite-plane/index.js +6 -0
- package/dist/components/infinite-plane/infinite-plane.js +75 -0
- package/dist/components/presence-stack/index.js +6 -0
- package/dist/components/presence-stack/presence-stack.js +108 -0
- package/dist/components/progress-tracker/index.js +20 -0
- package/dist/components/progress-tracker/progress-tracker.js +527 -0
- package/dist/components/selection-presence/index.js +6 -0
- package/dist/components/selection-presence/selection-presence.js +50 -0
- package/dist/components/thread-bubble/index.js +6 -0
- package/dist/components/thread-bubble/thread-bubble.js +85 -0
- package/dist/components/viewport-bookmarks/index.js +6 -0
- package/dist/components/viewport-bookmarks/viewport-bookmarks.js +116 -0
- package/dist/components/world-breadcrumbs/index.js +6 -0
- package/dist/components/world-breadcrumbs/world-breadcrumbs.js +114 -0
- package/dist/index.d.ts +705 -11
- package/package.json +7 -3
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { cn } from "../../lib/utils";
|
|
5
|
+
const CHECKLIST_PROGRESS_EVENT = "vllnt:checklist-progress-change";
|
|
5
6
|
function ChecklistItemRow({
|
|
6
7
|
isChecked,
|
|
7
8
|
item,
|
|
@@ -133,6 +134,11 @@ function Checklist({
|
|
|
133
134
|
`checklist:${persistKey}`,
|
|
134
135
|
JSON.stringify([...newChecked])
|
|
135
136
|
);
|
|
137
|
+
window.dispatchEvent(
|
|
138
|
+
new CustomEvent(CHECKLIST_PROGRESS_EVENT, {
|
|
139
|
+
detail: { persistKey }
|
|
140
|
+
})
|
|
141
|
+
);
|
|
136
142
|
} catch {
|
|
137
143
|
}
|
|
138
144
|
}
|
|
@@ -177,5 +183,6 @@ function Checklist({
|
|
|
177
183
|
] });
|
|
178
184
|
}
|
|
179
185
|
export {
|
|
186
|
+
CHECKLIST_PROGRESS_EVENT,
|
|
180
187
|
Checklist
|
|
181
188
|
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
forwardRef,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState
|
|
12
|
+
} from "react";
|
|
13
|
+
import { ArrowDown, RefreshCw, ThumbsDown, ThumbsUp } from "lucide-react";
|
|
14
|
+
import { cn } from "../../lib/utils";
|
|
15
|
+
import { ThinkingBlock } from "../thinking-block";
|
|
16
|
+
const ConversationThreadContext = createContext(null);
|
|
17
|
+
function useConversationThreadContext() {
|
|
18
|
+
const ctx = useContext(ConversationThreadContext);
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"ConversationThread compound components must be used within <ConversationThread>"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return ctx;
|
|
25
|
+
}
|
|
26
|
+
function MessageActions({ messageId }) {
|
|
27
|
+
const { onFeedback, onRetry } = useConversationThreadContext();
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-1", children: [
|
|
29
|
+
onRetry ? /* @__PURE__ */ jsx(
|
|
30
|
+
"button",
|
|
31
|
+
{
|
|
32
|
+
"aria-label": "Retry message",
|
|
33
|
+
className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
34
|
+
onClick: () => {
|
|
35
|
+
onRetry(messageId);
|
|
36
|
+
},
|
|
37
|
+
type: "button",
|
|
38
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3" })
|
|
39
|
+
}
|
|
40
|
+
) : null,
|
|
41
|
+
onFeedback ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
"button",
|
|
44
|
+
{
|
|
45
|
+
"aria-label": "Positive feedback",
|
|
46
|
+
className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
47
|
+
onClick: () => {
|
|
48
|
+
onFeedback(messageId, "positive");
|
|
49
|
+
},
|
|
50
|
+
type: "button",
|
|
51
|
+
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "h-3 w-3" })
|
|
52
|
+
}
|
|
53
|
+
),
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"button",
|
|
56
|
+
{
|
|
57
|
+
"aria-label": "Negative feedback",
|
|
58
|
+
className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
59
|
+
onClick: () => {
|
|
60
|
+
onFeedback(messageId, "negative");
|
|
61
|
+
},
|
|
62
|
+
type: "button",
|
|
63
|
+
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "h-3 w-3" })
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
] }) : null
|
|
67
|
+
] });
|
|
68
|
+
}
|
|
69
|
+
function MessageItem({ message }) {
|
|
70
|
+
const isUser = message.role === "user";
|
|
71
|
+
return /* @__PURE__ */ jsx(
|
|
72
|
+
"div",
|
|
73
|
+
{
|
|
74
|
+
className: cn(
|
|
75
|
+
"mb-4 flex gap-3",
|
|
76
|
+
isUser ? "justify-end" : "justify-start"
|
|
77
|
+
),
|
|
78
|
+
children: /* @__PURE__ */ jsxs(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
className: cn(
|
|
82
|
+
"max-w-[80%] rounded-2xl px-4 py-3 text-sm",
|
|
83
|
+
isUser ? "rounded-br-sm bg-primary text-primary-foreground" : "rounded-bl-sm bg-muted text-foreground"
|
|
84
|
+
),
|
|
85
|
+
children: [
|
|
86
|
+
!isUser && message.thinking ? /* @__PURE__ */ jsx(
|
|
87
|
+
ThinkingBlock,
|
|
88
|
+
{
|
|
89
|
+
isStreaming: message.isStreaming,
|
|
90
|
+
thinking: message.thinking
|
|
91
|
+
}
|
|
92
|
+
) : null,
|
|
93
|
+
message.toolCalls && message.toolCalls.length > 0 ? /* @__PURE__ */ jsx(
|
|
94
|
+
"ul",
|
|
95
|
+
{
|
|
96
|
+
"aria-label": "Tool calls",
|
|
97
|
+
className: "mb-2 flex flex-col gap-1 text-xs text-muted-foreground",
|
|
98
|
+
children: message.toolCalls.map((toolCall) => /* @__PURE__ */ jsx("li", { className: "font-mono", children: toolCall.name }, toolCall.id))
|
|
99
|
+
}
|
|
100
|
+
) : null,
|
|
101
|
+
/* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap leading-relaxed", children: message.content }),
|
|
102
|
+
isUser ? null : /* @__PURE__ */ jsx(MessageActions, { messageId: message.id })
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
function useConversationScroll(messages, isStreaming) {
|
|
110
|
+
const scrollContainerRef = useRef(null);
|
|
111
|
+
const messagesEndRef = useRef(null);
|
|
112
|
+
const isAtBottomRef = useRef(true);
|
|
113
|
+
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
114
|
+
const scrollToBottom = useCallback(() => {
|
|
115
|
+
const element = messagesEndRef.current;
|
|
116
|
+
if (element && typeof element.scrollIntoView === "function") {
|
|
117
|
+
element.scrollIntoView({ behavior: "smooth" });
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
120
|
+
const scrollToBottomInstant = useCallback(() => {
|
|
121
|
+
const element = messagesEndRef.current;
|
|
122
|
+
if (element && typeof element.scrollIntoView === "function") {
|
|
123
|
+
element.scrollIntoView({ behavior: "instant" });
|
|
124
|
+
}
|
|
125
|
+
}, []);
|
|
126
|
+
const handleScroll = useCallback(() => {
|
|
127
|
+
const container = scrollContainerRef.current;
|
|
128
|
+
if (!container) return;
|
|
129
|
+
const { clientHeight, scrollHeight, scrollTop } = container;
|
|
130
|
+
const nearBottom = scrollHeight - scrollTop - clientHeight <= 100;
|
|
131
|
+
isAtBottomRef.current = nearBottom;
|
|
132
|
+
setIsAtBottom(nearBottom);
|
|
133
|
+
}, []);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!isAtBottomRef.current) return;
|
|
136
|
+
scrollToBottomInstant();
|
|
137
|
+
}, [messages, scrollToBottomInstant]);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!isStreaming || !isAtBottomRef.current) return;
|
|
140
|
+
scrollToBottomInstant();
|
|
141
|
+
}, [isStreaming, scrollToBottomInstant]);
|
|
142
|
+
return {
|
|
143
|
+
handleScroll,
|
|
144
|
+
isAtBottom,
|
|
145
|
+
messagesEndRef,
|
|
146
|
+
scrollContainerRef,
|
|
147
|
+
scrollToBottom
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const ConversationThread = forwardRef(
|
|
151
|
+
({
|
|
152
|
+
children,
|
|
153
|
+
className,
|
|
154
|
+
isStreaming = false,
|
|
155
|
+
messages,
|
|
156
|
+
onFeedback,
|
|
157
|
+
onRetry,
|
|
158
|
+
onSend
|
|
159
|
+
}, reference) => {
|
|
160
|
+
const {
|
|
161
|
+
handleScroll,
|
|
162
|
+
isAtBottom,
|
|
163
|
+
messagesEndRef,
|
|
164
|
+
scrollContainerRef,
|
|
165
|
+
scrollToBottom
|
|
166
|
+
} = useConversationScroll(messages, isStreaming);
|
|
167
|
+
const contextValue = useMemo(
|
|
168
|
+
() => ({
|
|
169
|
+
handleScroll,
|
|
170
|
+
isAtBottom,
|
|
171
|
+
isStreaming,
|
|
172
|
+
messages,
|
|
173
|
+
messagesEndRef,
|
|
174
|
+
onFeedback,
|
|
175
|
+
onRetry,
|
|
176
|
+
onSend,
|
|
177
|
+
scrollContainerRef,
|
|
178
|
+
scrollToBottom
|
|
179
|
+
}),
|
|
180
|
+
[
|
|
181
|
+
handleScroll,
|
|
182
|
+
isAtBottom,
|
|
183
|
+
isStreaming,
|
|
184
|
+
messages,
|
|
185
|
+
messagesEndRef,
|
|
186
|
+
onFeedback,
|
|
187
|
+
onRetry,
|
|
188
|
+
onSend,
|
|
189
|
+
scrollContainerRef,
|
|
190
|
+
scrollToBottom
|
|
191
|
+
]
|
|
192
|
+
);
|
|
193
|
+
return /* @__PURE__ */ jsx(ConversationThreadContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
194
|
+
"div",
|
|
195
|
+
{
|
|
196
|
+
className: cn("flex h-full flex-col overflow-hidden", className),
|
|
197
|
+
ref: reference,
|
|
198
|
+
children
|
|
199
|
+
}
|
|
200
|
+
) });
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
ConversationThread.displayName = "ConversationThread";
|
|
204
|
+
const ConversationHeader = forwardRef(({ children, className }, reference) => {
|
|
205
|
+
return /* @__PURE__ */ jsx(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
className: cn("flex shrink-0 items-center border-b px-4 py-3", className),
|
|
209
|
+
ref: reference,
|
|
210
|
+
children
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
ConversationHeader.displayName = "ConversationHeader";
|
|
215
|
+
const ConversationTitle = forwardRef(({ children, className }, reference) => {
|
|
216
|
+
return /* @__PURE__ */ jsx(
|
|
217
|
+
"h2",
|
|
218
|
+
{
|
|
219
|
+
className: cn("text-sm font-semibold leading-none", className),
|
|
220
|
+
ref: reference,
|
|
221
|
+
children
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
ConversationTitle.displayName = "ConversationTitle";
|
|
226
|
+
const ConversationMessages = forwardRef(({ children, className }, reference) => {
|
|
227
|
+
const { handleScroll, messages, messagesEndRef, scrollContainerRef } = useConversationThreadContext();
|
|
228
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative min-h-0 flex-1", className), ref: reference, children: [
|
|
229
|
+
/* @__PURE__ */ jsx(
|
|
230
|
+
"div",
|
|
231
|
+
{
|
|
232
|
+
"aria-label": "Conversation messages",
|
|
233
|
+
"aria-live": "polite",
|
|
234
|
+
className: "absolute inset-0 overflow-y-auto",
|
|
235
|
+
onScroll: handleScroll,
|
|
236
|
+
ref: scrollContainerRef,
|
|
237
|
+
role: "log",
|
|
238
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-4", children: [
|
|
239
|
+
messages.map((message) => /* @__PURE__ */ jsx(MessageItem, { message }, message.id)),
|
|
240
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", ref: messagesEndRef })
|
|
241
|
+
] })
|
|
242
|
+
}
|
|
243
|
+
),
|
|
244
|
+
children
|
|
245
|
+
] });
|
|
246
|
+
});
|
|
247
|
+
ConversationMessages.displayName = "ConversationMessages";
|
|
248
|
+
const ConversationEmpty = forwardRef(({ children, className }, reference) => {
|
|
249
|
+
const { messages } = useConversationThreadContext();
|
|
250
|
+
if (messages.length > 0) return null;
|
|
251
|
+
return /* @__PURE__ */ jsx(
|
|
252
|
+
"div",
|
|
253
|
+
{
|
|
254
|
+
className: cn(
|
|
255
|
+
"pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-4 p-8",
|
|
256
|
+
className
|
|
257
|
+
),
|
|
258
|
+
ref: reference,
|
|
259
|
+
children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto flex flex-col items-center gap-4", children })
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
ConversationEmpty.displayName = "ConversationEmpty";
|
|
264
|
+
const ConversationSuggestions = forwardRef(({ className, suggestions = [] }, reference) => {
|
|
265
|
+
const { onSend } = useConversationThreadContext();
|
|
266
|
+
return /* @__PURE__ */ jsx(
|
|
267
|
+
"div",
|
|
268
|
+
{
|
|
269
|
+
className: cn("flex flex-wrap justify-center gap-2", className),
|
|
270
|
+
ref: reference,
|
|
271
|
+
children: suggestions.map((suggestion) => /* @__PURE__ */ jsx(
|
|
272
|
+
"button",
|
|
273
|
+
{
|
|
274
|
+
className: "rounded-full border bg-background px-4 py-2 text-sm transition-colors hover:bg-muted",
|
|
275
|
+
onClick: () => onSend?.(suggestion),
|
|
276
|
+
type: "button",
|
|
277
|
+
children: suggestion
|
|
278
|
+
},
|
|
279
|
+
suggestion
|
|
280
|
+
))
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
ConversationSuggestions.displayName = "ConversationSuggestions";
|
|
285
|
+
const ConversationScrollButton = forwardRef(({ className }, reference) => {
|
|
286
|
+
const { isAtBottom, scrollToBottom } = useConversationThreadContext();
|
|
287
|
+
if (isAtBottom) return null;
|
|
288
|
+
return /* @__PURE__ */ jsx(
|
|
289
|
+
"button",
|
|
290
|
+
{
|
|
291
|
+
"aria-label": "Scroll to bottom",
|
|
292
|
+
className: cn(
|
|
293
|
+
"absolute bottom-4 right-4 flex h-8 w-8 items-center justify-center rounded-full border bg-background shadow-md transition-colors hover:bg-muted",
|
|
294
|
+
className
|
|
295
|
+
),
|
|
296
|
+
onClick: scrollToBottom,
|
|
297
|
+
ref: reference,
|
|
298
|
+
type: "button",
|
|
299
|
+
children: /* @__PURE__ */ jsx(ArrowDown, { className: "h-4 w-4" })
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
ConversationScrollButton.displayName = "ConversationScrollButton";
|
|
304
|
+
const ConversationLoading = forwardRef(({ className }, reference) => {
|
|
305
|
+
const { isStreaming, messages } = useConversationThreadContext();
|
|
306
|
+
const lastMessage = messages.at(-1);
|
|
307
|
+
if (!isStreaming || lastMessage?.role !== "assistant") return null;
|
|
308
|
+
return /* @__PURE__ */ jsxs(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
"aria-label": "Assistant is typing",
|
|
312
|
+
className: cn(
|
|
313
|
+
"absolute bottom-4 left-4 flex items-center gap-1",
|
|
314
|
+
className
|
|
315
|
+
),
|
|
316
|
+
ref: reference,
|
|
317
|
+
role: "status",
|
|
318
|
+
children: [
|
|
319
|
+
/* @__PURE__ */ jsx(
|
|
320
|
+
"span",
|
|
321
|
+
{
|
|
322
|
+
className: "h-2 w-2 animate-bounce rounded-full bg-muted-foreground",
|
|
323
|
+
style: { animationDelay: "-0.3s" }
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
/* @__PURE__ */ jsx(
|
|
327
|
+
"span",
|
|
328
|
+
{
|
|
329
|
+
className: "h-2 w-2 animate-bounce rounded-full bg-muted-foreground",
|
|
330
|
+
style: { animationDelay: "-0.15s" }
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
/* @__PURE__ */ jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-muted-foreground" })
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
ConversationLoading.displayName = "ConversationLoading";
|
|
339
|
+
export {
|
|
340
|
+
ConversationEmpty,
|
|
341
|
+
ConversationHeader,
|
|
342
|
+
ConversationLoading,
|
|
343
|
+
ConversationMessages,
|
|
344
|
+
ConversationScrollButton,
|
|
345
|
+
ConversationSuggestions,
|
|
346
|
+
ConversationThread,
|
|
347
|
+
ConversationTitle
|
|
348
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConversationEmpty,
|
|
3
|
+
ConversationHeader,
|
|
4
|
+
ConversationLoading,
|
|
5
|
+
ConversationMessages,
|
|
6
|
+
ConversationScrollButton,
|
|
7
|
+
ConversationSuggestions,
|
|
8
|
+
ConversationThread,
|
|
9
|
+
ConversationTitle
|
|
10
|
+
} from "./conversation-thread";
|
|
11
|
+
export {
|
|
12
|
+
ConversationEmpty,
|
|
13
|
+
ConversationHeader,
|
|
14
|
+
ConversationLoading,
|
|
15
|
+
ConversationMessages,
|
|
16
|
+
ConversationScrollButton,
|
|
17
|
+
ConversationSuggestions,
|
|
18
|
+
ConversationThread,
|
|
19
|
+
ConversationTitle
|
|
20
|
+
};
|