section-user-preferences.tsx
1 'use client' 2 3 import { useState } from 'react' 4 import { useAppStore } from '@/stores/use-app-store' 5 import { AgentAvatar } from '@/components/agents/agent-avatar' 6 import type { SettingsSectionProps } from './types' 7 8 function buildWhatsAppContactId(): string { 9 if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { 10 return crypto.randomUUID() 11 } 12 return `wa-contact-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` 13 } 14 15 export function UserPreferencesSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) { 16 const agents = useAppStore((s) => s.agents) 17 const sortedAgents = Object.values(agents).sort((a, b) => a.name.localeCompare(b.name)) 18 const whatsappApprovedContacts = Array.isArray(appSettings.whatsappApprovedContacts) ? appSettings.whatsappApprovedContacts : [] 19 const [nextWhatsAppLabel, setNextWhatsAppLabel] = useState('') 20 const [nextWhatsAppPhone, setNextWhatsAppPhone] = useState('') 21 22 const addWhatsAppContact = () => { 23 const phone = nextWhatsAppPhone.trim() 24 if (!phone) return 25 const label = nextWhatsAppLabel.trim() || phone 26 patchSettings({ 27 whatsappApprovedContacts: [ 28 ...whatsappApprovedContacts, 29 { id: buildWhatsAppContactId(), label, phone }, 30 ], 31 }) 32 setNextWhatsAppLabel('') 33 setNextWhatsAppPhone('') 34 } 35 36 const removeWhatsAppContact = (id: string) => { 37 patchSettings({ 38 whatsappApprovedContacts: whatsappApprovedContacts.filter((entry) => entry.id !== id), 39 }) 40 } 41 42 return ( 43 <div className="mb-10"> 44 <h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2"> 45 User Preferences 46 </h3> 47 <p className="text-[12px] text-text-3 mb-5"> 48 Global instructions injected into ALL agent system prompts. Define your style, rules, and preferences. 49 </p> 50 <textarea 51 value={appSettings.userPrompt || ''} 52 onChange={(e) => patchSettings({ userPrompt: e.target.value })} 53 placeholder="e.g. Always respond concisely. Use TypeScript over JavaScript. Prefer functional patterns. My timezone is PST." 54 rows={4} 55 className={`${inputClass} resize-y min-h-[100px]`} 56 style={{ fontFamily: 'inherit' }} 57 /> 58 59 {/* Suggested replies toggle */} 60 <div className="mt-6 flex items-center justify-between"> 61 <div> 62 <label className="text-[12px] font-600 text-text-2 block">Suggested Replies</label> 63 <p className="text-[11px] text-text-3/60 mt-0.5"> 64 Show follow-up suggestions after each agent response. 65 </p> 66 </div> 67 <button 68 type="button" 69 onClick={() => patchSettings({ suggestionsEnabled: !appSettings.suggestionsEnabled })} 70 className={`relative w-9 h-5 rounded-full transition-colors ${appSettings.suggestionsEnabled ? 'bg-accent-bright' : 'bg-white/[0.10]'}`} 71 style={{ fontFamily: 'inherit' }} 72 > 73 <span className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${appSettings.suggestionsEnabled ? 'translate-x-4' : ''}`} /> 74 </button> 75 </div> 76 77 {/* Default agent */} 78 <div className="mt-6"> 79 <label className="text-[12px] font-600 text-text-2 block mb-1.5">Default Agent</label> 80 <p className="text-[11px] text-text-3/60 mb-2"> 81 The agent that opens automatically when you start the app or use the default-agent shortcut. 82 </p> 83 <div className="flex flex-wrap gap-2"> 84 <button 85 type="button" 86 onClick={() => patchSettings({ defaultAgentId: null })} 87 className={`flex items-center gap-2 px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border 88 ${!appSettings.defaultAgentId 89 ? 'bg-white/[0.06] border-accent-bright/30 text-text' 90 : 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`} 91 style={{ fontFamily: 'inherit' }} 92 > 93 Auto (first agent) 94 </button> 95 {sortedAgents.map((agent) => ( 96 <button 97 key={agent.id} 98 onClick={() => patchSettings({ defaultAgentId: agent.id })} 99 className={`flex items-center gap-2 px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border 100 ${appSettings.defaultAgentId === agent.id 101 ? 'bg-white/[0.06] border-accent-bright/30 text-text' 102 : 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`} 103 style={{ fontFamily: 'inherit' }} 104 > 105 <AgentAvatar seed={agent.avatarSeed || null} avatarUrl={agent.avatarUrl} name={agent.name} size={18} /> 106 {agent.name} 107 </button> 108 ))} 109 </div> 110 </div> 111 112 <div className="mt-6"> 113 <label className="text-[12px] font-600 text-text-2 block mb-1.5">WhatsApp Approved Users</label> 114 <p className="text-[11px] text-text-3/60 mb-3"> 115 These numbers or JIDs are globally approved for WhatsApp DMs. They bypass per-connector pairing and are merged into WhatsApp allowlists. 116 </p> 117 118 {whatsappApprovedContacts.length > 0 ? ( 119 <div className="space-y-2 mb-3"> 120 {whatsappApprovedContacts.map((entry) => ( 121 <div 122 key={entry.id} 123 className="flex items-center justify-between gap-3 rounded-[12px] border border-white/[0.06] bg-white/[0.03] px-3 py-2" 124 > 125 <div className="min-w-0"> 126 <div className="text-[12px] font-600 text-text truncate">{entry.label}</div> 127 <div className="text-[11px] text-text-3/70 truncate">{entry.phone}</div> 128 </div> 129 <button 130 type="button" 131 onClick={() => removeWhatsAppContact(entry.id)} 132 className="shrink-0 px-2.5 py-1.5 rounded-[8px] bg-white/[0.04] text-[11px] text-text-3 hover:text-text hover:bg-white/[0.08] transition-colors border-none cursor-pointer" 133 style={{ fontFamily: 'inherit' }} 134 > 135 Remove 136 </button> 137 </div> 138 ))} 139 </div> 140 ) : ( 141 <div className="mb-3 rounded-[12px] border border-dashed border-white/[0.08] bg-white/[0.02] px-3 py-3 text-[11px] text-text-3/70"> 142 No globally approved WhatsApp users yet. 143 </div> 144 )} 145 146 <div className="grid grid-cols-1 md:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)_auto] gap-2"> 147 <input 148 type="text" 149 value={nextWhatsAppLabel} 150 onChange={(e) => setNextWhatsAppLabel(e.target.value)} 151 onKeyDown={(e) => { 152 if (e.key === 'Enter') { 153 e.preventDefault() 154 addWhatsAppContact() 155 } 156 }} 157 placeholder="Label (e.g. Family, Alice)" 158 className={inputClass} 159 style={{ fontFamily: 'inherit' }} 160 /> 161 <input 162 type="text" 163 value={nextWhatsAppPhone} 164 onChange={(e) => setNextWhatsAppPhone(e.target.value)} 165 onKeyDown={(e) => { 166 if (e.key === 'Enter') { 167 e.preventDefault() 168 addWhatsAppContact() 169 } 170 }} 171 placeholder="+15551234567 or 15551234567@s.whatsapp.net" 172 className={inputClass} 173 style={{ fontFamily: 'inherit' }} 174 /> 175 <button 176 type="button" 177 onClick={addWhatsAppContact} 178 disabled={!nextWhatsAppPhone.trim()} 179 className="px-3 py-2 rounded-[10px] text-[12px] font-600 border border-white/[0.06] bg-white/[0.04] text-text transition-colors disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer hover:bg-white/[0.08]" 180 style={{ fontFamily: 'inherit' }} 181 > 182 Add User 183 </button> 184 </div> 185 </div> 186 </div> 187 ) 188 }