diff --git a/packages/frontend/src/api/client.ts b/packages/frontend/src/api/client.ts index 7401303..b234c29 100644 --- a/packages/frontend/src/api/client.ts +++ b/packages/frontend/src/api/client.ts @@ -4,7 +4,9 @@ const BASE = '/api'; async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE}${path}`, { - headers: { 'Content-Type': 'application/json' }, + headers: { + ...(options?.body ? { 'Content-Type': 'application/json' } : {}), + }, ...options, }); if (!res.ok) { diff --git a/packages/frontend/src/components/RecommendationCard.tsx b/packages/frontend/src/components/RecommendationCard.tsx index 212378f..84d1abd 100644 --- a/packages/frontend/src/components/RecommendationCard.tsx +++ b/packages/frontend/src/components/RecommendationCard.tsx @@ -17,6 +17,7 @@ const CATEGORY_COLORS: Record = { export function RecommendationCard({ show, existingFeedback, onFeedback }: RecommendationCardProps) { const [selectedStars, setSelectedStars] = useState(existingFeedback?.stars ?? 0); + const [hoverStar, setHoverStar] = useState(0); const [comment, setComment] = useState(existingFeedback?.feedback ?? ''); const [showComment, setShowComment] = useState(false); const [submitted, setSubmitted] = useState(!!existingFeedback); @@ -49,16 +50,21 @@ export function RecommendationCard({ show, existingFeedback, onFeedback }: Recom
- {[1, 2, 3].map((star) => ( - - ))} + {[1, 2, 3].map((star) => { + const effective = hoverStar || selectedStars; + return ( + + ); + })} {submitted && }
diff --git a/packages/frontend/src/hooks/useSSE.ts b/packages/frontend/src/hooks/useSSE.ts index c0ae2b8..85dd5bf 100644 --- a/packages/frontend/src/hooks/useSSE.ts +++ b/packages/frontend/src/hooks/useSSE.ts @@ -4,10 +4,13 @@ import type { SSEEvent } from '../types/index.js'; export function useSSE( url: string | null, onEvent: (event: SSEEvent) => void, + onClose?: () => void, ): { close: () => void } { const esRef = useRef(null); const onEventRef = useRef(onEvent); onEventRef.current = onEvent; + const onCloseRef = useRef(onClose); + onCloseRef.current = onClose; useEffect(() => { if (!url) return; @@ -32,6 +35,7 @@ export function useSSE( es.onerror = () => { es.close(); esRef.current = null; + onCloseRef.current?.(); }; return () => { diff --git a/packages/frontend/src/pages/Recom.css b/packages/frontend/src/pages/Recom.css index c7a2dd7..8e6d439 100644 --- a/packages/frontend/src/pages/Recom.css +++ b/packages/frontend/src/pages/Recom.css @@ -95,6 +95,27 @@ color: var(--accent); } +.rec-info-delete-row { + padding-top: 4px; +} + +.btn-danger { + background: none; + border: 1px solid rgba(239, 68, 68, 0.4); + color: #f87171; + border-radius: var(--radius); + padding: 5px 14px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; +} + +.btn-danger:hover { + background: rgba(239, 68, 68, 0.12); + border-color: #f87171; +} + .error-state { color: var(--text-muted); } diff --git a/packages/frontend/src/pages/Recom.tsx b/packages/frontend/src/pages/Recom.tsx index 6043152..c67938a 100644 --- a/packages/frontend/src/pages/Recom.tsx +++ b/packages/frontend/src/pages/Recom.tsx @@ -80,7 +80,25 @@ export function Recom({ id }: RecomProps) { [id, updateStatus, refreshList], ); - useSSE(sseUrl, handleSSEEvent); + const handleSSEClose = useCallback(() => { + void getRecommendation(id).then((data) => { + if (data.status === 'done') { + setSseUrl(null); + setRec(data); + setStages({ interpreter: 'done', retrieval: 'done', ranking: 'done', curator: 'done' }); + updateStatus(id, 'done'); + void refreshList(); + } else if (data.status === 'error') { + setSseUrl(null); + setRec(data); + updateStatus(id, 'error'); + } else { + setSseUrl(`/api/recommendations/${id}/stream`); + } + }); + }, [id, updateStatus, refreshList]); + + useSSE(sseUrl, handleSSEEvent, handleSSEClose); const handleRetry = async () => { await rerank(id); @@ -172,6 +190,18 @@ export function Recom({ id }: RecomProps) { enabled
)} +
+ +
)}