@brainfish-ai/components 0.36.1 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ import { default as React } from 'react';
2
+ import { SearchDictation } from './hooks/useSearchDictation';
3
+ export declare const isDictating: (dictation?: SearchDictation) => boolean;
4
+ export declare const splitPartial: (value: string, partial: string) => {
5
+ committed: string;
6
+ partial: string;
7
+ };
8
+ export declare const DictationMic: ({ onStart }: {
9
+ onStart: () => void;
10
+ }) => React.JSX.Element;
11
+ export declare const DictationToolbar: ({ dictation }: {
12
+ dictation: SearchDictation;
13
+ }) => React.JSX.Element;
14
+ export declare const DictationCaption: ({ dictation, hideError }: {
15
+ dictation: SearchDictation;
16
+ hideError?: boolean;
17
+ }) => React.JSX.Element | null;
18
+ export declare const DictationLiveRegion: ({ dictation }: {
19
+ dictation: SearchDictation;
20
+ }) => React.JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { TextConfig, AttachedFile } from './types';
3
+ import { SearchDictation } from './hooks/useSearchDictation';
3
4
  interface FollowUpSearchBarProps {
4
5
  initialQuery?: string;
5
6
  onQueryChange: (query: string) => void;
@@ -12,6 +13,7 @@ interface FollowUpSearchBarProps {
12
13
  onAddFiles?: (files: File[]) => void;
13
14
  onRemoveFile?: (id: string) => void;
14
15
  disabled?: boolean;
16
+ dictation?: SearchDictation;
15
17
  }
16
18
  export declare const FollowUpSearchBar: React.ForwardRefExoticComponent<FollowUpSearchBarProps & React.RefAttributes<HTMLTextAreaElement>>;
17
19
  export {};
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { Collection, Suggestion, AttachedFile } from './types';
3
+ import { SearchDictation } from './hooks/useSearchDictation';
3
4
  export declare const PrimarySearch: React.ForwardRefExoticComponent<{
4
5
  query: string;
5
6
  onQueryChange: (query: string) => void;
@@ -34,5 +35,6 @@ export declare const PrimarySearch: React.ForwardRefExoticComponent<{
34
35
  includePageLoading?: boolean;
35
36
  includePageDisabled?: boolean;
36
37
  onIncludePageToggle?: (enabled: boolean) => Promise<void>;
38
+ dictation?: SearchDictation;
37
39
  } & React.RefAttributes<HTMLTextAreaElement>>;
38
40
  export default PrimarySearch;
@@ -8,3 +8,4 @@ export { useAccessibleColor } from './useAccessibleColor';
8
8
  export { useIncludePage } from './useIncludePage';
9
9
  export { useFileAttachment } from './useFileAttachment';
10
10
  export { useToast } from './useToast';
11
+ export { useSearchDictation } from './useSearchDictation';
@@ -0,0 +1,6 @@
1
+ export declare const composeQuery: (prefix: string, session: string) => string;
2
+ /**
3
+ * False when the transcript carries no question: empty, punctuation-only, filler, or a
4
+ * silence annotation such as "[BLANK_AUDIO]". English-only; other languages pass unless empty.
5
+ */
6
+ export declare const isMeaningfulTranscript: (text: string) => boolean;
@@ -0,0 +1,25 @@
1
+ export type DictationStatus = 'idle' | 'connecting' | 'recording' | 'transcribing' | 'error';
2
+ export type DictationErrorTone = 'danger' | 'warning';
3
+ export type SearchDictation = {
4
+ available: boolean;
5
+ status: DictationStatus;
6
+ /** Always empty in batch mode; kept for story/API compatibility. */
7
+ partial: string;
8
+ /** Always empty in batch mode; kept for story/API compatibility. */
9
+ committed: string;
10
+ error: string | null;
11
+ errorTone: DictationErrorTone | null;
12
+ elapsedSeconds: number;
13
+ levels: number[];
14
+ start: () => void;
15
+ done: () => void;
16
+ cancel: () => void;
17
+ };
18
+ export declare const useSearchDictation: ({ endpoint, headers, getText, setText, onSubmit, }: {
19
+ endpoint?: string;
20
+ headers?: Record<string, string>;
21
+ getText: () => string;
22
+ setText: (value: string) => void;
23
+ /** When set, a meaningful transcript is handed here (with typed prefix) instead of written to the box. */
24
+ onSubmit?: (query: string) => void;
25
+ }) => SearchDictation;
@@ -231,6 +231,8 @@ export interface ChatSearchProps {
231
231
  resetHandoff?: () => void;
232
232
  followUpQuestionsEndpoint?: string;
233
233
  autocompleteEndpoint?: string;
234
+ /** When set, the search box shows a mic and POSTs recorded audio here for batch transcription. */
235
+ transcriptionTokenEndpoint?: string;
234
236
  contentTypeEndpoint?: string;
235
237
  disableFollowUpQuestions?: boolean;
236
238
  enableFileAttachment?: boolean;
@@ -34,6 +34,7 @@ import { MemoizedIcon } from '../ui/icon.js';
34
34
  import { Features } from '../../lib/features.js';
35
35
  import { useToast } from './hooks/useToast.js';
36
36
  import { useFileAttachment } from './hooks/useFileAttachment.js';
37
+ import { useSearchDictation } from './hooks/useSearchDictation.js';
37
38
  import { useConversationLoader } from './hooks/useConversationLoader.js';
38
39
  import { useSubscriptionManager } from './hooks/useSubscriptionManager.js';
39
40
  import { useIncludePage } from './hooks/useIncludePage.js';
@@ -61,6 +62,7 @@ const ChatSearchComponent = forwardRef(
61
62
  feedbackReasonEndpoint,
62
63
  loadConversationEndpoint,
63
64
  autocompleteEndpoint,
65
+ transcriptionTokenEndpoint,
64
66
  disableFollowUpQuestions = false,
65
67
  enableFileAttachment: enableFileAttachmentProp = false,
66
68
  disableFeedback = false,
@@ -120,6 +122,7 @@ const ChatSearchComponent = forwardRef(
120
122
  const enableFileAttachmentFlag = useBooleanFlagValue("enableFileAttachment", false);
121
123
  const enableFileAttachment = enableFileAttachmentProp || enableFileAttachmentFlag;
122
124
  const isHistoryTabEnabled = useBooleanFlagValue(Features.HistoryTab, false);
125
+ const isTranscribeEnabled = useBooleanFlagValue(Features.TranscribeEnabled, false);
123
126
  const { toasts, showToast, dismissToast } = useToast();
124
127
  const getOrCreateConversationId = useCallback(() => {
125
128
  let conversationId = currentConversationId;
@@ -147,8 +150,23 @@ const ChatSearchComponent = forwardRef(
147
150
  const followUpSearchRef = useRef(null);
148
151
  const bottomRef = useRef(null);
149
152
  const currentReaderRef = useRef(null);
153
+ const dictation = useSearchDictation({
154
+ endpoint: isTranscribeEnabled ? transcriptionTokenEndpoint : void 0,
155
+ headers,
156
+ getText: () => showResults ? followUpQuery : query,
157
+ setText: (value) => {
158
+ if (showResults) setFollowUpQuery(value);
159
+ else setQuery(value);
160
+ },
161
+ onSubmit: (dictatedQuery) => submitDictatedQueryRef.current(dictatedQuery)
162
+ });
163
+ const dictationActive = dictation.status === "connecting" || dictation.status === "recording" || dictation.status === "transcribing";
164
+ const cancelDictationRef = useRef(dictation.cancel);
165
+ cancelDictationRef.current = dictation.cancel;
166
+ const submitDictatedQueryRef = useRef(() => {
167
+ });
150
168
  const { suggestions: autocompleteSuggestions } = useAutocomplete({
151
- query,
169
+ query: dictationActive ? "" : query,
152
170
  endpoint: autocompleteEndpoint || "",
153
171
  allowedRegions,
154
172
  collectionId: currentCollectionId,
@@ -641,6 +659,7 @@ const ChatSearchComponent = forwardRef(
641
659
  }
642
660
  };
643
661
  const createNewThread = (firstQuery) => {
662
+ dictation.cancel();
644
663
  completeActiveConversations();
645
664
  setIsViewOnly(false);
646
665
  resetHandoff?.();
@@ -669,6 +688,7 @@ const ChatSearchComponent = forwardRef(
669
688
  }
670
689
  };
671
690
  const navigateHome = () => {
691
+ dictation.cancel();
672
692
  setIsViewOnly(false);
673
693
  resetHandoff?.();
674
694
  if (currentReaderRef.current) {
@@ -714,6 +734,14 @@ const ChatSearchComponent = forwardRef(
714
734
  textarea.style.height = `${textarea.scrollHeight}px`;
715
735
  };
716
736
  const handlePrimarySearch = async (searchQuery = query.trim()) => {
737
+ if (dictation.status === "recording") {
738
+ dictation.done();
739
+ return;
740
+ }
741
+ if (dictation.status === "connecting" || dictation.status === "transcribing") {
742
+ dictation.cancel();
743
+ return;
744
+ }
717
745
  await performSearch({ searchQuery, event: { name: "Primary Field Search Submitted" } });
718
746
  };
719
747
  const handleFeedback = async (response, searchQueryId, answerIndex) => {
@@ -759,14 +787,42 @@ const ChatSearchComponent = forwardRef(
759
787
  }
760
788
  };
761
789
  const handleFollowUpSearch = async (customQuery) => {
790
+ if (dictation.status === "recording") {
791
+ dictation.done();
792
+ return;
793
+ }
794
+ if (dictation.status === "connecting" || dictation.status === "transcribing") {
795
+ dictation.cancel();
796
+ return;
797
+ }
762
798
  const queryToUse = customQuery !== void 0 ? customQuery : followUpQuery;
763
799
  if (queryToUse.trim()) {
764
- resetHandoff?.();
800
+ await submitFollowUp(queryToUse);
801
+ }
802
+ };
803
+ const submitFollowUp = async (queryToUse, metaData) => {
804
+ resetHandoff?.();
805
+ setQuery(queryToUse);
806
+ setFollowUpQuery("");
807
+ await performSearch({ searchQuery: queryToUse, event: { name: "Follow-up Search Field Submitted", metaData } });
808
+ };
809
+ const submitDictatedQuery = async (queryToUse) => {
810
+ if (isSearching) {
811
+ if (showResults) setFollowUpQuery(queryToUse);
812
+ else setQuery(queryToUse);
813
+ return;
814
+ }
815
+ if (showResults) {
816
+ await submitFollowUp(queryToUse, { inputMethod: "voice" });
817
+ } else {
765
818
  setQuery(queryToUse);
766
- setFollowUpQuery("");
767
- await performSearch({ searchQuery: queryToUse, event: { name: "Follow-up Search Field Submitted" } });
819
+ await performSearch({
820
+ searchQuery: queryToUse,
821
+ event: { name: "Primary Field Search Submitted", metaData: { inputMethod: "voice" } }
822
+ });
768
823
  }
769
824
  };
825
+ submitDictatedQueryRef.current = submitDictatedQuery;
770
826
  const handleSuggestedQuestionClick = (suggestion) => {
771
827
  if (suggestion.conversationId) {
772
828
  trackEvent?.("Recent Conversation Clicked", {
@@ -795,6 +851,7 @@ const ChatSearchComponent = forwardRef(
795
851
  const urlParams = new URLSearchParams(window.location.search);
796
852
  const urlConversationId = urlParams.get("conversation");
797
853
  if (!urlConversationId) {
854
+ cancelDictationRef.current();
798
855
  setQuery("");
799
856
  setFollowUpQuery("");
800
857
  setShowResults(false);
@@ -984,7 +1041,8 @@ const ChatSearchComponent = forwardRef(
984
1041
  includePageEnabled,
985
1042
  includePageLoading,
986
1043
  includePageDisabled,
987
- onIncludePageToggle: handleIncludePageToggle
1044
+ onIncludePageToggle: handleIncludePageToggle,
1045
+ dictation: dictation.available ? dictation : void 0
988
1046
  }
989
1047
  )
990
1048
  )
@@ -1151,7 +1209,8 @@ const ChatSearchComponent = forwardRef(
1151
1209
  isUploading,
1152
1210
  onAddFiles: addFiles,
1153
1211
  onRemoveFile: removeFile,
1154
- disabled: isAwaitingGroup
1212
+ disabled: isAwaitingGroup,
1213
+ dictation: dictation.available ? dictation : void 0
1155
1214
  }
1156
1215
  );
1157
1216
  })(),
@@ -0,0 +1,68 @@
1
+ import React__default from 'react';
2
+ import { X, Check, Microphone } from '@phosphor-icons/react';
3
+ import { Button } from '../ui/button.js';
4
+ import { cn } from '../../lib/utils.js';
5
+
6
+ const isDictating = (dictation) => dictation?.status === "connecting" || dictation?.status === "recording" || dictation?.status === "transcribing";
7
+ const formatElapsed = (seconds) => {
8
+ const minutes = Math.floor(seconds / 60);
9
+ const rest = seconds % 60;
10
+ return `${minutes}:${rest.toString().padStart(2, "0")}`;
11
+ };
12
+ const DictationMic = ({ onStart }) => /* @__PURE__ */ React__default.createElement(
13
+ Button,
14
+ {
15
+ type: "button",
16
+ variant: "ghost",
17
+ size: "icon",
18
+ className: "size-8",
19
+ "aria-label": "Ask with your voice",
20
+ onClick: onStart
21
+ },
22
+ /* @__PURE__ */ React__default.createElement(Microphone, { weight: "regular", className: "size-4" })
23
+ );
24
+ const DictationToolbar = ({ dictation }) => {
25
+ const connecting = dictation.status === "connecting";
26
+ const transcribing = dictation.status === "transcribing";
27
+ const recording = dictation.status === "recording";
28
+ return /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-between gap-2 border-t border-border bg-background px-3 py-2 rounded-b-[4px]" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex min-w-0 items-center gap-2 text-sm text-foreground" }, connecting ? /* @__PURE__ */ React__default.createElement("span", null, "Connecting microphone…") : transcribing ? /* @__PURE__ */ React__default.createElement("span", null, "Transcribing…") : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("span", { className: "size-2 shrink-0 rounded-full bg-red", "aria-hidden": true }), /* @__PURE__ */ React__default.createElement("span", null, "Recording"), /* @__PURE__ */ React__default.createElement("span", { className: "tabular-nums text-subtle" }, formatElapsed(dictation.elapsedSeconds)), /* @__PURE__ */ React__default.createElement("span", { className: "flex h-4 items-center gap-px", "aria-hidden": true }, dictation.levels.map((level, index) => /* @__PURE__ */ React__default.createElement(
29
+ "span",
30
+ {
31
+ key: index,
32
+ className: "h-4 w-0.5 origin-center rounded-full bg-foreground",
33
+ style: { transform: `scaleY(${level})` }
34
+ }
35
+ ))))), /* @__PURE__ */ React__default.createElement("div", { className: "flex shrink-0 items-center gap-2" }, /* @__PURE__ */ React__default.createElement(
36
+ Button,
37
+ {
38
+ type: "button",
39
+ variant: "ghost",
40
+ size: "icon",
41
+ className: "size-8",
42
+ "aria-label": "Cancel voice input",
43
+ onClick: dictation.cancel
44
+ },
45
+ /* @__PURE__ */ React__default.createElement(X, { className: "size-4" })
46
+ ), recording && /* @__PURE__ */ React__default.createElement(Button, { size: "icon", className: "bg-primary text-primary", onClick: dictation.done, "aria-label": "Done" }, /* @__PURE__ */ React__default.createElement(Check, { weight: "bold", className: "size-4" }))));
47
+ };
48
+ const DictationCaption = ({ dictation, hideError = false }) => {
49
+ const active = isDictating(dictation);
50
+ if (!active && (!dictation.error || hideError)) return null;
51
+ const text = dictation.error ? dictation.error : dictation.status === "connecting" ? "Connecting microphone…" : dictation.status === "transcribing" ? "Turning your recording into text…" : "Press Done when you are finished. Press Esc to cancel.";
52
+ return /* @__PURE__ */ React__default.createElement(
53
+ "p",
54
+ {
55
+ className: cn(
56
+ "mt-2 text-center text-sm hidden md:block",
57
+ dictation.error ? dictation.errorTone === "warning" ? "text-warning" : "text-destructive" : "text-subtle"
58
+ )
59
+ },
60
+ text
61
+ );
62
+ };
63
+ const DictationLiveRegion = ({ dictation }) => {
64
+ const message = dictation.error ?? (dictation.status === "connecting" ? "Connecting microphone" : dictation.status === "transcribing" ? "Transcribing" : dictation.status === "recording" ? "Recording" : "");
65
+ return /* @__PURE__ */ React__default.createElement("div", { className: "sr-only", "aria-live": "polite" }, message);
66
+ };
67
+
68
+ export { DictationCaption, DictationLiveRegion, DictationMic, DictationToolbar, isDictating };
@@ -1,6 +1,7 @@
1
1
  import React__default, { useState, useRef, useEffect } from 'react';
2
- import { Image, MagnifyingGlass, ArrowRight } from '@phosphor-icons/react';
2
+ import { MagnifyingGlass, Image, ArrowRight } from '@phosphor-icons/react';
3
3
  import { motion } from 'framer-motion';
4
+ import { isDictating, DictationToolbar, DictationMic, DictationCaption, DictationLiveRegion } from './DictationChrome.js';
4
5
  import styles from './FollowUpSearchBar.module.css.js';
5
6
  import { Button } from '../ui/button.js';
6
7
  import { Textarea } from '../ui/textarea.js';
@@ -19,7 +20,8 @@ const FollowUpSearchBar = React__default.forwardRef(
19
20
  isUploading = false,
20
21
  onAddFiles,
21
22
  onRemoveFile,
22
- disabled = false
23
+ disabled = false,
24
+ dictation
23
25
  }, ref) => {
24
26
  const [localQuery, setLocalQuery] = useState(initialQuery);
25
27
  const [isDragOver, setIsDragOver] = useState(false);
@@ -90,6 +92,8 @@ const FollowUpSearchBar = React__default.forwardRef(
90
92
  const handleDragLeave = () => {
91
93
  setIsDragOver(false);
92
94
  };
95
+ const dictating = isDictating(dictation);
96
+ const errorPlaceholder = !dictating && dictation?.error && !localQuery ? dictation.error : void 0;
93
97
  return /* @__PURE__ */ React__default.createElement(
94
98
  motion.footer,
95
99
  {
@@ -98,11 +102,12 @@ const FollowUpSearchBar = React__default.forwardRef(
98
102
  transition: { duration: 0.5 },
99
103
  className: styles.followUpSearchBar
100
104
  },
101
- /* @__PURE__ */ React__default.createElement("div", { className: "container flex items-center justify-center p-3" }, /* @__PURE__ */ React__default.createElement(
105
+ /* @__PURE__ */ React__default.createElement("div", { className: "container flex items-center justify-center p-3 flex-col" }, /* @__PURE__ */ React__default.createElement(
102
106
  "div",
103
107
  {
104
108
  className: cn(
105
- "relative flex w-full flex-col rounded bg-card border border-dark-400",
109
+ "relative flex w-full flex-col rounded-md bg-background",
110
+ dictating ? "bg-yellowfin-gradient !border-2" : "border border-dark-400",
106
111
  isDragOver && enableFileAttachment && "ring-2 ring-primary"
107
112
  ),
108
113
  onDrop: handleDrop,
@@ -127,7 +132,20 @@ const FollowUpSearchBar = React__default.forwardRef(
127
132
  className: "pt-2 px-2"
128
133
  }
129
134
  ),
130
- /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2 px-2" }, enableFileAttachment && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
135
+ dictating && dictation ? /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("div", { className: "flex min-h-16 items-center bg-surface px-3 rounded-t-[4px]" }, /* @__PURE__ */ React__default.createElement(MagnifyingGlass, { className: "mr-2 size-5 shrink-0 text-muted-foreground", "aria-hidden": true }), /* @__PURE__ */ React__default.createElement("div", { className: "relative min-w-0 flex-1" }, /* @__PURE__ */ React__default.createElement(
136
+ Textarea,
137
+ {
138
+ ref: setCombinedRef,
139
+ value: localQuery,
140
+ readOnly: true,
141
+ onChange: handleChange,
142
+ onBlur: handleBlur,
143
+ onKeyDown: handleKeyPress,
144
+ placeholder: textConfig.followUpPlaceholder,
145
+ className: "min-h-11 w-full resize-none border-0 bg-transparent px-0 py-2.5 shadow-none focus:outline-none focus:ring-0 focus-visible:ring-0",
146
+ rows: 1
147
+ }
148
+ ))), /* @__PURE__ */ React__default.createElement(DictationToolbar, { dictation })) : /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2 px-2" }, enableFileAttachment && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
131
149
  "input",
132
150
  {
133
151
  type: "file",
@@ -161,12 +179,15 @@ const FollowUpSearchBar = React__default.forwardRef(
161
179
  onChange: handleChange,
162
180
  onBlur: handleBlur,
163
181
  onKeyDown: handleKeyPress,
164
- placeholder: textConfig.followUpPlaceholder,
182
+ placeholder: errorPlaceholder ?? textConfig.followUpPlaceholder,
165
183
  disabled,
166
- className: "min-h-11 w-full resize-none border-0 shadow-none bg-transparent px-0 py-2.5 focus:outline-none focus:ring-0 focus-visible:ring-0 disabled:opacity-50 disabled:cursor-not-allowed",
184
+ className: cn(
185
+ "min-h-11 w-full resize-none border-0 shadow-none bg-transparent px-0 py-2.5 focus:outline-none focus:ring-0 focus-visible:ring-0 disabled:opacity-50 disabled:cursor-not-allowed",
186
+ errorPlaceholder && (dictation?.errorTone === "warning" ? "placeholder:text-warning" : "placeholder:text-destructive")
187
+ ),
167
188
  rows: 1
168
189
  }
169
- ), /* @__PURE__ */ React__default.createElement(
190
+ ), dictation && /* @__PURE__ */ React__default.createElement(DictationMic, { onStart: dictation.start }), /* @__PURE__ */ React__default.createElement(
170
191
  Button,
171
192
  {
172
193
  type: "button",
@@ -180,7 +201,7 @@ const FollowUpSearchBar = React__default.forwardRef(
180
201
  },
181
202
  isSearching ? /* @__PURE__ */ React__default.createElement("div", { className: "animate-spin rounded-full size-4 sm:size-5 border-2 border-dark/40 !border-t-foreground" }) : /* @__PURE__ */ React__default.createElement(ArrowRight, { className: "size-6" })
182
203
  ))
183
- ))
204
+ ), dictation && /* @__PURE__ */ React__default.createElement(DictationCaption, { dictation, hideError: Boolean(errorPlaceholder) }), dictation && /* @__PURE__ */ React__default.createElement(DictationLiveRegion, { dictation }))
184
205
  );
185
206
  }
186
207
  );
@@ -2,7 +2,9 @@ import React__default, { useRef, useState, useEffect } from 'react';
2
2
  import { AnimatePresence, motion } from 'framer-motion';
3
3
  import { Globe, LockSimple, MagnifyingGlass, Image, ArrowRight, CaretRight } from '@phosphor-icons/react';
4
4
  import { getCountry } from 'countries-and-timezones';
5
+ import { isDictating, DictationToolbar, DictationMic, DictationCaption, DictationLiveRegion } from './DictationChrome.js';
5
6
  import { Button } from '../ui/button.js';
7
+ import { Textarea } from '../ui/textarea.js';
6
8
  import { AttachmentThumbnails } from './AttachmentThumbnails.js';
7
9
  import { IncludePageToggle } from './IncludePageToggle.js';
8
10
  import { Combobox } from '../combobox/combobox.js';
@@ -35,7 +37,8 @@ const PrimarySearch = React__default.forwardRef(
35
37
  includePageEnabled = false,
36
38
  includePageLoading = false,
37
39
  includePageDisabled = false,
38
- onIncludePageToggle
40
+ onIncludePageToggle,
41
+ dictation
39
42
  }, ref) => {
40
43
  const fileInputRef = useRef(null);
41
44
  const [showSuggestions, setShowSuggestions] = useState(false);
@@ -124,10 +127,6 @@ const PrimarySearch = React__default.forwardRef(
124
127
  onCollectionChange(collection.value);
125
128
  }
126
129
  };
127
- const adjustTextareaHeight = (textarea) => {
128
- textarea.style.height = "auto";
129
- textarea.style.height = `${textarea.scrollHeight}px`;
130
- };
131
130
  const handleKeyPress = (e) => {
132
131
  if (e.key === "Enter" && !e.shiftKey && !isUploading) {
133
132
  e.preventDefault();
@@ -148,7 +147,9 @@ const PrimarySearch = React__default.forwardRef(
148
147
  setShowSuggestions(true);
149
148
  }
150
149
  }, [query]);
151
- const shouldShowSuggestions = showSuggestions && query.trim() && autocompleteSuggestions.length > 0;
150
+ const dictating = isDictating(dictation);
151
+ const errorPlaceholder = !dictating && dictation?.error && !query ? dictation.error : void 0;
152
+ const shouldShowSuggestions = !dictating && showSuggestions && query.trim() && autocompleteSuggestions.length > 0;
152
153
  const dropdownVariants = {
153
154
  hidden: {
154
155
  opacity: 0,
@@ -199,8 +200,9 @@ const PrimarySearch = React__default.forwardRef(
199
200
  "div",
200
201
  {
201
202
  className: cn(
202
- "relative w-full overflow-hidden bg-background border border-border transition-all",
203
- shouldShowSuggestions ? "rounded-t-md" : "rounded-md",
203
+ "relative w-full overflow-hidden bg-background transition-all",
204
+ dictating ? "rounded-md bg-yellowfin-gradient !border-2" : "border border-border",
205
+ !dictating && (shouldShowSuggestions ? "rounded-t-md" : "rounded-md"),
204
206
  isDragOver && enableFileAttachment && "ring-2 ring-primary"
205
207
  ),
206
208
  onDrop: handleDrop,
@@ -225,27 +227,25 @@ const PrimarySearch = React__default.forwardRef(
225
227
  className: "pt-3 px-3 bg-card"
226
228
  }
227
229
  ),
228
- /* @__PURE__ */ React__default.createElement("div", { className: "p-3 flex items-center bg-card", "data-name": "search-input" }, /* @__PURE__ */ React__default.createElement(MagnifyingGlass, { className: "h-5 w-5 mr-2 text-muted-foreground", "aria-hidden": true, role: "presentation" }), /* @__PURE__ */ React__default.createElement(
229
- "textarea",
230
+ /* @__PURE__ */ React__default.createElement("div", { className: cn("p-3 flex items-start bg-surface", dictating && "min-h-16"), "data-name": "search-input" }, /* @__PURE__ */ React__default.createElement(MagnifyingGlass, { className: "size-5 mr-2 shrink-0 text-muted-foreground mt-3", "aria-hidden": true, role: "presentation" }), /* @__PURE__ */ React__default.createElement("div", { className: "relative min-w-0 flex-1" }, /* @__PURE__ */ React__default.createElement(
231
+ Textarea,
230
232
  {
233
+ id: "search-input",
231
234
  ref,
232
235
  value: query,
233
- onChange: (e) => {
234
- onQueryChange(e.target.value);
235
- adjustTextareaHeight(e.target);
236
- },
236
+ readOnly: dictating,
237
+ onChange: (e) => onQueryChange(e.target.value),
237
238
  onKeyDown: handleKeyPress,
238
239
  onFocus: () => setShowSuggestions(true),
239
- placeholder: textConfig.searchPlaceholder,
240
+ placeholder: errorPlaceholder ?? textConfig.searchPlaceholder,
240
241
  className: cn(
241
- "w-full resize-none !outline-none text-base py-1 min-h-9",
242
- "overflow-hidden border-none bg-transparent shadow-none",
243
- "placeholder:text-gray-400 text-dark-900 !ring-0"
242
+ "min-h-11 w-full resize-none border-0 bg-transparent px-0 py-2.5 shadow-none focus:outline-none focus:ring-0 focus-visible:ring-0",
243
+ errorPlaceholder && (dictation?.errorTone === "warning" ? "placeholder:text-warning" : "placeholder:text-destructive")
244
244
  ),
245
245
  rows: 1
246
246
  }
247
- ), isSearching && /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-center ml-2" }, /* @__PURE__ */ React__default.createElement("div", { className: "animate-spin rounded-full size-4 border-2 border-t-transparent border-primary" }))),
248
- !shouldShowSuggestions && /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-between px-3 py-2 border-t border-border" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2" }, enableFileAttachment && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
247
+ )), isSearching && /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-center ml-2" }, /* @__PURE__ */ React__default.createElement("div", { className: "animate-spin rounded-full size-4 border-2 border-t-transparent border-primary" }))),
248
+ dictating && dictation ? /* @__PURE__ */ React__default.createElement(DictationToolbar, { dictation }) : !shouldShowSuggestions && /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-between px-3 py-2 border-t border-border" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2" }, enableFileAttachment && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
249
249
  "input",
250
250
  {
251
251
  type: "file",
@@ -312,7 +312,7 @@ const PrimarySearch = React__default.forwardRef(
312
312
  disabled: includePageDisabled,
313
313
  onToggle: onIncludePageToggle
314
314
  }
315
- )), /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center", "data-name": "search-button" }, /* @__PURE__ */ React__default.createElement(
315
+ )), /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2", "data-name": "search-button" }, dictation && /* @__PURE__ */ React__default.createElement(DictationMic, { onStart: dictation.start }), /* @__PURE__ */ React__default.createElement(
316
316
  Button,
317
317
  {
318
318
  size: "sm",
@@ -337,7 +337,7 @@ const PrimarySearch = React__default.forwardRef(
337
337
  }
338
338
  )
339
339
  )))
340
- ), /* @__PURE__ */ React__default.createElement(AnimatePresence, { mode: "wait" }, shouldShowSuggestions && /* @__PURE__ */ React__default.createElement(
340
+ ), dictation && /* @__PURE__ */ React__default.createElement(DictationCaption, { dictation, hideError: Boolean(errorPlaceholder) }), dictation && /* @__PURE__ */ React__default.createElement(DictationLiveRegion, { dictation }), /* @__PURE__ */ React__default.createElement(AnimatePresence, { mode: "wait" }, shouldShowSuggestions && /* @__PURE__ */ React__default.createElement(
341
341
  motion.div,
342
342
  {
343
343
  variants: dropdownVariants,
@@ -0,0 +1,42 @@
1
+ const joinText = (left, right) => {
2
+ if (!left) return right;
3
+ if (!right) return left;
4
+ if (/\s$/.test(left) || /^\s/.test(right)) return `${left}${right}`;
5
+ return `${left} ${right}`;
6
+ };
7
+ const composeQuery = (prefix, session) => joinText(prefix, session.trim());
8
+ const NON_SPEECH_TAG = /\[[^\]]*\]|\([^)]*\)/g;
9
+ const PUNCTUATION = /\p{P}+/gu;
10
+ const FILLER_PHRASES = ["you know", "i mean", "thank you", "thanks for watching"];
11
+ const FILLER_WORDS = /* @__PURE__ */ new Set([
12
+ "um",
13
+ "umm",
14
+ "uh",
15
+ "uhh",
16
+ "er",
17
+ "erm",
18
+ "ah",
19
+ "hmm",
20
+ "hm",
21
+ "mm",
22
+ "mhm",
23
+ "huh",
24
+ "oh",
25
+ "okay",
26
+ "ok",
27
+ "yeah",
28
+ "so",
29
+ "well",
30
+ "you",
31
+ "bye"
32
+ ]);
33
+ const isMeaningfulTranscript = (text) => {
34
+ const cleaned = text.replace(NON_SPEECH_TAG, " ").replace(PUNCTUATION, " ").toLowerCase().replace(/\s+/g, " ").trim();
35
+ const withoutPhrases = FILLER_PHRASES.reduce(
36
+ (rest, phrase) => rest.replace(new RegExp(`\\b${phrase}\\b`, "g"), " "),
37
+ cleaned
38
+ );
39
+ return withoutPhrases.split(" ").some((word) => word && !FILLER_WORDS.has(word));
40
+ };
41
+
42
+ export { composeQuery, isMeaningfulTranscript };
@@ -0,0 +1,162 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+ import { useMicrophone, useReducedMotion, useEvent } from '@reactuses/core';
3
+ import { isMeaningfulTranscript, composeQuery } from './transcriptSession.js';
4
+ import { makeRequest } from '../../../lib/fetchClient.js';
5
+
6
+ const MAX_SESSION_MS = 6e4;
7
+ const BAR_COUNT = 16;
8
+ const MIN_LEVEL = 0.12;
9
+ const MIC_BLOCKED = "Allow microphone access in your browser settings, then try again.";
10
+ const UNAVAILABLE = "Voice input is unavailable right now. You can still type.";
11
+ const TRANSCRIBE_FAILED = "Could not transcribe your recording. Try again or type your question.";
12
+ const NO_SPEECH = "We didn't catch a question. Try again or type it.";
13
+ const MIC_CONSTRAINTS = { channelCount: 1 };
14
+ const FLAT_LEVELS = Array.from({ length: BAR_COUNT }, () => MIN_LEVEL);
15
+ const isPermissionError = (error) => {
16
+ const name = error?.name;
17
+ return name === "NotAllowedError" || name === "NotFoundError" || name === "SecurityError";
18
+ };
19
+ const toBarLevel = (level) => Math.min(1, MIN_LEVEL + level * 2.4);
20
+ const fileNameFor = (mediaType) => `dictation.${mediaType.includes("mp4") ? "mp4" : "webm"}`;
21
+ const useSearchDictation = ({
22
+ endpoint,
23
+ headers,
24
+ getText,
25
+ setText,
26
+ onSubmit
27
+ }) => {
28
+ const {
29
+ isSupported,
30
+ level,
31
+ error: micError,
32
+ start: openMic,
33
+ stop: closeMic,
34
+ startRecording,
35
+ stopRecording
36
+ } = useMicrophone({ constraints: MIC_CONSTRAINTS });
37
+ const reducedMotion = useReducedMotion();
38
+ const [status, setStatus] = useState("idle");
39
+ const [error, setError] = useState(null);
40
+ const [errorTone, setErrorTone] = useState(null);
41
+ const [elapsedSeconds, setElapsedSeconds] = useState(0);
42
+ const [levels, setLevels] = useState(FLAT_LEVELS);
43
+ const abortRef = useRef(null);
44
+ const prefixRef = useRef("");
45
+ const fail = useEvent((message, tone = "danger") => {
46
+ setError(message);
47
+ setErrorTone(tone);
48
+ setStatus("error");
49
+ });
50
+ const cancel = useEvent(() => {
51
+ abortRef.current?.abort();
52
+ abortRef.current = null;
53
+ closeMic();
54
+ setText(prefixRef.current);
55
+ setError(null);
56
+ setErrorTone(null);
57
+ setStatus("idle");
58
+ });
59
+ const start = useEvent(async () => {
60
+ const active = status === "connecting" || status === "recording" || status === "transcribing";
61
+ if (!endpoint || active) return;
62
+ const abort = new AbortController();
63
+ abortRef.current = abort;
64
+ prefixRef.current = getText();
65
+ setError(null);
66
+ setErrorTone(null);
67
+ setElapsedSeconds(0);
68
+ setLevels(FLAT_LEVELS);
69
+ setStatus("connecting");
70
+ try {
71
+ if (typeof MediaRecorder === "undefined") throw new Error("MediaRecorder is not supported");
72
+ await openMic();
73
+ if (abort.signal.aborted) {
74
+ closeMic();
75
+ return;
76
+ }
77
+ startRecording();
78
+ setStatus("recording");
79
+ } catch (caught) {
80
+ if (abort.signal.aborted) return;
81
+ closeMic();
82
+ fail(isPermissionError(caught) ? MIC_BLOCKED : UNAVAILABLE);
83
+ }
84
+ });
85
+ const done = useEvent(async () => {
86
+ const abort = abortRef.current;
87
+ if (!endpoint || !abort || status !== "recording") return;
88
+ setStatus("transcribing");
89
+ try {
90
+ const blob = await stopRecording();
91
+ closeMic();
92
+ if (abort.signal.aborted) return;
93
+ if (!blob?.size) return fail(TRANSCRIBE_FAILED);
94
+ const form = new FormData();
95
+ form.append("file", blob, fileNameFor(blob.type));
96
+ const payload = await makeRequest(endpoint, {
97
+ method: "POST",
98
+ headers,
99
+ body: form,
100
+ retry: { maxRetries: 0 },
101
+ signal: abort.signal
102
+ });
103
+ if (abort.signal.aborted) return;
104
+ const text = payload.data?.text?.trim() ?? "";
105
+ if (!isMeaningfulTranscript(text)) return fail(NO_SPEECH, "warning");
106
+ const query = composeQuery(prefixRef.current, text);
107
+ setStatus("idle");
108
+ if (onSubmit) onSubmit(query);
109
+ else setText(query);
110
+ } catch {
111
+ if (!abort.signal.aborted) fail(UNAVAILABLE);
112
+ } finally {
113
+ if (abortRef.current === abort) abortRef.current = null;
114
+ }
115
+ });
116
+ useEffect(() => {
117
+ if (status !== "recording") return;
118
+ const startedAt = Date.now();
119
+ const timer = window.setInterval(() => {
120
+ const elapsed = Date.now() - startedAt;
121
+ setElapsedSeconds(Math.floor(elapsed / 1e3));
122
+ if (elapsed >= MAX_SESSION_MS) void done();
123
+ }, 250);
124
+ return () => window.clearInterval(timer);
125
+ }, [status, done]);
126
+ useEffect(() => {
127
+ if (status !== "recording" || reducedMotion) return;
128
+ setLevels((previous) => [...previous.slice(1), toBarLevel(level)]);
129
+ }, [level, reducedMotion, status]);
130
+ useEffect(() => {
131
+ if (!micError || status !== "recording") return;
132
+ abortRef.current?.abort();
133
+ fail(UNAVAILABLE);
134
+ }, [micError, status, fail]);
135
+ useEffect(() => {
136
+ if (status !== "connecting" && status !== "recording" && status !== "transcribing") return;
137
+ const onKey = (event) => {
138
+ if (event.key === "Escape") {
139
+ event.preventDefault();
140
+ cancel();
141
+ }
142
+ };
143
+ window.addEventListener("keydown", onKey);
144
+ return () => window.removeEventListener("keydown", onKey);
145
+ }, [cancel, status]);
146
+ useEffect(() => () => abortRef.current?.abort(), []);
147
+ return {
148
+ available: Boolean(endpoint) && isSupported,
149
+ status,
150
+ partial: "",
151
+ committed: "",
152
+ error,
153
+ errorTone,
154
+ elapsedSeconds,
155
+ levels,
156
+ start: () => void start(),
157
+ done: () => void done(),
158
+ cancel
159
+ };
160
+ };
161
+
162
+ export { useSearchDictation };
@@ -1475,6 +1475,9 @@ video {
1475
1475
  .min-h-11{
1476
1476
  min-height: 2.75rem;
1477
1477
  }
1478
+ .min-h-16{
1479
+ min-height: 4rem;
1480
+ }
1478
1481
  .min-h-20{
1479
1482
  min-height: 5rem;
1480
1483
  }
@@ -1508,6 +1511,9 @@ video {
1508
1511
  .w-0{
1509
1512
  width: 0px;
1510
1513
  }
1514
+ .w-0\.5{
1515
+ width: 0.125rem;
1516
+ }
1511
1517
  .w-1{
1512
1518
  width: 0.25rem;
1513
1519
  }
@@ -1939,6 +1945,9 @@ video {
1939
1945
  .gap-\[2px\]{
1940
1946
  gap: 2px;
1941
1947
  }
1948
+ .gap-px{
1949
+ gap: 1px;
1950
+ }
1942
1951
  .-space-x-2 > :not([hidden]) ~ :not([hidden]){
1943
1952
  --tw-space-x-reverse: 0;
1944
1953
  margin-right: calc(-0.5rem * var(--tw-space-x-reverse));
@@ -2093,6 +2102,10 @@ video {
2093
2102
  border-bottom-right-radius: 0.25rem;
2094
2103
  border-bottom-left-radius: 0.25rem;
2095
2104
  }
2105
+ .rounded-b-\[4px\]{
2106
+ border-bottom-right-radius: 4px;
2107
+ border-bottom-left-radius: 4px;
2108
+ }
2096
2109
  .rounded-b-lg{
2097
2110
  border-bottom-right-radius: 0.5rem;
2098
2111
  border-bottom-left-radius: 0.5rem;
@@ -2121,6 +2134,10 @@ video {
2121
2134
  border-top-left-radius: 1rem;
2122
2135
  border-top-right-radius: 1rem;
2123
2136
  }
2137
+ .rounded-t-\[4px\]{
2138
+ border-top-left-radius: 4px;
2139
+ border-top-right-radius: 4px;
2140
+ }
2124
2141
  .rounded-t-lg{
2125
2142
  border-top-left-radius: 0.5rem;
2126
2143
  border-top-right-radius: 0.5rem;
@@ -2129,6 +2146,9 @@ video {
2129
2146
  border-top-left-radius: 0.375rem;
2130
2147
  border-top-right-radius: 0.375rem;
2131
2148
  }
2149
+ .\!border-2{
2150
+ border-width: 2px !important;
2151
+ }
2132
2152
  .border{
2133
2153
  border-width: 1px;
2134
2154
  }
@@ -2392,6 +2412,9 @@ video {
2392
2412
  .bg-primary{
2393
2413
  background-color: var(--bfc-primary);
2394
2414
  }
2415
+ .bg-red{
2416
+ background-color: var(--bfc-v2-color-red-700, #c9372c);
2417
+ }
2395
2418
  .bg-red-100{
2396
2419
  background-color: var(--bfc-v2-color-red-100, #ffeceb);
2397
2420
  }
@@ -3080,11 +3103,6 @@ video {
3080
3103
  .outline-lime-200{
3081
3104
  outline-color: var(--bfc-v2-color-lime-200, #d6f589);
3082
3105
  }
3083
- .\!ring-0{
3084
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color) !important;
3085
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color) !important;
3086
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) !important;
3087
- }
3088
3106
  .ring-0{
3089
3107
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
3090
3108
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -3250,6 +3268,9 @@ video {
3250
3268
  .ease-out{
3251
3269
  animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
3252
3270
  }
3271
+ .running{
3272
+ animation-play-state: running;
3273
+ }
3253
3274
  .\@container{
3254
3275
  container-type: inline-size;
3255
3276
  }
@@ -3331,6 +3352,14 @@ video {
3331
3352
  pointer-events: none;
3332
3353
  border-radius: inherit;
3333
3354
  z-index: 0;
3355
+ }
3356
+ .bg-yellowfin-gradient{
3357
+ border: 1.2px solid transparent;
3358
+ background: linear-gradient(145.85deg, #1868DB 7.35%, #357DE8 23.69%, #7EE2B8 61.42%, #B4EE2B 82.59%),
3359
+ linear-gradient(145.85deg, rgba(180, 238, 43, 0.7) 3.02%, rgba(126, 226, 184, 0.7) 24.19%, rgba(53, 125, 232, 0.7) 61.92%, rgba(24, 104, 219, 0.7) 78.26%);
3360
+ transition: all 0.2s ease-in-out;
3361
+ background-origin: border-box;
3362
+ background-clip: content-box, border-box;
3334
3363
  }
3335
3364
  .shimmer {
3336
3365
  animation: shimmer 3s infinite linear;
@@ -3588,14 +3617,12 @@ strong {
3588
3617
  color: var(--bfc-foreground);
3589
3618
  }
3590
3619
 
3591
- .placeholder\:text-gray-400::-moz-placeholder{
3592
- --tw-text-opacity: 1;
3593
- color: rgb(156 163 175 / var(--tw-text-opacity, 1));
3620
+ .placeholder\:text-destructive::-moz-placeholder{
3621
+ color: var(--bfc-destructive-text);
3594
3622
  }
3595
3623
 
3596
- .placeholder\:text-gray-400::placeholder{
3597
- --tw-text-opacity: 1;
3598
- color: rgb(156 163 175 / var(--tw-text-opacity, 1));
3624
+ .placeholder\:text-destructive::placeholder{
3625
+ color: var(--bfc-destructive-text);
3599
3626
  }
3600
3627
 
3601
3628
  .placeholder\:text-muted-foreground::-moz-placeholder{
@@ -3606,6 +3633,14 @@ strong {
3606
3633
  color: var(--bfc-muted-foreground);
3607
3634
  }
3608
3635
 
3636
+ .placeholder\:text-warning::-moz-placeholder{
3637
+ color: var(--bfc-warning-text);
3638
+ }
3639
+
3640
+ .placeholder\:text-warning::placeholder{
3641
+ color: var(--bfc-warning-text);
3642
+ }
3643
+
3609
3644
  .after\:absolute::after{
3610
3645
  content: var(--tw-content);
3611
3646
  position: absolute;
@@ -1,5 +1,6 @@
1
1
  const Features = {
2
- HistoryTab: "enableHistoryTab"
2
+ HistoryTab: "enableHistoryTab",
3
+ TranscribeEnabled: "help-center.transcribe.enabled"
3
4
  };
4
5
 
5
6
  export { Features };
@@ -18,6 +18,11 @@ let globalFallbackConfig = { ...defaultFallbackConfig };
18
18
  async function delay(ms) {
19
19
  return new Promise((resolve) => setTimeout(resolve, ms));
20
20
  }
21
+ function withoutContentType(headers) {
22
+ const result = new Headers(headers);
23
+ result.delete("Content-Type");
24
+ return result;
25
+ }
21
26
  async function makeRequest(url, config = {}) {
22
27
  const { retry = {}, headers = {}, body, isStream = false, fallback = {}, ...fetchConfig } = config;
23
28
  const retryOptions = {
@@ -29,15 +34,16 @@ async function makeRequest(url, config = {}) {
29
34
  ...fallback
30
35
  };
31
36
  let attempt = 1;
37
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
32
38
  while (true) {
33
39
  try {
34
40
  const response = await fetch(url, {
35
41
  ...fetchConfig,
36
- headers: {
42
+ headers: isFormData ? withoutContentType(headers) : {
37
43
  "Content-Type": "application/json",
38
44
  ...headers
39
45
  },
40
- body: body ? JSON.stringify(body) : void 0
46
+ body: isFormData ? body : body ? JSON.stringify(body) : void 0
41
47
  });
42
48
  if (response.ok) {
43
49
  if (isStream) {
@@ -1,4 +1,5 @@
1
1
  export declare const Features: {
2
2
  readonly UseSseForAnswers: "use-sse-for-answers";
3
3
  readonly HistoryTab: "enableHistoryTab";
4
+ readonly TranscribeEnabled: "help-center.transcribe.enabled";
4
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainfish-ai/components",
3
- "version": "0.36.1",
3
+ "version": "0.37.1",
4
4
  "description": "Brainfish Components",
5
5
  "license": "MIT",
6
6
  "main": "./dist/esm/index.js",
@@ -185,6 +185,7 @@
185
185
  "@radix-ui/react-switch": "^1.1.2",
186
186
  "@radix-ui/react-tabs": "^1.1.21",
187
187
  "@radix-ui/react-tooltip": "^1.2.8",
188
+ "@reactuses/core": "^6.6.1",
188
189
  "@tanstack/react-table": "^8.21.3",
189
190
  "ajv": "^8.20.0",
190
191
  "ajv-errors": "^3.0.0",
@@ -224,6 +225,7 @@
224
225
  "y-prosemirror": "^1.3.7",
225
226
  "y-protocols": "^1.0.6",
226
227
  "y-webrtc": "^10.3.0",
227
- "yjs": "^13.6.27"
228
+ "yjs": "^13.6.27",
229
+ "zod": "^3.25.76"
228
230
  }
229
231
  }