Skip to content

Commit b4f5270

Browse files
author
waleed
committed
consolidated differnet utils, enforce validation & cleanup subblocks
1 parent fa8ed0c commit b4f5270

6 files changed

Lines changed: 175 additions & 170 deletions

File tree

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export function Document({
322322
<div className='font-mono text-sm'>{chunk.chunkIndex}</div>
323323
</td>
324324
<td className='px-4 py-3'>
325-
<div className='text-sm' title={chunk.content}>
325+
<div className='whitespace-normal break-all text-sm' title={chunk.content}>
326326
<SearchHighlight text={truncateContent(chunk.content)} searchQuery={searchQuery} />
327327
</div>
328328
</td>

apps/sim/app/workspace/[workspaceId]/knowledge/components/document-tag-entry/document-tag-entry.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export function DocumentTagEntry({
8686
fieldType: 'text',
8787
value: '',
8888
})
89+
const [isValueValid, setIsValueValid] = useState(true)
8990

9091
const handleRemoveTag = async (index: number) => {
9192
const updatedTags = tags.filter((_, i) => i !== index)
@@ -449,11 +450,7 @@ export function DocumentTagEntry({
449450
'Enter tag value'
450451
}
451452
showInlineError={true}
452-
onValidityChange={(valid) => {
453-
if (!valid) {
454-
setEditForm((prev) => ({ ...prev }))
455-
}
456-
}}
453+
onValidityChange={(valid) => setIsValueValid(valid)}
457454
/>
458455
</div>
459456
</div>
@@ -478,7 +475,8 @@ export function DocumentTagEntry({
478475
<Button
479476
onClick={saveTagFromModal}
480477
disabled={(() => {
481-
if (!editForm.displayName.trim()) return true
478+
if (!editForm.displayName.trim() || !editForm.value.trim() || !isValueValid)
479+
return true
482480

483481
if (editingTagIndex !== null) return false
484482

apps/sim/app/workspace/[workspaceId]/knowledge/components/tag-input/typed-tag-input.tsx

Lines changed: 45 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SelectTrigger,
1010
SelectValue,
1111
} from '@/components/ui/select'
12+
import { FIELD_TYPE_METADATA, validateFieldValue } from '@/lib/knowledge/consts'
1213
import { cn } from '@/lib/utils'
1314

1415
interface TypedTagInputProps {
@@ -28,157 +29,59 @@ export function TypedTagInput({
2829
onChange,
2930
placeholder,
3031
disabled = false,
31-
className = '',
32+
className,
3233
showInlineError = false,
3334
onValidityChange,
3435
}: TypedTagInputProps) {
35-
const [inputValue, setInputValue] = useState(value || '')
36-
const [isValid, setIsValid] = useState(true)
37-
const [error, setError] = useState<string | undefined>()
36+
const [error, setError] = useState<string>('')
3837

38+
// Validate on value or fieldType change
3939
useEffect(() => {
40-
setInputValue(value || '')
41-
}, [value])
42-
43-
useEffect(() => {
44-
setIsValid(true)
45-
setError(undefined)
46-
if (inputValue.trim()) {
47-
validateAndUpdate(inputValue)
40+
if (!value.trim()) {
41+
setError('')
42+
onValidityChange?.(true)
43+
return
4844
}
49-
}, [fieldType])
50-
51-
const validateAndUpdate = (newValue: string) => {
52-
setInputValue(newValue)
5345

54-
let valid = true
55-
let errorMessage: string | undefined
56-
57-
if (newValue.trim()) {
58-
switch (fieldType) {
59-
case 'number': {
60-
const num = Number(newValue.trim())
61-
if (Number.isNaN(num) || !Number.isFinite(num)) {
62-
valid = false
63-
errorMessage = 'Must be a valid number'
64-
}
65-
break
66-
}
67-
case 'date': {
68-
const date = new Date(newValue.trim())
69-
if (Number.isNaN(date.getTime())) {
70-
valid = false
71-
errorMessage = 'Must be a valid date'
72-
}
73-
break
74-
}
75-
case 'boolean': {
76-
const lower = newValue.trim().toLowerCase()
77-
if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lower)) {
78-
valid = false
79-
errorMessage = 'Must be true, false, yes, no, 1, or 0'
80-
}
81-
break
82-
}
83-
}
84-
}
46+
const validation = validateFieldValue(fieldType, value)
47+
setError(validation.isValid ? '' : validation.error || '')
48+
onValidityChange?.(validation.isValid)
49+
// eslint-disable-next-line react-hooks/exhaustive-deps
50+
}, [value, fieldType])
8551

86-
setIsValid(valid)
87-
setError(errorMessage)
88-
if (onValidityChange) onValidityChange(valid)
52+
const metadata =
53+
FIELD_TYPE_METADATA[fieldType as keyof typeof FIELD_TYPE_METADATA] || FIELD_TYPE_METADATA.text
8954

90-
if (valid) {
91-
onChange(newValue)
92-
}
55+
// Boolean type uses Select
56+
if (fieldType === 'boolean') {
57+
return (
58+
<div className='space-y-1'>
59+
<Select value={value || 'true'} onValueChange={onChange} disabled={disabled}>
60+
<SelectTrigger className={cn('w-full', className)}>
61+
<SelectValue placeholder={placeholder || metadata.placeholder} />
62+
</SelectTrigger>
63+
<SelectContent>
64+
<SelectItem value='true'>True</SelectItem>
65+
<SelectItem value='false'>False</SelectItem>
66+
</SelectContent>
67+
</Select>
68+
</div>
69+
)
9370
}
9471

95-
switch (fieldType) {
96-
case 'boolean':
97-
return (
98-
<div className={className}>
99-
<Select
100-
value={inputValue || undefined}
101-
onValueChange={validateAndUpdate}
102-
disabled={disabled}
103-
>
104-
<SelectTrigger
105-
className={cn(
106-
'h-8 w-full justify-between rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]',
107-
!isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
108-
)}
109-
>
110-
<SelectValue placeholder='Select' />
111-
</SelectTrigger>
112-
<SelectContent className='rounded-lg border-[#E5E5E5] bg-[#FFFFFF] dark:border-[#414141] dark:bg-[var(--surface-elevated)]'>
113-
<SelectItem value='true'>True</SelectItem>
114-
<SelectItem value='false'>False</SelectItem>
115-
</SelectContent>
116-
</Select>
117-
{showInlineError && !isValid && error && (
118-
<p className='mt-1 text-red-600 text-xs'>{error}</p>
119-
)}
120-
</div>
121-
)
122-
123-
case 'date':
124-
return (
125-
<div className={className}>
126-
<Input
127-
type='text'
128-
value={inputValue}
129-
onChange={(e) => validateAndUpdate(e.target.value)}
130-
placeholder={placeholder || 'mm/dd/yyyy'}
131-
disabled={disabled}
132-
className={cn(
133-
'h-8 rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]',
134-
!isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
135-
)}
136-
/>
137-
{showInlineError && !isValid && error && (
138-
<p className='mt-1 text-red-600 text-xs'>{error}</p>
139-
)}
140-
</div>
141-
)
142-
143-
case 'number':
144-
return (
145-
<div className={className}>
146-
<Input
147-
type='text'
148-
inputMode='numeric'
149-
value={inputValue}
150-
onChange={(e) => validateAndUpdate(e.target.value)}
151-
placeholder={placeholder || 'Number'}
152-
disabled={disabled}
153-
className={cn(
154-
'h-8 rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]',
155-
!isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
156-
)}
157-
/>
158-
{showInlineError && !isValid && error && (
159-
<p className='mt-1 text-red-600 text-xs'>{error}</p>
160-
)}
161-
</div>
162-
)
163-
164-
default:
165-
return (
166-
<div className={className}>
167-
<Input
168-
type='text'
169-
value={inputValue}
170-
onChange={(e) => validateAndUpdate(e.target.value)}
171-
placeholder={placeholder || 'Value'}
172-
disabled={disabled}
173-
className={cn(
174-
'h-8 rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]',
175-
!isValid && 'border-red-300 focus:border-red-500 focus:ring-0'
176-
)}
177-
/>
178-
{showInlineError && !isValid && error && (
179-
<p className='mt-1 text-red-600 text-xs'>{error}</p>
180-
)}
181-
</div>
182-
)
183-
}
72+
// All other types use Input
73+
return (
74+
<div className='space-y-1'>
75+
<Input
76+
type={fieldType === 'number' ? 'text' : 'text'}
77+
inputMode={fieldType === 'number' ? 'numeric' : undefined}
78+
value={value}
79+
onChange={(e) => onChange(e.target.value)}
80+
placeholder={placeholder || metadata.placeholder}
81+
disabled={disabled}
82+
className={cn(error && showInlineError && 'border-red-500', className)}
83+
/>
84+
{showInlineError && error && <p className='text-red-600 text-xs'>{error}</p>}
85+
</div>
86+
)
18487
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-tag-entry/document-tag-entry.tsx

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { Calendar, Hash, Plus, ToggleLeft, Trash2, Type as TypeIcon } from 'luci
55
import { Button } from '@/components/ui/button'
66
import { formatDisplayText } from '@/components/ui/formatted-text'
77
import { Input } from '@/components/ui/input'
8-
import { TagDropdown } from '@/components/ui/tag-dropdown'
8+
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
99
import { FIELD_TYPE_METADATA, MAX_TAG_SLOTS, SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/consts'
1010
import { cn } from '@/lib/utils'
11-
import { TypedTagInput } from '@/app/workspace/[workspaceId]/knowledge/components/tag-input/typed-tag-input'
1211
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
12+
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
1313
import type { SubBlockConfig } from '@/blocks/types'
1414
import { useKnowledgeBaseTagDefinitions } from '@/hooks/use-knowledge-base-tag-definitions'
1515
import { useTagSelection } from '@/hooks/use-tag-selection'
@@ -68,6 +68,9 @@ export function DocumentTagEntry({
6868
element?: HTMLElement | null
6969
} | null>(null)
7070

71+
// Get accessible prefixes for tag dropdown
72+
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
73+
7174
const currentValue = isPreview ? previewValue : storeValue
7275

7376
const rows = useMemo(() => {
@@ -272,7 +275,7 @@ export function DocumentTagEntry({
272275
e.preventDefault()
273276
e.stopPropagation()
274277
if (!disabled && !isConnecting) {
275-
setShowDropdown(!showDropdown && matchedDefinitions.length > 0)
278+
setShowDropdown(!showDropdown || matchedDefinitions.length > 0)
276279
}
277280
}
278281

@@ -437,16 +440,66 @@ export function DocumentTagEntry({
437440
const cellValue = row.cells.value || ''
438441
const fieldType = row.cells.type || 'text'
439442

443+
// All types use Input with TagDropdown support
440444
return (
441445
<td className='p-1 pr-12'>
442-
<TypedTagInput
443-
fieldType={fieldType}
444-
value={cellValue}
445-
onChange={(newValue) => handleCellChange(rowIndex, 'value', newValue)}
446-
disabled={disabled || isConnecting}
447-
showInlineError={true}
448-
className='w-full'
449-
/>
446+
<div className='relative w-full'>
447+
<Input
448+
value={cellValue}
449+
onChange={(e) => {
450+
const newValue = e.target.value
451+
const cursorPosition = e.target.selectionStart ?? 0
452+
453+
handleCellChange(rowIndex, 'value', newValue)
454+
455+
// Check for tag trigger
456+
const tagTrigger = checkTagTrigger(newValue, cursorPosition)
457+
458+
setActiveTagDropdown({
459+
rowIndex,
460+
showTags: tagTrigger.show,
461+
cursorPosition,
462+
activeSourceBlockId: null,
463+
element: e.target,
464+
})
465+
}}
466+
onFocus={(e) => {
467+
if (!disabled && !isConnecting) {
468+
setActiveTagDropdown({
469+
rowIndex,
470+
showTags: false,
471+
cursorPosition: 0,
472+
activeSourceBlockId: null,
473+
element: e.target,
474+
})
475+
}
476+
}}
477+
onBlur={() => {
478+
setTimeout(() => setActiveTagDropdown(null), 200)
479+
}}
480+
onKeyDown={(e) => {
481+
if (e.key === 'Escape') {
482+
setActiveTagDropdown(null)
483+
}
484+
}}
485+
disabled={disabled || isConnecting}
486+
placeholder={
487+
FIELD_TYPE_METADATA[fieldType as keyof typeof FIELD_TYPE_METADATA]?.placeholder ||
488+
'Enter value'
489+
}
490+
type={fieldType === 'number' ? 'text' : 'text'}
491+
inputMode={fieldType === 'number' ? 'numeric' : undefined}
492+
className='w-full border-0 text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
493+
/>
494+
<div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden bg-transparent px-3 text-sm'>
495+
<div className='whitespace-pre'>
496+
{formatDisplayText(cellValue, {
497+
accessiblePrefixes,
498+
highlightAll: !accessiblePrefixes,
499+
})}
500+
</div>
501+
</div>
502+
</div>
450503
</td>
451504
)
452505
}

0 commit comments

Comments
 (0)