diff --git a/features/conversation-assistant/frontend-dev/src/components/DraftResponsePanel.module.css b/features/conversation-assistant/frontend-dev/src/components/DraftResponsePanel.module.css new file mode 100644 index 000000000..acc738e03 --- /dev/null +++ b/features/conversation-assistant/frontend-dev/src/components/DraftResponsePanel.module.css @@ -0,0 +1,179 @@ +.panel { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 6px; + margin-left: 8px; +} + +.card { + background-color: var(--bg-secondary); + border: 1px dashed var(--border); + border-radius: 10px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 10px; + max-width: 520px; +} + +.topRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.themeBadge { + font-size: 11px; + font-weight: 600; + padding: 3px 8px; + border-radius: 12px; + background-color: var(--bg-tertiary); + color: var(--text-secondary); + white-space: nowrap; +} + +.draftText { + font-size: 13px; + color: var(--text-primary); + line-height: 1.5; + margin: 0; + white-space: pre-wrap; + word-break: break-word; +} + +.actions { + display: flex; + gap: 6px; +} + +.acceptButton, +.editButton, +.skipButton { + display: flex; + align-items: center; + gap: 5px; + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; +} + +.acceptButton { + background-color: rgba(34, 197, 94, 0.15); + color: #22c55e; +} + +.acceptButton:hover:not(:disabled) { + background-color: rgba(34, 197, 94, 0.25); +} + +.editButton { + background-color: rgba(99, 102, 241, 0.12); + color: #818cf8; +} + +.editButton:hover:not(:disabled) { + background-color: rgba(99, 102, 241, 0.22); +} + +.skipButton { + background-color: var(--bg-tertiary); + color: var(--text-secondary); +} + +.skipButton:hover:not(:disabled) { + background-color: var(--border); + color: var(--text-primary); +} + +.acceptButton:disabled, +.editButton:disabled, +.skipButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.editArea { + display: flex; + flex-direction: column; + gap: 8px; +} + +.textarea { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background-color: var(--bg-tertiary); + color: var(--text-primary); + font-size: 13px; + line-height: 1.5; + resize: vertical; + font-family: inherit; + box-sizing: border-box; +} + +.textarea:focus { + outline: none; + border-color: var(--accent); +} + +.textarea:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.editActions { + display: flex; + gap: 6px; +} + +.saveButton { + display: flex; + align-items: center; + gap: 5px; + padding: 6px 14px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; + background-color: rgba(34, 197, 94, 0.15); + color: #22c55e; +} + +.saveButton:hover:not(:disabled) { + background-color: rgba(34, 197, 94, 0.25); +} + +.saveButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.cancelButton { + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background-color: transparent; + color: var(--text-secondary); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.cancelButton:hover:not(:disabled) { + background-color: var(--bg-tertiary); +} + +.cancelButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/features/conversation-assistant/frontend-dev/src/components/DraftResponsePanel.tsx b/features/conversation-assistant/frontend-dev/src/components/DraftResponsePanel.tsx new file mode 100644 index 000000000..ebde81b23 --- /dev/null +++ b/features/conversation-assistant/frontend-dev/src/components/DraftResponsePanel.tsx @@ -0,0 +1,197 @@ +import { useState } from 'react'; + +import { CheckIcon, EditIcon, SkipForwardIcon } from '@lilith/ui-icons'; + +import { + useAcceptResponse, + useRejectResponse, + useEditResponse, + type GeneratedResponse, +} from '@/api'; +import { ConfidenceIndicator } from '@/components/ConfidenceIndicator'; + +import styles from './DraftResponsePanel.module.css'; + +interface DraftResponsePanelProps { + drafts: GeneratedResponse[]; + onDraftActioned: () => void; +} + +type Theme = GeneratedResponse['theme']; + +const THEME_LABELS: Record, string> = { + flirty: '💬 Flirty', + flirty_closing: '📅 Closing', + deflection: '🛡 Deflect', +}; + +function getThemeLabel(theme: Theme): string { + if (theme && theme in THEME_LABELS) { + return THEME_LABELS[theme as NonNullable]; + } + return '✨ Draft'; +} + +interface DraftCardProps { + draft: GeneratedResponse; + onDismiss: (id: string) => void; + onActioned: () => void; +} + +const DraftCard = ({ draft, onDismiss, onActioned }: DraftCardProps) => { + const [isEditing, setIsEditing] = useState(false); + const [editText, setEditText] = useState(draft.editedResponse ?? draft.response); + + const acceptMutation = useAcceptResponse(); + const rejectMutation = useRejectResponse(); + const editMutation = useEditResponse(); + + const isPending = + acceptMutation.isPending || rejectMutation.isPending || editMutation.isPending; + + const handleAccept = () => { + acceptMutation.mutate(draft.id, { + onSuccess: () => { + onDismiss(draft.id); + onActioned(); + }, + }); + }; + + const handleSkip = () => { + rejectMutation.mutate( + { id: draft.id, reason: 'skipped' }, + { + onSuccess: () => { + onDismiss(draft.id); + onActioned(); + }, + } + ); + }; + + const handleSaveEdit = () => { + editMutation.mutate( + { id: draft.id, response: editText }, + { + onSuccess: () => { + acceptMutation.mutate(draft.id, { + onSuccess: () => { + onDismiss(draft.id); + onActioned(); + }, + }); + }, + } + ); + }; + + return ( +
+
+ {getThemeLabel(draft.theme)} + +
+ + {isEditing ? ( +
+