From 29d86a55077a1b31ea67ef5908e276cfecefa143 Mon Sep 17 00:00:00 2001 From: Lilith Date: Thu, 22 Jan 2026 23:03:10 -0800 Subject: [PATCH] =?UTF-8?q?chore(conversation-assistant):=20=F0=9F=8E=A8?= =?UTF-8?q?=20Enhance=20conversation=20assistant=20studio=20with=20UI=20co?= =?UTF-8?q?mponents=20(ScoreGauge,=20SignalsCard)=20and=20new=20features?= =?UTF-8?q?=20(bulk=20actions,=20search=20controls,=20auto-refresh).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/studio/ScoreGauge.tsx | 2 +- .../src/components/studio/SignalsCard.tsx | 6 +++-- .../src/components/studio/StudioLayout.tsx | 7 +++--- .../studio/SuggestedActionsCard.tsx | 6 +++-- .../src/hooks/useAutoRefreshPrimer.ts | 1 + .../frontend-dev/src/main.tsx | 4 ++- .../src/pages/ContactDetailPage.tsx | 12 +++++---- .../src/pages/ContactsBulkActions.tsx | 10 +++++--- .../src/pages/ContactsPage.styles.ts | 1 + .../frontend-dev/src/pages/ContactsPage.tsx | 25 +++++++++++-------- .../src/pages/ContactsPage.utils.ts | 5 ++-- .../src/pages/ContactsSearchControls.tsx | 15 +++++------ .../frontend-dev/src/pages/ContactsTable.tsx | 14 +++++++---- .../src/pages/ConversationDetailPage.tsx | 24 ++++++++++-------- .../src/pages/ConversationsPage.tsx | 18 +++++++------ 15 files changed, 89 insertions(+), 61 deletions(-) diff --git a/features/conversation-assistant/frontend-dev/src/components/studio/ScoreGauge.tsx b/features/conversation-assistant/frontend-dev/src/components/studio/ScoreGauge.tsx index 501364ce0..695d1206a 100755 --- a/features/conversation-assistant/frontend-dev/src/components/studio/ScoreGauge.tsx +++ b/features/conversation-assistant/frontend-dev/src/components/studio/ScoreGauge.tsx @@ -7,7 +7,7 @@ interface ScoreGaugeProps { inverse?: boolean; } -export function ScoreGauge({ value, label, inverse = false }: ScoreGaugeProps) { +export const ScoreGauge = ({ value, label, inverse = false }: ScoreGaugeProps) => { // Clamp value between 0 and 100 const clampedValue = Math.max(0, Math.min(100, value)); diff --git a/features/conversation-assistant/frontend-dev/src/components/studio/SignalsCard.tsx b/features/conversation-assistant/frontend-dev/src/components/studio/SignalsCard.tsx index 6ac58bb4c..fb5aa0dd4 100755 --- a/features/conversation-assistant/frontend-dev/src/components/studio/SignalsCard.tsx +++ b/features/conversation-assistant/frontend-dev/src/components/studio/SignalsCard.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; + import { Zap, CheckCircle, XCircle, ChevronDown } from 'lucide-react'; + import styles from './SignalsCard.module.css'; interface SignalsCardProps { @@ -10,11 +12,11 @@ interface SignalsCardProps { const MAX_VISIBLE = 5; -export function SignalsCard({ +export const SignalsCard = ({ positiveSignals, negativeSignals, defaultExpanded = false, -}: SignalsCardProps) { +}: SignalsCardProps) => { const [isExpanded, setIsExpanded] = useState(defaultExpanded); const [expandedPositive, setExpandedPositive] = useState(false); const [expandedNegative, setExpandedNegative] = useState(false); diff --git a/features/conversation-assistant/frontend-dev/src/components/studio/StudioLayout.tsx b/features/conversation-assistant/frontend-dev/src/components/studio/StudioLayout.tsx index 9984e876c..6051964db 100755 --- a/features/conversation-assistant/frontend-dev/src/components/studio/StudioLayout.tsx +++ b/features/conversation-assistant/frontend-dev/src/components/studio/StudioLayout.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from 'react'; + import styles from './StudioLayout.module.css'; interface StudioLayoutProps { @@ -6,11 +7,9 @@ interface StudioLayoutProps { analysisPanel: ReactNode; } -export function StudioLayout({ messagesPanel, analysisPanel }: StudioLayoutProps) { - return ( +export const StudioLayout = ({ messagesPanel, analysisPanel }: StudioLayoutProps) => (
{messagesPanel}
{analysisPanel}
- ); -} + ) diff --git a/features/conversation-assistant/frontend-dev/src/components/studio/SuggestedActionsCard.tsx b/features/conversation-assistant/frontend-dev/src/components/studio/SuggestedActionsCard.tsx index a8b733969..14ff639c2 100755 --- a/features/conversation-assistant/frontend-dev/src/components/studio/SuggestedActionsCard.tsx +++ b/features/conversation-assistant/frontend-dev/src/components/studio/SuggestedActionsCard.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; + import { Lightbulb, ChevronDown } from 'lucide-react'; + import styles from './SuggestedActionsCard.module.css'; interface SuggestedActionsCardProps { @@ -7,10 +9,10 @@ interface SuggestedActionsCardProps { defaultExpanded?: boolean; } -export function SuggestedActionsCard({ +export const SuggestedActionsCard = ({ actions, defaultExpanded = true, -}: SuggestedActionsCardProps) { +}: SuggestedActionsCardProps) => { const [isExpanded, setIsExpanded] = useState(defaultExpanded); return ( diff --git a/features/conversation-assistant/frontend-dev/src/hooks/useAutoRefreshPrimer.ts b/features/conversation-assistant/frontend-dev/src/hooks/useAutoRefreshPrimer.ts index a248ac4ad..15d06b5a9 100755 --- a/features/conversation-assistant/frontend-dev/src/hooks/useAutoRefreshPrimer.ts +++ b/features/conversation-assistant/frontend-dev/src/hooks/useAutoRefreshPrimer.ts @@ -1,4 +1,5 @@ import { useEffect, useRef } from 'react'; + import { useQueryClient } from '@tanstack/react-query'; /** diff --git a/features/conversation-assistant/frontend-dev/src/main.tsx b/features/conversation-assistant/frontend-dev/src/main.tsx index 311cbfb7b..864fdb0fc 100755 --- a/features/conversation-assistant/frontend-dev/src/main.tsx +++ b/features/conversation-assistant/frontend-dev/src/main.tsx @@ -1,6 +1,8 @@ import type { ComponentType, ReactNode } from 'react'; -import { bootstrap } from '@lilith/service-react-bootstrap'; + import { AuthProvider } from '@lilith/auth-provider'; +import { bootstrap } from '@lilith/service-react-bootstrap'; + import { App } from './App'; import './index.css'; diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactDetailPage.tsx b/features/conversation-assistant/frontend-dev/src/pages/ContactDetailPage.tsx index 4b3897e2c..a0ed01cd6 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactDetailPage.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactDetailPage.tsx @@ -1,8 +1,10 @@ -import { useParams, useNavigate } from '@lilith/ui-router'; -import { ArrowLeft, User, Clock, Bot } from 'lucide-react'; -import styled from '@lilith/ui-styled-components'; -import type { ThemeInterface } from '@lilith/ui-theme'; import { Spinner } from '@lilith/ui-primitives'; +import { useParams, useNavigate } from '@lilith/ui-router'; +import styled from '@lilith/ui-styled-components'; +import { ArrowLeft, User, Clock, Bot } from 'lucide-react'; + +import type { ThemeInterface } from '@lilith/ui-theme'; + import { useContact, useClassificationHistory, @@ -286,7 +288,7 @@ const NoHistory = styled.p` margin: 0; `; -export function ContactDetailPage() { +export const ContactDetailPage = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactsBulkActions.tsx b/features/conversation-assistant/frontend-dev/src/pages/ContactsBulkActions.tsx index 307230e79..98f7b0c7c 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactsBulkActions.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactsBulkActions.tsx @@ -1,5 +1,3 @@ -import type { ContactClassification } from '@/api'; -import { ClassificationSelector } from '@/components/ClassificationSelector'; import { BulkActions, BulkButton, @@ -8,6 +6,10 @@ import { SelectedCount, } from './ContactsPage.styles'; +import type { ContactClassification } from '@/api'; + +import { ClassificationSelector } from '@/components/ClassificationSelector'; + interface ContactsBulkActionsProps { selectedCount: number; showBulkAction: boolean; @@ -16,13 +18,13 @@ interface ContactsBulkActionsProps { onBulkClassify: (classification: ContactClassification, reason?: string) => void; } -export function ContactsBulkActions({ +export const ContactsBulkActions = ({ selectedCount, showBulkAction, isPending, onShowBulkAction, onBulkClassify, -}: ContactsBulkActionsProps) { +}: ContactsBulkActionsProps) => { if (selectedCount === 0) { return null; } diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.styles.ts b/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.styles.ts index d49b6e1fe..46f1ee610 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.styles.ts +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.styles.ts @@ -1,5 +1,6 @@ import styled from '@lilith/ui-styled-components'; import { Search } from 'lucide-react'; + import type { ThemeInterface } from '@lilith/ui-theme'; export const Container = styled.div` diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.tsx b/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.tsx index 5c4dcf659..d6eb67ab2 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.tsx @@ -1,5 +1,13 @@ import { useState, useEffect } from 'react'; + import { useNavigate, useSearchParams } from '@lilith/ui-router'; + +import { ContactsBulkActions } from './ContactsBulkActions'; +import { Container, Header, Title } from './ContactsPage.styles'; +import { validSortOptions, validSortOrders } from './ContactsPage.utils'; +import { ContactsSearchControls } from './ContactsSearchControls'; +import { ContactsTable } from './ContactsTable'; + import { useContacts, useBulkClassifyContacts, @@ -7,13 +15,8 @@ import { type ContactSortBy, type SortOrder, } from '@/api'; -import { ContactsBulkActions } from './ContactsBulkActions'; -import { ContactsSearchControls } from './ContactsSearchControls'; -import { ContactsTable } from './ContactsTable'; -import { Container, Header, Title } from './ContactsPage.styles'; -import { validSortOptions, validSortOrders } from './ContactsPage.utils'; -export function ContactsPage() { +export const ContactsPage = () => { const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); @@ -37,10 +40,10 @@ export function ContactsPage() { // Sync state to URL useEffect(() => { const params = new URLSearchParams(); - if (sortBy !== 'lastMessage') params.set('sortBy', sortBy); - if (sortOrder !== 'desc') params.set('sortOrder', sortOrder); - if (search) params.set('search', search); - if (filter !== 'all') params.set('classification', filter); + if (sortBy !== 'lastMessage') {params.set('sortBy', sortBy);} + if (sortOrder !== 'desc') {params.set('sortOrder', sortOrder);} + if (search) {params.set('search', search);} + if (filter !== 'all') {params.set('classification', filter);} setSearchParams(params, { replace: true }); }, [sortBy, sortOrder, search, filter, setSearchParams]); @@ -73,7 +76,7 @@ export function ContactsPage() { const bulkClassify = useBulkClassifyContacts(); const handleSelectAll = () => { - if (!contacts) return; + if (!contacts) {return;} if (selectedIds.size === contacts.length) { setSelectedIds(new Set()); } else { diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.utils.ts b/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.utils.ts index 24dee13cf..18b4f5b5d 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.utils.ts +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactsPage.utils.ts @@ -1,4 +1,5 @@ import { formatRelativeTime } from '@lilith/ui-utils'; + import type { Contact, ContactClassification, ContactSortBy } from '@/api'; /** @@ -70,7 +71,7 @@ export function getSortColumnHeader(sortBy: ContactSortBy): string { } } -export const classificationFilters: (ContactClassification | 'all')[] = [ +export const classificationFilters: Array = [ 'all', 'safe', 'unknown', @@ -79,7 +80,7 @@ export const classificationFilters: (ContactClassification | 'all')[] = [ 'confirmed-scam', ]; -export const sortOptions: { value: ContactSortBy; label: string }[] = [ +export const sortOptions: Array<{ value: ContactSortBy; label: string }> = [ { value: 'lastMessage', label: 'Last Message' }, { value: 'upcomingBirthday', label: 'Upcoming Birthday' }, { value: 'age', label: 'Age' }, diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactsSearchControls.tsx b/features/conversation-assistant/frontend-dev/src/pages/ContactsSearchControls.tsx index bc4bf23b2..77a725e9f 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactsSearchControls.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactsSearchControls.tsx @@ -1,6 +1,5 @@ import { ArrowUp, ArrowDown, MapPin } from 'lucide-react'; -import type { ContactClassification, ContactSortBy, SortOrder } from '@/api'; -import { ClassificationBadge } from '@/components/ClassificationBadge'; + import { Controls, FilterButton, @@ -15,6 +14,10 @@ import { } from './ContactsPage.styles'; import { classificationFilters, sortOptions } from './ContactsPage.utils'; +import type { ContactClassification, ContactSortBy, SortOrder } from '@/api'; + +import { ClassificationBadge } from '@/components/ClassificationBadge'; + interface ContactsSearchControlsProps { search: string; filter: ContactClassification | 'all'; @@ -27,7 +30,7 @@ interface ContactsSearchControlsProps { onSortOrderChange: (sortOrder: SortOrder) => void; } -export function ContactsSearchControls({ +export const ContactsSearchControls = ({ search, filter, sortBy, @@ -37,8 +40,7 @@ export function ContactsSearchControls({ onFilterChange, onSortByChange, onSortOrderChange, -}: ContactsSearchControlsProps) { - return ( +}: ContactsSearchControlsProps) => ( @@ -85,5 +87,4 @@ export function ContactsSearchControls({ )} - ); -} + ) diff --git a/features/conversation-assistant/frontend-dev/src/pages/ContactsTable.tsx b/features/conversation-assistant/frontend-dev/src/pages/ContactsTable.tsx index 7401ca165..e1e4b3219 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ContactsTable.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ContactsTable.tsx @@ -1,7 +1,6 @@ -import { User } from 'lucide-react'; import { Spinner } from '@lilith/ui-primitives'; -import type { Contact, ContactSortBy } from '@/api'; -import { ClassificationBadge } from '@/components/ClassificationBadge'; +import { User } from 'lucide-react'; + import { Avatar, Cell, @@ -16,6 +15,11 @@ import { } from './ContactsPage.styles'; import { formatSortValue, getSortColumnHeader } from './ContactsPage.utils'; +import type { Contact, ContactSortBy } from '@/api'; + +import { ClassificationBadge } from '@/components/ClassificationBadge'; + + interface ContactsTableProps { contacts: Contact[] | undefined; isLoading: boolean; @@ -26,7 +30,7 @@ interface ContactsTableProps { onRowClick: (id: string) => void; } -export function ContactsTable({ +export const ContactsTable = ({ contacts, isLoading, selectedIds, @@ -34,7 +38,7 @@ export function ContactsTable({ onSelectAll, onSelect, onRowClick, -}: ContactsTableProps) { +}: ContactsTableProps) => { if (isLoading) { return ( diff --git a/features/conversation-assistant/frontend-dev/src/pages/ConversationDetailPage.tsx b/features/conversation-assistant/frontend-dev/src/pages/ConversationDetailPage.tsx index 0eb4871cd..4d20ec7dc 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ConversationDetailPage.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ConversationDetailPage.tsx @@ -1,11 +1,15 @@ -import { useParams, Navigate, Link } from '@lilith/ui-router'; import { useState, useRef, useEffect, useMemo, useCallback } from 'react'; -import { Sparkles, Loader2, BrainCircuit } from 'lucide-react'; -import styled from '@lilith/ui-styled-components'; -import type { ThemeInterface } from '@lilith/ui-theme'; + +import { useToast } from '@lilith/ui-feedback'; import { MessageBubble } from '@lilith/ui-messaging'; import { Spinner } from '@lilith/ui-primitives'; -import { useToast } from '@lilith/ui-feedback'; +import { useParams, Navigate, Link } from '@lilith/ui-router'; +import styled from '@lilith/ui-styled-components'; +import { Sparkles, Loader2, BrainCircuit } from 'lucide-react'; + +import type { ThemeInterface } from '@lilith/ui-theme'; + + import { useConversation, useMessagesInfinite, @@ -192,7 +196,7 @@ function toUiMessageWithUrl( return uiMessage; } -export function ConversationDetailPage() { +export const ConversationDetailPage = () => { const { id } = useParams<{ id: string }>(); const toast = useToast(); const [selectedMessage, setSelectedMessage] = useState(null); @@ -232,7 +236,7 @@ export function ConversationDetailPage() { // Flatten, sort, and transform messages (oldest first, newest at bottom) const sortedMessages = useMemo(() => { - if (!messagesData?.pages) return []; + if (!messagesData?.pages) {return [];} // Flatten all pages, then sort ascending by sentAt const allMessages = messagesData.pages.flatMap((page) => page.data); const sorted = [...allMessages].sort( @@ -246,7 +250,7 @@ export function ConversationDetailPage() { // Keep original messages for direction check (generate button) const originalMessages = useMemo(() => { - if (!messagesData?.pages) return new Map(); + if (!messagesData?.pages) {return new Map();} const allMessages = messagesData.pages.flatMap((page) => page.data); return new Map(allMessages.map((msg) => [msg.id, msg as BackendMessage])); }, [messagesData]); @@ -269,7 +273,7 @@ export function ConversationDetailPage() { useEffect(() => { const element = loadMoreRef.current; - if (!element) return; + if (!element) {return;} const observer = new IntersectionObserver(handleIntersection, { root: messagesContainerRef.current, @@ -283,7 +287,7 @@ export function ConversationDetailPage() { // Maintain scroll position when loading older messages useEffect(() => { const container = messagesContainerRef.current; - if (!container || isFetchingNextPage) return; + if (!container || isFetchingNextPage) {return;} if (isInitialLoad.current && sortedMessages.length > 0) { // Initial load: scroll to bottom diff --git a/features/conversation-assistant/frontend-dev/src/pages/ConversationsPage.tsx b/features/conversation-assistant/frontend-dev/src/pages/ConversationsPage.tsx index a09ddf4d3..be794fb92 100755 --- a/features/conversation-assistant/frontend-dev/src/pages/ConversationsPage.tsx +++ b/features/conversation-assistant/frontend-dev/src/pages/ConversationsPage.tsx @@ -1,10 +1,14 @@ import { useRef, useEffect, useMemo, useCallback } from 'react'; -import { Link } from '@lilith/ui-router'; -import { User, Users, ChevronRight } from 'lucide-react'; -import styled from '@lilith/ui-styled-components'; -import type { ThemeInterface } from '@lilith/ui-theme'; + import { Spinner } from '@lilith/ui-primitives'; +import { Link } from '@lilith/ui-router'; +import styled from '@lilith/ui-styled-components'; import { formatRelativeTime } from '@lilith/ui-utils'; +import { User, Users, ChevronRight } from 'lucide-react'; + +import type { ThemeInterface } from '@lilith/ui-theme'; + + import { useConversationsInfinite } from '@/api'; const Container = styled.div` @@ -179,7 +183,7 @@ const LoadingMore = styled.div` font-size: 14px; `; -export function ConversationsPage() { +export const ConversationsPage = () => { const { data, isLoading, @@ -192,7 +196,7 @@ export function ConversationsPage() { // Flatten pages into single array const conversations = useMemo(() => { - if (!data?.pages) return []; + if (!data?.pages) {return [];} return data.pages.flatMap((page) => page.data); }, [data]); @@ -211,7 +215,7 @@ export function ConversationsPage() { useEffect(() => { const element = loadMoreRef.current; - if (!element) return; + if (!element) {return;} const observer = new IntersectionObserver(handleIntersection, { rootMargin: '200px', // Start loading before reaching the end