@brainfish-ai/components 0.16.10 → 0.17.0

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/convos.d.ts CHANGED
@@ -6,6 +6,17 @@ import { VariantProps } from 'class-variance-authority';
6
6
 
7
7
  declare function AccordionItem({ className, ...props }: React_2.ComponentProps<typeof AccordionPrimitive.Item>): React_2.JSX.Element;
8
8
 
9
+ declare type AnswerDiagnostics = {
10
+ confidence_score: string;
11
+ answer_generation_type?: AnswerGenerationType;
12
+ query_context_relevance?: QueryContextRelevance;
13
+ context_sufficiency?: ContextSufficiency;
14
+ confidence_justification?: string;
15
+ evidence_from_context?: EvidenceFromContext[];
16
+ };
17
+
18
+ declare type AnswerGenerationType = 'Direct Extraction' | 'Summarization' | 'Inference' | 'Combination' | 'Cannot Answer';
19
+
9
20
  declare function Badge({ className, variant, ...props }: BadgeProps): React_2.JSX.Element;
10
21
 
11
22
  declare interface BadgeProps extends React_2.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
@@ -15,6 +26,8 @@ declare const badgeVariants: (props?: ({
15
26
  variant?: "default" | "destructive" | "success" | "warning" | null | undefined;
16
27
  } & ClassProp) | undefined) => string;
17
28
 
29
+ declare type ContextSufficiency = 'Sufficient' | 'Partially Sufficient' | 'Insufficient';
30
+
18
31
  /**
19
32
  * Convo component - A single conversation item in an accordion.
20
33
  * Displays conversation metadata and messages, with support for lazy loading.
@@ -22,7 +35,7 @@ declare const badgeVariants: (props?: ({
22
35
  * This is a presentational component - it receives messages and loading state as props
23
36
  * rather than managing fetching logic itself.
24
37
  */
25
- export declare function Convo({ locale, value: queryId, question, timestamp, location, source, status, statusMessage, hasAttributes, attributes, messages, isLoading, onClick, className, ...props }: ConvoProps): default_2.JSX.Element;
38
+ export declare function Convo({ locale: localeOverride, value: queryId, question, timestamp, location, source, status, statusMessage, hasAttributes, onClick, className, ...props }: ConvoProps): default_2.JSX.Element;
26
39
 
27
40
  declare interface ConvoInput {
28
41
  value: string;
@@ -33,7 +46,7 @@ declare interface ConvoInput {
33
46
  status?: default_2.ComponentProps<typeof StatusBadge>['variant'];
34
47
  statusMessage?: string;
35
48
  hasAttributes?: boolean;
36
- attributes?: Record<string, any>;
49
+ attributes?: Record<string, any>[];
37
50
  locale?: string;
38
51
  }
39
52
 
@@ -46,11 +59,47 @@ export declare type ConvoProps = default_2.ComponentPropsWithRef<typeof Accordio
46
59
  * @param convos - Array of conversation data
47
60
  * @param fetchMessages - Async function to fetch messages for a conversation
48
61
  */
49
- export declare function Convos({ convos, fetchMessages }: ConvosProps): default_2.JSX.Element;
62
+ export declare function Convos({ convos, fetchMessages, fetchAttributes, fetchAnswerDiagnostics, onDiscard, onApprove, }: ConvosProps): default_2.JSX.Element;
63
+
64
+ declare interface ConvosContextValue {
65
+ fetchMessages: (conversationId: string) => Promise<MessageProps[]>;
66
+ fetchAttributes: (conversationId: string) => Promise<Record<string, any>[]>;
67
+ getMessages: (conversationId: string) => MessageProps[];
68
+ isLoading: (conversationId: string) => boolean;
69
+ handleFetchMessages: (conversationId: string) => void;
70
+ locale?: string;
71
+ fetchAnswerDiagnostics: (messageId: string) => Promise<AnswerDiagnostics>;
72
+ onDiscard: (id: string, reason: DiscardReason, suggestedAnswer?: string) => Promise<void>;
73
+ onApprove: (id: string) => Promise<void>;
74
+ }
50
75
 
51
76
  declare interface ConvosProps {
52
77
  convos: ConvoInput[];
53
78
  fetchMessages: (conversationId: string) => Promise<MessageProps[]>;
79
+ fetchAttributes: (conversationId: string) => Promise<Record<string, any>[]>;
80
+ fetchAnswerDiagnostics: (messageId: string) => Promise<AnswerDiagnostics>;
81
+ onDiscard: (id: string, reason: DiscardReason, suggestedAnswer?: string) => Promise<void>;
82
+ onApprove: (id: string) => Promise<void>;
83
+ }
84
+
85
+ export declare function ConvosProvider({ children, value }: ConvosProviderProps): default_2.JSX.Element;
86
+
87
+ declare interface ConvosProviderProps {
88
+ children: default_2.ReactNode;
89
+ value: ConvosContextValue;
90
+ }
91
+
92
+ declare enum DiscardReason {
93
+ INCORRECT_ANSWER = "incorrect_answer",
94
+ HALLUCINATED_ANSWER = "hallucinated_answer",
95
+ OVERSHARING = "oversharing",
96
+ WRONG_ARTICLE_SURFACED = "wrong_article_surfaced",
97
+ OTHER = "other"
98
+ }
99
+
100
+ declare interface EvidenceFromContext {
101
+ supporting_quote?: string;
102
+ url?: string;
54
103
  }
55
104
 
56
105
  export declare enum Feedback {
@@ -58,7 +107,7 @@ export declare enum Feedback {
58
107
  Negative = "negative"
59
108
  }
60
109
 
61
- export declare function Message({ type, role, content, relatedArticles, feedback, timestamp, locale }: MessageProps): default_2.JSX.Element;
110
+ export declare function Message({ id, type, role, content, relatedArticles, feedback, timestamp, locale: localeOverride, discarded, discardedMeta, }: MessageProps): default_2.JSX.Element;
62
111
 
63
112
  export declare interface MessageProps {
64
113
  id: string;
@@ -69,6 +118,11 @@ export declare interface MessageProps {
69
118
  feedback?: Feedback;
70
119
  type?: MessageType;
71
120
  locale?: string;
121
+ discarded?: boolean;
122
+ discardedMeta?: {
123
+ reason: DiscardReason;
124
+ suggestedAnswer?: string;
125
+ };
72
126
  }
73
127
 
74
128
  export declare enum MessageType {
@@ -89,12 +143,11 @@ declare interface Props_2 {
89
143
  status?: default_2.ComponentProps<typeof StatusBadge>['variant'];
90
144
  statusMessage?: string;
91
145
  hasAttributes?: boolean;
92
- attributes?: Record<string, any>;
93
- messages?: MessageProps[];
94
- isLoading?: boolean;
95
146
  onClick?: () => void;
96
147
  }
97
148
 
149
+ declare type QueryContextRelevance = 'High' | 'Medium' | 'Low';
150
+
98
151
  export declare interface RelatedArticle {
99
152
  id: string;
100
153
  title: string;
@@ -104,4 +157,6 @@ export declare interface RelatedArticle {
104
157
 
105
158
  export declare function StatusBadge({ ...props }: Props): default_2.JSX.Element;
106
159
 
160
+ export declare function useConvosContext(): ConvosContextValue;
161
+
107
162
  export { }
@@ -1,16 +1,156 @@
1
- import React__default from 'react';
2
- import { Circle, ArrowSquareOut, UserCircle, UserFocus } from '@phosphor-icons/react';
1
+ import React__default, { useState, useEffect } from 'react';
2
+ import { Circle, ArrowSquareOut, ThumbsDown, ThumbsUp, UserCircle, UserFocus } from '@phosphor-icons/react';
3
3
  import { Badge } from './ui/badge.js';
4
4
  import { F as FormattedMessage } from '../chunks/FormattedMessage.BcAkzCZt.js';
5
+ import { useForm, Controller } from 'react-hook-form';
6
+ import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from './ui/dialog.js';
7
+ import { Button } from './ui/button.js';
8
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from './ui/select.js';
9
+ import { Label } from './ui/label.js';
10
+ import { ButtonGroup } from '../componentns/ui/button-group.js';
11
+ import { Textarea } from './ui/textarea.js';
12
+ import { ScrollArea } from './ui/scroll-area.js';
13
+ import { Spinner } from './ui/spinner.js';
14
+ import { c as cn } from '../chunks/utils.Cwtlq8dh.js';
5
15
  import { G as GeneratingStar } from '../chunks/generating-star.COkD0gHd.js';
6
16
  import { Item, ItemHeader, ItemTitle, ItemContent, ItemDescription } from './ui/item.js';
7
17
  import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './ui/accordion.js';
8
- import { c as cn } from '../chunks/utils.Cwtlq8dh.js';
18
+ import { Table, TableBody, TableRow, TableCell } from './ui/table.js';
9
19
 
10
20
  import '../convos.css';function StatusBadge({ ...props }) {
11
21
  return /* @__PURE__ */ React__default.createElement(Badge, { ...props }, /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React__default.createElement(Circle, { weight: "fill", size: 8, className: "size-2" }), props.children));
12
22
  }
13
23
 
24
+ function AnswerDiagnosticsSection({ messageId }) {
25
+ const { fetchAnswerDiagnostics } = useConvosContext();
26
+ const [answerDiagnostics, setAnswerDiagnostics] = useState(null);
27
+ const [isLoading, setIsLoading] = useState(true);
28
+ useEffect(() => {
29
+ void fetchAnswerDiagnostics(messageId).then((diagnostics) => setAnswerDiagnostics(diagnostics)).finally(() => setIsLoading(false));
30
+ }, [fetchAnswerDiagnostics, messageId]);
31
+ return /* @__PURE__ */ React__default.createElement(
32
+ ScrollArea,
33
+ {
34
+ type: "always",
35
+ className: "h-[440px] border border-dark-300 rounded-lg",
36
+ "data-name": "discard-dialog-answer-diagnostic"
37
+ },
38
+ /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-4 p-4 flex-shrink-0 bg-surface" }, /* @__PURE__ */ React__default.createElement("h3", { className: "text-lg font-medium my-0" }, "Answer diagnostic"), isLoading ? /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React__default.createElement(Spinner, null)) : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, answerDiagnostics && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("dl", { className: "grid grid-cols-[1fr_2fr] grid-rows-2 gap-5" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-1" }, /* @__PURE__ */ React__default.createElement("dt", { className: "font-medium" }, "Confidence"), /* @__PURE__ */ React__default.createElement("dd", { className: "text-xs" }, answerDiagnostics.confidence_score)), answerDiagnostics.answer_generation_type && /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-1" }, /* @__PURE__ */ React__default.createElement("dt", { className: "font-medium" }, "Answer Type"), /* @__PURE__ */ React__default.createElement("dd", { className: "text-xs" }, answerDiagnostics.answer_generation_type)), answerDiagnostics.query_context_relevance && /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-1" }, /* @__PURE__ */ React__default.createElement("dt", { className: "font-medium" }, "Relevance"), /* @__PURE__ */ React__default.createElement("dd", { className: "text-xs" }, answerDiagnostics.query_context_relevance)), answerDiagnostics.context_sufficiency && /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-1" }, /* @__PURE__ */ React__default.createElement("dt", { className: "font-medium" }, "Context"), /* @__PURE__ */ React__default.createElement("dd", { className: "text-xs" }, answerDiagnostics.context_sufficiency))), answerDiagnostics.confidence_justification && /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-2", "data-name": "diagnostic-justification" }, /* @__PURE__ */ React__default.createElement("span", { className: "font-medium" }, "Justification"), /* @__PURE__ */ React__default.createElement("p", { className: "text-sm my-0" }, answerDiagnostics.confidence_justification)), answerDiagnostics.evidence_from_context && answerDiagnostics.evidence_from_context.length > 0 && /* @__PURE__ */ React__default.createElement("div", { className: "flex flex-col gap-2", "data-name": "diagnostic-evidence" }, /* @__PURE__ */ React__default.createElement("span", { className: "font-medium" }, "Evidence (from Knowledge Base)"), answerDiagnostics.evidence_from_context.map((evidence, index) => /* @__PURE__ */ React__default.createElement("div", { key: index, className: "flex flex-col gap-1" }, evidence.supporting_quote && /* @__PURE__ */ React__default.createElement("p", { className: "text-sm my-0" }, evidence.supporting_quote), evidence.url && /* @__PURE__ */ React__default.createElement(
39
+ "a",
40
+ {
41
+ href: evidence.url,
42
+ target: "_blank",
43
+ rel: "noopener noreferrer",
44
+ className: "flex gap-1 text-sm self-end items-center"
45
+ },
46
+ /* @__PURE__ */ React__default.createElement("span", null, "Source"),
47
+ /* @__PURE__ */ React__default.createElement(ArrowSquareOut, { size: 16 })
48
+ )))))))
49
+ );
50
+ }
51
+
52
+ function DiscardDialog({
53
+ id,
54
+ discarded,
55
+ discardedMeta,
56
+ className,
57
+ open: externalOpen,
58
+ onOpenChange: externalOnOpenChange,
59
+ ...args
60
+ }) {
61
+ const { onDiscard, onApprove } = useConvosContext();
62
+ const [isSelectOpen, setIsSelectOpen] = useState(false);
63
+ const [internalOpen, setInternalOpen] = useState(false);
64
+ const isOpen = externalOpen ?? internalOpen;
65
+ const setIsOpen = externalOnOpenChange ?? setInternalOpen;
66
+ const {
67
+ control,
68
+ register,
69
+ handleSubmit,
70
+ reset,
71
+ formState: { isSubmitting }
72
+ } = useForm({
73
+ defaultValues: {
74
+ reason: discardedMeta?.reason,
75
+ preferredAnswer: discardedMeta?.suggestedAnswer || ""
76
+ }
77
+ });
78
+ useEffect(() => {
79
+ reset({
80
+ reason: discardedMeta?.reason,
81
+ preferredAnswer: discardedMeta?.suggestedAnswer || ""
82
+ });
83
+ }, [discardedMeta, reset]);
84
+ const onSubmitForm = async (data) => {
85
+ await onDiscard(id, data.reason, data.preferredAnswer).then(() => setIsOpen(false));
86
+ };
87
+ return /* @__PURE__ */ React__default.createElement(ButtonGroup, { className }, /* @__PURE__ */ React__default.createElement(Dialog, { ...args, open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React__default.createElement(DialogTrigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(
88
+ Button,
89
+ {
90
+ variant: "shadow",
91
+ className: cn(
92
+ "size-7",
93
+ discarded ? "bg-destructive text-destructive !opacity-100" : "bg-surface text-dark-800"
94
+ ),
95
+ size: "icon"
96
+ },
97
+ /* @__PURE__ */ React__default.createElement(ThumbsDown, { weight: discarded ? "fill" : "regular" })
98
+ )), /* @__PURE__ */ React__default.createElement(
99
+ DialogContent,
100
+ {
101
+ className: "min-w-[826px] bg-dark-100 pt-0 pb-4",
102
+ onEscapeKeyDown: (e) => {
103
+ if (isSelectOpen) {
104
+ e.preventDefault();
105
+ setIsSelectOpen(false);
106
+ }
107
+ }
108
+ },
109
+ /* @__PURE__ */ React__default.createElement(DialogHeader, { className: "py-6" }, /* @__PURE__ */ React__default.createElement(DialogTitle, { className: "heading-xxl !text-2xl" }, "Answer feedback")),
110
+ /* @__PURE__ */ React__default.createElement("form", { onSubmit: handleSubmit(onSubmitForm) }, /* @__PURE__ */ React__default.createElement("div", { className: "grid grid-cols-[1fr_2fr] gap-6 pb-4" }, /* @__PURE__ */ React__default.createElement("div", { className: "w-[340px] p-4", "data-name": "discard-dialog-reason" }, /* @__PURE__ */ React__default.createElement("fieldset", { className: "space-y-4" }, /* @__PURE__ */ React__default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React__default.createElement(Label, { htmlFor: "reason", className: "text-sm" }, "Reason"), /* @__PURE__ */ React__default.createElement(
111
+ Controller,
112
+ {
113
+ control,
114
+ name: "reason",
115
+ rules: { required: true },
116
+ render: ({ field }) => /* @__PURE__ */ React__default.createElement(
117
+ Select,
118
+ {
119
+ value: field.value,
120
+ onValueChange: field.onChange,
121
+ open: isSelectOpen,
122
+ onOpenChange: setIsSelectOpen,
123
+ required: true
124
+ },
125
+ /* @__PURE__ */ React__default.createElement(SelectTrigger, { className: "border-dark-300 bg-surface" }, /* @__PURE__ */ React__default.createElement(SelectValue, { placeholder: "Select a reason" })),
126
+ /* @__PURE__ */ React__default.createElement(SelectContent, null, /* @__PURE__ */ React__default.createElement(SelectItem, { value: "incorrect_answer" /* INCORRECT_ANSWER */ }, "Incorrect answer"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "hallucinated_answer" /* HALLUCINATED_ANSWER */ }, "Hallucinated answer"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "oversharing" /* OVERSHARING */ }, "Oversharing"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "wrong_article_surfaced" /* WRONG_ARTICLE_SURFACED */ }, "Wrong article surfaced"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "other" /* OTHER */ }, "Other"))
127
+ )
128
+ }
129
+ )), /* @__PURE__ */ React__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React__default.createElement(Label, { htmlFor: "preferredAnswer", className: "text-base font-normal" }, "Preferred answer"), /* @__PURE__ */ React__default.createElement(
130
+ Textarea,
131
+ {
132
+ id: "preferredAnswer",
133
+ required: true,
134
+ minLength: 10,
135
+ placeholder: "Explain what you'd like Brainfish to do next time in this case....",
136
+ rows: 4,
137
+ className: "h-52 resize-none bg-surface",
138
+ ...register("preferredAnswer")
139
+ }
140
+ ), /* @__PURE__ */ React__default.createElement("span", { className: "text-xs text-dark-1000 hidden ml-1" }, 'Tell Brainfish why this is the incorrect answer. You can use "@" to reference the right Knowledge Article.')))), /* @__PURE__ */ React__default.createElement(AnswerDiagnosticsSection, { messageId: id })), /* @__PURE__ */ React__default.createElement(DialogFooter, { className: "py-6" }, /* @__PURE__ */ React__default.createElement(DialogClose, { asChild: true }, /* @__PURE__ */ React__default.createElement(Button, { variant: "link", size: "sm", type: "button" }, "Cancel")), /* @__PURE__ */ React__default.createElement(Button, { type: "submit", variant: "shadow", size: "sm", disabled: isSubmitting }, isSubmitting ? "Submitting..." : "Submit")))
141
+ )), /* @__PURE__ */ React__default.createElement(
142
+ Button,
143
+ {
144
+ variant: "shadow",
145
+ className: cn("bg-surface size-7 text-dark-800", { "!opacity-100 bg-dark-300": discarded }),
146
+ size: "icon",
147
+ disabled: discarded,
148
+ onClick: () => onApprove(id)
149
+ },
150
+ /* @__PURE__ */ React__default.createElement(ThumbsUp, null)
151
+ ));
152
+ }
153
+
14
154
  function formatDate(date, locale) {
15
155
  const defaultLocale = "en-US";
16
156
  if (!locale) {
@@ -42,7 +182,20 @@ var Feedback = /* @__PURE__ */ ((Feedback2) => {
42
182
  Feedback2["Negative"] = "negative";
43
183
  return Feedback2;
44
184
  })(Feedback || {});
45
- function Message({ type, role, content, relatedArticles, feedback, timestamp, locale }) {
185
+ function Message({
186
+ id,
187
+ type,
188
+ role,
189
+ content,
190
+ relatedArticles,
191
+ feedback,
192
+ timestamp,
193
+ locale: localeOverride,
194
+ discarded,
195
+ discardedMeta
196
+ }) {
197
+ const { locale: contextLocale } = useConvosContext();
198
+ const locale = localeOverride || contextLocale;
46
199
  const formattedDate = formatDate(timestamp, locale);
47
200
  if (role === "ai") {
48
201
  return /* @__PURE__ */ React__default.createElement("li", { className: "relative before-line after-line" }, /* @__PURE__ */ React__default.createElement(Item, { className: "message-item" }, /* @__PURE__ */ React__default.createElement(ItemHeader, { className: "justify-start gap-0" }, /* @__PURE__ */ React__default.createElement(GeneratingStar, { variant: "gradient", className: "relative -left-[6px]" }), /* @__PURE__ */ React__default.createElement(ItemTitle, { className: "text-xs font-bold" }, "Generated answer")), /* @__PURE__ */ React__default.createElement(
@@ -53,6 +206,15 @@ function Message({ type, role, content, relatedArticles, feedback, timestamp, lo
53
206
  "pt-6 pb-0": relatedArticles?.length
54
207
  })
55
208
  },
209
+ /* @__PURE__ */ React__default.createElement(
210
+ DiscardDialog,
211
+ {
212
+ id,
213
+ discarded,
214
+ discardedMeta,
215
+ className: "absolute -top-4 right-5"
216
+ }
217
+ ),
56
218
  /* @__PURE__ */ React__default.createElement("div", { className: "message-content-wrapper" }, /* @__PURE__ */ React__default.createElement(FormattedMessage, { message: { content } }), /* @__PURE__ */ React__default.createElement("span", { className: "message-timestamp float-right mt-2" }, formattedDate)),
57
219
  !!relatedArticles?.length && /* @__PURE__ */ React__default.createElement(Accordion, { type: "single", collapsible: true, className: "p-0 custom-dashed-border-t-dark-300" }, /* @__PURE__ */ React__default.createElement(AccordionItem, { value: "related-articles" }, /* @__PURE__ */ React__default.createElement(AccordionTrigger, { className: "px-6" }, "Related articles"), /* @__PURE__ */ React__default.createElement(AccordionContent, null, /* @__PURE__ */ React__default.createElement("ul", { className: "message-article-list" }, relatedArticles.map((article) => /* @__PURE__ */ React__default.createElement("li", { key: article.id, className: "message-meta" }, /* @__PURE__ */ React__default.createElement(Badge, { variant: "warning" }, Math.round(article.score * 100), "%"), /* @__PURE__ */ React__default.createElement(
58
220
  "a",
@@ -70,8 +232,51 @@ function Message({ type, role, content, relatedArticles, feedback, timestamp, lo
70
232
  return /* @__PURE__ */ React__default.createElement("li", null, /* @__PURE__ */ React__default.createElement(Item, { className: "message-item" }, /* @__PURE__ */ React__default.createElement(ItemHeader, { className: "basis-0" }, /* @__PURE__ */ React__default.createElement(UserCircle, { weight: "fill", size: 24 })), /* @__PURE__ */ React__default.createElement(ItemContent, { className: "message-user-content" }, type === "question" /* QUESTION */ && /* @__PURE__ */ React__default.createElement("div", { className: "message-header" }, /* @__PURE__ */ React__default.createElement(FormattedMessage, { message: { content } }), /* @__PURE__ */ React__default.createElement("span", { className: "message-timestamp" }, formattedDate)), type === "feedback" /* FEEDBACK */ && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", { className: "message-header" }, /* @__PURE__ */ React__default.createElement("div", { className: "message-feedback-text" }, feedback === "positive" /* Positive */ ? "Upvoted as helpful" : "Downvoted as not helpful"), /* @__PURE__ */ React__default.createElement("div", { className: "message-meta" }, /* @__PURE__ */ React__default.createElement("span", { className: "message-timestamp-simple" }, formattedDate), /* @__PURE__ */ React__default.createElement(StatusBadge, { variant: feedback === "positive" /* Positive */ ? "success" : "destructive" }, feedback === "positive" /* Positive */ ? "Answered" : "Unable to help"))), content && /* @__PURE__ */ React__default.createElement("div", { className: "text-base" }, content)), type === "action" /* ACTION */ && /* @__PURE__ */ React__default.createElement("div", { className: "message-header" }, /* @__PURE__ */ React__default.createElement("span", { className: "text-subtle text-sm italic" }, content), /* @__PURE__ */ React__default.createElement("div", { className: "message-meta" }, /* @__PURE__ */ React__default.createElement("span", { className: "message-timestamp-simple" }, formattedDate), /* @__PURE__ */ React__default.createElement(StatusBadge, { variant: "warning" }, "Action"))))));
71
233
  }
72
234
 
235
+ function groupAttributesByKey(attributes) {
236
+ return attributes.reduce(
237
+ (acc, attr) => {
238
+ Object.entries(attr).forEach(([key, value]) => {
239
+ if (!acc[key]) {
240
+ acc[key] = [];
241
+ }
242
+ acc[key].push(value);
243
+ });
244
+ return acc;
245
+ },
246
+ {}
247
+ );
248
+ }
249
+
250
+ function AttributesDialog({ conversationId, ...args }) {
251
+ const [attributes, setAttributes] = useState();
252
+ const { fetchAttributes } = useConvosContext();
253
+ useEffect(() => {
254
+ void fetchAttributes(conversationId).then((attrs) => setAttributes(attrs)).catch(() => {
255
+ });
256
+ }, [fetchAttributes, conversationId]);
257
+ if (!attributes?.length) {
258
+ return null;
259
+ }
260
+ const groupedAttributes = groupAttributesByKey(attributes);
261
+ return /* @__PURE__ */ React__default.createElement(Dialog, { ...args }, /* @__PURE__ */ React__default.createElement(DialogTrigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(
262
+ Button,
263
+ {
264
+ variant: "ghost",
265
+ className: cn("size-7"),
266
+ size: "icon",
267
+ onClick: (e) => {
268
+ e.stopPropagation();
269
+ },
270
+ onKeyUp: (e) => {
271
+ e.stopPropagation();
272
+ }
273
+ },
274
+ /* @__PURE__ */ React__default.createElement(UserFocus, { size: 16 })
275
+ )), /* @__PURE__ */ React__default.createElement(DialogContent, { className: "min-w-[826px] bg-dark-100 pt-0 pb-4" }, /* @__PURE__ */ React__default.createElement(DialogHeader, { className: "py-6" }, /* @__PURE__ */ React__default.createElement(DialogTitle, { className: "heading-xxl !text-2xl" }, "Custom attributes")), /* @__PURE__ */ React__default.createElement("div", { className: "pb-4" }, /* @__PURE__ */ React__default.createElement(Table, { className: "bg-surface" }, /* @__PURE__ */ React__default.createElement(TableBody, { className: "space-y-1" }, Object.entries(groupedAttributes).map(([key, values]) => /* @__PURE__ */ React__default.createElement(TableRow, { key, className: "border-none" }, /* @__PURE__ */ React__default.createElement(TableCell, { className: "capitalize font-bold" }, key), values.map((value, index) => /* @__PURE__ */ React__default.createElement(TableCell, { key: `${key}-${index}` }, value)))))))));
276
+ }
277
+
73
278
  function Convo({
74
- locale,
279
+ locale: localeOverride,
75
280
  value: queryId,
76
281
  question,
77
282
  timestamp,
@@ -80,33 +285,46 @@ function Convo({
80
285
  status,
81
286
  statusMessage,
82
287
  hasAttributes,
83
- attributes,
84
- messages = [],
85
- isLoading = false,
86
288
  onClick,
87
289
  className,
88
290
  ...props
89
291
  }) {
292
+ const { getMessages, isLoading: isLoadingFromContext, locale: contextLocale } = useConvosContext();
293
+ const locale = localeOverride || contextLocale;
294
+ const messages = getMessages(queryId);
295
+ const isLoading = isLoadingFromContext(queryId);
90
296
  const formattedDate = formatDate(timestamp, locale);
91
297
  const description = [formattedDate, location, source].filter(Boolean).join(" • ");
92
- return /* @__PURE__ */ React__default.createElement(AccordionItem, { value: queryId, className: cn("border-none rounded-lg bg-surface", className), ...props }, /* @__PURE__ */ React__default.createElement(AccordionTrigger, { className: "flex-row-reverse px-4 gap-1 items-center", onClick }, /* @__PURE__ */ React__default.createElement(Item, { size: "sm", className: "flex-1 p-0" }, /* @__PURE__ */ React__default.createElement(ItemContent, null, /* @__PURE__ */ React__default.createElement(ItemTitle, { className: "text-base text-default" }, question), /* @__PURE__ */ React__default.createElement(ItemDescription, { className: "text-xs text-default" }, description)), /* @__PURE__ */ React__default.createElement(StatusBadge, { variant: status }, statusMessage), hasAttributes && /* @__PURE__ */ React__default.createElement(
93
- UserFocus,
94
- {
95
- size: 16,
96
- onClick: (e) => {
97
- e.stopPropagation();
98
- console.log("display custom attributes", attributes);
99
- },
100
- onKeyUp: (e) => {
101
- e.stopPropagation();
102
- console.log("display custom attributes", attributes);
103
- },
104
- tabIndex: 0
298
+ const handleAccordionClick = (e) => {
299
+ if (e.target.closest('[role="dialog"]')) {
300
+ e.preventDefault();
301
+ e.stopPropagation();
302
+ return;
105
303
  }
106
- ))), /* @__PURE__ */ React__default.createElement(AccordionContent, { className: "flex flex-col gap-4 text-balance px-3" }, isLoading ? /* @__PURE__ */ React__default.createElement("div", { className: "text-center text-subtle py-4" }, "Loading messages...") : /* @__PURE__ */ React__default.createElement("ul", { className: "list-none space-y-4" }, messages.slice(1).map((message) => /* @__PURE__ */ React__default.createElement(Message, { key: message.id, ...message })))));
304
+ onClick?.();
305
+ };
306
+ return /* @__PURE__ */ React__default.createElement(AccordionItem, { value: queryId, className: cn("border-none rounded-lg bg-surface", className), ...props }, /* @__PURE__ */ React__default.createElement(AccordionTrigger, { className: "flex-row-reverse px-4 gap-1 items-center", onClick: handleAccordionClick }, /* @__PURE__ */ React__default.createElement(Item, { size: "sm", className: "flex-1 p-0" }, /* @__PURE__ */ React__default.createElement(ItemContent, null, /* @__PURE__ */ React__default.createElement(ItemTitle, { className: "text-base text-default" }, question), /* @__PURE__ */ React__default.createElement(ItemDescription, { className: "text-xs text-default" }, description)), /* @__PURE__ */ React__default.createElement(StatusBadge, { variant: status }, statusMessage), hasAttributes && /* @__PURE__ */ React__default.createElement(AttributesDialog, { conversationId: queryId }))), /* @__PURE__ */ React__default.createElement(AccordionContent, { className: "flex flex-col gap-4 text-balance px-3" }, isLoading ? /* @__PURE__ */ React__default.createElement("div", { className: "text-center text-subtle py-4" }, "Loading messages...") : /* @__PURE__ */ React__default.createElement("ul", { className: "list-none space-y-4" }, messages.slice(1).map((message) => /* @__PURE__ */ React__default.createElement(Message, { key: message.id, ...message })))));
107
307
  }
108
308
 
109
- function Convos({ convos, fetchMessages }) {
309
+ const ConvosContext = React__default.createContext(void 0);
310
+ function useConvosContext() {
311
+ const context = React__default.useContext(ConvosContext);
312
+ if (context === void 0) {
313
+ throw new Error("useConvosContext must be used within a ConvosProvider");
314
+ }
315
+ return context;
316
+ }
317
+ function ConvosProvider({ children, value }) {
318
+ return /* @__PURE__ */ React__default.createElement(ConvosContext.Provider, { value }, children);
319
+ }
320
+ function Convos({
321
+ convos,
322
+ fetchMessages,
323
+ fetchAttributes,
324
+ fetchAnswerDiagnostics,
325
+ onDiscard,
326
+ onApprove
327
+ }) {
110
328
  const [messagesMap, setMessagesMap] = React__default.useState({});
111
329
  const [loadingMap, setLoadingMap] = React__default.useState({});
112
330
  const fetchedSetRef = React__default.useRef(/* @__PURE__ */ new Set());
@@ -133,7 +351,32 @@ function Convos({ convos, fetchMessages }) {
133
351
  },
134
352
  [fetchMessages]
135
353
  );
136
- return /* @__PURE__ */ React__default.createElement(Accordion, { type: "single", collapsible: true, className: "w-full space-y-2" }, convos.map((convo) => /* @__PURE__ */ React__default.createElement(
354
+ const defaultLocale = convos[0]?.locale;
355
+ const contextValue = React__default.useMemo(
356
+ () => ({
357
+ fetchMessages,
358
+ fetchAttributes,
359
+ getMessages: (conversationId) => messagesMap[conversationId] || [],
360
+ isLoading: (conversationId) => loadingMap[conversationId] || false,
361
+ handleFetchMessages,
362
+ locale: defaultLocale,
363
+ fetchAnswerDiagnostics: (messageId) => fetchAnswerDiagnostics(messageId),
364
+ onDiscard,
365
+ onApprove
366
+ }),
367
+ [
368
+ fetchMessages,
369
+ fetchAttributes,
370
+ messagesMap,
371
+ loadingMap,
372
+ handleFetchMessages,
373
+ defaultLocale,
374
+ fetchAnswerDiagnostics,
375
+ onDiscard,
376
+ onApprove
377
+ ]
378
+ );
379
+ return /* @__PURE__ */ React__default.createElement(ConvosProvider, { value: contextValue }, /* @__PURE__ */ React__default.createElement(Accordion, { type: "single", collapsible: true, className: "w-full space-y-2" }, convos.map((convo) => /* @__PURE__ */ React__default.createElement(
137
380
  Convo,
138
381
  {
139
382
  key: convo.value,
@@ -145,14 +388,11 @@ function Convos({ convos, fetchMessages }) {
145
388
  status: convo.status,
146
389
  statusMessage: convo.statusMessage,
147
390
  hasAttributes: convo.hasAttributes,
148
- attributes: convo.attributes,
149
391
  locale: convo.locale,
150
- messages: messagesMap[convo.value] || [],
151
- isLoading: loadingMap[convo.value] || false,
152
392
  onClick: () => handleFetchMessages(convo.value)
153
393
  }
154
- )));
394
+ ))));
155
395
  }
156
396
 
157
- export { Convo, Convos, Feedback, Message, MessageType, StatusBadge };
397
+ export { Convo, Convos, ConvosProvider, Feedback, Message, MessageType, StatusBadge, useConvosContext };
158
398
  //# sourceMappingURL=convos.js.map