mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-07 22:24:06 -05:00
improvement(ui): updated kb tag component to match existing table (#2498)
* improvement(ui): updated kb tag component to match existing table * fix selection * fix more ui --------- Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
This commit is contained in:
@@ -1,18 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverItem,
|
||||
PopoverScrollArea,
|
||||
Trash,
|
||||
} from '@/components/emcn'
|
||||
import { Button, Combobox, type ComboboxOption, Label, Trash } from '@/components/emcn'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { FIELD_TYPE_LABELS, getPlaceholderForFieldType } from '@/lib/knowledge/constants'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
|
||||
@@ -74,9 +65,6 @@ export function DocumentTagEntry({
|
||||
|
||||
const emitTagSelection = useTagSelection(blockId, subBlock.id)
|
||||
|
||||
// State for tag name dropdown visibility - one for each row
|
||||
const [dropdownStates, setDropdownStates] = useState<Record<number, boolean>>({})
|
||||
|
||||
// Use preview value when in preview mode, otherwise use store value
|
||||
const currentValue = isPreview ? previewValue : storeValue
|
||||
|
||||
@@ -194,7 +182,13 @@ export function DocumentTagEntry({
|
||||
}
|
||||
|
||||
const handleDeleteRow = (rowIndex: number) => {
|
||||
if (isPreview || disabled || rows.length <= 1) return
|
||||
if (isPreview || disabled) return
|
||||
|
||||
if (rows.length <= 1) {
|
||||
// Clear the single row instead of deleting
|
||||
setStoreValue('')
|
||||
return
|
||||
}
|
||||
|
||||
const updatedRows = rows.filter((_, idx) => idx !== rowIndex)
|
||||
const tableDataForStorage = updatedRows.map((row) => ({
|
||||
@@ -227,88 +221,55 @@ export function DocumentTagEntry({
|
||||
|
||||
if (tagDefinitions.length === 0) {
|
||||
return (
|
||||
<div className='rounded-md border p-4 text-center text-muted-foreground text-sm'>
|
||||
No tags defined for this knowledge base.
|
||||
<br />
|
||||
Define tags at the knowledge base level first.
|
||||
<div className='flex h-32 items-center justify-center rounded-lg border border-muted-foreground/25 bg-muted/20'>
|
||||
<div className='text-center'>
|
||||
<p className='font-medium text-[var(--text-secondary)] text-sm'>
|
||||
No tags defined for this knowledge base
|
||||
</p>
|
||||
<p className='mt-1 text-[var(--text-muted)] text-xs'>
|
||||
Define tags at the knowledge base level first
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHeader = () => (
|
||||
<thead>
|
||||
<tr className='border-b'>
|
||||
<th className='w-2/5 border-r px-4 py-2 text-center font-medium text-sm'>Tag</th>
|
||||
<th className='border-r px-4 py-2 text-center font-medium text-sm'>Value</th>
|
||||
<th className='w-10' />
|
||||
<thead className='bg-transparent'>
|
||||
<tr className='border-[var(--border-strong)] border-b bg-transparent'>
|
||||
<th className='w-[50%] min-w-0 border-[var(--border-strong)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Tag
|
||||
</th>
|
||||
<th className='w-[50%] min-w-0 bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Value
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
)
|
||||
|
||||
const renderTagNameCell = (row: DocumentTagRow, rowIndex: number) => {
|
||||
const cellValue = row.cells.tagName || ''
|
||||
const isOpen = dropdownStates[rowIndex] || false
|
||||
|
||||
const setIsOpen = (open: boolean) => {
|
||||
setDropdownStates((prev) => ({ ...prev, [rowIndex]: open }))
|
||||
}
|
||||
|
||||
// Show tags that are either available OR currently selected for this row
|
||||
const selectableTags = tagDefinitions.filter(
|
||||
(def) => def.displayName === cellValue || !usedTagNames.has(def.displayName.toLowerCase())
|
||||
)
|
||||
|
||||
const tagOptions: ComboboxOption[] = selectableTags.map((tag) => ({
|
||||
value: tag.displayName,
|
||||
label: `${tag.displayName} (${FIELD_TYPE_LABELS[tag.fieldType] || 'Text'})`,
|
||||
}))
|
||||
|
||||
return (
|
||||
<td className='relative border-r p-1'>
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverAnchor asChild>
|
||||
<div
|
||||
className='relative w-full cursor-pointer'
|
||||
onClick={() => !disabled && setIsOpen(true)}
|
||||
>
|
||||
<Input
|
||||
value={cellValue}
|
||||
readOnly
|
||||
disabled={disabled}
|
||||
autoComplete='off'
|
||||
placeholder='Select tag'
|
||||
className='w-full cursor-pointer border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden bg-transparent px-[8px] font-medium font-sans text-sm'>
|
||||
<span className='truncate'>
|
||||
{cellValue || <span className='text-muted-foreground/50'>Select tag</span>}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverAnchor>
|
||||
{selectableTags.length > 0 && (
|
||||
<PopoverContent
|
||||
side='bottom'
|
||||
align='start'
|
||||
sideOffset={4}
|
||||
maxHeight={192}
|
||||
className='w-[200px]'
|
||||
>
|
||||
<PopoverScrollArea>
|
||||
{selectableTags.map((tagDef) => (
|
||||
<PopoverItem
|
||||
key={tagDef.id}
|
||||
active={tagDef.displayName === cellValue}
|
||||
onClick={() => {
|
||||
handleTagSelection(rowIndex, tagDef.displayName)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
<span className='flex-1 truncate'>{tagDef.displayName}</span>
|
||||
<span className='flex-shrink-0 rounded bg-muted px-1 py-0.5 text-[10px] text-muted-foreground'>
|
||||
{FIELD_TYPE_LABELS[tagDef.fieldType] || 'Text'}
|
||||
</span>
|
||||
</PopoverItem>
|
||||
))}
|
||||
</PopoverScrollArea>
|
||||
</PopoverContent>
|
||||
)}
|
||||
</Popover>
|
||||
<td className='relative min-w-0 overflow-hidden border-[var(--border-strong)] border-r bg-transparent p-0'>
|
||||
<Combobox
|
||||
options={tagOptions}
|
||||
value={cellValue}
|
||||
onChange={(value) => handleTagSelection(rowIndex, value)}
|
||||
disabled={disabled || isLoading}
|
||||
placeholder='Select tag'
|
||||
className='!border-0 !bg-transparent hover:!bg-transparent px-[10px] py-[8px] font-medium text-sm leading-[21px] focus-visible:ring-0 focus-visible:ring-offset-0 [&>span]:truncate'
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
@@ -333,7 +294,7 @@ export function DocumentTagEntry({
|
||||
)
|
||||
|
||||
return (
|
||||
<td className='p-1'>
|
||||
<td className='relative min-w-0 overflow-hidden bg-transparent p-0'>
|
||||
<div className='relative w-full'>
|
||||
<Input
|
||||
ref={(el) => {
|
||||
@@ -347,10 +308,10 @@ export function DocumentTagEntry({
|
||||
disabled={disabled || !isTagSelected}
|
||||
autoComplete='off'
|
||||
placeholder={isTagSelected ? placeholder : 'Select a tag first'}
|
||||
className='w-full border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
className='w-full border-0 bg-transparent px-[10px] py-[8px] font-medium text-sm text-transparent leading-[21px] caret-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden bg-transparent px-[8px] font-medium font-sans text-sm'>
|
||||
<div className='whitespace-pre'>
|
||||
<div className='scrollbar-hide pointer-events-none absolute top-0 right-[10px] bottom-0 left-[10px] overflow-x-auto overflow-y-hidden bg-transparent'>
|
||||
<div className='whitespace-pre py-[8px] font-medium text-[var(--text-primary)] text-sm leading-[21px]'>
|
||||
{formatDisplayText(cellValue, {
|
||||
accessiblePrefixes,
|
||||
highlightAll: !accessiblePrefixes,
|
||||
@@ -379,29 +340,32 @@ export function DocumentTagEntry({
|
||||
}
|
||||
|
||||
const renderDeleteButton = (rowIndex: number) => {
|
||||
const canDelete = !isPreview && !disabled
|
||||
if (isPreview || disabled) return null
|
||||
|
||||
return canDelete ? (
|
||||
<td className='w-10 p-1'>
|
||||
return (
|
||||
<td className='w-0 p-0'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-8 w-8 p-0 opacity-0 group-hover:opacity-100'
|
||||
className='-translate-y-1/2 absolute top-1/2 right-[8px] transition-opacity'
|
||||
onClick={() => handleDeleteRow(rowIndex)}
|
||||
>
|
||||
<Trash className='h-4 w-4 text-muted-foreground' />
|
||||
<Trash className='h-[14px] w-[14px]' />
|
||||
</Button>
|
||||
</td>
|
||||
) : null
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className='overflow-visible rounded-md border'>
|
||||
<table className='w-full'>
|
||||
<div className='relative w-full'>
|
||||
<div className='overflow-hidden rounded-[4px] border border-[var(--border-strong)] bg-[var(--surface-2)] dark:bg-[#1F1F1F]'>
|
||||
<table className='w-full table-fixed bg-transparent'>
|
||||
{renderHeader()}
|
||||
<tbody>
|
||||
<tbody className='bg-transparent'>
|
||||
{rows.map((row, rowIndex) => (
|
||||
<tr key={row.id} className='group relative border-t'>
|
||||
<tr
|
||||
key={row.id}
|
||||
className='group relative border-[var(--border-strong)] border-t bg-transparent'
|
||||
>
|
||||
{renderTagNameCell(row, rowIndex)}
|
||||
{renderValueCell(row, rowIndex)}
|
||||
{renderDeleteButton(rowIndex)}
|
||||
@@ -411,15 +375,10 @@ export function DocumentTagEntry({
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Add Row Button */}
|
||||
{/* Add Tag Button */}
|
||||
{!isPreview && !disabled && (
|
||||
<div className='mt-3'>
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handleAddRow}
|
||||
disabled={!canAddMoreTags}
|
||||
className='h-7 px-2 text-xs'
|
||||
>
|
||||
<Button onClick={handleAddRow} disabled={!canAddMoreTags} className='h-7 px-2 text-xs'>
|
||||
<Plus className='mr-1 h-2.5 w-2.5' />
|
||||
Add Tag
|
||||
</Button>
|
||||
|
||||
@@ -2,19 +2,8 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
Combobox,
|
||||
type ComboboxOption,
|
||||
Input,
|
||||
Label,
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverItem,
|
||||
PopoverScrollArea,
|
||||
Trash,
|
||||
} from '@/components/emcn'
|
||||
import { Button, Combobox, type ComboboxOption, Label, Trash } from '@/components/emcn'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { FIELD_TYPE_LABELS, getPlaceholderForFieldType } from '@/lib/knowledge/constants'
|
||||
import { type FilterFieldType, getOperatorsForFieldType } from '@/lib/knowledge/filters/types'
|
||||
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
|
||||
@@ -66,21 +55,15 @@ export function KnowledgeTagFilters({
|
||||
previewValue,
|
||||
}: KnowledgeTagFiltersProps) {
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<string | null>(blockId, subBlock.id)
|
||||
|
||||
// Hook for immediate tag/dropdown selections
|
||||
const emitTagSelection = useTagSelection(blockId, subBlock.id)
|
||||
|
||||
// Get the knowledge base ID from other sub-blocks
|
||||
const [knowledgeBaseIdValue] = useSubBlockValue(blockId, 'knowledgeBaseId')
|
||||
const knowledgeBaseId = knowledgeBaseIdValue || null
|
||||
|
||||
// Use KB tag definitions hook to get available tags
|
||||
const { tagDefinitions, isLoading } = useKnowledgeBaseTagDefinitions(knowledgeBaseId)
|
||||
|
||||
// Get accessible prefixes for variable highlighting
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
|
||||
// State for managing tag dropdown
|
||||
const [activeTagDropdown, setActiveTagDropdown] = useState<{
|
||||
rowIndex: number
|
||||
showTags: boolean
|
||||
@@ -89,15 +72,10 @@ export function KnowledgeTagFilters({
|
||||
element?: HTMLElement | null
|
||||
} | null>(null)
|
||||
|
||||
// State for dropdown visibility - one for each row
|
||||
const [dropdownStates, setDropdownStates] = useState<Record<number, boolean>>({})
|
||||
|
||||
// Parse the current value to extract filters
|
||||
const parseFilters = (filterValue: string | null): TagFilter[] => {
|
||||
if (!filterValue) return []
|
||||
try {
|
||||
const parsed = JSON.parse(filterValue)
|
||||
// Handle legacy format (without fieldType/operator)
|
||||
return parsed.map((f: TagFilter) => ({
|
||||
...f,
|
||||
fieldType: f.fieldType || 'text',
|
||||
@@ -111,7 +89,6 @@ export function KnowledgeTagFilters({
|
||||
const currentValue = isPreview ? previewValue : storeValue
|
||||
const filters = parseFilters(currentValue || null)
|
||||
|
||||
// Transform filters to table format for display
|
||||
const rows: TagFilterRow[] =
|
||||
filters.length > 0
|
||||
? filters.map((filter) => ({
|
||||
@@ -128,7 +105,7 @@ export function KnowledgeTagFilters({
|
||||
: [
|
||||
{
|
||||
id: 'empty-row-0',
|
||||
cells: { tagName: '', fieldType: 'text', operator: 'eq', value: '' },
|
||||
cells: { tagName: '', fieldType: 'text', operator: '', value: '' },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -138,17 +115,18 @@ export function KnowledgeTagFilters({
|
||||
setStoreValue(value)
|
||||
}
|
||||
|
||||
/** Convert rows back to TagFilter format */
|
||||
const rowsToFilters = (rowsToConvert: TagFilterRow[]): TagFilter[] => {
|
||||
return rowsToConvert.map((row) => ({
|
||||
id: row.id,
|
||||
tagName: row.cells.tagName || '',
|
||||
tagSlot: row.cells.tagSlot,
|
||||
fieldType: row.cells.fieldType || 'text',
|
||||
operator: row.cells.operator || 'eq',
|
||||
tagValue: row.cells.value || '',
|
||||
valueTo: row.cells.valueTo,
|
||||
}))
|
||||
return rowsToConvert
|
||||
.filter((row) => row.cells.tagName?.trim())
|
||||
.map((row) => ({
|
||||
id: row.id,
|
||||
tagName: row.cells.tagName || '',
|
||||
tagSlot: row.cells.tagSlot,
|
||||
fieldType: row.cells.fieldType || 'text',
|
||||
operator: row.cells.operator || 'eq',
|
||||
tagValue: row.cells.value || '',
|
||||
valueTo: row.cells.valueTo,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleCellChange = (rowIndex: number, column: string, value: string | FilterFieldType) => {
|
||||
@@ -158,15 +136,13 @@ export function KnowledgeTagFilters({
|
||||
if (idx === rowIndex) {
|
||||
const newCells = { ...row.cells, [column]: value }
|
||||
|
||||
// Reset operator when field type changes
|
||||
if (column === 'fieldType') {
|
||||
const operators = getOperatorsForFieldType(value as FilterFieldType)
|
||||
newCells.operator = operators[0]?.value || 'eq'
|
||||
newCells.value = '' // Reset value when type changes
|
||||
newCells.value = ''
|
||||
newCells.valueTo = undefined
|
||||
}
|
||||
|
||||
// Reset valueTo if operator is not 'between'
|
||||
if (column === 'operator' && value !== 'between') {
|
||||
newCells.valueTo = undefined
|
||||
}
|
||||
@@ -179,11 +155,9 @@ export function KnowledgeTagFilters({
|
||||
updateFilters(rowsToFilters(updatedRows))
|
||||
}
|
||||
|
||||
/** Handle tag name selection from dropdown */
|
||||
const handleTagNameSelection = (rowIndex: number, tagName: string) => {
|
||||
if (isPreview || disabled) return
|
||||
|
||||
// Find the tag definition to get fieldType and tagSlot
|
||||
const tagDef = tagDefinitions.find((t) => t.displayName === tagName)
|
||||
const fieldType = (tagDef?.fieldType || 'text') as FilterFieldType
|
||||
const operators = getOperatorsForFieldType(fieldType)
|
||||
@@ -198,7 +172,7 @@ export function KnowledgeTagFilters({
|
||||
tagSlot: tagDef?.tagSlot,
|
||||
fieldType,
|
||||
operator: operators[0]?.value || 'eq',
|
||||
value: '', // Reset value when tag changes
|
||||
value: '',
|
||||
valueTo: undefined,
|
||||
},
|
||||
}
|
||||
@@ -242,7 +216,14 @@ export function KnowledgeTagFilters({
|
||||
}
|
||||
|
||||
const handleDeleteRow = (rowIndex: number) => {
|
||||
if (isPreview || disabled || rows.length <= 1) return
|
||||
if (isPreview || disabled) return
|
||||
|
||||
if (rows.length <= 1) {
|
||||
// Clear the single row instead of deleting
|
||||
setStoreValue(null)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedRows = rows.filter((_, idx) => idx !== rowIndex)
|
||||
updateFilters(rowsToFilters(updatedRows))
|
||||
}
|
||||
@@ -261,80 +242,48 @@ export function KnowledgeTagFilters({
|
||||
}
|
||||
|
||||
const renderHeader = () => (
|
||||
<thead>
|
||||
<tr className='border-b'>
|
||||
<th className='w-[35%] border-r px-2 py-2 text-center font-medium text-sm'>Tag</th>
|
||||
<th className='w-[25%] border-r px-2 py-2 text-center font-medium text-sm'>Operator</th>
|
||||
<th className='border-r px-2 py-2 text-center font-medium text-sm'>Value</th>
|
||||
<th className='w-10' />
|
||||
<thead className='bg-transparent'>
|
||||
<tr className='border-[var(--border-strong)] border-b bg-transparent'>
|
||||
<th className='w-[35%] min-w-0 border-[var(--border-strong)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Tag
|
||||
</th>
|
||||
<th className='w-[35%] min-w-0 border-[var(--border-strong)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Operator
|
||||
</th>
|
||||
<th className='w-[30%] min-w-0 bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Value
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
)
|
||||
|
||||
const renderTagNameCell = (row: TagFilterRow, rowIndex: number) => {
|
||||
const cellValue = row.cells.tagName || ''
|
||||
const isOpen = dropdownStates[rowIndex] || false
|
||||
|
||||
const setIsOpen = (open: boolean) => {
|
||||
setDropdownStates((prev) => ({ ...prev, [rowIndex]: open }))
|
||||
}
|
||||
const tagOptions: ComboboxOption[] = tagDefinitions.map((tag) => ({
|
||||
value: tag.displayName,
|
||||
label: `${tag.displayName} (${FIELD_TYPE_LABELS[tag.fieldType] || 'Text'})`,
|
||||
}))
|
||||
|
||||
return (
|
||||
<td className='relative border-r p-1'>
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverAnchor asChild>
|
||||
<div
|
||||
className='relative w-full cursor-pointer'
|
||||
onClick={() => !disabled && !isLoading && setIsOpen(true)}
|
||||
>
|
||||
<Input
|
||||
value={cellValue}
|
||||
readOnly
|
||||
disabled={disabled || isLoading}
|
||||
autoComplete='off'
|
||||
className='w-full cursor-pointer border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden bg-transparent px-[8px] font-medium font-sans text-sm'>
|
||||
<span className='truncate'>{cellValue || 'Select tag'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverAnchor>
|
||||
{tagDefinitions.length > 0 && (
|
||||
<PopoverContent
|
||||
side='bottom'
|
||||
align='start'
|
||||
sideOffset={4}
|
||||
maxHeight={192}
|
||||
className='w-[200px]'
|
||||
>
|
||||
<PopoverScrollArea>
|
||||
{tagDefinitions.map((tag) => (
|
||||
<PopoverItem
|
||||
key={tag.id}
|
||||
onClick={() => {
|
||||
handleTagNameSelection(rowIndex, tag.displayName)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
<span className='flex-1 truncate'>{tag.displayName}</span>
|
||||
<span className='flex-shrink-0 rounded bg-muted px-1 py-0.5 text-[10px] text-muted-foreground'>
|
||||
{FIELD_TYPE_LABELS[tag.fieldType] || 'Text'}
|
||||
</span>
|
||||
</PopoverItem>
|
||||
))}
|
||||
</PopoverScrollArea>
|
||||
</PopoverContent>
|
||||
)}
|
||||
</Popover>
|
||||
<td className='relative min-w-0 overflow-hidden border-[var(--border-strong)] border-r bg-transparent p-0'>
|
||||
<Combobox
|
||||
options={tagOptions}
|
||||
value={cellValue}
|
||||
onChange={(value) => handleTagNameSelection(rowIndex, value)}
|
||||
disabled={disabled || isLoading}
|
||||
placeholder='Select tag'
|
||||
className='!border-0 !bg-transparent hover:!bg-transparent px-[10px] py-[8px] font-medium text-sm leading-[21px] focus-visible:ring-0 focus-visible:ring-offset-0 [&>span]:truncate'
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
|
||||
/** Render operator cell */
|
||||
const renderOperatorCell = (row: TagFilterRow, rowIndex: number) => {
|
||||
const fieldType = row.cells.fieldType || 'text'
|
||||
const operator = row.cells.operator || 'eq'
|
||||
const operator = row.cells.operator || ''
|
||||
const operators = getOperatorsForFieldType(fieldType)
|
||||
const isOperatorDisabled = disabled || !row.cells.tagName
|
||||
|
||||
const operatorOptions: ComboboxOption[] = operators.map((op) => ({
|
||||
value: op.value,
|
||||
@@ -342,14 +291,14 @@ export function KnowledgeTagFilters({
|
||||
}))
|
||||
|
||||
return (
|
||||
<td className='border-r p-1'>
|
||||
<td className='relative min-w-0 overflow-hidden border-[var(--border-strong)] border-r bg-transparent p-0'>
|
||||
<Combobox
|
||||
options={operatorOptions}
|
||||
value={operator}
|
||||
onChange={(value) => handleCellChange(rowIndex, 'operator', value)}
|
||||
disabled={disabled || !row.cells.tagName}
|
||||
placeholder='Operator'
|
||||
size='sm'
|
||||
disabled={isOperatorDisabled}
|
||||
placeholder='Select operator'
|
||||
className='!border-0 !bg-transparent hover:!bg-transparent px-[10px] py-[8px] font-medium text-sm leading-[21px] focus-visible:ring-0 focus-visible:ring-offset-0 [&>span]:truncate'
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
@@ -364,7 +313,6 @@ export function KnowledgeTagFilters({
|
||||
const isDisabled = disabled || !row.cells.tagName
|
||||
const placeholder = getPlaceholderForFieldType(fieldType)
|
||||
|
||||
// Single text input for all field types with variable support
|
||||
const renderInput = (value: string, column: 'value' | 'valueTo') => (
|
||||
<div className='relative w-full'>
|
||||
<Input
|
||||
@@ -375,7 +323,6 @@ export function KnowledgeTagFilters({
|
||||
|
||||
handleCellChange(rowIndex, column, newValue)
|
||||
|
||||
// Check for tag trigger (only for primary value input)
|
||||
if (column === 'value') {
|
||||
const tagTrigger = checkTagTrigger(newValue, cursorPosition)
|
||||
|
||||
@@ -412,10 +359,10 @@ export function KnowledgeTagFilters({
|
||||
disabled={isDisabled}
|
||||
autoComplete='off'
|
||||
placeholder={placeholder}
|
||||
className='w-full border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
className='w-full border-0 bg-transparent px-[10px] py-[8px] font-medium text-sm text-transparent leading-[21px] caret-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden bg-transparent px-[8px] font-medium font-sans text-sm'>
|
||||
<div className='whitespace-pre'>
|
||||
<div className='scrollbar-hide pointer-events-none absolute top-0 right-[10px] bottom-0 left-[10px] overflow-x-auto overflow-y-hidden bg-transparent'>
|
||||
<div className='whitespace-pre py-[8px] font-medium text-[var(--text-primary)] text-sm leading-[21px]'>
|
||||
{formatDisplayText(value || '', {
|
||||
accessiblePrefixes,
|
||||
highlightAll: !accessiblePrefixes,
|
||||
@@ -425,11 +372,10 @@ export function KnowledgeTagFilters({
|
||||
</div>
|
||||
)
|
||||
|
||||
// Render with optional "between" second input
|
||||
if (isBetween) {
|
||||
return (
|
||||
<td className='p-1'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<td className='relative min-w-0 overflow-hidden bg-transparent p-0'>
|
||||
<div className='flex items-center gap-1 px-[10px]'>
|
||||
{renderInput(cellValue, 'value')}
|
||||
<span className='flex-shrink-0 text-muted-foreground text-xs'>to</span>
|
||||
{renderInput(valueTo, 'valueTo')}
|
||||
@@ -438,23 +384,27 @@ export function KnowledgeTagFilters({
|
||||
)
|
||||
}
|
||||
|
||||
return <td className='p-1'>{renderInput(cellValue, 'value')}</td>
|
||||
return (
|
||||
<td className='relative min-w-0 overflow-hidden bg-transparent p-0'>
|
||||
{renderInput(cellValue, 'value')}
|
||||
</td>
|
||||
)
|
||||
}
|
||||
|
||||
const renderDeleteButton = (rowIndex: number) => {
|
||||
const canDelete = !isPreview && !disabled
|
||||
if (isPreview || disabled) return null
|
||||
|
||||
return canDelete ? (
|
||||
<td className='w-10 p-1'>
|
||||
return (
|
||||
<td className='w-0 p-0'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-8 w-8 p-0 opacity-0 group-hover:opacity-100'
|
||||
className='-translate-y-1/2 absolute top-1/2 right-[8px] transition-opacity'
|
||||
onClick={() => handleDeleteRow(rowIndex)}
|
||||
>
|
||||
<Trash className='h-4 w-4 text-muted-foreground' />
|
||||
<Trash className='h-[14px] w-[14px]' />
|
||||
</Button>
|
||||
</td>
|
||||
) : null
|
||||
)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
@@ -462,13 +412,16 @@ export function KnowledgeTagFilters({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className='overflow-visible rounded-md border'>
|
||||
<table className='w-full'>
|
||||
<div className='relative w-full'>
|
||||
<div className='overflow-hidden rounded-[4px] border border-[var(--border-strong)] bg-[var(--surface-2)] dark:bg-[#1F1F1F]'>
|
||||
<table className='w-full table-fixed bg-transparent'>
|
||||
{renderHeader()}
|
||||
<tbody>
|
||||
<tbody className='bg-transparent'>
|
||||
{rows.map((row, rowIndex) => (
|
||||
<tr key={row.id} className='group relative border-t'>
|
||||
<tr
|
||||
key={row.id}
|
||||
className='group relative border-[var(--border-strong)] border-t bg-transparent'
|
||||
>
|
||||
{renderTagNameCell(row, rowIndex)}
|
||||
{renderOperatorCell(row, rowIndex)}
|
||||
{renderValueCell(row, rowIndex)}
|
||||
@@ -502,7 +455,7 @@ export function KnowledgeTagFilters({
|
||||
{/* Add Filter Button */}
|
||||
{!isPreview && !disabled && (
|
||||
<div className='mt-3 flex items-center justify-between'>
|
||||
<Button variant='outline' onClick={handleAddRow} className='h-7 px-2 text-xs'>
|
||||
<Button onClick={handleAddRow} className='h-7 px-2 text-xs'>
|
||||
<Plus className='mr-1 h-2.5 w-2.5' />
|
||||
Add Filter
|
||||
</Button>
|
||||
|
||||
@@ -57,7 +57,6 @@ export const KnowledgeBlock: BlockConfig = {
|
||||
type: 'knowledge-tag-filters',
|
||||
placeholder: 'Add tag filters',
|
||||
condition: { field: 'operation', value: 'search' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'documentId',
|
||||
|
||||
@@ -18,8 +18,13 @@
|
||||
import * as React from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn/components/button/button'
|
||||
import {
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
} from '@/components/emcn/components/popover/popover'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { Popover, PopoverAnchor, PopoverContent } from '../popover/popover'
|
||||
|
||||
/**
|
||||
* Variant styles for the date picker trigger button.
|
||||
@@ -388,13 +393,9 @@ const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
|
||||
{/* Today Button */}
|
||||
<div className='border-[var(--surface-11)] border-t px-[8px] py-[8px]'>
|
||||
<button
|
||||
type='button'
|
||||
className='w-full rounded-[4px] py-[6px] text-[12px] text-[var(--text-muted)] transition-colors hover:bg-[var(--surface-9)] hover:text-[var(--text-primary)]'
|
||||
onClick={handleSelectToday}
|
||||
>
|
||||
<Button variant='active' className='w-full' onClick={handleSelectToday}>
|
||||
Today
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user