Skip to content

Commit a545006

Browse files
committed
fix(knowledge): judge a caller's reranker key by the resolver's policy, and let a custom window be cleared
- the credential check tries the platform key before a workspace lookup, and counts a caller's own key only off hosted Sim, as the resolver does - a custom window with no days yet searches nothing and says so; another window drops the days; the picker's Clear reaches the URL through a forwarded `onClear`
1 parent 4d3e75d commit a545006

6 files changed

Lines changed: 73 additions & 9 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/components/knowledge-search-results/knowledge-search-results.test.tsx‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,47 @@ describe('result paging and the custom window', () => {
225225
expect(mocks.search.mock.calls.at(-1)![3]).toBe(20)
226226
})
227227

228+
it('drops the custom days when another window is chosen', async () => {
229+
mocks.overview.mockReturnValue({ data: { providers: [], hasSearchableDocuments: true } })
230+
mocks.search.mockReturnValue({
231+
data: { query: 'launch', results: [], retrieval: { status: 'complete', timedOutLegs: [] } },
232+
isPending: false,
233+
isFetching: false,
234+
isPlaceholderData: false,
235+
isError: false,
236+
refetch: mocks.retry,
237+
})
238+
await render(undefined, '?updated=custom&from=2026-09-01&to=2026-09-10')
239+
expect(mocks.search.mock.calls.at(-1)![2]).toHaveProperty('modifiedBefore')
240+
const anyTime = [...container.querySelectorAll('button')].find(
241+
(b) => b.textContent === 'Any time'
242+
)!
243+
await act(async () => anyTime.click())
244+
expect(mocks.search.mock.calls.at(-1)![2]).toEqual({})
245+
await act(async () =>
246+
[...container.querySelectorAll('button')]
247+
.find((b) => b.textContent === 'Custom range')!
248+
.click()
249+
)
250+
/** Back on the custom window, the old days are gone: nothing is searched until new ones are chosen. */
251+
expect(mocks.search.mock.calls.at(-1)![1]).toBe('')
252+
})
253+
254+
it('searches nothing while a custom window has no days yet', async () => {
255+
mocks.overview.mockReturnValue({ data: { providers: [], hasSearchableDocuments: true } })
256+
mocks.search.mockReturnValue({
257+
data: undefined,
258+
isPending: true,
259+
isFetching: false,
260+
isPlaceholderData: false,
261+
isError: false,
262+
refetch: mocks.retry,
263+
})
264+
await render(undefined, '?updated=custom')
265+
expect(mocks.search.mock.calls.at(-1)![1]).toBe('')
266+
expect(container.textContent).toContain('Choose the days to search.')
267+
})
268+
228269
it('searches a custom window as an inclusive range of days', async () => {
229270
mocks.overview.mockReturnValue({ data: { providers: [], hasSearchableDocuments: true } })
230271
mocks.search.mockReturnValue({

‎apps/sim/app/workspace/[workspaceId]/home/components/knowledge-search-results/knowledge-search-results.tsx‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ function SearchResults({ scope, query, onSummarize }: SearchResultsProps) {
167167
}
168168
const filtersKey = JSON.stringify(searchFilters)
169169
const expanded = expandedFor === filtersKey
170+
/** A custom window with no days chosen yet is not "any time": nothing is searched until it has them. */
171+
const awaitingRange = custom && !filters.from && !filters.to
170172
const {
171173
data: search,
172174
isPending,
@@ -176,7 +178,7 @@ function SearchResults({ scope, query, onSummarize }: SearchResultsProps) {
176178
refetch: refetchSearch,
177179
} = useWorkspaceKnowledgeSearch(
178180
scope,
179-
query,
181+
awaitingRange ? '' : query,
180182
searchFilters,
181183
expanded
182184
? WORKSPACE_KNOWLEDGE_SEARCH_LIMITS.expanded
@@ -231,7 +233,11 @@ function SearchResults({ scope, query, onSummarize }: SearchResultsProps) {
231233
<div className='flex flex-col'>
232234
<div className='flex items-center gap-2 px-2 py-2'>
233235
<div className='min-w-0 flex-1'>
234-
{fetching || (pending && !failed) ? (
236+
{awaitingRange ? (
237+
<p role='status' className='text-[var(--text-muted)] text-caption'>
238+
Choose the days to search.
239+
</p>
240+
) : fetching || (pending && !failed) ? (
235241
<ActivityStatus label={pending ? 'Searching…' : 'Updating results…'} isActive />
236242
) : (
237243
<p role='status' className='text-[var(--text-muted)] text-caption'>
@@ -292,7 +298,13 @@ function SearchResults({ scope, query, onSummarize }: SearchResultsProps) {
292298
shape='round'
293299
active={filters.updated === window.id}
294300
aria-pressed={filters.updated === window.id}
295-
onClick={() => setFilters({ updated: window.id })}
301+
onClick={() =>
302+
setFilters(
303+
window.id === 'custom'
304+
? { updated: window.id }
305+
: { updated: window.id, from: null, to: null }
306+
)
307+
}
296308
>
297309
{window.label}
298310
</Chip>
@@ -301,11 +313,12 @@ function SearchResults({ scope, query, onSummarize }: SearchResultsProps) {
301313
<ChipDatePicker
302314
mode='range'
303315
label='Updated between'
304-
startDate={filters.from?.toISOString()}
305-
endDate={filters.to?.toISOString()}
316+
startDate={filters.from?.toISOString().slice(0, 10)}
317+
endDate={filters.to?.toISOString().slice(0, 10)}
306318
onRangeChange={(start, end) =>
307319
void setFilters({ from: new Date(start), to: new Date(end) })
308320
}
321+
onClear={() => void setFilters({ from: null, to: null })}
309322
/>
310323
)}
311324
</div>

‎apps/sim/lib/knowledge/application/search.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,8 @@ describe('knowledge search application use case', () => {
873873

874874
const result = await rerankedSearch(true)
875875

876+
/** A caller's own key is judged by the same policy the resolver applies, not taken on faith. */
877+
expect(mocks.hasRerankerCredential).toHaveBeenLastCalledWith(expect.anything(), undefined)
876878
expect(mocks.rerank).not.toHaveBeenCalled()
877879
expect(result.rerankerStatus).toBe('unavailable')
878880
expect(result.results[0]).not.toHaveProperty('rerankerScore')

‎apps/sim/lib/knowledge/application/search.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ const searchKnowledgeUseCase = defineAuthorizedKnowledgeUseCase({
394394
/** A surface may ask to rerank; without a key for the workspace or the platform there is nothing to ask. */
395395
const useReranker =
396396
Boolean(input.rerankerEnabled && hasQuery) &&
397-
(Boolean(input.rerankerApiKey) || (await hasRerankerCredential(context.workspaceId)))
397+
(await hasRerankerCredential(context.workspaceId, input.rerankerApiKey))
398398
const candidateTopK = useReranker
399399
? input.rerankerInputCount !== undefined
400400
? Math.min(

‎apps/sim/lib/knowledge/reranker.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,14 @@ const RERANK_OPERATION_TIMEOUT_MS = 30_000
3434
* Whether a search for this workspace could be reranked at all: a workspace key, or one of the
3535
* platform's. A surface that reranks "when configured" asks this before spending a call on it.
3636
*/
37-
export async function hasRerankerCredential(workspaceId?: string): Promise<boolean> {
38-
if (workspaceId && (await getBYOKKey(workspaceId, 'cohere'))) return true
39-
return Boolean(env.COHERE_API_KEY) || hasRotatingApiKey('cohere')
37+
export async function hasRerankerCredential(
38+
workspaceId?: string,
39+
userApiKey?: string
40+
): Promise<boolean> {
41+
/** The same policy as the key resolver: a caller's own key counts only off hosted Sim. */
42+
if (!isHosted && userApiKey) return true
43+
if (env.COHERE_API_KEY || hasRotatingApiKey('cohere')) return true
44+
return Boolean(workspaceId && (await getBYOKKey(workspaceId, 'cohere')))
4045
}
4146

4247
/**

‎packages/emcn/src/components/chip-date-picker/chip-date-picker.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ interface ChipDatePickerRangeProps extends ChipDatePickerBaseProps {
6262
showTime?: boolean
6363
/** Called on Apply with the ordered range bounds. */
6464
onRangeChange: (start: string, end: string) => void
65+
/** Called on Clear, so a committed range can be dropped by whoever owns it. */
66+
onClear?: () => void
6567
}
6668

6769
export type ChipDatePickerProps = ChipDatePickerSingleProps | ChipDatePickerRangeProps
@@ -161,6 +163,7 @@ const ChipDatePicker = forwardRef<HTMLButtonElement, ChipDatePickerProps>(
161163
setOpen(false)
162164
}}
163165
onCancel={() => setOpen(false)}
166+
onClear={props.onClear}
164167
/>
165168
) : (
166169
<Calendar

0 commit comments

Comments
 (0)