@brainfish-ai/components 0.25.3 → 0.25.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.
@@ -1,8 +1,8 @@
1
- import React__default, { useMemo, useContext, createContext, useCallback, useRef, useEffect, useLayoutEffect, useState, Suspense, forwardRef, Component, createElement, useImperativeHandle } from 'react';
1
+ import React__default, { useRef, useLayoutEffect, useMemo, useContext, createContext, useCallback, useEffect, useState, Suspense, forwardRef, Component, createElement, useImperativeHandle } from 'react';
2
2
  import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../components/ui/tooltip.js';
3
3
  import { c as cn } from './utils.Cwtlq8dh.js';
4
4
  import { ScrollArea } from '../components/ui/scroll-area.js';
5
- import { Sparkle, ThumbsUp, ThumbsDown, Copy, DotsThreeVertical, X as X$1, Plus, CaretRight, Stop, ArrowUp } from '@phosphor-icons/react';
5
+ import { ThumbsUp, ThumbsDown, Copy, DotsThreeVertical, X as X$1, Plus, CaretRight, Stop, ArrowUp } from '@phosphor-icons/react';
6
6
  import { F as FormattedMessage } from './FormattedMessage.CRbM-hF6.js';
7
7
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from '../components/ui/dropdown-menu.js';
8
8
  import { Popover, PopoverTrigger, PopoverContent } from '../components/ui/popover.js';
@@ -18,6 +18,9 @@ import { CodeNode, CodeHighlightNode, $isCodeNode } from '@lexical/code';
18
18
  import { L as Logo } from './logo.D5BMN6Db.js';
19
19
 
20
20
  const ConversationContext = createContext(null);
21
+ const defaultCallbacks = {
22
+ onCopy: (_messageId, content) => void navigator.clipboard.writeText(content)
23
+ };
21
24
  function useConversation() {
22
25
  const ctx = useContext(ConversationContext);
23
26
  if (!ctx) {
@@ -27,16 +30,37 @@ function useConversation() {
27
30
  }
28
31
  function ConversationProvider({ messages, callbacks, children }) {
29
32
  const isStreaming = messages.some((m) => m.status === "streaming");
30
- const value = useMemo(() => ({ messages, callbacks, isStreaming }), [messages, callbacks, isStreaming]);
33
+ const callbacksRef = useRef({ ...defaultCallbacks, ...callbacks });
34
+ useLayoutEffect(() => {
35
+ callbacksRef.current = { ...defaultCallbacks, ...callbacks };
36
+ });
37
+ const stableCallbacks = useMemo(
38
+ () => ({
39
+ onSend: (...args) => callbacksRef.current.onSend?.(...args),
40
+ onStop: (...args) => callbacksRef.current.onStop?.(...args),
41
+ onSuggestionClick: (...args) => callbacksRef.current.onSuggestionClick?.(...args),
42
+ onActionClick: (...args) => callbacksRef.current.onActionClick?.(...args),
43
+ onFeedback: (...args) => callbacksRef.current.onFeedback?.(...args),
44
+ onCitationClick: (...args) => callbacksRef.current.onCitationClick?.(...args),
45
+ onAttachmentAction: (...args) => callbacksRef.current.onAttachmentAction?.(...args),
46
+ onContextPillRemove: (...args) => callbacksRef.current.onContextPillRemove?.(...args),
47
+ onCopy: (...args) => callbacksRef.current.onCopy?.(...args)
48
+ }),
49
+ []
50
+ );
51
+ const value = useMemo(
52
+ () => ({ messages, callbacks: stableCallbacks, isStreaming }),
53
+ [messages, stableCallbacks, isStreaming]
54
+ );
31
55
  return /* @__PURE__ */ React__default.createElement(ConversationContext.Provider, { value }, children);
32
56
  }
33
57
 
34
- const TextBlock = ({ part, isStreaming }) => {
35
- return /* @__PURE__ */ React__default.createElement(FormattedMessage, { message: { content: part.text }, isStreaming });
58
+ const TextBlockComponent = ({ block, isStreaming }) => {
59
+ return /* @__PURE__ */ React__default.createElement(FormattedMessage, { message: { content: block.text }, isStreaming });
36
60
  };
37
61
 
38
- const CitationBlock = ({ part, onCitationClick }) => {
39
- return /* @__PURE__ */ React__default.createElement("span", { className: "inline-flex flex-wrap gap-1" }, part.citations.map((citation) => /* @__PURE__ */ React__default.createElement(CitationBadge, { key: citation.id, citation, onClick: onCitationClick })));
62
+ const CitationBlock = ({ citations, onCitationClick }) => {
63
+ return /* @__PURE__ */ React__default.createElement("span", { className: "inline-flex flex-wrap gap-1" }, citations.map((citation) => /* @__PURE__ */ React__default.createElement(CitationBadge, { key: citation.id, citation, onClick: onCitationClick })));
40
64
  };
41
65
  const CitationBadge = ({ citation, onClick }) => {
42
66
  return /* @__PURE__ */ React__default.createElement(Tooltip, null, /* @__PURE__ */ React__default.createElement(TooltipTrigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(
@@ -50,39 +74,24 @@ const CitationBadge = ({ citation, onClick }) => {
50
74
  )), /* @__PURE__ */ React__default.createElement(TooltipContent, { side: "top", className: "max-w-xs" }, /* @__PURE__ */ React__default.createElement("p", { className: "text-sm font-medium" }, citation.title), /* @__PURE__ */ React__default.createElement("p", { className: "text-xs text-muted-foreground truncate" }, citation.url)));
51
75
  };
52
76
 
53
- const ImageBlock = ({ part }) => {
54
- return /* @__PURE__ */ React__default.createElement("div", { className: "my-2" }, /* @__PURE__ */ React__default.createElement("img", { src: part.url, alt: part.alt ?? "", className: "max-w-full rounded-lg", loading: "lazy" }));
55
- };
56
-
57
- const ToolCallBlock = ({ part }) => {
58
- return /* @__PURE__ */ React__default.createElement("div", { className: "my-2 rounded-lg border border-border bg-muted/50 p-3 text-sm" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React__default.createElement("span", { className: "font-medium" }, part.toolName), /* @__PURE__ */ React__default.createElement("span", { className: "text-xs text-muted-foreground capitalize" }, "(", part.status, ")")), part.result && /* @__PURE__ */ React__default.createElement("pre", { className: "mt-2 whitespace-pre-wrap text-xs text-muted-foreground" }, part.result));
59
- };
60
-
61
- const ThinkingBlock = ({ part }) => {
62
- return /* @__PURE__ */ React__default.createElement("div", { className: "my-2 rounded-lg border border-dashed border-border bg-muted/30 p-3 text-sm italic text-muted-foreground" }, part.text);
63
- };
64
-
65
- const BlockRenderer = ({ part, isStreaming, onCitationClick }) => {
66
- switch (part.type) {
67
- case "text":
68
- return /* @__PURE__ */ React__default.createElement(TextBlock, { part, isStreaming });
69
- case "citation":
70
- return /* @__PURE__ */ React__default.createElement(CitationBlock, { part, onCitationClick });
71
- case "image":
72
- return /* @__PURE__ */ React__default.createElement(ImageBlock, { part });
73
- case "tool-call":
74
- return /* @__PURE__ */ React__default.createElement(ToolCallBlock, { part });
75
- case "thinking":
76
- return /* @__PURE__ */ React__default.createElement(ThinkingBlock, { part });
77
- case "action-buttons":
78
- return null;
79
- default:
80
- return null;
77
+ const BlockRenderer = ({ block, isStreaming, onCitationClick }) => {
78
+ if (block.type === "text") {
79
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(TextBlockComponent, { block, isStreaming }), block.citations && block.citations.length > 0 && /* @__PURE__ */ React__default.createElement(CitationBlock, { citations: block.citations, onCitationClick }));
81
80
  }
81
+ return null;
82
82
  };
83
83
 
84
- const ConversationLoading = ({ className }) => {
85
- return /* @__PURE__ */ React__default.createElement("div", { className: cn("flex gap-3 py-4", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "flex-shrink-0 mt-1" }, /* @__PURE__ */ React__default.createElement(Sparkle, { className: "h-5 w-5 text-primary animate-pulse", weight: "fill" })), /* @__PURE__ */ React__default.createElement("div", { className: "flex-1 space-y-3" }, /* @__PURE__ */ React__default.createElement("div", { className: "h-3 w-3/4 animate-pulse rounded bg-muted" }), /* @__PURE__ */ React__default.createElement("div", { className: "h-3 w-full animate-pulse rounded bg-muted" }), /* @__PURE__ */ React__default.createElement("div", { className: "h-3 w-5/6 animate-pulse rounded bg-muted" }), /* @__PURE__ */ React__default.createElement("div", { className: "h-3 w-2/3 animate-pulse rounded bg-muted" })));
84
+ const ConversationStatus = ({ className }) => {
85
+ return /* @__PURE__ */ React__default.createElement(
86
+ "span",
87
+ {
88
+ className: cn(
89
+ "text-sm font-medium animate-sweep bg-sweep-yellowfin bg-clip-text text-transparent [background-size:200%_auto]",
90
+ className
91
+ )
92
+ },
93
+ "Working..."
94
+ );
86
95
  };
87
96
 
88
97
  const ConversationMessageAssistant = ({ message, className }) => {
@@ -90,16 +99,23 @@ const ConversationMessageAssistant = ({ message, className }) => {
90
99
  const isStreaming = message.status === "streaming";
91
100
  const isPending = message.status === "pending";
92
101
  const isCompleted = message.status === "completed";
93
- const actionButtonsParts = message.parts.filter((p) => p.type === "action-buttons");
94
- const contentParts = message.parts.filter((p) => p.type !== "action-buttons");
95
102
  const handleCopy = useCallback(() => {
96
- const textContent = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
103
+ const textContent = message.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
97
104
  callbacks.onCopy?.(message.id, textContent);
98
105
  }, [message, callbacks]);
99
- if (isPending && contentParts.length === 0) {
100
- return /* @__PURE__ */ React__default.createElement(ConversationLoading, { className });
106
+ const hasContent = message.content.some((block) => block.text.length > 0);
107
+ if (isPending && message.content.length === 0 || isStreaming && !hasContent) {
108
+ return /* @__PURE__ */ React__default.createElement(ConversationStatus, { className });
101
109
  }
102
- return /* @__PURE__ */ React__default.createElement("div", { className: cn("flex gap-3 p-4 bg-surface rounded", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React__default.createElement("div", { className: "space-y-1" }, contentParts.map((part, i) => /* @__PURE__ */ React__default.createElement(BlockRenderer, { key: i, part, isStreaming, onCitationClick: callbacks.onCitationClick }))), isCompleted && /* @__PURE__ */ React__default.createElement("div", { className: "mt-3 flex items-center gap-1" }, /* @__PURE__ */ React__default.createElement(
110
+ return /* @__PURE__ */ React__default.createElement("div", { className: cn("flex gap-3 p-4 bg-surface rounded", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React__default.createElement("div", { className: "space-y-1" }, message.content.map((block, i) => /* @__PURE__ */ React__default.createElement(
111
+ BlockRenderer,
112
+ {
113
+ key: i,
114
+ block,
115
+ isStreaming,
116
+ onCitationClick: callbacks.onCitationClick
117
+ }
118
+ ))), isCompleted && /* @__PURE__ */ React__default.createElement("div", { className: "mt-3 flex items-center gap-1" }, /* @__PURE__ */ React__default.createElement(
103
119
  ActionBarButton,
104
120
  {
105
121
  tooltip: "Helpful",
@@ -115,18 +131,7 @@ const ConversationMessageAssistant = ({ message, className }) => {
115
131
  onClick: () => callbacks.onFeedback?.(message.id, "negative")
116
132
  },
117
133
  /* @__PURE__ */ React__default.createElement(ThumbsDown, { className: "h-4 w-4", weight: message.feedback === "negative" ? "fill" : "regular" })
118
- ), /* @__PURE__ */ React__default.createElement(ActionBarButton, { tooltip: "Copy", onClick: handleCopy }, /* @__PURE__ */ React__default.createElement(Copy, { className: "h-4 w-4" })), /* @__PURE__ */ React__default.createElement(ActionBarButton, { tooltip: "More" }, /* @__PURE__ */ React__default.createElement(DotsThreeVertical, { className: "h-4 w-4" })), actionButtonsParts.map(
119
- (part) => part.type === "action-buttons" ? part.actions.map((action) => /* @__PURE__ */ React__default.createElement(
120
- "button",
121
- {
122
- key: action.id,
123
- type: "button",
124
- className: "ml-2 inline-flex items-center gap-1.5 rounded-full border border-border px-3 py-1 text-xs text-muted-foreground hover:bg-muted transition-colors",
125
- onClick: () => callbacks.onActionClick?.(action, message.id)
126
- },
127
- action.label
128
- )) : null
129
- ))));
134
+ ), /* @__PURE__ */ React__default.createElement(ActionBarButton, { tooltip: "Copy", onClick: handleCopy }, /* @__PURE__ */ React__default.createElement(Copy, { className: "h-4 w-4" })), /* @__PURE__ */ React__default.createElement(ActionBarButton, { tooltip: "More" }, /* @__PURE__ */ React__default.createElement(DotsThreeVertical, { className: "h-4 w-4" })))));
130
135
  };
131
136
  const ActionBarButton = ({ children, tooltip, active, onClick }) => {
132
137
  return /* @__PURE__ */ React__default.createElement(Tooltip, null, /* @__PURE__ */ React__default.createElement(TooltipTrigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(
@@ -144,13 +149,13 @@ const ActionBarButton = ({ children, tooltip, active, onClick }) => {
144
149
  };
145
150
 
146
151
  const ConversationMessageUser = ({ message, className }) => {
147
- const textContent = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
148
- return /* @__PURE__ */ React__default.createElement("div", { className: cn("flex justify-end py-4", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "max-w-[85%] rounded bg-surface px-4 py-2.5 text-primary-foreground" }, /* @__PURE__ */ React__default.createElement("p", { className: "whitespace-pre-wrap" }, textContent)));
152
+ const paragraphs = message.content.filter((b) => b.type === "text").map((b) => b.text).join("\n").split(/\n\n+/);
153
+ return /* @__PURE__ */ React__default.createElement("div", { className: cn("flex justify-end py-4", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "max-w-[85%] rounded bg-surface px-4 py-2.5 text-foreground" }, paragraphs.map((paragraph, i) => /* @__PURE__ */ React__default.createElement("p", { key: i, className: "m-0 mb-4 whitespace-pre-wrap last:mb-0" }, paragraph))));
149
154
  };
150
155
 
151
156
  const ConversationMessage = ({ message, className }) => {
152
157
  switch (message.role) {
153
- case "assistant":
158
+ case "ai":
154
159
  return /* @__PURE__ */ React__default.createElement(ConversationMessageAssistant, { message, className });
155
160
  case "user":
156
161
  return /* @__PURE__ */ React__default.createElement(ConversationMessageUser, { message, className });
@@ -182,7 +187,7 @@ const ConversationMessages = ({ className, bottomPadding = 0, onContentBehind })
182
187
  observer.observe(sentinel);
183
188
  return () => observer.disconnect();
184
189
  }, [onContentBehind, bottomPadding]);
185
- return /* @__PURE__ */ React__default.createElement(ScrollArea, { ref: viewportRef, className: cn("h-full", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "px-4" }, messages.map((message) => /* @__PURE__ */ React__default.createElement(ConversationMessage, { key: message.id, message })), /* @__PURE__ */ React__default.createElement("div", { ref: sentinelRef, className: "h-px" })), /* @__PURE__ */ React__default.createElement("div", { style: { height: bottomPadding + 8 } }));
190
+ return /* @__PURE__ */ React__default.createElement(ScrollArea, { ref: viewportRef, className: cn("h-full", className) }, /* @__PURE__ */ React__default.createElement("div", { className: "px-6" }, messages.map((message) => /* @__PURE__ */ React__default.createElement(ConversationMessage, { key: message.id, message })), /* @__PURE__ */ React__default.createElement("div", { ref: sentinelRef, className: "h-px" })), /* @__PURE__ */ React__default.createElement("div", { style: { height: bottomPadding + 8 } }));
186
191
  };
187
192
 
188
193
  /**
@@ -540,7 +545,7 @@ function LexicalEditor({
540
545
  x$1,
541
546
  {
542
547
  autoFocus,
543
- className: "outline-none w-full max-h-[7.5rem] overflow-y-auto [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5"
548
+ className: "outline-none w-full max-h-[7.5rem] overflow-y-auto [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_p]:my-0 [&_ul]:my-0 [&_ol]:my-0 [&_pre]:my-0 [&_blockquote]:my-0"
544
549
  }
545
550
  ),
546
551
  placeholder: placeholder ? /* @__PURE__ */ React__default.createElement("div", { className: "pointer-events-none absolute top-0 left-0 select-none text-muted-foreground" }, placeholder) : null,
@@ -633,7 +638,7 @@ const ConversationInput = ({
633
638
  },
634
639
  [handleAttachmentAction]
635
640
  );
636
- return /* @__PURE__ */ React__default.createElement("div", { className: cn("px-2", className) }, /* @__PURE__ */ React__default.createElement(
641
+ return /* @__PURE__ */ React__default.createElement("div", { className: cn("", className) }, /* @__PURE__ */ React__default.createElement(
637
642
  "div",
638
643
  {
639
644
  className: cn(
@@ -796,7 +801,7 @@ const ConversationRoot = ({
796
801
  observer.observe(el);
797
802
  return () => observer.disconnect();
798
803
  }, []);
799
- return /* @__PURE__ */ React__default.createElement(TooltipProvider, null, /* @__PURE__ */ React__default.createElement(ConversationProvider, { messages, callbacks }, /* @__PURE__ */ React__default.createElement("div", { className: cn("flex h-full flex-col", className) }, isCompound ? children : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", { className: "relative min-h-0 flex-1 grid grid-rows-[1fr] grid-cols-[1fr] pb-2" }, /* @__PURE__ */ React__default.createElement("div", { className: "row-start-1 col-start-1 min-h-0" }, /* @__PURE__ */ React__default.createElement(ConversationMessages, { bottomPadding: bottomHeight, onContentBehind: setContentBehind })), /* @__PURE__ */ React__default.createElement("div", { ref: bottomRef, className: "row-start-1 col-start-1 self-end z-10 pointer-events-none" }, /* @__PURE__ */ React__default.createElement("div", { className: "pointer-events-auto" }, suggestions.length > 0 && /* @__PURE__ */ React__default.createElement(ConversationSuggestions, { items: suggestions }), actions.length > 0 && /* @__PURE__ */ React__default.createElement(
804
+ return /* @__PURE__ */ React__default.createElement(TooltipProvider, null, /* @__PURE__ */ React__default.createElement(ConversationProvider, { messages, callbacks }, /* @__PURE__ */ React__default.createElement("div", { className: cn("flex h-full flex-col pb-4", className) }, isCompound ? children : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", { className: "relative min-h-0 flex-1 grid grid-rows-[1fr] grid-cols-[1fr]" }, /* @__PURE__ */ React__default.createElement("div", { className: "row-start-1 col-start-1 min-h-0" }, /* @__PURE__ */ React__default.createElement(ConversationMessages, { bottomPadding: bottomHeight, onContentBehind: setContentBehind })), /* @__PURE__ */ React__default.createElement("div", { ref: bottomRef, className: "row-start-1 col-start-1 self-end z-10 pointer-events-none px-4" }, /* @__PURE__ */ React__default.createElement("div", { className: "pointer-events-auto" }, suggestions.length > 0 && /* @__PURE__ */ React__default.createElement(ConversationSuggestions, { items: suggestions }), actions.length > 0 && /* @__PURE__ */ React__default.createElement(
800
805
  ConversationActions,
801
806
  {
802
807
  actions,
@@ -811,7 +816,7 @@ const ConversationRoot = ({
811
816
  contextPills,
812
817
  isElevated: hasContentBehind
813
818
  }
814
- )))), showBranding && /* @__PURE__ */ React__default.createElement("div", { className: "relative z-10 pb-2" }, /* @__PURE__ */ React__default.createElement(Branding, null))))));
819
+ )))), showBranding && /* @__PURE__ */ React__default.createElement("div", { className: "relative z-10 pt-2" }, /* @__PURE__ */ React__default.createElement(Branding, null))))));
815
820
  };
816
821
  const Branding = () => /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-center gap-1 text-[0.625rem] text-subtle" }, /* @__PURE__ */ React__default.createElement("span", null, "Powered by"), /* @__PURE__ */ React__default.createElement(Logo, { variant: "full", color: "outline", className: "h-2.5 w-auto" }));
817
822
  const Conversation = Object.assign(ConversationRoot, {
@@ -819,8 +824,8 @@ const Conversation = Object.assign(ConversationRoot, {
819
824
  Input: ConversationInput,
820
825
  Suggestions: ConversationSuggestions,
821
826
  Actions: ConversationActions,
822
- Loading: ConversationLoading
827
+ Loading: ConversationStatus
823
828
  });
824
829
 
825
- export { ContextPillBadge as C, Conversation as a, ConversationActions as b, ConversationInput as c, ConversationLoading as d, ConversationMessageAssistant as e, ConversationMessageUser as f, ConversationMessages as g, ConversationProvider as h, ConversationSuggestions as i, useConversation as u };
826
- //# sourceMappingURL=Conversation.BEDav4lr.js.map
830
+ export { ContextPillBadge as C, Conversation as a, ConversationActions as b, ConversationInput as c, ConversationMessageAssistant as d, ConversationMessageUser as e, ConversationMessages as f, ConversationProvider as g, ConversationStatus as h, ConversationSuggestions as i, useConversation as u };
831
+ //# sourceMappingURL=Conversation.BriXFYqU.js.map