@standardagents/cli 0.10.1-next.bbd142a → 0.11.0-next.99fb790

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.
@@ -0,0 +1,437 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
4
+ import type { Thread } from '../hooks/useThreads'
5
+ import { Logo } from './Logo'
6
+
7
+ interface Agent {
8
+ id: string
9
+ name: string
10
+ title?: string
11
+ description?: string
12
+ }
13
+
14
+ interface PendingAttachment {
15
+ id: string
16
+ name: string
17
+ mimeType: string
18
+ data: string // base64
19
+ width?: number
20
+ height?: number
21
+ preview?: string // data URL for preview
22
+ }
23
+
24
+ interface EmptyStateProps {
25
+ selectedAgentId: string
26
+ onAgentChange: (agentId: string) => void
27
+ onThreadCreated: (thread: Thread) => void
28
+ }
29
+
30
+ // Helper to get API config
31
+ function getApiConfig() {
32
+ const isVite = typeof import.meta !== 'undefined' && import.meta.env
33
+ const baseUrl = isVite
34
+ ? (import.meta.env.VITE_AGENTBUILDER_URL || '')
35
+ : (process.env.NEXT_PUBLIC_AGENTBUILDER_URL || '')
36
+
37
+ // Token is stored in localStorage by auth flow
38
+ const token = typeof localStorage !== 'undefined'
39
+ ? localStorage.getItem('agentbuilder_auth_token') || ''
40
+ : ''
41
+
42
+ return { baseUrl, token }
43
+ }
44
+
45
+ // Read file as base64
46
+ function readFileAsBase64(file: File): Promise<string> {
47
+ return new Promise((resolve, reject) => {
48
+ const reader = new FileReader()
49
+ reader.onload = () => {
50
+ const result = reader.result as string
51
+ const base64 = result.split(',')[1]
52
+ resolve(base64)
53
+ }
54
+ reader.onerror = reject
55
+ reader.readAsDataURL(file)
56
+ })
57
+ }
58
+
59
+ // Get image dimensions
60
+ function getImageDimensions(file: File): Promise<{ width: number; height: number } | null> {
61
+ return new Promise((resolve) => {
62
+ if (!file.type.startsWith('image/')) {
63
+ resolve(null)
64
+ return
65
+ }
66
+ const img = new Image()
67
+ img.onload = () => {
68
+ resolve({ width: img.naturalWidth, height: img.naturalHeight })
69
+ URL.revokeObjectURL(img.src)
70
+ }
71
+ img.onerror = () => resolve(null)
72
+ img.src = URL.createObjectURL(file)
73
+ })
74
+ }
75
+
76
+ export function EmptyState({
77
+ selectedAgentId,
78
+ onAgentChange,
79
+ onThreadCreated,
80
+ }: EmptyStateProps) {
81
+ const [message, setMessage] = useState('')
82
+ const [attachments, setAttachments] = useState<PendingAttachment[]>([])
83
+ const [isCreating, setIsCreating] = useState(false)
84
+ const [agents, setAgents] = useState<Agent[]>([])
85
+ const [agentsLoading, setAgentsLoading] = useState(true)
86
+ const [dropdownOpen, setDropdownOpen] = useState(false)
87
+ const dropdownRef = useRef<HTMLDivElement>(null)
88
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
89
+ const fileInputRef = useRef<HTMLInputElement>(null)
90
+
91
+ const { baseUrl, token } = useMemo(() => getApiConfig(), [])
92
+ const headers = useMemo(() => {
93
+ const h: Record<string, string> = { 'Content-Type': 'application/json' }
94
+ if (token) h['Authorization'] = `Bearer ${token}`
95
+ return h
96
+ }, [token])
97
+
98
+ useEffect(() => {
99
+ const fetchAgents = async () => {
100
+ try {
101
+ const response = await fetch(`${baseUrl}/api/agents`, { headers })
102
+ if (!response.ok) throw new Error('Failed to fetch agents')
103
+ const data = await response.json()
104
+ const agentList = data.agents || []
105
+ console.log('Agents from API:', agentList)
106
+ setAgents(agentList)
107
+ if (!selectedAgentId && agentList.length > 0) {
108
+ onAgentChange(agentList[0].id)
109
+ }
110
+ } catch (err) {
111
+ console.error('Failed to load agents:', err)
112
+ } finally {
113
+ setAgentsLoading(false)
114
+ }
115
+ }
116
+ fetchAgents()
117
+ }, [selectedAgentId, onAgentChange, baseUrl, headers])
118
+
119
+ useEffect(() => {
120
+ const handleClickOutside = (e: MouseEvent) => {
121
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
122
+ setDropdownOpen(false)
123
+ }
124
+ }
125
+ document.addEventListener('mousedown', handleClickOutside)
126
+ return () => document.removeEventListener('mousedown', handleClickOutside)
127
+ }, [])
128
+
129
+ useEffect(() => {
130
+ const textarea = textareaRef.current
131
+ if (textarea) {
132
+ textarea.style.height = 'auto'
133
+ textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'
134
+ }
135
+ }, [message])
136
+
137
+ const selectedAgent = agents.find(a => a.id === selectedAgentId)
138
+
139
+ const handleFileSelect = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
140
+ const files = e.target.files
141
+ if (!files || files.length === 0) return
142
+
143
+ const newAttachments: PendingAttachment[] = []
144
+
145
+ for (const file of Array.from(files)) {
146
+ try {
147
+ const base64 = await readFileAsBase64(file)
148
+ const dimensions = await getImageDimensions(file)
149
+ const preview = file.type.startsWith('image/')
150
+ ? URL.createObjectURL(file)
151
+ : undefined
152
+
153
+ newAttachments.push({
154
+ id: crypto.randomUUID(),
155
+ name: file.name,
156
+ mimeType: file.type || 'application/octet-stream',
157
+ data: base64,
158
+ width: dimensions?.width,
159
+ height: dimensions?.height,
160
+ preview,
161
+ })
162
+ } catch (err) {
163
+ console.error('Failed to read file:', err)
164
+ }
165
+ }
166
+
167
+ setAttachments(prev => [...prev, ...newAttachments])
168
+ // Reset file input and refocus textarea
169
+ if (fileInputRef.current) {
170
+ fileInputRef.current.value = ''
171
+ }
172
+ textareaRef.current?.focus()
173
+ }, [])
174
+
175
+ const removeAttachment = useCallback((id: string) => {
176
+ setAttachments(prev => {
177
+ const attachment = prev.find(a => a.id === id)
178
+ if (attachment?.preview) {
179
+ URL.revokeObjectURL(attachment.preview)
180
+ }
181
+ return prev.filter(a => a.id !== id)
182
+ })
183
+ }, [])
184
+
185
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
186
+ e.preventDefault()
187
+ if ((!message.trim() && attachments.length === 0) || !selectedAgentId || isCreating) return
188
+
189
+ setIsCreating(true)
190
+ const title = message.slice(0, 50) + (message.length > 50 ? '...' : '') || 'New conversation'
191
+ const currentAttachments = [...attachments]
192
+
193
+ try {
194
+ // Create thread
195
+ const threadRes = await fetch(`${baseUrl}/api/threads`, {
196
+ method: 'POST',
197
+ headers,
198
+ body: JSON.stringify({ agent_id: selectedAgentId }),
199
+ })
200
+
201
+ if (!threadRes.ok) throw new Error('Failed to create thread')
202
+ const threadData = await threadRes.json()
203
+ const threadId = threadData.threadId
204
+
205
+ // Set title on the thread via tags
206
+ await fetch(`${baseUrl}/api/threads/${threadId}`, {
207
+ method: 'PATCH',
208
+ headers,
209
+ body: JSON.stringify({ tags: [`title:${title}`] }),
210
+ })
211
+
212
+ // Build message payload with attachments
213
+ const messagePayload: {
214
+ role: string
215
+ content: string
216
+ attachments?: Array<{
217
+ name: string
218
+ mimeType: string
219
+ data: string
220
+ width?: number
221
+ height?: number
222
+ }>
223
+ } = {
224
+ role: 'user',
225
+ content: message,
226
+ }
227
+
228
+ if (currentAttachments.length > 0) {
229
+ messagePayload.attachments = currentAttachments.map(a => ({
230
+ name: a.name,
231
+ mimeType: a.mimeType,
232
+ data: a.data,
233
+ ...(a.width && a.height ? { width: a.width, height: a.height } : {}),
234
+ }))
235
+ }
236
+
237
+ // Send the first message
238
+ await fetch(`${baseUrl}/api/threads/${threadId}/messages`, {
239
+ method: 'POST',
240
+ headers,
241
+ body: JSON.stringify(messagePayload),
242
+ })
243
+
244
+ // Navigate to the thread - use agent info from selected agent
245
+ const selectedAgent = agents.find(a => a.id === selectedAgentId)
246
+ onThreadCreated({
247
+ id: threadId,
248
+ agent_id: selectedAgentId,
249
+ user_id: null,
250
+ tags: [`title:${title}`],
251
+ created_at: Math.floor(Date.now() / 1000), // API uses seconds
252
+ agent: {
253
+ name: selectedAgent?.name,
254
+ title: selectedAgent?.title,
255
+ type: 'ai_human',
256
+ },
257
+ })
258
+ setMessage('')
259
+ setAttachments([])
260
+ } catch (error) {
261
+ console.error('Failed to create thread:', error)
262
+ } finally {
263
+ setIsCreating(false)
264
+ }
265
+ }, [message, attachments, selectedAgentId, isCreating, onThreadCreated, baseUrl, headers, agents])
266
+
267
+ return (
268
+ <div className="flex-1 flex flex-col items-center px-4 py-8">
269
+ <div className="w-full max-w-2xl mt-[15vh]">
270
+ {/* Logo and Greeting */}
271
+ <div className="text-center mb-12">
272
+ <Logo className="h-16 text-[var(--text-secondary)] mx-auto mb-20" />
273
+ <h1 className="text-2xl font-medium text-[var(--text-secondary)]">
274
+ What can I help with?
275
+ </h1>
276
+ </div>
277
+
278
+ {/* Input box */}
279
+ <div className="relative bg-[var(--bg-tertiary)] rounded-2xl border border-[var(--border-primary)] shadow-lg shadow-black/10">
280
+ {/* Attachment drawer */}
281
+ {attachments.length > 0 && (
282
+ <div className="px-3 pt-3 pb-2 border-b border-[var(--border-secondary)]">
283
+ <div className="flex flex-wrap gap-2">
284
+ {attachments.map((attachment) => (
285
+ <div key={attachment.id} className="relative group">
286
+ {attachment.preview ? (
287
+ <img
288
+ src={attachment.preview}
289
+ alt={attachment.name}
290
+ className="h-16 w-auto rounded-lg object-cover border border-[var(--border-secondary)]"
291
+ />
292
+ ) : (
293
+ <div className="h-16 px-3 flex items-center gap-2 rounded-lg bg-[var(--bg-hover)] border border-[var(--border-secondary)]">
294
+ <svg className="w-4 h-4 text-[var(--text-secondary)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
295
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
296
+ </svg>
297
+ <span className="text-xs text-[var(--text-secondary)] max-w-[100px] truncate">{attachment.name}</span>
298
+ </div>
299
+ )}
300
+ <button
301
+ type="button"
302
+ onClick={() => removeAttachment(attachment.id)}
303
+ className="absolute -top-1.5 -right-1.5 w-5 h-5 bg-[var(--bg-active)] hover:bg-[var(--bg-hover)] rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
304
+ >
305
+ <svg className="w-3 h-3 text-[var(--text-primary)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
306
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
307
+ </svg>
308
+ </button>
309
+ </div>
310
+ ))}
311
+ </div>
312
+ </div>
313
+ )}
314
+
315
+ {/* Agent selector row */}
316
+ <div className="px-4 pt-3 pb-1" ref={dropdownRef}>
317
+ <button
318
+ onClick={() => setDropdownOpen(!dropdownOpen)}
319
+ disabled={agentsLoading || agents.length === 0}
320
+ className="inline-flex items-center gap-1.5 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors disabled:opacity-50"
321
+ >
322
+ {agentsLoading ? (
323
+ <span className="text-[var(--text-tertiary)]">Loading agents...</span>
324
+ ) : agents.length === 0 ? (
325
+ <span className="text-[var(--text-tertiary)]">No agents available</span>
326
+ ) : (
327
+ <>
328
+ <span className="text-[var(--text-primary)] font-medium">{selectedAgent?.title || selectedAgent?.name || 'Select agent'}</span>
329
+ <svg className={`w-3.5 h-3.5 text-[var(--text-tertiary)] transition-transform ${dropdownOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
330
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
331
+ </svg>
332
+ </>
333
+ )}
334
+ </button>
335
+
336
+ {/* Dropdown menu */}
337
+ {dropdownOpen && agents.length > 0 && (
338
+ <div className="absolute left-4 top-12 z-50 min-w-[280px] max-w-[360px] bg-[var(--bg-elevated)] border border-[var(--border-secondary)] rounded-xl shadow-xl overflow-hidden animate-fade-in">
339
+ {agents.map((agent) => (
340
+ <button
341
+ key={agent.id}
342
+ onClick={() => {
343
+ onAgentChange(agent.id)
344
+ setDropdownOpen(false)
345
+ }}
346
+ className={`w-full px-4 py-3 text-left transition-colors ${
347
+ agent.id === selectedAgentId
348
+ ? 'bg-[var(--bg-active)]'
349
+ : 'hover:bg-[var(--bg-hover)]'
350
+ }`}
351
+ >
352
+ <div className={`text-sm font-medium ${agent.id === selectedAgentId ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}`}>
353
+ {agent.title || agent.name}
354
+ </div>
355
+ {agent.description && (
356
+ <div className="text-xs text-[var(--text-muted)] mt-0.5 line-clamp-2">
357
+ {agent.description}
358
+ </div>
359
+ )}
360
+ </button>
361
+ ))}
362
+ </div>
363
+ )}
364
+ </div>
365
+
366
+ {/* Textarea */}
367
+ <form onSubmit={handleSubmit}>
368
+ <textarea
369
+ ref={textareaRef}
370
+ value={message}
371
+ onChange={(e) => setMessage(e.target.value)}
372
+ onKeyDown={(e) => {
373
+ if (e.key === 'Enter' && !e.shiftKey) {
374
+ e.preventDefault()
375
+ handleSubmit(e)
376
+ }
377
+ }}
378
+ placeholder="Message..."
379
+ rows={1}
380
+ disabled={isCreating || !selectedAgentId}
381
+ className="w-full px-4 py-3 bg-transparent resize-none text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none disabled:opacity-50 text-[15px] leading-relaxed"
382
+ />
383
+
384
+ {/* Actions row */}
385
+ <div className="flex items-center justify-between px-3 pb-3">
386
+ {/* Attachment button */}
387
+ <div>
388
+ <input
389
+ ref={fileInputRef}
390
+ type="file"
391
+ multiple
392
+ accept="image/*,.pdf,.txt,.md,.json,.csv"
393
+ onChange={handleFileSelect}
394
+ className="hidden"
395
+ />
396
+ <button
397
+ type="button"
398
+ onClick={() => fileInputRef.current?.click()}
399
+ disabled={isCreating || !selectedAgentId}
400
+ className="p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-hover)] rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
401
+ title="Attach files"
402
+ >
403
+ <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
404
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13" />
405
+ </svg>
406
+ </button>
407
+ </div>
408
+
409
+ {/* Submit button */}
410
+ <button
411
+ type="submit"
412
+ disabled={(!message.trim() && attachments.length === 0) || !selectedAgentId || isCreating}
413
+ className="p-2 rounded-xl bg-[var(--text-primary)] text-[var(--bg-primary)] hover:opacity-90 disabled:opacity-30 disabled:cursor-not-allowed transition-all duration-150"
414
+ >
415
+ {isCreating ? (
416
+ <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
417
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
418
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
419
+ </svg>
420
+ ) : (
421
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
422
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 10l7-7m0 0l7 7m-7-7v18" />
423
+ </svg>
424
+ )}
425
+ </button>
426
+ </div>
427
+ </form>
428
+ </div>
429
+
430
+ {/* Subtle hint */}
431
+ <p className="text-center text-xs text-[var(--text-muted)] mt-4">
432
+ Press Enter to send, Shift+Enter for new line
433
+ </p>
434
+ </div>
435
+ </div>
436
+ )
437
+ }
@@ -0,0 +1,139 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, type FormEvent } from 'react'
4
+ import { useAuth } from '../hooks/useAuth'
5
+ import { Logo } from './Logo'
6
+
7
+ export function Login() {
8
+ const [username, setUsername] = useState('')
9
+ const [password, setPassword] = useState('')
10
+ const [error, setError] = useState('')
11
+ const [isSubmitting, setIsSubmitting] = useState(false)
12
+ const { login } = useAuth()
13
+
14
+ const handleSubmit = useCallback(async (e: FormEvent) => {
15
+ e.preventDefault()
16
+ setError('')
17
+ setIsSubmitting(true)
18
+
19
+ const result = await login(username, password)
20
+
21
+ if (!result.success) {
22
+ setError(result.error || 'Login failed')
23
+ setIsSubmitting(false)
24
+ }
25
+ }, [username, password, login])
26
+
27
+ return (
28
+ <div className="min-h-screen flex items-center justify-center px-4 relative overflow-hidden bg-[var(--bg-primary)]">
29
+ {/* Subtle gradient background */}
30
+ <div
31
+ className="absolute inset-0 opacity-50"
32
+ style={{
33
+ background: 'radial-gradient(ellipse at top, var(--bg-secondary) 0%, transparent 50%), radial-gradient(ellipse at bottom right, var(--bg-tertiary) 0%, transparent 50%)',
34
+ }}
35
+ />
36
+
37
+ {/* Noise texture overlay */}
38
+ <div
39
+ className="absolute inset-0 opacity-[0.015] pointer-events-none"
40
+ style={{
41
+ backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
42
+ }}
43
+ />
44
+
45
+ {/* Content */}
46
+ <div className="w-full max-w-md relative z-10">
47
+ {/* Logo above the card */}
48
+ <div className="flex justify-center mb-10">
49
+ <Logo className="w-72 h-auto text-[var(--text-secondary)] drop-shadow-sm" />
50
+ </div>
51
+
52
+ {/* Login card */}
53
+ <div className="bg-[var(--bg-secondary)]/80 backdrop-blur-xl rounded-3xl border border-[var(--border-primary)] shadow-2xl shadow-black/10 p-8">
54
+ <form onSubmit={handleSubmit} className="space-y-5">
55
+ {error && (
56
+ <div className="p-4 rounded-2xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm flex items-center gap-3">
57
+ <svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
58
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
59
+ </svg>
60
+ {error}
61
+ </div>
62
+ )}
63
+
64
+ <div className="space-y-2">
65
+ <label
66
+ htmlFor="username"
67
+ className="block text-sm font-medium text-[var(--text-secondary)]"
68
+ >
69
+ Username
70
+ </label>
71
+ <input
72
+ id="username"
73
+ type="text"
74
+ value={username}
75
+ onChange={(e) => setUsername(e.target.value)}
76
+ className="w-full px-4 py-3 rounded-xl bg-[var(--bg-primary)] border border-[var(--border-primary)] text-[var(--text-primary)] placeholder-[var(--text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--text-secondary)]/20 focus:border-[var(--border-secondary)] transition-all"
77
+ placeholder="Enter your username"
78
+ required
79
+ autoComplete="username"
80
+ disabled={isSubmitting}
81
+ />
82
+ </div>
83
+
84
+ <div className="space-y-2">
85
+ <label
86
+ htmlFor="password"
87
+ className="block text-sm font-medium text-[var(--text-secondary)]"
88
+ >
89
+ Password
90
+ </label>
91
+ <input
92
+ id="password"
93
+ type="password"
94
+ value={password}
95
+ onChange={(e) => setPassword(e.target.value)}
96
+ className="w-full px-4 py-3 rounded-xl bg-[var(--bg-primary)] border border-[var(--border-primary)] text-[var(--text-primary)] placeholder-[var(--text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--text-secondary)]/20 focus:border-[var(--border-secondary)] transition-all"
97
+ placeholder="Enter your password"
98
+ required
99
+ autoComplete="current-password"
100
+ disabled={isSubmitting}
101
+ />
102
+ </div>
103
+
104
+ <button
105
+ type="submit"
106
+ disabled={isSubmitting || !username || !password}
107
+ className="w-full py-3.5 px-4 rounded-xl bg-[var(--text-primary)] text-[var(--bg-primary)] font-semibold hover:opacity-90 hover:scale-[1.01] active:scale-[0.99] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:scale-100 transition-all duration-150 shadow-lg shadow-black/10"
108
+ >
109
+ {isSubmitting ? (
110
+ <span className="flex items-center justify-center gap-2">
111
+ <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
112
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
113
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
114
+ </svg>
115
+ Signing in...
116
+ </span>
117
+ ) : (
118
+ 'Sign in'
119
+ )}
120
+ </button>
121
+ </form>
122
+ </div>
123
+
124
+ {/* Subtle footer */}
125
+ <p className="text-center text-xs text-[var(--text-muted)] mt-6">
126
+ Powered by{' '}
127
+ <a
128
+ href="https://standardagentbuilder.com"
129
+ target="_blank"
130
+ rel="noopener noreferrer"
131
+ className="text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors"
132
+ >
133
+ AgentBuilder
134
+ </a>
135
+ </p>
136
+ </div>
137
+ </div>
138
+ )
139
+ }
@@ -0,0 +1,39 @@
1
+ export function LogoMark({ className }: { className?: string }) {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ viewBox="0 0 150 150"
6
+ fill="currentColor"
7
+ className={className}
8
+ >
9
+ <path d="M44.06,0v44.08H0v105.92h105.93v-44.07h44.07V0H44.06ZM19.09,130.91c-16.47-16.47-5.29-54.45,24.96-85.18v60.2h60.23c-30.73,30.27-68.71,41.47-85.2,24.98ZM105.93,104.29v-60.21h-60.21C76.46,13.8,114.42,2.6,130.91,19.09c16.51,16.49,5.31,54.47-24.98,85.2Z" />
10
+ </svg>
11
+ )
12
+ }
13
+
14
+ export function Logo({ className }: { className?: string }) {
15
+ return (
16
+ <svg
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ viewBox="0 0 877.15 150"
19
+ fill="currentColor"
20
+ className={className}
21
+ >
22
+ <path d="M44.06,0v44.08H0v105.92h105.93v-44.07h44.07V0H44.06ZM19.09,130.91c-16.47-16.47-5.29-54.45,24.96-85.18v60.2h60.23c-30.73,30.27-68.71,41.47-85.2,24.98ZM105.93,104.29v-60.21h-60.21C76.46,13.8,114.42,2.6,130.91,19.09c16.51,16.49,5.31,54.47-24.98,85.2Z" />
23
+ <g>
24
+ <path d="M203.01,112.34c-8.14,0-16.05-1.81-17.52-2.03-.79.68-1.36,2.03-1.47,2.26h-3.84c-.45-3.84-1.58-14.58-2.26-18.98l3.5-.68,3.73,5.31c3.62,5.2,10.17,7.46,18.87,7.46,10.85,0,16.84-4.18,16.84-13.56,0-10.51-10.51-14.01-19.1-16.72-11.87-3.73-22.6-7.46-22.6-22.94,0-9.38,8.14-20.23,23.28-20.23,3.84,0,6.67.56,9.04,1.13s4.29,1.36,6.44,1.58c.34,0,2.15-1.58,2.37-1.69h2.6l2.94,16.95-4.29.45-2.71-4.97c-2.15-3.96-7.91-7.57-16.5-7.57s-14.24,4.07-14.24,12.54c0,8.93,8.7,11.64,18.08,14.8,11.98,4.07,24.3,8.81,24.3,24.63,0,12.32-11.75,22.26-27.46,22.26Z" />
25
+ <path d="M276.01,63.52h-19.55v34.02c0,3.28.45,4.52,1.24,5.42,1.24,1.36,3.16,2.37,5.31,2.37,4.97,0,8.93-2.03,12.09-3.73v4.75c-2.83,2.26-10.51,6.21-14.92,6.21-7.46,0-13.9-3.73-13.9-10.06,0-2.71.45-10.06.45-20.91v-17.18l-6.44-1.58v-2.03c3.28-2.83,9.38-8.7,14.01-13.56h2.71s-.45,7.57-.57,10.28h20.68l-1.13,5.99Z" />
26
+ <path d="M323.35,112.56c-3.16,0-7.01-.79-8.25-4.75-5.2,2.15-11.53,4.52-17.4,4.52-8.93,0-14.24-4.97-14.24-13.11,0-6.55,2.26-10.62,10.96-12.88,5.54-1.47,12.09-2.37,20.12-3.5v-7.8c0-1.24,0-2.6-.34-4.75-.79-5.2-6.44-7.68-10.85-7.68-2.37,0-4.63.68-6.67,2.03-1.69,1.13-2.26,2.37-2.26,4.18l-8.36,2.03c-.23-.57-.34-1.13-.34-1.81,0-2.15,1.69-4.29,3.62-5.76,2.26-1.81,4.52-3.28,7.34-4.52,3.39-1.58,7.91-3.16,12.32-3.16,3.96,0,7.23,1.02,9.72,2.6,2.6,1.69,4.29,4.18,4.86,7.57.34,1.92.34,4.18.34,7.35v26.56c0,4.52,2.37,6.33,5.31,6.33s4.97-1.24,6.89-2.71l.9,3.5c-4.41,3.05-8.93,5.76-13.67,5.76ZM314.54,87.25c-7.01.79-11.41,1.58-16.61,2.94-4.52,1.13-5.65,3.05-5.65,6.67,0,5.2,3.5,9.72,9.49,9.72,4.18,0,8.48-1.47,12.77-3.05v-16.27Z" />
27
+ <path d="M376.35,110.76v-2.94l2.37-.79c2.49-.79,2.94-2.71,2.94-5.08v-24.18c0-5.2-.45-9.04-2.94-11.3-1.7-1.58-4.18-2.37-7.01-2.37-3.28,0-5.99.79-8.7,1.81-2.49.9-4.75,2.03-6.1,2.83v33.45c0,3.5.68,3.73,3.73,4.29l5.31.9v3.39h-23.96v-2.94l2.49-.79c2.49-.79,2.71-2.37,2.71-5.08v-30.51c0-3.5-.45-4.75-2.15-5.99l-2.6-1.92v-1.92l12.66-6.67,1.81.68v8.93c2.83-2.03,12.54-8.59,19.32-8.59,8.48,0,15.14,5.65,15.14,14.35v31.87c0,3.5.79,3.84,3.73,4.29l5.54.9v3.39h-24.3Z" />
28
+ <path d="M447.65,113.92l-1.13-.9-.68-6.89c-4.52,2.6-10.85,6.33-17.74,6.33-13.33,0-21.58-10.74-21.58-26.22s11.75-29.16,29.27-29.16c3.39,0,8.02.79,10.06,1.36v-13.56c0-4.29-.9-5.65-2.83-6.78l-2.6-1.47v-1.81l13.67-7.8,2.26.79s-.79,8.81-.79,12.77v62.38c0,2.49,1.02,4.07,3.5,4.07.34,0,.68,0,1.13-.11l4.41-.68v4.18l-16.95,3.5ZM445.84,66.91c-4.07-2.15-7.91-3.62-13.56-3.62-9.83,0-16.16,3.62-16.16,17.63s5.31,24.41,17.06,24.41c4.41,0,9.27-1.24,12.66-2.49v-35.94Z" />
29
+ <path d="M512.51,112.56c-3.16,0-7.01-.79-8.25-4.75-5.2,2.15-11.53,4.52-17.4,4.52-8.93,0-14.24-4.97-14.24-13.11,0-6.55,2.26-10.62,10.96-12.88,5.54-1.47,12.09-2.37,20.12-3.5v-7.8c0-1.24,0-2.6-.34-4.75-.79-5.2-6.44-7.68-10.85-7.68-2.37,0-4.63.68-6.67,2.03-1.69,1.13-2.26,2.37-2.26,4.18l-8.36,2.03c-.23-.57-.34-1.13-.34-1.81,0-2.15,1.69-4.29,3.62-5.76,2.26-1.81,4.52-3.28,7.34-4.52,3.39-1.58,7.91-3.16,12.32-3.16,3.96,0,7.23,1.02,9.72,2.6,2.6,1.69,4.29,4.18,4.86,7.57.34,1.92.34,4.18.34,7.35v26.56c0,4.52,2.37,6.33,5.31,6.33s4.97-1.24,6.89-2.71l.9,3.5c-4.41,3.05-8.93,5.76-13.67,5.76ZM503.7,87.25c-7.01.79-11.41,1.58-16.61,2.94-4.52,1.13-5.65,3.05-5.65,6.67,0,5.2,3.5,9.72,9.49,9.72,4.18,0,8.48-1.47,12.77-3.05v-16.27Z" />
30
+ <path d="M564.49,68.94h-.23c-1.47-.79-3.73-1.92-3.73-1.92-2.03-1.02-3.62-1.58-5.08-1.58-1.7,0-3.39.79-5.42,2.37l-3.96,3.05v31.3c0,3.05,1.58,3.5,3.73,3.84l8.14,1.36v3.39h-26.78v-2.94l2.49-.68c2.49-.68,2.71-2.6,2.71-5.2v-29.04c0-3.62-.11-5.31-2.03-7.01l-2.71-2.37v-1.92l12.66-6.67,1.81.68v10.17c1.7-1.7,9.27-7.68,9.27-7.68,2.71-2.26,4.29-3.16,5.54-3.16,1.36,0,2.26,1.13,3.84,3.73l2.03,3.5c-.57,2.03-1.58,5.09-2.26,6.78Z" />
31
+ <path d="M611.95,113.92l-1.13-.9-.68-6.89c-4.52,2.6-10.85,6.33-17.74,6.33-13.33,0-21.58-10.74-21.58-26.22s11.75-29.16,29.27-29.16c3.39,0,8.02.79,10.06,1.36v-13.56c0-4.29-.9-5.65-2.83-6.78l-2.6-1.47v-1.81l13.67-7.8,2.26.79s-.79,8.81-.79,12.77v62.38c0,2.49,1.02,4.07,3.5,4.07.34,0,.68,0,1.13-.11l4.41-.68v4.18l-16.95,3.5ZM610.14,66.91c-4.07-2.15-7.91-3.62-13.56-3.62-9.83,0-16.16,3.62-16.16,17.63s5.31,24.41,17.06,24.41c4.41,0,9.27-1.24,12.66-2.49v-35.94Z" />
32
+ <path d="M703.59,107.37c-5.99,3.05-14.46,5.2-24.07,5.2-26.33,0-39.1-20.34-39.1-40s13.45-40.34,40.34-40.34c7.91,0,13.67,1.58,20,4.18l2.49-3.05h3.39v19.1h-3.96c-1.02-2.83-2.26-5.88-3.39-7.57-4.86-4.07-11.53-6.22-18.19-6.22-14.01,0-28.25,9.27-28.25,32.88,0,20.45,12.21,34.69,29.72,34.69,5.54,0,11.08-1.24,16.27-3.84,1.47-1.13,3.28-3.62,4.52-5.99l3.62.79c-.68,3.5-2.49,8.7-3.39,10.17Z" />
33
+ <path d="M752.86,110.76v-2.94l2.37-.68c2.49-.68,2.83-2.6,2.83-5.2v-24.64c0-8.25-5.76-13.22-12.32-13.22-5.76,0-10.06,3.28-14.13,6.33v31.75c0,3.5.68,3.73,3.73,4.29l5.31.9v3.39h-23.96v-2.94l2.49-.68c2.49-.68,2.71-1.7,2.71-5.2v-55.83c0-4.86-.34-6.55-2.94-8.14l-2.26-1.36v-1.81l13.33-7.8,2.26.79s-.68,9.04-.68,18.31v19.44c5.54-5.54,13.67-9.61,18.99-9.61,8.48,0,17.18,8.02,17.18,17.63v28.59c0,3.5.79,3.84,3.73,4.29l5.42.9v3.39h-24.07Z" />
34
+ <path d="M824.05,112.56c-3.16,0-7.01-.79-8.25-4.75-5.2,2.15-11.53,4.52-17.4,4.52-8.93,0-14.24-4.97-14.24-13.11,0-6.55,2.26-10.62,10.96-12.88,5.54-1.47,12.09-2.37,20.12-3.5v-7.8c0-1.24,0-2.6-.34-4.75-.79-5.2-6.44-7.68-10.85-7.68-2.37,0-4.63.68-6.67,2.03-1.69,1.13-2.26,2.37-2.26,4.18l-8.36,2.03c-.23-.57-.34-1.13-.34-1.81,0-2.15,1.69-4.29,3.62-5.76,2.26-1.81,4.52-3.28,7.34-4.52,3.39-1.58,7.91-3.16,12.32-3.16,3.96,0,7.23,1.02,9.72,2.6,2.6,1.69,4.29,4.18,4.86,7.57.34,1.92.34,4.18.34,7.35v26.56c0,4.52,2.37,6.33,5.31,6.33s4.97-1.24,6.89-2.71l.9,3.5c-4.41,3.05-8.93,5.76-13.67,5.76ZM815.24,87.25c-7.01.79-11.41,1.58-16.61,2.94-4.52,1.13-5.65,3.05-5.65,6.67,0,5.2,3.5,9.72,9.49,9.72,4.18,0,8.48-1.47,12.77-3.05v-16.27Z" />
35
+ <path d="M876.03,63.52h-19.55v34.02c0,3.28.45,4.52,1.24,5.42,1.24,1.36,3.16,2.37,5.31,2.37,4.97,0,8.93-2.03,12.09-3.73v4.75c-2.83,2.26-10.51,6.21-14.92,6.21-7.46,0-13.9-3.73-13.9-10.06,0-2.71.45-10.06.45-20.91v-17.18l-6.44-1.58v-2.03c3.28-2.83,9.38-8.7,14.01-13.56h2.71s-.45,7.57-.57,10.28h20.68l-1.13,5.99Z" />
36
+ </g>
37
+ </svg>
38
+ )
39
+ }