diff --git a/apps/desktop/src/components/command-palette.tsx b/apps/desktop/src/components/command-palette.tsx index ba55205076..2896f24179 100644 --- a/apps/desktop/src/components/command-palette.tsx +++ b/apps/desktop/src/components/command-palette.tsx @@ -89,6 +89,117 @@ const sortSessionMatches = (matches: (SearchMatch & { type: "session" })[], sort }); }; +const extractParticipantSnippet = async (sessionId: string, query: string) => { + try { + const participants = await dbCommands.sessionListParticipants(sessionId); + const matchingParticipants = participants.filter(p => + (p.full_name && p.full_name.toLowerCase().includes(query.toLowerCase())) + || (p.email && p.email.toLowerCase().includes(query.toLowerCase())) + ); + + if (matchingParticipants.length > 0) { + const names = matchingParticipants + .map(p => p.full_name || p.email) + .filter(Boolean) + .slice(0, 3); // Limit to 3 names + + const nameText = names.join(", "); + const extraCount = matchingParticipants.length - names.length; + + return extraCount > 0 + ? `Meeting with ${nameText} and ${extraCount} other${extraCount > 1 ? "s" : ""}` + : `Meeting with ${nameText}`; + } + } catch (error) { + console.error("Error fetching participants:", error); + } + return null; +}; + +// Create a separate component for session items +function SessionItem({ match, query, onSelect }: { + match: SearchMatch & { type: "session" }; + query: string; + onSelect: () => void; +}) { + const [participantSnippet, setParticipantSnippet] = useState(null); + const titleMatches = (match.item.title || "").toLowerCase().includes(query.toLowerCase()); + + // Try content snippets first + const contentSnippet = !titleMatches + ? (() => { + if (match.item.enhanced_memo_html) { + const enhancedSnippet = extractContentSnippet(match.item.enhanced_memo_html, query); + if (enhancedSnippet) { + return enhancedSnippet; + } + } + + if (match.item.raw_memo_html) { + return extractContentSnippet(match.item.raw_memo_html, query); + } + + return null; + })() + : null; + + // Fetch participant snippet only if no content snippet + useEffect(() => { + if (!titleMatches && !contentSnippet) { + extractParticipantSnippet(match.item.id, query).then(setParticipantSnippet); + } else { + setParticipantSnippet(null); + } + }, [match.item.id, query, titleMatches, contentSnippet]); + + const snippet = contentSnippet + ? { type: "content" as const, text: contentSnippet } + : participantSnippet + ? { type: "participants" as const, text: participantSnippet } + : null; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(); + }; + + return ( + + +
+
+ + {highlightText(match.item.title || "Untitled Note", query)} + +
+ + {formatDate(match.item.created_at)} + + {snippet && ( +
+ {snippet.type === "participants" && } + + {snippet.type === "participants" + ? snippet.text + : highlightText(snippet.text, query)} + +
+ )} +
+
+ ); +} + export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { const inputRef = useRef(null); const { userId } = useHypr(); @@ -262,41 +373,14 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) { {/* Notes Section with content snippets */} {sessionMatches.length > 0 && ( - {sortSessionMatches(sessionMatches, sortBy).map((match) => { - const titleMatches = (match.item.title || "").toLowerCase().includes(query.toLowerCase()); - const snippet = !titleMatches - ? extractContentSnippet( - match.item.enhanced_memo_html || match.item.raw_memo_html || "", - query, - ) - : null; - - return ( - handleSelectItem(match)} - > - -
-
- - {highlightText(match.item.title || "Untitled Note", query)} - -
- - {formatDate(match.item.created_at)} - - {snippet && ( -
- {highlightText(snippet, query)} -
- )} -
-
- ); - })} + {sortSessionMatches(sessionMatches, sortBy).map((match) => ( + handleSelectItem(match)} + /> + ))}
)} diff --git a/crates/db-user/src/sessions_ops.rs b/crates/db-user/src/sessions_ops.rs index 81be2a9067..11738fdaf5 100644 --- a/crates/db-user/src/sessions_ops.rs +++ b/crates/db-user/src/sessions_ops.rs @@ -122,21 +122,27 @@ impl UserDatabase { specific: ListSessionFilterSpecific::Search { query }, }) => { conn.query( - "SELECT * FROM sessions - WHERE user_id = ? AND ( - title LIKE ? OR - REPLACE(REPLACE(REPLACE(enhanced_memo_html, '<', ' '), '>', ' '), ' ', ' ') LIKE ? OR - REPLACE(REPLACE(REPLACE(raw_memo_html, '<', ' '), '>', ' '), ' ', ' ') LIKE ? + "SELECT DISTINCT s.* FROM sessions s + LEFT JOIN session_participants sp ON s.id = sp.session_id + LEFT JOIN humans h ON sp.human_id = h.id + WHERE s.user_id = ? AND ( + s.title LIKE ? OR + REPLACE(REPLACE(REPLACE(s.enhanced_memo_html, '<', ' '), '>', ' '), ' ', ' ') LIKE ? OR + REPLACE(REPLACE(REPLACE(s.raw_memo_html, '<', ' '), '>', ' '), ' ', ' ') LIKE ? OR + h.full_name LIKE ? OR + h.email LIKE ? ) ORDER BY - CASE WHEN title LIKE ? THEN 0 ELSE 1 END, - created_at DESC + CASE WHEN s.title LIKE ? THEN 0 ELSE 1 END, + s.created_at DESC LIMIT ?", vec![ user_id, format!("%{}%", query), // title search format!("%{}%", query), // enhanced_memo search (HTML stripped) format!("%{}%", query), // raw_memo search (HTML stripped) + format!("%{}%", query), // participant name search + format!("%{}%", query), // participant email search format!("%{}%", query), // title priority check limit.unwrap_or(100).to_string(), ],