diff --git a/frontend/src/api/searches.ts b/frontend/src/api/searches.ts index a8d03d7..963baf4 100644 --- a/frontend/src/api/searches.ts +++ b/frontend/src/api/searches.ts @@ -3,6 +3,7 @@ import type { PagedSearchResponse, SearchRequest, ServiceItem, + SyncRequest, } from "../types/search"; export const search = (req: SearchRequest) => @@ -10,3 +11,6 @@ export const search = (req: SearchRequest) => method: "POST", body: JSON.stringify(req), }); + +export const syncVectorData = (req: SyncRequest) => + apiFetch("/sync", { method: "POST", body: JSON.stringify(req) }); diff --git a/frontend/src/components/search/SearchModal.tsx b/frontend/src/components/search/SearchModal.tsx index 0494226..7bc977f 100644 --- a/frontend/src/components/search/SearchModal.tsx +++ b/frontend/src/components/search/SearchModal.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type SyntheticEvent } from 'react' import '../ui/Modal.scss' -import { type PagedSearchResponse, type ServiceItem, type SearchRequest } from '../../types/search' -import { search } from '../../api/searches' +import { type PagedSearchResponse, type ServiceItem, type SearchRequest, type SyncRequest } from '../../types/search' +import { syncVectorData, search } from '../../api/searches' import type { ServiceType } from '../../types/connection' interface Props { @@ -17,9 +17,10 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R const [results, setResults] = useState | null>(null) const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) + const [searchError, setSearchError] = useState(null) + const [syncError, setSyncError] = useState(null) //TODO: implement aiSearch - const [aiSearch, setAiSearch] = useState(false); + const [aiSearch, setAiSearch] = useState(false) const firstInputRef = useRef(null) const dialogRef = useRef(null) @@ -40,16 +41,27 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R const handleSubmit = async (e: SyntheticEvent) => { e.preventDefault() - setError(null) + setSearchError(null) setLoading(true) try { const req: SearchRequest = { appUrl, serviceType, username, query, aiSearch} const res = await search(req) - console.log(req) setResults(res) - console.log('results', results) } 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 { setLoading(false) } @@ -66,10 +78,12 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R setQuery(e.target.value)} /> - {error &&

{error}

} - + {searchError &&

{searchError}

} + {syncError &&

{syncError}

} +
+ + +
{results && (
diff --git a/frontend/src/components/ui/Modal.scss b/frontend/src/components/ui/Modal.scss index 0bdd995..ea7f5f7 100644 --- a/frontend/src/components/ui/Modal.scss +++ b/frontend/src/components/ui/Modal.scss @@ -44,8 +44,13 @@ line-height: 1; padding: 4px; border-radius: 4px; - &:hover { color: var(--text-h); } - &:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + &:hover { + color: var(--text-h); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } } &__form { @@ -94,62 +99,81 @@ border: 1px solid rgba(239, 68, 68, 0.2); } - &__submit { + &__actions { + display: flex; + justify-content: flex-end; + gap: 8px; margin-top: 4px; + } + + &__submit, + &__other { padding: 10px 20px; font-size: 15px; font-weight: 500; border-radius: 6px; border: none; - background: var(--accent); - color: #fff; cursor: pointer; transition: opacity 0.2s; - align-self: flex-end; min-width: 100px; + color: #fff; - &:hover:not(:disabled) { opacity: 0.85; } - &:disabled { opacity: 0.6; cursor: not-allowed; } - &:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + &:hover:not(:disabled) { + opacity: 0.85; + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } } - &--expanded { - max-height: 70vh; - } - - &__results { - overflow-y: auto; - flex: 1; - min-height: 0; // required: flex children won't shrink without this - margin-top: 20px; - display: flex; - flex-direction: column; - gap: 8px; - } - - &__results-count { - font-size: 12px; - color: var(--text); - margin: 0 0 8px 0; - } - - &__result-item { - padding: 10px 12px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - } - - &__result-title { - font-weight: 500; - font-size: 14px; - color: var(--text-h); - margin: 0; - } - - &__result-desc { - font-size: 13px; - color: var(--text); - margin: 4px 0 0 0; - } + &__submit { + background: var(--accent); + } + + &--expanded { + max-height: 70vh; + } + + &__results { + overflow-y: auto; + flex: 1; + min-height: 0; // required: flex children won't shrink without this + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 8px; + } + + &__results-count { + font-size: 12px; + color: var(--text); + margin: 0 0 8px 0; + } + + &__result-item { + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + } + + &__result-title { + font-weight: 500; + font-size: 14px; + color: var(--text-h); + margin: 0; + } + + &__result-desc { + font-size: 13px; + color: var(--text); + margin: 4px 0 0 0; + } } diff --git a/frontend/src/types/search.ts b/frontend/src/types/search.ts index 9adb4b2..12bfafb 100644 --- a/frontend/src/types/search.ts +++ b/frontend/src/types/search.ts @@ -24,3 +24,9 @@ export interface PagedSearchResponse { last: boolean; sort: string; } + +export interface SyncRequest { + appUrl: string; + username: string; + serviceType: ServiceType; +}