add query settings
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
||||
import { Checkbox } from '@/components/ui/Checkbox'
|
||||
import Checkbox from '@/components/ui/Checkbox'
|
||||
import Button from '@/components/ui/Button'
|
||||
import Separator from '@/components/ui/Separator'
|
||||
import Input from '@/components/ui/Input'
|
||||
|
279
lightrag_webui/src/components/retrieval/QuerySettings.tsx
Normal file
279
lightrag_webui/src/components/retrieval/QuerySettings.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
import { useCallback } from 'react'
|
||||
import { QueryMode, QueryRequest } from '@/api/lightrag'
|
||||
import Text from '@/components/ui/Text'
|
||||
import Input from '@/components/ui/Input'
|
||||
import Checkbox from '@/components/ui/Checkbox'
|
||||
import NumberInput from '@/components/ui/NumberInput'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/Select'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
|
||||
export default function QuerySettings() {
|
||||
const querySettings = useSettingsStore((state) => state.querySettings)
|
||||
|
||||
const handleChange = useCallback((key: keyof QueryRequest, value: any) => {
|
||||
useSettingsStore.getState().updateQuerySettings({ [key]: value })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card className="flex shrink-0 flex-col">
|
||||
<CardHeader className="px-4 pt-4 pb-2">
|
||||
<CardTitle>Parameters</CardTitle>
|
||||
<CardDescription>Configure your query parameters</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="m-0 flex grow flex-col p-0 text-xs">
|
||||
<div className="relative size-full">
|
||||
<div className="absolute inset-0 flex flex-col gap-2 overflow-auto px-2">
|
||||
{/* Query Mode */}
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Query Mode"
|
||||
tooltip="Select the retrieval strategy:\n• Naive: Basic search without advanced techniques\n• Local: Context-dependent information retrieval\n• Global: Utilizes global knowledge base\n• Hybrid: Combines local and global retrieval\n• Mix: Integrates knowledge graph with vector retrieval"
|
||||
side="left"
|
||||
/>
|
||||
<Select
|
||||
value={querySettings.mode}
|
||||
onValueChange={(v) => handleChange('mode', v as QueryMode)}
|
||||
>
|
||||
<SelectTrigger className="hover:bg-primary/5 h-9 cursor-pointer focus:ring-0 focus:ring-offset-0 focus:outline-0 active:right-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="naive">Naive</SelectItem>
|
||||
<SelectItem value="local">Local</SelectItem>
|
||||
<SelectItem value="global">Global</SelectItem>
|
||||
<SelectItem value="hybrid">Hybrid</SelectItem>
|
||||
<SelectItem value="mix">Mix</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
|
||||
{/* Response Format */}
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Response Format"
|
||||
tooltip="Defines the response format. Examples:\n• Multiple Paragraphs\n• Single Paragraph\n• Bullet Points"
|
||||
side="left"
|
||||
/>
|
||||
<Select
|
||||
value={querySettings.response_type}
|
||||
onValueChange={(v) => handleChange('response_type', v)}
|
||||
>
|
||||
<SelectTrigger className="hover:bg-primary/5 h-9 cursor-pointer focus:ring-0 focus:ring-offset-0 focus:outline-0 active:right-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="Multiple Paragraphs">Single Paragraph</SelectItem>
|
||||
<SelectItem value="Single Paragraph">Single Paragraph</SelectItem>
|
||||
<SelectItem value="Bullet Points">Bullet Points</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
|
||||
{/* Top K */}
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Top K Results"
|
||||
tooltip="Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode"
|
||||
side="left"
|
||||
/>
|
||||
<NumberInput
|
||||
id="top_k"
|
||||
stepper={1}
|
||||
value={querySettings.top_k}
|
||||
onValueChange={(v) => handleChange('top_k', v)}
|
||||
min={1}
|
||||
placeholder="Number of results"
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* Max Tokens */}
|
||||
<>
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Max Tokens for Text Unit"
|
||||
tooltip="Maximum number of tokens allowed for each retrieved text chunk"
|
||||
side="left"
|
||||
/>
|
||||
<NumberInput
|
||||
id="max_token_for_text_unit"
|
||||
stepper={500}
|
||||
value={querySettings.max_token_for_text_unit}
|
||||
onValueChange={(v) => handleChange('max_token_for_text_unit', v)}
|
||||
min={1}
|
||||
placeholder="Max tokens for text unit"
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<Text
|
||||
text="Max Tokens for Global Context"
|
||||
tooltip="Maximum number of tokens allocated for relationship descriptions in global retrieval"
|
||||
side="left"
|
||||
/>
|
||||
<NumberInput
|
||||
id="max_token_for_global_context"
|
||||
stepper={500}
|
||||
value={querySettings.max_token_for_global_context}
|
||||
onValueChange={(v) => handleChange('max_token_for_global_context', v)}
|
||||
min={1}
|
||||
placeholder="Max tokens for global context"
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Max Tokens for Local Context"
|
||||
tooltip="Maximum number of tokens allocated for entity descriptions in local retrieval"
|
||||
side="left"
|
||||
/>
|
||||
<NumberInput
|
||||
id="max_token_for_local_context"
|
||||
stepper={500}
|
||||
value={querySettings.max_token_for_local_context}
|
||||
onValueChange={(v) => handleChange('max_token_for_local_context', v)}
|
||||
min={1}
|
||||
placeholder="Max tokens for local context"
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
|
||||
{/* History Turns */}
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="History Turns"
|
||||
tooltip="Number of complete conversation turns (user-assistant pairs) to consider in the response context"
|
||||
side="left"
|
||||
/>
|
||||
<NumberInput
|
||||
className="!border-input"
|
||||
id="history_turns"
|
||||
stepper={1}
|
||||
type="text"
|
||||
value={querySettings.history_turns}
|
||||
onValueChange={(v) => handleChange('history_turns', v)}
|
||||
min={0}
|
||||
placeholder="Number of history turns"
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* Keywords */}
|
||||
<>
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="High-Level Keywords"
|
||||
tooltip="List of high-level keywords to prioritize in retrieval. Separate with commas"
|
||||
side="left"
|
||||
/>
|
||||
<Input
|
||||
id="hl_keywords"
|
||||
type="text"
|
||||
value={querySettings.hl_keywords?.join(', ')}
|
||||
onChange={(e) => {
|
||||
const keywords = e.target.value
|
||||
.split(',')
|
||||
.map((k) => k.trim())
|
||||
.filter((k) => k !== '')
|
||||
handleChange('hl_keywords', keywords)
|
||||
}}
|
||||
placeholder="Enter keywords"
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Low-Level Keywords"
|
||||
tooltip="List of low-level keywords to refine retrieval focus. Separate with commas"
|
||||
side="left"
|
||||
/>
|
||||
<Input
|
||||
id="ll_keywords"
|
||||
type="text"
|
||||
value={querySettings.ll_keywords?.join(', ')}
|
||||
onChange={(e) => {
|
||||
const keywords = e.target.value
|
||||
.split(',')
|
||||
.map((k) => k.trim())
|
||||
.filter((k) => k !== '')
|
||||
handleChange('ll_keywords', keywords)
|
||||
}}
|
||||
placeholder="Enter keywords"
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
|
||||
{/* Toggle Options */}
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Only Need Context"
|
||||
tooltip="If True, only returns the retrieved context without generating a response"
|
||||
side="left"
|
||||
/>
|
||||
<div className="grow" />
|
||||
<Checkbox
|
||||
className="mr-1 cursor-pointer"
|
||||
id="only_need_context"
|
||||
checked={querySettings.only_need_context}
|
||||
onCheckedChange={(checked) => handleChange('only_need_context', checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Only Need Prompt"
|
||||
tooltip="If True, only returns the generated prompt without producing a response"
|
||||
side="left"
|
||||
/>
|
||||
<div className="grow" />
|
||||
<Checkbox
|
||||
className="mr-1 cursor-pointer"
|
||||
id="only_need_prompt"
|
||||
checked={querySettings.only_need_prompt}
|
||||
onCheckedChange={(checked) => handleChange('only_need_prompt', checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Text
|
||||
className="ml-1"
|
||||
text="Stream Response"
|
||||
tooltip="If True, enables streaming output for real-time responses"
|
||||
side="left"
|
||||
/>
|
||||
<div className="grow" />
|
||||
<Checkbox
|
||||
className="mr-1 cursor-pointer"
|
||||
id="stream"
|
||||
checked={querySettings.stream}
|
||||
onCheckedChange={(checked) => handleChange('stream', checked)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
@@ -23,4 +23,4 @@ const Checkbox = React.forwardRef<
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
export { Checkbox }
|
||||
export default Checkbox
|
||||
|
131
lightrag_webui/src/components/ui/NumberInput.tsx
Normal file
131
lightrag_webui/src/components/ui/NumberInput.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { forwardRef, useCallback, useEffect, useState } from 'react'
|
||||
import { NumericFormat, NumericFormatProps } from 'react-number-format'
|
||||
import Button from '@/components/ui/Button'
|
||||
import Input from '@/components/ui/Input'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface NumberInputProps extends Omit<NumericFormatProps, 'value' | 'onValueChange'> {
|
||||
stepper?: number
|
||||
thousandSeparator?: string
|
||||
placeholder?: string
|
||||
defaultValue?: number
|
||||
min?: number
|
||||
max?: number
|
||||
value?: number // Controlled value
|
||||
suffix?: string
|
||||
prefix?: string
|
||||
onValueChange?: (value: number | undefined) => void
|
||||
fixedDecimalScale?: boolean
|
||||
decimalScale?: number
|
||||
}
|
||||
|
||||
const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
||||
(
|
||||
{
|
||||
stepper,
|
||||
thousandSeparator,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
min = -Infinity,
|
||||
max = Infinity,
|
||||
onValueChange,
|
||||
fixedDecimalScale = false,
|
||||
decimalScale = 0,
|
||||
className = undefined,
|
||||
suffix,
|
||||
prefix,
|
||||
value: controlledValue,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [value, setValue] = useState<number | undefined>(controlledValue ?? defaultValue)
|
||||
|
||||
const handleIncrement = useCallback(() => {
|
||||
setValue((prev) =>
|
||||
prev === undefined ? (stepper ?? 1) : Math.min(prev + (stepper ?? 1), max)
|
||||
)
|
||||
}, [stepper, max])
|
||||
|
||||
const handleDecrement = useCallback(() => {
|
||||
setValue((prev) =>
|
||||
prev === undefined ? -(stepper ?? 1) : Math.max(prev - (stepper ?? 1), min)
|
||||
)
|
||||
}, [stepper, min])
|
||||
|
||||
useEffect(() => {
|
||||
if (controlledValue !== undefined) {
|
||||
setValue(controlledValue)
|
||||
}
|
||||
}, [controlledValue])
|
||||
|
||||
const handleChange = (values: { value: string; floatValue: number | undefined }) => {
|
||||
const newValue = values.floatValue === undefined ? undefined : values.floatValue
|
||||
setValue(newValue)
|
||||
if (onValueChange) {
|
||||
onValueChange(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
if (value !== undefined) {
|
||||
if (value < min) {
|
||||
setValue(min)
|
||||
;(ref as React.RefObject<HTMLInputElement>).current!.value = String(min)
|
||||
} else if (value > max) {
|
||||
setValue(max)
|
||||
;(ref as React.RefObject<HTMLInputElement>).current!.value = String(max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex">
|
||||
<NumericFormat
|
||||
value={value}
|
||||
onValueChange={handleChange}
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalScale={decimalScale}
|
||||
fixedDecimalScale={fixedDecimalScale}
|
||||
allowNegative={min < 0}
|
||||
valueIsNumericString
|
||||
onBlur={handleBlur}
|
||||
max={max}
|
||||
min={min}
|
||||
suffix={suffix}
|
||||
prefix={prefix}
|
||||
customInput={(props) => <Input {...props} className={cn('w-full', className)} />}
|
||||
placeholder={placeholder}
|
||||
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||
getInputRef={ref}
|
||||
{...props}
|
||||
/>
|
||||
<div className="absolute top-0 right-0 bottom-0 flex flex-col">
|
||||
<Button
|
||||
aria-label="Increase value"
|
||||
className="border-input h-1/2 rounded-l-none rounded-br-none border-b border-l px-2 focus-visible:relative"
|
||||
variant="outline"
|
||||
onClick={handleIncrement}
|
||||
disabled={value === max}
|
||||
>
|
||||
<ChevronUp size={15} />
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Decrease value"
|
||||
className="border-input h-1/2 rounded-l-none rounded-tr-none border-b border-l px-2 focus-visible:relative"
|
||||
variant="outline"
|
||||
onClick={handleDecrement}
|
||||
disabled={value === min}
|
||||
>
|
||||
<ChevronDown size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
NumberInput.displayName = 'NumberInput'
|
||||
|
||||
export default NumberInput
|
151
lightrag_webui/src/components/ui/Select.tsx
Normal file
151
lightrag_webui/src/components/ui/Select.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import * as React from 'react'
|
||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = 'popper', ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn('py-1.5 pr-2 pl-8 text-sm font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ComponentRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton
|
||||
}
|
@@ -8,19 +8,31 @@ const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const processTooltipContent = (content: string) => {
|
||||
if (typeof content !== 'string') return content
|
||||
return content.split('\\n').map((line, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{line}
|
||||
{i < content.split('\\n').length - 1 && <br />}
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ComponentRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
>(({ className, sideOffset = 4, children, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 mx-1 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
|
||||
'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 mx-1 max-w-sm overflow-hidden rounded-md border px-3 py-2 text-sm shadow-md',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
{typeof children === 'string' ? processTooltipContent(children) : children}
|
||||
</TooltipPrimitive.Content>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
|
Reference in New Issue
Block a user