@@ -225,6 +225,7 @@ const AI_DOCK_DEFAULT_WIDTH = 360
225225const AI_DOCK_MAX_WIDTH_RATIO = 0.5
226226const AI_DOCK_MAXIMIZED_WIDTH = 1200
227227const DEFAULT_SEARCH_FRAMEWORK : Framework = 'react'
228+ const KAPA_RECAPTCHA_READY_TIMEOUT_MS = 8_000
228229
229230type SearchSurface = 'modal' | 'dock'
230231type AiDockStyle = React . CSSProperties & {
@@ -268,6 +269,71 @@ function writeAiDockWidth(width: number) {
268269 localStorage . setItem ( AI_DOCK_WIDTH_STORAGE_KEY , String ( Math . round ( width ) ) )
269270}
270271
272+ function waitForKapaRecaptchaReady ( ) {
273+ if ( typeof window === 'undefined' ) {
274+ return Promise . resolve ( )
275+ }
276+
277+ return new Promise < void > ( ( resolve ) => {
278+ let timeoutId : number | null = null
279+ let intervalId : number | null = null
280+ let isSettled = false
281+ let hasRegisteredReadyCallback = false
282+
283+ const cleanup = ( ) => {
284+ if ( timeoutId !== null ) {
285+ window . clearTimeout ( timeoutId )
286+ timeoutId = null
287+ }
288+
289+ if ( intervalId !== null ) {
290+ window . clearInterval ( intervalId )
291+ intervalId = null
292+ }
293+ }
294+
295+ const finish = ( ) => {
296+ if ( isSettled ) {
297+ return
298+ }
299+
300+ isSettled = true
301+ cleanup ( )
302+ window . setTimeout ( resolve , 0 )
303+ }
304+
305+ const checkReady = ( ) => {
306+ if ( hasRegisteredReadyCallback ) {
307+ return true
308+ }
309+
310+ const enterprise = window . grecaptcha ?. enterprise
311+
312+ if ( ! enterprise ) {
313+ return false
314+ }
315+
316+ hasRegisteredReadyCallback = true
317+
318+ if ( intervalId !== null ) {
319+ window . clearInterval ( intervalId )
320+ intervalId = null
321+ }
322+
323+ enterprise . ready ( finish )
324+ return true
325+ }
326+
327+ timeoutId = window . setTimeout ( finish , KAPA_RECAPTCHA_READY_TIMEOUT_MS )
328+
329+ if ( checkReady ( ) ) {
330+ return
331+ }
332+
333+ intervalId = window . setInterval ( checkReady , 50 )
334+ } )
335+ }
336+
271337function buildSearchFilters ( {
272338 selectedLibrary,
273339 selectedFramework,
@@ -627,10 +693,107 @@ function KapaMarkdownLink({
627693 )
628694}
629695
696+ function KapaMarkdownTable ( {
697+ children,
698+ className,
699+ ...props
700+ } : React . TableHTMLAttributes < HTMLTableElement > ) {
701+ return (
702+ < div className = "my-3 max-w-full overflow-x-auto rounded-xl border border-gray-200 bg-white dark:border-white/[0.12] dark:bg-black/20" >
703+ < table
704+ className = { twMerge (
705+ className ,
706+ 'w-full min-w-[34rem] border-collapse text-left' ,
707+ ) }
708+ { ...props }
709+ >
710+ { children }
711+ </ table >
712+ </ div >
713+ )
714+ }
715+
716+ function KapaMarkdownTableHead ( {
717+ children,
718+ className,
719+ ...props
720+ } : React . HTMLAttributes < HTMLTableSectionElement > ) {
721+ return (
722+ < thead
723+ className = { twMerge (
724+ className ,
725+ 'border-b border-gray-200 bg-gray-50/80 dark:border-white/[0.12] dark:bg-white/[0.04]' ,
726+ ) }
727+ { ...props }
728+ >
729+ { children }
730+ </ thead >
731+ )
732+ }
733+
734+ function KapaMarkdownTableBody ( {
735+ children,
736+ className,
737+ ...props
738+ } : React . HTMLAttributes < HTMLTableSectionElement > ) {
739+ return (
740+ < tbody
741+ className = { twMerge (
742+ className ,
743+ '[&_tr:last-child_td]:border-b-0 [&_tr:last-child_th]:border-b-0' ,
744+ ) }
745+ { ...props }
746+ >
747+ { children }
748+ </ tbody >
749+ )
750+ }
751+
752+ function KapaMarkdownTableCell ( {
753+ children,
754+ className,
755+ ...props
756+ } : React . TdHTMLAttributes < HTMLTableCellElement > ) {
757+ return (
758+ < td
759+ className = { twMerge (
760+ className ,
761+ 'border-b border-l border-gray-200/80 px-3 py-2 align-top first:border-l-0 dark:border-white/[0.08]' ,
762+ ) }
763+ { ...props }
764+ >
765+ { children }
766+ </ td >
767+ )
768+ }
769+
770+ function KapaMarkdownTableHeaderCell ( {
771+ children,
772+ className,
773+ ...props
774+ } : React . ThHTMLAttributes < HTMLTableCellElement > ) {
775+ return (
776+ < th
777+ className = { twMerge (
778+ className ,
779+ 'border-b border-l border-gray-200 px-3 py-2 font-semibold align-top first:border-l-0 dark:border-white/[0.1]' ,
780+ ) }
781+ { ...props }
782+ >
783+ { children }
784+ </ th >
785+ )
786+ }
787+
630788const streamdownComponents = {
631789 pre : CodeBlock ,
632790 code : InlineCode ,
633791 a : KapaMarkdownLink ,
792+ table : KapaMarkdownTable ,
793+ thead : KapaMarkdownTableHead ,
794+ tbody : KapaMarkdownTableBody ,
795+ td : KapaMarkdownTableCell ,
796+ th : KapaMarkdownTableHeaderCell ,
634797 ul : ( { children } : { children ?: React . ReactNode } ) => (
635798 < ul className = "list-disc list-outside pl-5 space-y-1 my-2" > { children } </ ul >
636799 ) ,
@@ -699,13 +862,10 @@ class KapaThreadOverrideApiService {
699862 args : KapaSubmitQueryArgs ,
700863 callbacks : KapaChatStreamCallbacks ,
701864 ) : Promise < void > {
702- const threadIdOverride = this . threadIdOverrideRef . current
703- const nextArgs =
704- threadIdOverride && ! args . threadId
705- ? { ...args , threadId : threadIdOverride }
706- : args
707-
708- return this . service . submitQuery ( nextArgs , callbacks )
865+ return this . service . submitQuery (
866+ { ...args , threadId : this . threadIdOverrideRef . current } ,
867+ callbacks ,
868+ )
709869 }
710870
711871 addFeedback ( args : KapaSubmitFeedbackArgs ) : Promise < void > {
@@ -1630,6 +1790,7 @@ function KapaChatPanel({
16301790 const isBusy = isGeneratingAnswer || isPreparingAnswer
16311791 const isDock = surface === 'dock'
16321792 const isSubmittingRef = React . useRef ( false )
1793+ const pendingSubmitIdRef = React . useRef ( 0 )
16331794 const lockedToBottom = React . useRef ( true )
16341795 const handledNewChatRequestId = React . useRef ( newChatRequestId )
16351796 const handledAiDockAskRequestId = React . useRef ( 0 )
@@ -1709,8 +1870,17 @@ function KapaChatPanel({
17091870 return
17101871 }
17111872
1873+ const submitId = pendingSubmitIdRef . current + 1
1874+ pendingSubmitIdRef . current = submitId
17121875 isSubmittingRef . current = true
1713- submitQuery ( trimmed )
1876+
1877+ waitForKapaRecaptchaReady ( ) . then ( ( ) => {
1878+ if ( pendingSubmitIdRef . current !== submitId ) {
1879+ return
1880+ }
1881+
1882+ submitQuery ( trimmed )
1883+ } )
17141884 } ,
17151885 [ isBusy , submitQuery ] ,
17161886 )
@@ -1730,6 +1900,8 @@ function KapaChatPanel({
17301900 )
17311901
17321902 const clearActiveChat = React . useCallback ( ( ) => {
1903+ pendingSubmitIdRef . current += 1
1904+ isSubmittingRef . current = false
17331905 resetConversation ( )
17341906 threadIdOverrideRef . current = null
17351907 setSelectedHistoryItem ( null )
@@ -1770,17 +1942,16 @@ function KapaChatPanel({
17701942
17711943 handledAiDockAskRequestId . current = aiDockAskRequest . id
17721944 clearActiveChat ( )
1773- isSubmittingRef . current = true
1774- submitQuery ( aiDockAskRequest . question )
1945+ ask ( aiDockAskRequest . question )
17751946 clearAiDockAskRequest ( aiDockAskRequest . id )
17761947 } , [
1948+ ask ,
17771949 aiDockAskRequest ,
17781950 clearActiveChat ,
17791951 clearAiDockAskRequest ,
17801952 isBusy ,
17811953 isDock ,
17821954 stopGeneration ,
1783- submitQuery ,
17841955 ] )
17851956
17861957 return (
0 commit comments