Skip to content

Commit 3432a10

Browse files
author
priyanshu.solanki
committed
feat: add UI for tag field types with filter operators
- Update base-tags-modal with field type selector dropdown - Update document-tags-modal with different input types per fieldType - Update knowledge-tag-filters with operator dropdown and type-specific inputs - Update search routes to support all tag slot types - Update hook to use AllTagSlot type
1 parent 4f5f140 commit 3432a10

11 files changed

Lines changed: 687 additions & 171 deletions

File tree

apps/sim/app/api/knowledge/search/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { z } from 'zod'
33
import { generateRequestId } from '@/lib/core/utils/request'
4-
import { TAG_SLOTS } from '@/lib/knowledge/constants'
4+
import { ALL_TAG_SLOTS } from '@/lib/knowledge/constants'
55
import { getDocumentTagDefinitions } from '@/lib/knowledge/tags/service'
66
import { createLogger } from '@/lib/logs/console/logger'
77
import { estimateTokenCount } from '@/lib/tokenization/estimators'
@@ -257,9 +257,9 @@ export async function POST(request: NextRequest) {
257257
// Create tags object with display names
258258
const tags: Record<string, any> = {}
259259

260-
TAG_SLOTS.forEach((slot) => {
260+
ALL_TAG_SLOTS.forEach((slot) => {
261261
const tagValue = (result as any)[slot]
262-
if (tagValue) {
262+
if (tagValue !== null && tagValue !== undefined) {
263263
const displayName = kbTagMap[slot] || slot
264264
logger.debug(
265265
`[${requestId}] Mapping ${slot}="${tagValue}" -> "${displayName}"="${tagValue}"`

apps/sim/app/api/knowledge/search/utils.ts

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,38 @@ export interface SearchResult {
3434
content: string
3535
documentId: string
3636
chunkIndex: number
37+
// Text tags
3738
tag1: string | null
3839
tag2: string | null
3940
tag3: string | null
4041
tag4: string | null
4142
tag5: string | null
4243
tag6: string | null
4344
tag7: string | null
45+
// Number tags
46+
number1: number | null
47+
number2: number | null
48+
number3: number | null
49+
number4: number | null
50+
number5: number | null
51+
number6: number | null
52+
number7: number | null
53+
// Date tags
54+
date1: Date | null
55+
date2: Date | null
56+
date3: Date | null
57+
date4: Date | null
58+
date5: Date | null
59+
date6: Date | null
60+
date7: Date | null
61+
// Boolean tags
62+
boolean1: boolean | null
63+
boolean2: boolean | null
64+
boolean3: boolean | null
65+
boolean4: boolean | null
66+
boolean5: boolean | null
67+
boolean6: boolean | null
68+
boolean7: boolean | null
4469
distance: number
4570
knowledgeBaseId: string
4671
}
@@ -56,36 +81,81 @@ export interface SearchParams {
5681
// Use shared embedding utility
5782
export { generateSearchEmbedding } from '@/lib/knowledge/embeddings'
5883

59-
function getTagFilters(filters: Record<string, string>, embedding: any) {
84+
/** All valid tag slot keys */
85+
const TAG_SLOT_KEYS = [
86+
// Text tags
87+
'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7',
88+
// Number tags
89+
'number1', 'number2', 'number3', 'number4', 'number5', 'number6', 'number7',
90+
// Date tags
91+
'date1', 'date2', 'date3', 'date4', 'date5', 'date6', 'date7',
92+
// Boolean tags
93+
'boolean1', 'boolean2', 'boolean3', 'boolean4', 'boolean5', 'boolean6', 'boolean7',
94+
] as const
95+
96+
type TagSlotKey = (typeof TAG_SLOT_KEYS)[number]
97+
98+
function isTagSlotKey(key: string): key is TagSlotKey {
99+
return TAG_SLOT_KEYS.includes(key as TagSlotKey)
100+
}
101+
102+
function getTagFilters(filters: Record<string, string>, embeddingTable: any) {
60103
return Object.entries(filters).map(([key, value]) => {
61104
// Handle OR logic within same tag
62105
const values = value.includes('|OR|') ? value.split('|OR|') : [value]
63106
logger.debug(`[getTagFilters] Processing ${key}="${value}" -> values:`, values)
64107

65-
const getColumnForKey = (key: string) => {
66-
switch (key) {
67-
case 'tag1':
68-
return embedding.tag1
69-
case 'tag2':
70-
return embedding.tag2
71-
case 'tag3':
72-
return embedding.tag3
73-
case 'tag4':
74-
return embedding.tag4
75-
case 'tag5':
76-
return embedding.tag5
77-
case 'tag6':
78-
return embedding.tag6
79-
case 'tag7':
80-
return embedding.tag7
81-
default:
82-
return null
108+
// Check if the key is a valid tag slot
109+
if (!isTagSlotKey(key)) {
110+
logger.debug(`[getTagFilters] Unknown tag slot key: ${key}`)
111+
return sql`1=1` // No-op for unknown keys
112+
}
113+
114+
const column = embeddingTable[key]
115+
if (!column) return sql`1=1` // No-op if column doesn't exist
116+
117+
// Determine if this is a text, number, date, or boolean column
118+
const isTextTag = key.startsWith('tag')
119+
const isNumberTag = key.startsWith('number')
120+
const isDateTag = key.startsWith('date')
121+
const isBooleanTag = key.startsWith('boolean')
122+
123+
if (isBooleanTag) {
124+
// Boolean comparison
125+
const boolValue = values[0].toLowerCase() === 'true'
126+
logger.debug(`[getTagFilters] Boolean filter: ${key} = ${boolValue}`)
127+
return sql`${column} = ${boolValue}`
128+
}
129+
130+
if (isNumberTag) {
131+
// Number comparison - for simple equality
132+
const numValue = parseFloat(values[0])
133+
if (values.length === 1) {
134+
logger.debug(`[getTagFilters] Number filter: ${key} = ${numValue}`)
135+
return sql`${column} = ${numValue}`
83136
}
137+
// Multiple values - OR logic
138+
const numValues = values.map((v) => parseFloat(v))
139+
logger.debug(`[getTagFilters] OR number filter: ${key} IN (${numValues.join(', ')})`)
140+
const orConditions = numValues.map((v) => sql`${column} = ${v}`)
141+
return sql`(${sql.join(orConditions, sql` OR `)})`
84142
}
85143

86-
const column = getColumnForKey(key)
87-
if (!column) return sql`1=1` // No-op for unknown keys
144+
if (isDateTag) {
145+
// Date comparison - for simple equality
146+
const dateValue = new Date(values[0])
147+
if (values.length === 1) {
148+
logger.debug(`[getTagFilters] Date filter: ${key} = ${dateValue.toISOString()}`)
149+
return sql`${column} = ${dateValue}`
150+
}
151+
// Multiple values - OR logic
152+
const dateValues = values.map((v) => new Date(v))
153+
logger.debug(`[getTagFilters] OR date filter: ${key} IN (${dateValues.map((d) => d.toISOString()).join(', ')})`)
154+
const orConditions = dateValues.map((v) => sql`${column} = ${v}`)
155+
return sql`(${sql.join(orConditions, sql` OR `)})`
156+
}
88157

158+
// Text tag - case-insensitive comparison
89159
if (values.length === 1) {
90160
// Single value - simple equality
91161
logger.debug(`[getTagFilters] Single value filter: ${key} = ${values[0]}`)

0 commit comments

Comments
 (0)