@crmy/web 0.5.1 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/assets/index-CskfWp8E.js +560 -0
  2. package/dist/assets/index-D763l57m.css +1 -0
  3. package/{index.html → dist/index.html} +2 -1
  4. package/package.json +4 -1
  5. package/postcss.config.js +0 -6
  6. package/src/App.tsx +0 -158
  7. package/src/api/client.ts +0 -82
  8. package/src/api/hooks.ts +0 -689
  9. package/src/components/CustomFields.tsx +0 -240
  10. package/src/components/NavLink.tsx +0 -28
  11. package/src/components/crm/AIFab.tsx +0 -37
  12. package/src/components/crm/AccountDrawer.tsx +0 -372
  13. package/src/components/crm/ActivityTimeline.tsx +0 -115
  14. package/src/components/crm/AssignmentDrawer.tsx +0 -396
  15. package/src/components/crm/BriefingPanel.tsx +0 -217
  16. package/src/components/crm/CommandPalette.tsx +0 -254
  17. package/src/components/crm/ContactAvatar.tsx +0 -49
  18. package/src/components/crm/ContactDrawer.tsx +0 -438
  19. package/src/components/crm/ContextPanel.tsx +0 -200
  20. package/src/components/crm/CrmWidgets.tsx +0 -417
  21. package/src/components/crm/DrawerShell.tsx +0 -77
  22. package/src/components/crm/ListToolbar.tsx +0 -252
  23. package/src/components/crm/OpportunityDrawer.tsx +0 -372
  24. package/src/components/crm/PaginationBar.tsx +0 -111
  25. package/src/components/crm/QuickAddDrawer.tsx +0 -652
  26. package/src/components/crm/ShortcutsOverlay.tsx +0 -65
  27. package/src/components/crm/UseCaseDrawer.tsx +0 -454
  28. package/src/components/layout/MobileNav.tsx +0 -49
  29. package/src/components/layout/Sidebar.tsx +0 -157
  30. package/src/components/layout/TopBar.tsx +0 -54
  31. package/src/components/settings/ActorsSettings.tsx +0 -1190
  32. package/src/components/ui/accordion.tsx +0 -52
  33. package/src/components/ui/alert-dialog.tsx +0 -104
  34. package/src/components/ui/alert.tsx +0 -43
  35. package/src/components/ui/aspect-ratio.tsx +0 -5
  36. package/src/components/ui/avatar.tsx +0 -38
  37. package/src/components/ui/badge.tsx +0 -29
  38. package/src/components/ui/breadcrumb.tsx +0 -90
  39. package/src/components/ui/button.tsx +0 -47
  40. package/src/components/ui/calendar.tsx +0 -54
  41. package/src/components/ui/card.tsx +0 -43
  42. package/src/components/ui/carousel.tsx +0 -224
  43. package/src/components/ui/chart.tsx +0 -303
  44. package/src/components/ui/checkbox.tsx +0 -26
  45. package/src/components/ui/collapsible.tsx +0 -9
  46. package/src/components/ui/command.tsx +0 -132
  47. package/src/components/ui/context-menu.tsx +0 -178
  48. package/src/components/ui/date-picker.tsx +0 -313
  49. package/src/components/ui/dialog.tsx +0 -95
  50. package/src/components/ui/drawer.tsx +0 -87
  51. package/src/components/ui/dropdown-menu.tsx +0 -179
  52. package/src/components/ui/form.tsx +0 -129
  53. package/src/components/ui/hover-card.tsx +0 -27
  54. package/src/components/ui/input-otp.tsx +0 -61
  55. package/src/components/ui/input.tsx +0 -22
  56. package/src/components/ui/label.tsx +0 -17
  57. package/src/components/ui/menubar.tsx +0 -207
  58. package/src/components/ui/navigation-menu.tsx +0 -120
  59. package/src/components/ui/pagination.tsx +0 -81
  60. package/src/components/ui/popover.tsx +0 -29
  61. package/src/components/ui/progress.tsx +0 -23
  62. package/src/components/ui/radio-group.tsx +0 -36
  63. package/src/components/ui/resizable.tsx +0 -37
  64. package/src/components/ui/scroll-area.tsx +0 -38
  65. package/src/components/ui/select.tsx +0 -143
  66. package/src/components/ui/separator.tsx +0 -20
  67. package/src/components/ui/sheet.tsx +0 -107
  68. package/src/components/ui/sidebar.tsx +0 -637
  69. package/src/components/ui/skeleton.tsx +0 -7
  70. package/src/components/ui/slider.tsx +0 -23
  71. package/src/components/ui/sonner.tsx +0 -24
  72. package/src/components/ui/switch.tsx +0 -27
  73. package/src/components/ui/table.tsx +0 -72
  74. package/src/components/ui/tabs.tsx +0 -53
  75. package/src/components/ui/textarea.tsx +0 -21
  76. package/src/components/ui/toast.tsx +0 -111
  77. package/src/components/ui/toaster.tsx +0 -24
  78. package/src/components/ui/toggle-group.tsx +0 -49
  79. package/src/components/ui/toggle.tsx +0 -37
  80. package/src/components/ui/tooltip.tsx +0 -28
  81. package/src/components/ui/use-toast.ts +0 -1
  82. package/src/components/ui/utils.ts +0 -9
  83. package/src/contexts/AgentSettingsContext.tsx +0 -24
  84. package/src/hooks/use-mobile.tsx +0 -19
  85. package/src/hooks/use-toast.ts +0 -186
  86. package/src/hooks/useKeyboardShortcuts.ts +0 -95
  87. package/src/hooks/useTheme.ts +0 -24
  88. package/src/index.css +0 -245
  89. package/src/lib/entityColors.ts +0 -18
  90. package/src/lib/stageConfig.ts +0 -32
  91. package/src/lib/utils.ts +0 -6
  92. package/src/main.tsx +0 -25
  93. package/src/pages/Accounts.tsx +0 -205
  94. package/src/pages/Activities.tsx +0 -251
  95. package/src/pages/Agent.tsx +0 -237
  96. package/src/pages/AgentSettings.tsx +0 -544
  97. package/src/pages/Assignments.tsx +0 -750
  98. package/src/pages/Contacts.tsx +0 -200
  99. package/src/pages/Dashboard.tsx +0 -143
  100. package/src/pages/Inbox.tsx +0 -615
  101. package/src/pages/NotFound.tsx +0 -24
  102. package/src/pages/Opportunities.tsx +0 -386
  103. package/src/pages/SearchResults.tsx +0 -49
  104. package/src/pages/Settings.tsx +0 -1884
  105. package/src/pages/UseCases.tsx +0 -396
  106. package/src/pages/auth/Login.tsx +0 -261
  107. package/src/pages/hitl/HITL.tsx +0 -101
  108. package/src/store/appStore.ts +0 -103
  109. package/src/vite-env.d.ts +0 -14
  110. package/tailwind.config.js +0 -121
  111. package/tsconfig.json +0 -24
  112. package/vite.config.ts +0 -27
  113. /package/{public → dist}/android-chrome-192x192.png +0 -0
  114. /package/{public → dist}/android-chrome-512x512.png +0 -0
  115. /package/{public → dist}/apple-touch-icon.png +0 -0
  116. /package/{src/assets/crmy-logo.png → dist/assets/crmy-logo-DWN0xBPW.png} +0 -0
  117. /package/{public → dist}/favicon-16x16.png +0 -0
  118. /package/{public → dist}/favicon-32x32.png +0 -0
  119. /package/{public → dist}/favicon.ico +0 -0
  120. /package/{public → dist}/favicon.svg +0 -0
  121. /package/{public → dist}/site.webmanifest +0 -0
@@ -1,544 +0,0 @@
1
- // Copyright 2026 CRMy Contributors
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- import React, { useState, useRef, useEffect } from 'react';
5
- import { Sparkles, KeyRound, Users, TriangleAlert, Eye, EyeOff, X } from 'lucide-react';
6
- import { Switch } from '@/components/ui/switch';
7
- import { toast } from '@/hooks/use-toast';
8
- import { useAgentSettings } from '@/contexts/AgentSettingsContext';
9
-
10
- // ─── Types ───────────────────────────────────────────────────────────────────
11
-
12
- type Provider = 'anthropic' | 'openai' | 'openrouter' | 'ollama' | 'custom';
13
- // ─── Constants ───────────────────────────────────────────────────────────────
14
-
15
- const providerModels: Record<Provider, string[]> = {
16
- anthropic: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514', 'claude-haiku-4-5-20251001'],
17
- openai: ['gpt-4o', 'gpt-4o-mini', 'o3-mini'],
18
- openrouter: ['openrouter/auto', 'anthropic/claude-sonnet-4', 'openai/gpt-4o', 'google/gemini-2.0-flash'],
19
- ollama: ['llama3.2', 'mistral', 'deepseek-r1'],
20
- custom: [],
21
- };
22
-
23
- const providerUrls: Record<Provider, string> = {
24
- anthropic: 'https://api.anthropic.com/v1',
25
- openai: 'https://api.openai.com/v1',
26
- openrouter: 'https://openrouter.ai/api/v1',
27
- ollama: 'http://localhost:11434/v1',
28
- custom: 'https://your-gateway.example.com/v1',
29
- };
30
-
31
- const providerColors: Record<Provider, { dot: string; active: string }> = {
32
- anthropic: { dot: 'bg-amber-500', active: 'border-amber-500/50 bg-amber-500/10 text-amber-600 dark:text-amber-400' },
33
- openai: { dot: 'bg-green-500', active: 'border-green-500/50 bg-green-500/10 text-green-600 dark:text-green-400' },
34
- openrouter: { dot: 'bg-purple-500', active: 'border-purple-500/50 bg-purple-500/10 text-purple-600 dark:text-purple-400' },
35
- ollama: { dot: 'bg-blue-500', active: 'border-blue-500/50 bg-blue-500/10 text-blue-600 dark:text-blue-400' },
36
- custom: { dot: 'bg-gray-400', active: 'border-gray-400/50 bg-gray-400/10 text-gray-600 dark:text-gray-400' },
37
- };
38
-
39
- const tokenCosts: Record<number, string> = {
40
- 1000: '~$0.002 / turn at current model pricing',
41
- 4000: '~$0.006 / turn at current model pricing',
42
- 8000: '~$0.012 / turn at current model pricing',
43
- 16000: '~$0.024 / turn at current model pricing',
44
- };
45
-
46
- const defaultSettings = {
47
- provider: 'anthropic' as Provider,
48
- baseUrl: 'https://api.anthropic.com/v1',
49
- apiKey: 'sk-ant-api03-••••••••••••••••••••••••••••••••',
50
- model: 'claude-sonnet-4-20250514',
51
- historyRetentionDays: 90,
52
- systemPrompt: `You are a CRMy AI agent helping [User Name] manage their real estate/mortgage/insurance pipeline. You have access to their contacts, accounts, opportunities, use cases, and activity history via MCP tools. Be concise, accurate, and always confirm before making changes to CRM objects.`,
53
- maxTokensPerTurn: 4000,
54
- canCreateAssignments: true,
55
- canLogActivities: true,
56
- canWriteObjects: false,
57
- };
58
-
59
- // ─── Shared style constants ───────────────────────────────────────────────────
60
-
61
- const inputCls = 'w-full h-9 px-3 rounded-lg border border-border bg-background text-sm text-foreground placeholder:text-muted-foreground outline-none focus:ring-1 focus:ring-ring';
62
- const selectCls = `${inputCls} cursor-pointer`;
63
- const primaryBtn = 'px-3 py-1.5 rounded-lg bg-primary text-primary-foreground text-xs font-semibold hover:bg-primary/90 transition-colors disabled:opacity-40';
64
- const ghostBtn = 'px-3 py-1.5 rounded-lg border border-border text-xs font-semibold text-muted-foreground hover:text-foreground hover:border-foreground/30 transition-colors';
65
- const dangerBtn = 'px-3 py-1.5 rounded-lg bg-destructive text-destructive-foreground text-xs font-semibold hover:bg-destructive/90 transition-colors';
66
-
67
- // ─── Main Component ───────────────────────────────────────────────────────────
68
-
69
- export default function AgentSettings() {
70
- const { enabled, setEnabled } = useAgentSettings();
71
-
72
- // Section 2 — Provider & Model
73
- const [provider, setProvider] = useState<Provider>(defaultSettings.provider);
74
- const [baseUrl, setBaseUrl] = useState(defaultSettings.baseUrl);
75
- const [apiKey, setApiKey] = useState(defaultSettings.apiKey);
76
- const [showApiKey, setShowApiKey] = useState(false);
77
- const [model, setModel] = useState(defaultSettings.model);
78
- const [customModel, setCustomModel] = useState('');
79
- const [testingConn, setTestingConn] = useState(false);
80
- const [savingProvider, setSavingProvider] = useState(false);
81
- const [saveProviderDone, setSaveProviderDone] = useState(false);
82
-
83
- // Section 3 — Per-User Behaviour
84
- const [historyRetention, setHistoryRetention] = useState(defaultSettings.historyRetentionDays);
85
- const [systemPrompt, setSystemPrompt] = useState(defaultSettings.systemPrompt);
86
- const [editingPrompt, setEditingPrompt] = useState(false);
87
- const [promptDraft, setPromptDraft] = useState(defaultSettings.systemPrompt);
88
- const [maxTokens, setMaxTokens] = useState(defaultSettings.maxTokensPerTurn);
89
- const [canCreateAssignments, setCanCreateAssignments] = useState(defaultSettings.canCreateAssignments);
90
- const [canLogActivities, setCanLogActivities] = useState(defaultSettings.canLogActivities);
91
- const [canWriteObjects, setCanWriteObjects] = useState(defaultSettings.canWriteObjects);
92
- const textareaRef = useRef<HTMLTextAreaElement>(null);
93
-
94
- // Section 4 — Danger Zone
95
- const [showClearConfirm, setShowClearConfirm] = useState(false);
96
- const [clearConfirmText, setClearConfirmText] = useState('');
97
-
98
- // Focus textarea when prompt editor opens
99
- useEffect(() => {
100
- if (editingPrompt && textareaRef.current) {
101
- textareaRef.current.focus();
102
- }
103
- }, [editingPrompt]);
104
-
105
- // Provider change — update URL and model
106
- const handleProviderChange = (p: Provider) => {
107
- setProvider(p);
108
- setBaseUrl(providerUrls[p]);
109
- const models = providerModels[p];
110
- setModel(models[0] ?? '');
111
- setCustomModel('');
112
- setSaveProviderDone(false);
113
- };
114
-
115
- // Test connection
116
- const handleTestConnection = async () => {
117
- setTestingConn(true);
118
- await new Promise(r => setTimeout(r, 1200));
119
- setTestingConn(false);
120
- if (Math.random() > 0.3) {
121
- toast({ title: 'Connection successful', description: `Connected to ${provider} at ${baseUrl}` });
122
- } else {
123
- toast({ title: 'Connection failed', description: 'Could not reach the provider. Check your API key and base URL.', variant: 'destructive' });
124
- }
125
- };
126
-
127
- // Save provider settings
128
- const handleSaveProvider = async () => {
129
- setSavingProvider(true);
130
- await new Promise(r => setTimeout(r, 1500));
131
- setSavingProvider(false);
132
- setSaveProviderDone(true);
133
- toast({ title: 'Settings saved', description: 'Provider configuration updated.' });
134
- setTimeout(() => setSaveProviderDone(false), 3000);
135
- };
136
-
137
- // Clear all histories
138
- const handleClearAll = () => {
139
- setShowClearConfirm(false);
140
- setClearConfirmText('');
141
- toast({ title: 'Chat histories cleared', description: 'All agent chat histories have been deleted.' });
142
- };
143
-
144
- const dimCls = !enabled ? 'opacity-40 pointer-events-none' : '';
145
-
146
- return (
147
- <div className="space-y-5 max-w-2xl">
148
- <div>
149
- <h2 className="font-display font-bold text-lg text-foreground mb-1">Local AI Agent</h2>
150
- <p className="text-sm text-muted-foreground">Configure the AI agent for your workspace.</p>
151
- </div>
152
-
153
- {/* ── SECTION 1: Enable AI Agent ─────────────────────────────────────── */}
154
- <div className="rounded-xl border border-border bg-card overflow-hidden">
155
- {/* Header */}
156
- <div className="flex items-center justify-between px-5 py-4 border-b border-border">
157
- <div className="flex items-center gap-3">
158
- <div className="w-9 h-9 rounded-xl bg-amber-500/10 flex items-center justify-center">
159
- <Sparkles className="w-4 h-4 text-amber-500" />
160
- </div>
161
- <div>
162
- <h3 className="text-sm font-semibold text-foreground">Enable In-App AI agent</h3>
163
- <p className="text-xs text-muted-foreground">Toggle AI features on or off for this workspace</p>
164
- </div>
165
- </div>
166
- <div className="flex items-center gap-3">
167
- <span className="text-[11px] font-semibold px-2 py-0.5 rounded-full border bg-muted text-muted-foreground border-border">
168
- Coming soon
169
- </span>
170
- <Switch
171
- checked={false}
172
- onCheckedChange={() => {}}
173
- disabled
174
- aria-label="Enable In-App AI agent"
175
- />
176
- </div>
177
- </div>
178
-
179
- {/* Info banner */}
180
- <div className="px-5 py-3 bg-muted/40 border-b border-border">
181
- <p className="text-xs text-muted-foreground">
182
- Backend support for the AI agent is not yet available. This toggle will be enabled once the agent service is configured and deployed.
183
- </p>
184
- </div>
185
-
186
- {/* Feature tags */}
187
- {enabled && (
188
- <div className="px-5 py-3 flex flex-wrap gap-2">
189
- {[
190
- { icon: '✦', label: 'Sparkle column (tables)' },
191
- { icon: '💬', label: 'Chat button (object detail)' },
192
- { icon: '◎', label: 'Floating AI icon' },
193
- { icon: '✎', label: 'AI-assist on edit' },
194
- ].map((tag) => (
195
- <span key={tag.label} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded-lg bg-muted text-muted-foreground border border-border">
196
- <span>{tag.icon}</span> {tag.label}
197
- </span>
198
- ))}
199
- </div>
200
- )}
201
- </div>
202
-
203
- {/* ── SECTION 2: Provider & Model ────────────────────────────────────── */}
204
- <div className={`rounded-xl border border-border bg-card overflow-hidden transition-opacity ${dimCls}`} aria-disabled={!enabled}>
205
- {/* Header */}
206
- <div className="flex items-center justify-between px-5 py-4 border-b border-border">
207
- <div className="flex items-center gap-3">
208
- <div className="w-9 h-9 rounded-xl bg-blue-500/10 flex items-center justify-center">
209
- <KeyRound className="w-4 h-4 text-blue-500" />
210
- </div>
211
- <div>
212
- <h3 className="text-sm font-semibold text-foreground">Provider &amp; model</h3>
213
- <p className="text-xs text-muted-foreground">Connect to an LLM gateway or direct provider</p>
214
- </div>
215
- </div>
216
- <div className="flex items-center gap-1.5">
217
- <span className="w-2 h-2 rounded-full bg-green-500" />
218
- <span className="text-xs text-muted-foreground">Connected</span>
219
- </div>
220
- </div>
221
-
222
- <div className="divide-y divide-border px-5">
223
- {/* Provider pills */}
224
- <div className="py-4 space-y-2">
225
- <label className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Provider</label>
226
- <div className="flex flex-wrap gap-2">
227
- {(['anthropic', 'openai', 'openrouter', 'ollama', 'custom'] as Provider[]).map((p) => {
228
- const isActive = provider === p;
229
- return (
230
- <button
231
- key={p}
232
- onClick={() => handleProviderChange(p)}
233
- className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-semibold transition-colors ${isActive ? providerColors[p].active + ' border' : 'border-border text-muted-foreground hover:text-foreground bg-muted/40'}`}
234
- >
235
- <span className={`w-1.5 h-1.5 rounded-full ${providerColors[p].dot}`} />
236
- {p.charAt(0).toUpperCase() + p.slice(1)}
237
- </button>
238
- );
239
- })}
240
- </div>
241
- </div>
242
-
243
- {/* Base URL */}
244
- <div className="py-4 space-y-1.5">
245
- <label className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Base URL</label>
246
- <input
247
- value={baseUrl}
248
- onChange={e => setBaseUrl(e.target.value)}
249
- placeholder={providerUrls[provider]}
250
- className={inputCls}
251
- />
252
- </div>
253
-
254
- {/* API Key */}
255
- {provider !== 'ollama' ? (
256
- <div className="py-4 space-y-1.5">
257
- <label className="text-xs font-medium text-muted-foreground uppercase tracking-wider">API Key</label>
258
- <div className="relative">
259
- <input
260
- type={showApiKey ? 'text' : 'password'}
261
- value={apiKey}
262
- onChange={e => setApiKey(e.target.value)}
263
- placeholder="sk-..."
264
- className={inputCls + ' pr-10'}
265
- />
266
- <button
267
- type="button"
268
- onClick={() => setShowApiKey(v => !v)}
269
- className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
270
- >
271
- {showApiKey ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
272
- </button>
273
- </div>
274
- </div>
275
- ) : (
276
- <div className="py-4">
277
- <p className="text-xs text-muted-foreground italic">Ollama runs locally — no API key required.</p>
278
- </div>
279
- )}
280
-
281
- {/* Model */}
282
- <div className="py-4 space-y-1.5">
283
- <label className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Model</label>
284
- {provider === 'custom' ? (
285
- <input
286
- value={customModel}
287
- onChange={e => setCustomModel(e.target.value)}
288
- placeholder="e.g. my-custom-model"
289
- className={inputCls}
290
- />
291
- ) : (
292
- <select
293
- value={model}
294
- onChange={e => setModel(e.target.value)}
295
- className={selectCls}
296
- >
297
- {providerModels[provider].map(m => (
298
- <option key={m} value={m}>{m}</option>
299
- ))}
300
- </select>
301
- )}
302
- </div>
303
-
304
- {/* Actions */}
305
- <div className="py-4 flex items-center gap-2 flex-wrap">
306
- <button
307
- onClick={handleTestConnection}
308
- disabled={testingConn}
309
- className={ghostBtn + ' disabled:opacity-40'}
310
- >
311
- {testingConn ? 'Testing…' : 'Test connection'}
312
- </button>
313
- <button
314
- onClick={handleSaveProvider}
315
- disabled={savingProvider}
316
- className={primaryBtn}
317
- >
318
- {savingProvider ? 'Saving…' : saveProviderDone ? 'Saved ✓' : 'Save changes'}
319
- </button>
320
- </div>
321
- </div>
322
- </div>
323
-
324
- {/* ── SECTION 3: Per-User Agent Behaviour ────────────────────────────── */}
325
- <div className={`rounded-xl border border-border bg-card overflow-hidden transition-opacity ${dimCls}`} aria-disabled={!enabled}>
326
- {/* Header */}
327
- <div className="flex items-center gap-3 px-5 py-4 border-b border-border">
328
- <div className="w-9 h-9 rounded-xl bg-purple-500/10 flex items-center justify-center">
329
- <Users className="w-4 h-4 text-purple-500" />
330
- </div>
331
- <div>
332
- <h3 className="text-sm font-semibold text-foreground">Per-user agent behaviour</h3>
333
- <p className="text-xs text-muted-foreground">Fine-tune how the agent behaves for this workspace</p>
334
- </div>
335
- </div>
336
-
337
- <div className="divide-y divide-border px-5">
338
- {/* History retention */}
339
- <div className="flex items-center justify-between py-3 gap-4">
340
- <div>
341
- <p className="text-sm font-medium text-foreground">Chat history retention</p>
342
- <p className="text-xs text-muted-foreground">How long to keep conversation history</p>
343
- </div>
344
- <select
345
- value={historyRetention}
346
- onChange={e => setHistoryRetention(Number(e.target.value))}
347
- className="h-9 px-3 rounded-lg border border-border bg-background text-sm text-foreground outline-none focus:ring-1 focus:ring-ring cursor-pointer"
348
- >
349
- <option value={30}>30 days</option>
350
- <option value={90}>90 days</option>
351
- <option value={180}>180 days</option>
352
- <option value={0}>Forever</option>
353
- </select>
354
- </div>
355
-
356
- {/* System prompt */}
357
- <div className="py-3 space-y-2">
358
- <div className="flex items-center justify-between">
359
- <div>
360
- <p className="text-sm font-medium text-foreground">System prompt</p>
361
- <p className="text-xs text-muted-foreground">Instructions sent to the agent at the start of every conversation</p>
362
- </div>
363
- <button
364
- onClick={() => { setPromptDraft(systemPrompt); setEditingPrompt(true); }}
365
- className="text-xs font-semibold text-primary hover:underline whitespace-nowrap"
366
- >
367
- Edit prompt →
368
- </button>
369
- </div>
370
- <div className="p-3 rounded-lg bg-muted/30 border border-border">
371
- <p className="text-xs text-muted-foreground font-mono leading-relaxed line-clamp-2">{systemPrompt}</p>
372
- </div>
373
- </div>
374
-
375
- {/* Max tokens */}
376
- <div className="py-3 space-y-1.5">
377
- <div className="flex items-center justify-between">
378
- <div>
379
- <p className="text-sm font-medium text-foreground">Max tokens per turn</p>
380
- </div>
381
- <select
382
- value={maxTokens}
383
- onChange={e => setMaxTokens(Number(e.target.value))}
384
- className="h-9 px-3 rounded-lg border border-border bg-background text-sm text-foreground outline-none focus:ring-1 focus:ring-ring cursor-pointer"
385
- >
386
- <option value={1000}>1,000</option>
387
- <option value={4000}>4,000</option>
388
- <option value={8000}>8,000</option>
389
- <option value={16000}>16,000</option>
390
- </select>
391
- </div>
392
- <p className="text-xs text-muted-foreground">{tokenCosts[maxTokens]}</p>
393
- </div>
394
-
395
- {/* Can create assignments */}
396
- <div className="flex items-center justify-between py-3">
397
- <div>
398
- <p className="text-sm font-medium text-foreground">Allow agent to create assignments</p>
399
- <p className="text-xs text-muted-foreground">Agent can assign tasks to users and actors</p>
400
- </div>
401
- <Switch
402
- checked={canCreateAssignments}
403
- onCheckedChange={setCanCreateAssignments}
404
- aria-label="Allow agent to create assignments"
405
- />
406
- </div>
407
-
408
- {/* Can log activities */}
409
- <div className="flex items-center justify-between py-3">
410
- <div>
411
- <p className="text-sm font-medium text-foreground">Allow agent to log activities</p>
412
- <p className="text-xs text-muted-foreground">Agent can record calls, notes, and emails</p>
413
- </div>
414
- <Switch
415
- checked={canLogActivities}
416
- onCheckedChange={setCanLogActivities}
417
- aria-label="Allow agent to log activities"
418
- />
419
- </div>
420
-
421
- {/* Can write objects */}
422
- <div className="py-3 space-y-2">
423
- <div className="flex items-center justify-between">
424
- <div>
425
- <p className="text-sm font-medium text-foreground">Allow agent to write CRM objects</p>
426
- <p className="text-xs text-muted-foreground">Agent can create and edit contacts, accounts, and opportunities</p>
427
- </div>
428
- <Switch
429
- checked={canWriteObjects}
430
- onCheckedChange={setCanWriteObjects}
431
- aria-label="Allow agent to write CRM objects"
432
- />
433
- </div>
434
- {canWriteObjects && (
435
- <div className="flex items-start gap-2 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20">
436
- <TriangleAlert className="w-3.5 h-3.5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
437
- <p className="text-xs text-amber-700 dark:text-amber-400">
438
- Granting write access means the agent can modify CRM records autonomously. Review agent actions regularly.
439
- </p>
440
- </div>
441
- )}
442
- </div>
443
- </div>
444
- </div>
445
-
446
- {/* ── SECTION 4: Danger Zone ─────────────────────────────────────────── */}
447
- <div className="rounded-xl border border-red-200 dark:border-red-900/50 bg-card overflow-hidden">
448
- {/* Header */}
449
- <div className="flex items-center gap-3 px-5 py-4 border-b border-red-200 dark:border-red-900/50">
450
- <div className="w-9 h-9 rounded-xl bg-red-500/10 flex items-center justify-center">
451
- <TriangleAlert className="w-4 h-4 text-red-500" />
452
- </div>
453
- <div>
454
- <h3 className="text-sm font-semibold text-red-600 dark:text-red-400">Danger zone</h3>
455
- <p className="text-xs text-muted-foreground">Irreversible actions — proceed with care</p>
456
- </div>
457
- </div>
458
-
459
- <div className="divide-y divide-red-100 dark:divide-red-900/30 px-5">
460
- {/* Clear histories */}
461
- <div className="flex items-center justify-between py-3 gap-4">
462
- <div>
463
- <p className="text-sm font-medium text-foreground">Clear all chat histories</p>
464
- <p className="text-xs text-muted-foreground">Permanently delete all agent conversation history for this workspace</p>
465
- </div>
466
- <button onClick={() => setShowClearConfirm(true)} className={dangerBtn + ' whitespace-nowrap'}>
467
- Clear all histories
468
- </button>
469
- </div>
470
-
471
- </div>
472
- </div>
473
-
474
- {/* ── System Prompt Editor Overlay ───────────────────────────────────── */}
475
- {editingPrompt && (
476
- <div className="fixed inset-0 z-[200] flex items-center justify-center bg-foreground/20 backdrop-blur-sm">
477
- <div className="bg-card border border-border rounded-xl p-6 max-w-2xl w-full mx-4 shadow-xl space-y-4">
478
- <div className="flex items-center justify-between">
479
- <h3 className="font-semibold text-foreground">Edit system prompt</h3>
480
- <button onClick={() => setEditingPrompt(false)} className="text-muted-foreground hover:text-foreground transition-colors">
481
- <X className="w-4 h-4" />
482
- </button>
483
- </div>
484
- <textarea
485
- ref={textareaRef}
486
- value={promptDraft}
487
- onChange={e => setPromptDraft(e.target.value)}
488
- rows={10}
489
- className="w-full px-3 py-2 rounded-lg border border-border bg-background text-sm text-foreground placeholder:text-muted-foreground outline-none focus:ring-1 focus:ring-ring font-mono resize-none"
490
- />
491
- <div className="flex gap-2 justify-end">
492
- <button
493
- onClick={() => setEditingPrompt(false)}
494
- className={ghostBtn}
495
- >
496
- Cancel
497
- </button>
498
- <button
499
- onClick={() => { setSystemPrompt(promptDraft); setEditingPrompt(false); toast({ title: 'System prompt saved' }); }}
500
- className={primaryBtn}
501
- >
502
- Save prompt
503
- </button>
504
- </div>
505
- </div>
506
- </div>
507
- )}
508
-
509
- {/* ── Clear Confirm Modal ─────────────────────────────────────────────── */}
510
- {showClearConfirm && (
511
- <div className="fixed inset-0 z-[300] flex items-center justify-center bg-foreground/20 backdrop-blur-sm">
512
- <div className="bg-card border border-border rounded-xl p-6 max-w-sm w-full mx-4 shadow-xl space-y-4">
513
- <h3 className="font-semibold text-foreground">Clear all chat histories?</h3>
514
- <p className="text-sm text-muted-foreground">
515
- This is irreversible. Type <code className="font-mono bg-muted px-1 rounded">CLEAR</code> to confirm.
516
- </p>
517
- <input
518
- value={clearConfirmText}
519
- onChange={e => setClearConfirmText(e.target.value)}
520
- placeholder="CLEAR"
521
- className="w-full h-9 px-3 rounded-lg border border-border bg-background text-sm font-mono outline-none focus:ring-1 focus:ring-ring"
522
- />
523
- <div className="flex gap-2 justify-end">
524
- <button
525
- onClick={() => { setShowClearConfirm(false); setClearConfirmText(''); }}
526
- className={ghostBtn}
527
- >
528
- Cancel
529
- </button>
530
- <button
531
- disabled={clearConfirmText !== 'CLEAR'}
532
- onClick={handleClearAll}
533
- className={dangerBtn + ' disabled:opacity-40'}
534
- >
535
- Clear histories
536
- </button>
537
- </div>
538
- </div>
539
- </div>
540
- )}
541
-
542
- </div>
543
- );
544
- }