@wealthx/shadcn 1.5.34 → 1.5.36
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/.turbo/turbo-build.log +109 -109
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-EEI4FLEE.mjs → chunk-DIH2NZZ3.mjs} +347 -272
- package/dist/components/ui/ai-conversations/index.js +419 -350
- package/dist/components/ui/ai-conversations/index.mjs +1 -1
- package/dist/index.js +4403 -4334
- package/dist/index.mjs +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/ai-conversations/bubble.tsx +242 -0
- package/src/components/ui/ai-conversations/index.tsx +9 -8
- package/src/components/ui/ai-conversations/thread.tsx +11 -163
- package/src/styles/styles-css.ts +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactMarkdown from "react-markdown";
|
|
3
|
+
import rehypeRaw from "rehype-raw";
|
|
4
|
+
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
|
|
5
|
+
import { Bot } from "lucide-react";
|
|
6
|
+
import { cn, getInitials } from "@/lib/utils";
|
|
7
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
8
|
+
import { Separator } from "@/components/ui/separator";
|
|
9
|
+
import type { AiConvChannel, AiConvMessage, AiConvMessageRole } from "./types";
|
|
10
|
+
|
|
11
|
+
// Strips <style>, <script>, and <head> subtrees entirely so that CSS/JS text
|
|
12
|
+
// inside those tags is never surfaced as visible content when rendering
|
|
13
|
+
// full HTML email payloads.
|
|
14
|
+
const EMAIL_SANITIZE_SCHEMA = {
|
|
15
|
+
...defaultSchema,
|
|
16
|
+
strip: ["script", "style", "head", ...(defaultSchema.strip ?? [])],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Internal primitives
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
function BubbleAvatar({
|
|
24
|
+
role,
|
|
25
|
+
senderName,
|
|
26
|
+
}: {
|
|
27
|
+
role: AiConvMessageRole;
|
|
28
|
+
senderName?: string;
|
|
29
|
+
}) {
|
|
30
|
+
if (role === "bot") {
|
|
31
|
+
return (
|
|
32
|
+
<Avatar size="sm">
|
|
33
|
+
<AvatarFallback className="border border-border bg-muted">
|
|
34
|
+
<Bot className="size-3.5 text-muted-foreground" />
|
|
35
|
+
</AvatarFallback>
|
|
36
|
+
</Avatar>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (role === "advisor") {
|
|
40
|
+
return (
|
|
41
|
+
<Avatar size="sm">
|
|
42
|
+
<AvatarFallback className="font-semibold">
|
|
43
|
+
{getInitials(senderName ?? "Advisor")}
|
|
44
|
+
</AvatarFallback>
|
|
45
|
+
</Avatar>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return (
|
|
49
|
+
<Avatar size="sm">
|
|
50
|
+
<AvatarFallback>{getInitials(senderName ?? "?")}</AvatarFallback>
|
|
51
|
+
</Avatar>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function SystemDivider({
|
|
56
|
+
content,
|
|
57
|
+
className,
|
|
58
|
+
}: {
|
|
59
|
+
content: string;
|
|
60
|
+
className?: string;
|
|
61
|
+
}) {
|
|
62
|
+
return (
|
|
63
|
+
<div className={cn("my-2 flex items-center gap-3 px-2", className)}>
|
|
64
|
+
<Separator className="flex-1" />
|
|
65
|
+
<span className="shrink-0 text-caption text-muted-foreground">
|
|
66
|
+
{content}
|
|
67
|
+
</span>
|
|
68
|
+
<Separator className="flex-1" />
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function ChatMessageBubble({
|
|
74
|
+
message,
|
|
75
|
+
className,
|
|
76
|
+
}: {
|
|
77
|
+
message: AiConvMessage;
|
|
78
|
+
className?: string;
|
|
79
|
+
}) {
|
|
80
|
+
const { role, content, timestamp, senderName } = message;
|
|
81
|
+
const isAdvisor = role === "advisor";
|
|
82
|
+
const isBot = role === "bot";
|
|
83
|
+
const isVisitor = role === "visitor";
|
|
84
|
+
|
|
85
|
+
const displayName = isBot
|
|
86
|
+
? "AI Assistant"
|
|
87
|
+
: isAdvisor
|
|
88
|
+
? senderName ?? "Advisor"
|
|
89
|
+
: senderName ?? "Lead";
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
className={cn(
|
|
94
|
+
"flex gap-2.5",
|
|
95
|
+
isAdvisor ? "flex-row-reverse" : "flex-row",
|
|
96
|
+
className
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<BubbleAvatar role={role} senderName={senderName} />
|
|
100
|
+
|
|
101
|
+
<div
|
|
102
|
+
className={cn(
|
|
103
|
+
"flex max-w-[70%] flex-col gap-1",
|
|
104
|
+
isAdvisor && "items-end"
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
<span className="text-caption text-muted-foreground">
|
|
108
|
+
{displayName}
|
|
109
|
+
</span>
|
|
110
|
+
|
|
111
|
+
<div
|
|
112
|
+
className={cn(
|
|
113
|
+
"break-words px-3 py-2 text-sm leading-relaxed [&_a]:underline [&_p]:m-0",
|
|
114
|
+
isBot &&
|
|
115
|
+
"border border-border bg-muted/60 text-foreground [&_a]:text-primary",
|
|
116
|
+
isVisitor &&
|
|
117
|
+
"border border-border bg-background text-foreground [&_a]:text-primary",
|
|
118
|
+
isAdvisor &&
|
|
119
|
+
"bg-primary text-primary-foreground [&_a]:text-primary-foreground"
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
<ReactMarkdown
|
|
123
|
+
rehypePlugins={[rehypeRaw, [rehypeSanitize, defaultSchema]]}
|
|
124
|
+
>
|
|
125
|
+
{content}
|
|
126
|
+
</ReactMarkdown>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{timestamp && (
|
|
130
|
+
<span className="text-caption text-muted-foreground">
|
|
131
|
+
{timestamp}
|
|
132
|
+
</span>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function EmailMessageBubble({
|
|
140
|
+
message,
|
|
141
|
+
className,
|
|
142
|
+
}: {
|
|
143
|
+
message: AiConvMessage;
|
|
144
|
+
className?: string;
|
|
145
|
+
}) {
|
|
146
|
+
const { role, content, timestamp, senderName, subject } = message;
|
|
147
|
+
const isAdvisor = role === "advisor";
|
|
148
|
+
const isBot = role === "bot";
|
|
149
|
+
|
|
150
|
+
const displayName = isBot
|
|
151
|
+
? "AI Assistant"
|
|
152
|
+
: isAdvisor
|
|
153
|
+
? senderName ?? "Advisor"
|
|
154
|
+
: senderName ?? "Lead";
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div
|
|
158
|
+
className={cn(
|
|
159
|
+
"flex gap-2.5",
|
|
160
|
+
isAdvisor ? "flex-row-reverse" : "flex-row",
|
|
161
|
+
className
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
<BubbleAvatar role={role} senderName={senderName} />
|
|
165
|
+
|
|
166
|
+
<div
|
|
167
|
+
className={cn(
|
|
168
|
+
"flex w-full max-w-[85%] flex-col gap-1",
|
|
169
|
+
isAdvisor && "items-end"
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
<span className="text-caption text-muted-foreground">
|
|
173
|
+
{displayName}
|
|
174
|
+
</span>
|
|
175
|
+
|
|
176
|
+
<div
|
|
177
|
+
className={cn(
|
|
178
|
+
"w-full min-w-0 break-words whitespace-pre-line border border-border bg-background px-4 py-3 text-sm leading-relaxed [overflow-wrap:anywhere]",
|
|
179
|
+
"[&_p]:mb-2 [&_p:last-child]:mb-0",
|
|
180
|
+
"[&_ul]:mb-2 [&_ul]:list-disc [&_ul]:pl-4",
|
|
181
|
+
"[&_ol]:mb-2 [&_ol]:list-decimal [&_ol]:pl-4",
|
|
182
|
+
"[&_li]:mb-0.5",
|
|
183
|
+
"[&_h1]:mb-2 [&_h1]:text-sm [&_h1]:font-bold",
|
|
184
|
+
"[&_h2]:mb-1.5 [&_h2]:text-sm [&_h2]:font-semibold",
|
|
185
|
+
"[&_h3]:mb-1 [&_h3]:text-sm [&_h3]:font-medium",
|
|
186
|
+
"[&_strong]:font-semibold [&_em]:italic",
|
|
187
|
+
"[&_a]:text-primary [&_a]:underline",
|
|
188
|
+
"[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:italic [&_blockquote]:text-muted-foreground",
|
|
189
|
+
"[&_hr]:my-3 [&_hr]:border-border",
|
|
190
|
+
"[&_pre]:whitespace-pre-wrap [&_pre]:break-words [&_pre]:bg-transparent [&_pre]:p-0 [&_pre]:font-[inherit] [&_pre]:text-[length:inherit]",
|
|
191
|
+
"[&_code]:whitespace-pre-wrap [&_code]:break-words [&_code]:bg-transparent [&_code]:p-0 [&_code]:font-[inherit] [&_code]:text-[length:inherit]",
|
|
192
|
+
isAdvisor && "bg-muted/30"
|
|
193
|
+
)}
|
|
194
|
+
>
|
|
195
|
+
{subject && (
|
|
196
|
+
<div className="mb-2.5 border-b border-border pb-2">
|
|
197
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
198
|
+
Subject:
|
|
199
|
+
</span>{" "}
|
|
200
|
+
<span className="text-sm font-semibold text-foreground">
|
|
201
|
+
{subject}
|
|
202
|
+
</span>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
<ReactMarkdown
|
|
206
|
+
rehypePlugins={[rehypeRaw, [rehypeSanitize, EMAIL_SANITIZE_SCHEMA]]}
|
|
207
|
+
>
|
|
208
|
+
{content}
|
|
209
|
+
</ReactMarkdown>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{timestamp && (
|
|
213
|
+
<span className="text-caption text-muted-foreground">
|
|
214
|
+
{timestamp}
|
|
215
|
+
</span>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// ChatBubble — public API
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
export interface ChatBubbleProps {
|
|
227
|
+
message: AiConvMessage;
|
|
228
|
+
/** Channel the conversation is using — determines chat vs email rendering. */
|
|
229
|
+
channel?: AiConvChannel;
|
|
230
|
+
className?: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Routes to ChatMessageBubble, EmailMessageBubble, or SystemDivider. */
|
|
234
|
+
export function ChatBubble({ message, channel, className }: ChatBubbleProps) {
|
|
235
|
+
if (message.role === "system") {
|
|
236
|
+
return <SystemDivider content={message.content} className={className} />;
|
|
237
|
+
}
|
|
238
|
+
if (channel === "email") {
|
|
239
|
+
return <EmailMessageBubble message={message} className={className} />;
|
|
240
|
+
}
|
|
241
|
+
return <ChatMessageBubble message={message} className={className} />;
|
|
242
|
+
}
|
|
@@ -51,6 +51,7 @@ import { LeadInfoPanel } from "./lead-panel";
|
|
|
51
51
|
// Re-export everything so consumers import from the one public path.
|
|
52
52
|
export * from "./types";
|
|
53
53
|
export * from "./list";
|
|
54
|
+
export * from "./bubble";
|
|
54
55
|
export * from "./thread";
|
|
55
56
|
export * from "./lead-panel";
|
|
56
57
|
|
|
@@ -186,7 +187,7 @@ export function ConversationsPage({
|
|
|
186
187
|
className,
|
|
187
188
|
}: ConversationsPageProps) {
|
|
188
189
|
const [mobilePanel, setMobilePanel] = useState<"list" | "chat" | "lead">(
|
|
189
|
-
"list"
|
|
190
|
+
"list"
|
|
190
191
|
);
|
|
191
192
|
|
|
192
193
|
const handleSelectConversation = (id: string) => {
|
|
@@ -221,7 +222,7 @@ export function ConversationsPage({
|
|
|
221
222
|
onLoadMore={onLoadMore}
|
|
222
223
|
className={cn(
|
|
223
224
|
"shrink-0 md:w-[320px]",
|
|
224
|
-
mobilePanel === "list" ? "flex w-full md:flex" : "hidden md:flex"
|
|
225
|
+
mobilePanel === "list" ? "flex w-full md:flex" : "hidden md:flex"
|
|
225
226
|
)}
|
|
226
227
|
/>
|
|
227
228
|
|
|
@@ -258,14 +259,14 @@ export function ConversationsPage({
|
|
|
258
259
|
"min-w-0 flex-1 border-r border-border",
|
|
259
260
|
mobilePanel === "chat"
|
|
260
261
|
? "flex flex-col md:flex"
|
|
261
|
-
: "hidden md:flex"
|
|
262
|
+
: "hidden md:flex"
|
|
262
263
|
)}
|
|
263
264
|
/>
|
|
264
265
|
) : (
|
|
265
266
|
<div
|
|
266
267
|
className={cn(
|
|
267
268
|
"min-w-0 flex-1 items-center justify-center border-r border-border bg-muted/10",
|
|
268
|
-
mobilePanel === "chat" ? "flex md:flex" : "hidden md:flex"
|
|
269
|
+
mobilePanel === "chat" ? "flex md:flex" : "hidden md:flex"
|
|
269
270
|
)}
|
|
270
271
|
>
|
|
271
272
|
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
|
@@ -284,7 +285,7 @@ export function ConversationsPage({
|
|
|
284
285
|
? "flex w-full shrink-0 flex-col"
|
|
285
286
|
: "hidden",
|
|
286
287
|
"md:block md:shrink-0 md:overflow-hidden md:transition-[width] md:duration-200 md:ease-in-out",
|
|
287
|
-
showLeadPanel ? "md:w-[320px]" : "md:w-0"
|
|
288
|
+
showLeadPanel ? "md:w-[320px]" : "md:w-0"
|
|
288
289
|
)}
|
|
289
290
|
>
|
|
290
291
|
<LeadInfoPanel
|
|
@@ -363,13 +364,13 @@ export function AiConvAssignAdvisorDialog({
|
|
|
363
364
|
const [roleFilter, setRoleFilter] = useState("");
|
|
364
365
|
|
|
365
366
|
const roles = Array.from(
|
|
366
|
-
new Set(advisors.map((a) => a.role).filter(Boolean))
|
|
367
|
+
new Set(advisors.map((a) => a.role).filter(Boolean))
|
|
367
368
|
) as string[];
|
|
368
369
|
|
|
369
370
|
const filtered = advisors.filter(
|
|
370
371
|
(a) =>
|
|
371
372
|
a.name.toLowerCase().includes(search.toLowerCase()) &&
|
|
372
|
-
(!roleFilter || a.role === roleFilter)
|
|
373
|
+
(!roleFilter || a.role === roleFilter)
|
|
373
374
|
);
|
|
374
375
|
|
|
375
376
|
const handleOpenChange = (v: boolean) => {
|
|
@@ -435,7 +436,7 @@ export function AiConvAssignAdvisorDialog({
|
|
|
435
436
|
onClick={() => onValueChange(advisor.id)}
|
|
436
437
|
className={cn(
|
|
437
438
|
"flex w-full items-center gap-3 px-3 py-2.5 text-left text-sm transition-colors hover:bg-muted",
|
|
438
|
-
value === advisor.id && "bg-muted font-medium"
|
|
439
|
+
value === advisor.id && "bg-muted font-medium"
|
|
439
440
|
)}
|
|
440
441
|
>
|
|
441
442
|
<Avatar size="sm">
|
|
@@ -3,9 +3,6 @@ import { useEditor, EditorContent } from "@tiptap/react";
|
|
|
3
3
|
import StarterKit from "@tiptap/starter-kit";
|
|
4
4
|
import TiptapUnderline from "@tiptap/extension-underline";
|
|
5
5
|
import TiptapLink from "@tiptap/extension-link";
|
|
6
|
-
import ReactMarkdown from "react-markdown";
|
|
7
|
-
import rehypeRaw from "rehype-raw";
|
|
8
|
-
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
|
|
9
6
|
import {
|
|
10
7
|
Archive,
|
|
11
8
|
ArrowLeft,
|
|
@@ -25,8 +22,7 @@ import {
|
|
|
25
22
|
Underline,
|
|
26
23
|
UserCheck,
|
|
27
24
|
} from "lucide-react";
|
|
28
|
-
import { cn
|
|
29
|
-
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
25
|
+
import { cn } from "@/lib/utils";
|
|
30
26
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
31
27
|
import {
|
|
32
28
|
DropdownMenu,
|
|
@@ -48,7 +44,6 @@ import type {
|
|
|
48
44
|
AiConvChannel,
|
|
49
45
|
AiConvContact,
|
|
50
46
|
AiConvMessage,
|
|
51
|
-
AiConvMessageRole,
|
|
52
47
|
AiConvMode,
|
|
53
48
|
AiConvStatus,
|
|
54
49
|
} from "./types";
|
|
@@ -58,154 +53,7 @@ import {
|
|
|
58
53
|
PANEL_HEADER_HEIGHT,
|
|
59
54
|
} from "./helpers.tsx";
|
|
60
55
|
import { ConversationStatusChip } from "./list";
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// ChatBubble
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
export interface ChatBubbleProps {
|
|
67
|
-
message: AiConvMessage;
|
|
68
|
-
/** Channel the conversation is using — affects rendering style for email. */
|
|
69
|
-
channel?: AiConvChannel;
|
|
70
|
-
className?: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function BubbleAvatar({
|
|
74
|
-
role,
|
|
75
|
-
senderName,
|
|
76
|
-
}: {
|
|
77
|
-
role: AiConvMessageRole;
|
|
78
|
-
senderName?: string;
|
|
79
|
-
}) {
|
|
80
|
-
if (role === "bot") {
|
|
81
|
-
return (
|
|
82
|
-
<Avatar size="sm">
|
|
83
|
-
<AvatarFallback className="border border-border bg-muted">
|
|
84
|
-
<Bot className="size-3.5 text-muted-foreground" />
|
|
85
|
-
</AvatarFallback>
|
|
86
|
-
</Avatar>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
if (role === "advisor") {
|
|
90
|
-
return (
|
|
91
|
-
<Avatar size="sm">
|
|
92
|
-
<AvatarFallback className="font-semibold">
|
|
93
|
-
{getInitials(senderName ?? "Advisor")}
|
|
94
|
-
</AvatarFallback>
|
|
95
|
-
</Avatar>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
return (
|
|
99
|
-
<Avatar size="sm">
|
|
100
|
-
<AvatarFallback>{getInitials(senderName ?? "?")}</AvatarFallback>
|
|
101
|
-
</Avatar>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function ChatBubble({ message, channel, className }: ChatBubbleProps) {
|
|
106
|
-
const { role, content, timestamp, senderName, subject } = message;
|
|
107
|
-
const isEmail = channel === "email";
|
|
108
|
-
|
|
109
|
-
if (role === "system") {
|
|
110
|
-
return (
|
|
111
|
-
<div className={cn("my-2 flex items-center gap-3 px-2", className)}>
|
|
112
|
-
<Separator className="flex-1" />
|
|
113
|
-
<span className="shrink-0 text-caption text-muted-foreground">
|
|
114
|
-
{content}
|
|
115
|
-
</span>
|
|
116
|
-
<Separator className="flex-1" />
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const isAdvisor = role === "advisor";
|
|
122
|
-
const isBot = role === "bot";
|
|
123
|
-
const isVisitor = role === "visitor";
|
|
124
|
-
|
|
125
|
-
const displayName = isBot
|
|
126
|
-
? "AI Assistant"
|
|
127
|
-
: isAdvisor
|
|
128
|
-
? (senderName ?? "Advisor")
|
|
129
|
-
: (senderName ?? "Lead");
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<div
|
|
133
|
-
className={cn(
|
|
134
|
-
"flex gap-2.5",
|
|
135
|
-
isAdvisor ? "flex-row-reverse" : "flex-row",
|
|
136
|
-
className,
|
|
137
|
-
)}
|
|
138
|
-
>
|
|
139
|
-
<BubbleAvatar role={role} senderName={senderName} />
|
|
140
|
-
|
|
141
|
-
{/* Bubble + label */}
|
|
142
|
-
<div
|
|
143
|
-
className={cn(
|
|
144
|
-
"flex flex-col gap-1",
|
|
145
|
-
isEmail ? "max-w-[85%] w-full" : "max-w-[70%]",
|
|
146
|
-
isAdvisor && "items-end",
|
|
147
|
-
)}
|
|
148
|
-
>
|
|
149
|
-
<span className="text-caption text-muted-foreground">
|
|
150
|
-
{displayName}
|
|
151
|
-
</span>
|
|
152
|
-
|
|
153
|
-
<div
|
|
154
|
-
className={cn(
|
|
155
|
-
"text-sm leading-relaxed break-words",
|
|
156
|
-
!isEmail && "px-3 py-2 [&_p]:m-0 [&_a]:underline",
|
|
157
|
-
isEmail && [
|
|
158
|
-
"border border-border bg-background px-4 py-3 w-full",
|
|
159
|
-
"[&_p]:mb-2 [&_p:last-child]:mb-0",
|
|
160
|
-
"[&_ul]:mb-2 [&_ul]:list-disc [&_ul]:pl-4",
|
|
161
|
-
"[&_ol]:mb-2 [&_ol]:list-decimal [&_ol]:pl-4",
|
|
162
|
-
"[&_li]:mb-0.5",
|
|
163
|
-
"[&_h1]:mb-2 [&_h1]:text-sm [&_h1]:font-bold",
|
|
164
|
-
"[&_h2]:mb-1.5 [&_h2]:text-sm [&_h2]:font-semibold",
|
|
165
|
-
"[&_h3]:mb-1 [&_h3]:text-sm [&_h3]:font-medium",
|
|
166
|
-
"[&_strong]:font-semibold [&_em]:italic",
|
|
167
|
-
"[&_a]:underline [&_a]:text-primary",
|
|
168
|
-
"[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_blockquote]:italic",
|
|
169
|
-
"[&_hr]:my-3 [&_hr]:border-border",
|
|
170
|
-
],
|
|
171
|
-
!isEmail &&
|
|
172
|
-
isBot &&
|
|
173
|
-
"border border-border bg-muted/60 text-foreground [&_a]:text-primary",
|
|
174
|
-
!isEmail &&
|
|
175
|
-
isVisitor &&
|
|
176
|
-
"border border-border bg-background text-foreground [&_a]:text-primary",
|
|
177
|
-
!isEmail &&
|
|
178
|
-
isAdvisor &&
|
|
179
|
-
"bg-primary text-primary-foreground [&_a]:text-primary-foreground",
|
|
180
|
-
isEmail && isAdvisor && "bg-muted/30",
|
|
181
|
-
)}
|
|
182
|
-
>
|
|
183
|
-
{isEmail && subject && (
|
|
184
|
-
<div className="mb-2.5 border-b border-border pb-2">
|
|
185
|
-
<span className="text-xs font-medium text-muted-foreground">
|
|
186
|
-
Subject:
|
|
187
|
-
</span>{" "}
|
|
188
|
-
<span className="text-sm font-semibold text-foreground">
|
|
189
|
-
{subject}
|
|
190
|
-
</span>
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
193
|
-
<ReactMarkdown
|
|
194
|
-
rehypePlugins={[rehypeRaw, [rehypeSanitize, defaultSchema]]}
|
|
195
|
-
>
|
|
196
|
-
{content}
|
|
197
|
-
</ReactMarkdown>
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
{timestamp && (
|
|
201
|
-
<span className="text-caption text-muted-foreground">
|
|
202
|
-
{timestamp}
|
|
203
|
-
</span>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
);
|
|
208
|
-
}
|
|
56
|
+
import { ChatBubble } from "./bubble";
|
|
209
57
|
|
|
210
58
|
// ---------------------------------------------------------------------------
|
|
211
59
|
// ChatComposer
|
|
@@ -266,7 +114,7 @@ function ComposerToolbarButton({
|
|
|
266
114
|
"flex size-7 items-center justify-center transition-colors",
|
|
267
115
|
pressed
|
|
268
116
|
? "bg-foreground text-background"
|
|
269
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
117
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
270
118
|
)}
|
|
271
119
|
>
|
|
272
120
|
<Icon className="size-3.5" />
|
|
@@ -308,7 +156,7 @@ function ComposerLinkPopover({
|
|
|
308
156
|
"flex size-7 items-center justify-center transition-colors",
|
|
309
157
|
editor?.isActive("link")
|
|
310
158
|
? "bg-foreground text-background"
|
|
311
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
159
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
312
160
|
)}
|
|
313
161
|
>
|
|
314
162
|
<Link2 className="size-3.5" />
|
|
@@ -369,16 +217,16 @@ export function ChatComposer({
|
|
|
369
217
|
// Force chat when email isn't integrated so the panel never lands on a hidden tab.
|
|
370
218
|
const initialChannelRef = React.useRef(channelProp);
|
|
371
219
|
const [channel, setChannel] = React.useState<AiConvChannel>(
|
|
372
|
-
isEmailIntegrated ? channelProp : "chat"
|
|
220
|
+
isEmailIntegrated ? channelProp : "chat"
|
|
373
221
|
);
|
|
374
222
|
const [emailTo, setEmailTo] = React.useState(contactEmail);
|
|
375
223
|
const [emailCc, setEmailCc] = React.useState("");
|
|
376
224
|
const [showCc, setShowCc] = React.useState(false);
|
|
377
225
|
const [emailSubject, setEmailSubject] = React.useState(
|
|
378
|
-
emailReplySubject ? `Re: ${emailReplySubject}` : ""
|
|
226
|
+
emailReplySubject ? `Re: ${emailReplySubject}` : ""
|
|
379
227
|
);
|
|
380
228
|
const [emailMode, setEmailMode] = React.useState<"reply" | "new">(
|
|
381
|
-
emailReplySubject ? "reply" : "new"
|
|
229
|
+
emailReplySubject ? "reply" : "new"
|
|
382
230
|
);
|
|
383
231
|
|
|
384
232
|
const [, forceUpdate] = React.useReducer((x: number) => x + 1, 0);
|
|
@@ -424,7 +272,7 @@ export function ChatComposer({
|
|
|
424
272
|
<div
|
|
425
273
|
className={cn(
|
|
426
274
|
"flex flex-col border-t border-border bg-background",
|
|
427
|
-
className
|
|
275
|
+
className
|
|
428
276
|
)}
|
|
429
277
|
>
|
|
430
278
|
{isEmailIntegrated && (
|
|
@@ -468,7 +316,7 @@ export function ChatComposer({
|
|
|
468
316
|
<div
|
|
469
317
|
className={cn(
|
|
470
318
|
"flex flex-col",
|
|
471
|
-
channel !== "email" && "invisible pointer-events-none"
|
|
319
|
+
channel !== "email" && "invisible pointer-events-none"
|
|
472
320
|
)}
|
|
473
321
|
aria-hidden={channel !== "email"}
|
|
474
322
|
>
|
|
@@ -750,7 +598,7 @@ export function ChatThread({
|
|
|
750
598
|
<div
|
|
751
599
|
className={cn(
|
|
752
600
|
PANEL_HEADER_HEIGHT,
|
|
753
|
-
"flex items-center gap-3 border-b border-border px-4"
|
|
601
|
+
"flex items-center gap-3 border-b border-border px-4"
|
|
754
602
|
)}
|
|
755
603
|
>
|
|
756
604
|
{onBack && (
|
|
@@ -803,7 +651,7 @@ export function ChatThread({
|
|
|
803
651
|
<DropdownMenuTrigger
|
|
804
652
|
className={cn(
|
|
805
653
|
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
806
|
-
"size-8"
|
|
654
|
+
"size-8"
|
|
807
655
|
)}
|
|
808
656
|
aria-label="More actions"
|
|
809
657
|
>
|