1- import { PencilSquareIcon , PlusIcon , SparklesIcon } from "@heroicons/react/20/solid" ;
1+ import { CheckIcon , PencilSquareIcon , PlusIcon , XMarkIcon } from "@heroicons/react/20/solid" ;
22import { AnimatePresence , motion } from "framer-motion" ;
33import { Suspense , lazy , useCallback , useEffect , useRef , useState } from "react" ;
4- import { AISparkleIcon } from "~/assets/icons/AISparkleIcon" ;
4+ import { Button } from "~/components/primitives/Buttons" ;
5+ import { Spinner } from "~/components/primitives/Spinner" ;
6+ import { useEnvironment } from "~/hooks/useEnvironment" ;
7+ import { useOrganization } from "~/hooks/useOrganizations" ;
8+ import { useProject } from "~/hooks/useProject" ;
9+ import type { AITimeFilter } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/types" ;
10+ import { cn } from "~/utils/cn" ;
511
612// Lazy load streamdown components to avoid SSR issues
713const StreamdownRenderer = lazy ( ( ) =>
@@ -13,13 +19,6 @@ const StreamdownRenderer = lazy(() =>
1319 ) ,
1420 } ) )
1521) ;
16- import { Button } from "~/components/primitives/Buttons" ;
17- import { Spinner } from "~/components/primitives/Spinner" ;
18- import { useEnvironment } from "~/hooks/useEnvironment" ;
19- import { useOrganization } from "~/hooks/useOrganizations" ;
20- import { useProject } from "~/hooks/useProject" ;
21- import type { AITimeFilter } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/types" ;
22- import { cn } from "~/utils/cn" ;
2322
2423type StreamEventType =
2524 | { type : "thinking" ; content : string }
@@ -179,21 +178,7 @@ export function AIQueryInput({
179178 setThinking ( ( prev ) => prev + event . content ) ;
180179 break ;
181180 case "tool_call" :
182- if ( event . tool === "setTimeFilter" ) {
183- setThinking ( ( prev ) => {
184- if ( prev . trimEnd ( ) . endsWith ( "Setting time filter..." ) ) {
185- return prev ;
186- }
187- return prev + `\nSetting time filter...\n` ;
188- } ) ;
189- } else {
190- setThinking ( ( prev ) => {
191- if ( prev . trimEnd ( ) . endsWith ( "Validating query..." ) ) {
192- return prev ;
193- }
194- return prev + `\nValidating query...\n` ;
195- } ) ;
196- }
181+ // Tool calls are handled silently — no UI text needed
197182 break ;
198183 case "time_filter" :
199184 // Apply time filter immediately when the AI sets it
@@ -262,13 +247,13 @@ export function AIQueryInput({
262247 } , [ error ] ) ;
263248
264249 return (
265- < div className = "flex flex-col gap-3 " >
250+ < div className = "flex flex-col" >
266251 { /* Gradient border wrapper like the schedules AI input */ }
267252 < div
268- className = "rounded-md p-px"
253+ className = "overflow-hidden rounded-md p-px"
269254 style = { { background : "linear-gradient(to bottom right, #E543FF, #286399)" } }
270255 >
271- < div className = "overflow-hidden rounded-[5px] bg-background-bright" >
256+ < div className = "overflow-hidden rounded-md bg-background-bright" >
272257 < form onSubmit = { handleSubmit } >
273258 < textarea
274259 ref = { textareaRef }
@@ -297,10 +282,10 @@ export function AIQueryInput({
297282 variant = "tertiary/small"
298283 disabled = { true }
299284 LeadingIcon = { Spinner }
300- className = "pl-1.5 "
285+ className = "pl-2 "
301286 iconSpacing = "gap-1.5"
302287 >
303- { mode === "edit" ? "Editing... " : "Generating... " }
288+ { mode === "edit" ? "Editing… " : "Generating… " }
304289 </ Button >
305290 ) : (
306291 < >
@@ -366,64 +351,60 @@ export function AIQueryInput({
366351 transition = { { duration : 0.2 } }
367352 className = "overflow-hidden"
368353 >
369- < div className = "rounded-md border border-grid-dimmed bg-charcoal-850 p-3" >
370- < div className = "mb-2 flex items-center justify-between" >
371- < div className = "flex items-center gap-2" >
372- { isLoading ? (
373- < Spinner
374- color = { {
375- background : "rgba(99, 102, 241, 0.3)" ,
376- foreground : "rgba(99, 102, 241, 1)" ,
377- } }
378- className = "size-3"
379- />
380- ) : lastResult === "success" ? (
381- < div className = "size-3 rounded-full bg-success" />
382- ) : lastResult === "error" ? (
383- < div className = "size-3 rounded-full bg-error" />
384- ) : null }
385- < span className = "text-xs font-medium text-text-dimmed" >
386- { isLoading
387- ? "AI is thinking..."
388- : lastResult === "success"
354+ < div className = "px-1" >
355+ < div className = "rounded-b-lg border-x border-b border-grid-dimmed bg-charcoal-850 p-3 pb-1" >
356+ < div className = "mb-1 flex items-center justify-between" >
357+ < div className = "flex items-center gap-1" >
358+ { isLoading ? (
359+ < Spinner className = "size-4" />
360+ ) : lastResult === "success" ? (
361+ < CheckIcon className = "size-4 text-success" />
362+ ) : lastResult === "error" ? (
363+ < XMarkIcon className = "size-4 text-error" />
364+ ) : null }
365+ < span className = "text-xs font-medium text-text-dimmed" >
366+ { isLoading
367+ ? "AI is thinking…"
368+ : lastResult === "success"
389369 ? "Query generated"
390370 : lastResult === "error"
391- ? "Generation failed"
392- : "AI response" }
393- </ span >
371+ ? "Generation failed"
372+ : "AI response" }
373+ </ span >
374+ </ div >
375+ { isLoading ? (
376+ < Button
377+ variant = "minimal/small"
378+ onClick = { ( ) => {
379+ if ( abortControllerRef . current ) {
380+ abortControllerRef . current . abort ( ) ;
381+ }
382+ setIsLoading ( false ) ;
383+ setShowThinking ( false ) ;
384+ setThinking ( "" ) ;
385+ } }
386+ className = "text-xs"
387+ >
388+ Cancel
389+ </ Button >
390+ ) : (
391+ < Button
392+ variant = "minimal/small"
393+ onClick = { ( ) => {
394+ setShowThinking ( false ) ;
395+ setThinking ( "" ) ;
396+ } }
397+ className = "text-xs"
398+ >
399+ Dismiss
400+ </ Button >
401+ ) }
402+ </ div >
403+ < div className = "streamdown-container max-h-96 overflow-y-auto text-xs text-text-dimmed scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" >
404+ < Suspense fallback = { < p className = "whitespace-pre-wrap" > { thinking } </ p > } >
405+ < StreamdownRenderer isAnimating = { isLoading } > { thinking } </ StreamdownRenderer >
406+ </ Suspense >
394407 </ div >
395- { isLoading ? (
396- < Button
397- variant = "minimal/small"
398- onClick = { ( ) => {
399- if ( abortControllerRef . current ) {
400- abortControllerRef . current . abort ( ) ;
401- }
402- setIsLoading ( false ) ;
403- setShowThinking ( false ) ;
404- setThinking ( "" ) ;
405- } }
406- className = "text-xs"
407- >
408- Cancel
409- </ Button >
410- ) : (
411- < Button
412- variant = "minimal/small"
413- onClick = { ( ) => {
414- setShowThinking ( false ) ;
415- setThinking ( "" ) ;
416- } }
417- className = "text-xs"
418- >
419- Dismiss
420- </ Button >
421- ) }
422- </ div >
423- < div className = "streamdown-container max-h-96 overflow-y-auto text-xs text-text-dimmed scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" >
424- < Suspense fallback = { < p className = "whitespace-pre-wrap" > { thinking } </ p > } >
425- < StreamdownRenderer isAnimating = { isLoading } > { thinking } </ StreamdownRenderer >
426- </ Suspense >
427408 </ div >
428409 </ div >
429410 </ motion . div >
0 commit comments