Add manual sync trigger to SearchModal
Wires the existing /sync endpoint to a "Refresh Data" button so Homebox data can be re-vectorized before searching, without waiting for it to be triggered automatically on login. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
|||||||
PagedSearchResponse,
|
PagedSearchResponse,
|
||||||
SearchRequest,
|
SearchRequest,
|
||||||
ServiceItem,
|
ServiceItem,
|
||||||
|
SyncRequest,
|
||||||
} from "../types/search";
|
} from "../types/search";
|
||||||
|
|
||||||
export const search = (req: SearchRequest) =>
|
export const search = (req: SearchRequest) =>
|
||||||
@@ -10,3 +11,6 @@ export const search = (req: SearchRequest) =>
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(req),
|
body: JSON.stringify(req),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const syncVectorData = (req: SyncRequest) =>
|
||||||
|
apiFetch<Object>("/sync", { method: "POST", body: JSON.stringify(req) });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
|
import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
|
||||||
import '../ui/Modal.scss'
|
import '../ui/Modal.scss'
|
||||||
import { type PagedSearchResponse, type ServiceItem, type SearchRequest } from '../../types/search'
|
import { type PagedSearchResponse, type ServiceItem, type SearchRequest, type SyncRequest } from '../../types/search'
|
||||||
import { search } from '../../api/searches'
|
import { syncVectorData, search } from '../../api/searches'
|
||||||
import type { ServiceType } from '../../types/connection'
|
import type { ServiceType } from '../../types/connection'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -17,9 +17,10 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
const [results, setResults] = useState<PagedSearchResponse<ServiceItem> | null>(null)
|
const [results, setResults] = useState<PagedSearchResponse<ServiceItem> | null>(null)
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [searchError, setSearchError] = useState<string | null>(null)
|
||||||
|
const [syncError, setSyncError] = useState<string | null>(null)
|
||||||
//TODO: implement aiSearch
|
//TODO: implement aiSearch
|
||||||
const [aiSearch, setAiSearch] = useState(false);
|
const [aiSearch, setAiSearch] = useState(false)
|
||||||
const firstInputRef = useRef<HTMLInputElement>(null)
|
const firstInputRef = useRef<HTMLInputElement>(null)
|
||||||
const dialogRef = useRef<HTMLDialogElement>(null)
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
||||||
|
|
||||||
@@ -40,16 +41,27 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
|
|
||||||
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setError(null)
|
setSearchError(null)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const req: SearchRequest = { appUrl, serviceType, username, query, aiSearch}
|
const req: SearchRequest = { appUrl, serviceType, username, query, aiSearch}
|
||||||
const res = await search(req)
|
const res = await search(req)
|
||||||
console.log(req)
|
|
||||||
setResults(res)
|
setResults(res)
|
||||||
console.log('results', results)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Search failed')
|
setSearchError(err instanceof Error ? err.message : 'Search failed')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSync = async () => {
|
||||||
|
setSyncError(null)
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const req: SyncRequest = { appUrl, serviceType, username}
|
||||||
|
await syncVectorData(req)
|
||||||
|
} catch (err) {
|
||||||
|
setSyncError(err instanceof Error ? err.message: 'Refreshing data failed')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -66,10 +78,12 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
<input id='search' className='modal__input'
|
<input id='search' className='modal__input'
|
||||||
value={query} onChange={e => setQuery(e.target.value)} />
|
value={query} onChange={e => setQuery(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
{error && <p className='modal__error'>{error}</p>}
|
{searchError && <p className='modal__error'>{searchError}</p>}
|
||||||
<button className='modal__submit' type='submit' disabled={loading}>
|
{syncError && <p className='modal__error'>{syncError}</p>}
|
||||||
Search
|
<div className='modal__actions'>
|
||||||
</button>
|
<button className='modal__other' title={`Syncs your ${serviceType} database for vectorization`} type='button' disabled={loading} onClick={handleSync}>Refresh Data</button>
|
||||||
|
<button className='modal__submit' type='submit' disabled={loading}>Search</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{results && (
|
{results && (
|
||||||
<div className='modal__results'>
|
<div className='modal__results'>
|
||||||
|
|||||||
@@ -44,8 +44,13 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
&:hover { color: var(--text-h); }
|
&:hover {
|
||||||
&:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
color: var(--text-h);
|
||||||
|
}
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__form {
|
&__form {
|
||||||
@@ -94,23 +99,42 @@
|
|||||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__submit {
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__submit,
|
||||||
|
&__other {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: none;
|
border: none;
|
||||||
background: var(--accent);
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
align-self: flex-end;
|
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
&:hover:not(:disabled) { opacity: 0.85; }
|
&:hover:not(:disabled) {
|
||||||
&:disabled { opacity: 0.6; cursor: not-allowed; }
|
opacity: 0.85;
|
||||||
&:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__submit {
|
||||||
|
background: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--expanded {
|
&--expanded {
|
||||||
|
|||||||
@@ -24,3 +24,9 @@ export interface PagedSearchResponse<T> {
|
|||||||
last: boolean;
|
last: boolean;
|
||||||
sort: string;
|
sort: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SyncRequest {
|
||||||
|
appUrl: string;
|
||||||
|
username: string;
|
||||||
|
serviceType: ServiceType;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user