/ src / views / settings / section-user-preferences.tsx
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  }