Skip to content

Commit 5f6d219

Browse files
authored
fix(kb-ui): fixed upload files modal ui, processing ui to match the rest of the kb (#991)
* fix(kb-ui): fixed upload files modal, processing ui to match the rest of the kb * more ui fixes * ack PR comments * fix help modal
1 parent bab7430 commit 5f6d219

File tree

8 files changed

+124
-116
lines changed

8 files changed

+124
-116
lines changed

apps/sim/app/api/files/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export function findLocalFile(filename: string): string | null {
178178
* Create a file response with appropriate headers
179179
*/
180180
export function createFileResponse(file: FileResponse): NextResponse {
181-
return new NextResponse(file.buffer, {
181+
return new NextResponse(file.buffer as BodyInit, {
182182
status: 200,
183183
headers: {
184184
'Content-Type': file.contentType,

apps/sim/app/api/help/route.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { Resend } from 'resend'
33
import { z } from 'zod'
4+
import { getSession } from '@/lib/auth'
45
import { env } from '@/lib/env'
56
import { createLogger } from '@/lib/logs/console/logger'
67
import { getEmailDomain } from '@/lib/urls/utils'
@@ -9,7 +10,6 @@ const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null
910
const logger = createLogger('HelpAPI')
1011

1112
const helpFormSchema = z.object({
12-
email: z.string().email('Invalid email address'),
1313
subject: z.string().min(1, 'Subject is required'),
1414
message: z.string().min(1, 'Message is required'),
1515
type: z.enum(['bug', 'feedback', 'feature_request', 'other']),
@@ -19,6 +19,15 @@ export async function POST(req: NextRequest) {
1919
const requestId = crypto.randomUUID().slice(0, 8)
2020

2121
try {
22+
// Get user session
23+
const session = await getSession()
24+
if (!session?.user?.email) {
25+
logger.warn(`[${requestId}] Unauthorized help request attempt`)
26+
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
27+
}
28+
29+
const email = session.user.email
30+
2231
// Check if Resend API key is configured
2332
if (!resend) {
2433
logger.error(`[${requestId}] RESEND_API_KEY not configured`)
@@ -35,7 +44,6 @@ export async function POST(req: NextRequest) {
3544
const formData = await req.formData()
3645

3746
// Extract form fields
38-
const email = formData.get('email') as string
3947
const subject = formData.get('subject') as string
4048
const message = formData.get('message') as string
4149
const type = formData.get('type') as string
@@ -47,7 +55,6 @@ export async function POST(req: NextRequest) {
4755

4856
// Validate the form data
4957
const result = helpFormSchema.safeParse({
50-
email,
5158
subject,
5259
message,
5360
type,
@@ -97,9 +104,9 @@ ${message}
97104
}
98105

99106
// Send email using Resend
100-
const { data, error } = await resend.emails.send({
101-
from: `Sim <noreply@${getEmailDomain()}>`,
102-
to: [`help@${getEmailDomain()}`],
107+
const { error } = await resend.emails.send({
108+
from: `Sim <noreply@${env.EMAIL_DOMAIN || getEmailDomain()}>`,
109+
to: [`help@${env.EMAIL_DOMAIN || getEmailDomain()}`],
103110
subject: `[${type.toUpperCase()}] ${subject}`,
104111
replyTo: email,
105112
text: emailText,
@@ -121,7 +128,7 @@ ${message}
121128
// Send confirmation email to the user
122129
await resend.emails
123130
.send({
124-
from: `Sim <noreply@${getEmailDomain()}>`,
131+
from: `Sim <noreply@${env.EMAIL_DOMAIN || getEmailDomain()}>`,
125132
to: [email],
126133
subject: `Your ${type} request has been received: ${subject}`,
127134
text: `
@@ -137,7 +144,7 @@ ${images.length > 0 ? `You attached ${images.length} image(s).` : ''}
137144
Best regards,
138145
The Sim Team
139146
`,
140-
replyTo: `help@${getEmailDomain()}`,
147+
replyTo: `help@${env.EMAIL_DOMAIN || getEmailDomain()}`,
141148
})
142149
.catch((err) => {
143150
logger.warn(`[${requestId}] Failed to send confirmation email`, err)

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const getStatusDisplay = (doc: DocumentData) => {
8686
</>
8787
),
8888
className:
89-
'inline-flex items-center rounded-md bg-[var(--brand-primary-hex)]/10 px-2 py-1 text-xs font-medium text-[var(--brand-primary-hex)] dark:bg-[var(--brand-primary-hex)]/20 dark:text-[var(--brand-primary-hex)]',
89+
'inline-flex items-center rounded-md bg-purple-100 px-2 py-1 text-xs font-medium text-[var(--brand-primary-hex)] dark:bg-purple-900/30 dark:text-[var(--brand-primary-hex)]',
9090
}
9191
case 'failed':
9292
return {

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/upload-modal/upload-modal.tsx

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
77
import { Label } from '@/components/ui/label'
88
import { Progress } from '@/components/ui/progress'
99
import { createLogger } from '@/lib/logs/console/logger'
10+
import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components'
1011
import { useKnowledgeUpload } from '@/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload'
1112

1213
const logger = createLogger('UploadModal')
@@ -152,6 +153,19 @@ export function UploadModal({
152153
}
153154
}
154155

156+
const getFileIcon = (mimeType: string, filename: string) => {
157+
const IconComponent = getDocumentIcon(mimeType, filename)
158+
return <IconComponent className='h-10 w-8' />
159+
}
160+
161+
const formatFileSize = (bytes: number): string => {
162+
if (bytes === 0) return '0 B'
163+
const k = 1024
164+
const sizes = ['B', 'KB', 'MB', 'GB']
165+
const i = Math.floor(Math.log(bytes) / Math.log(k))
166+
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`
167+
}
168+
155169
// Calculate progress percentage
156170
const progressPercentage =
157171
uploadProgress.totalFiles > 0
@@ -221,56 +235,55 @@ export function UploadModal({
221235
multiple
222236
/>
223237
<p className='text-sm'>
224-
{isDragging ? 'Drop more files here!' : 'Add more files'}
238+
{isDragging ? 'Drop more files here!' : 'Drop more files or click to browse'}
225239
</p>
226240
</div>
227241

228-
<div className='max-h-60 space-y-1.5 overflow-auto'>
242+
<div className='max-h-60 space-y-2 overflow-auto'>
229243
{files.map((file, index) => {
230244
const fileStatus = uploadProgress.fileStatuses?.[index]
231245
const isCurrentlyUploading = fileStatus?.status === 'uploading'
232246
const isCompleted = fileStatus?.status === 'completed'
233247
const isFailed = fileStatus?.status === 'failed'
234248

235249
return (
236-
<div key={index} className='space-y-1.5 rounded-md border p-2'>
237-
<div className='flex items-center justify-between'>
250+
<div key={index} className='rounded-md border p-3'>
251+
<div className='flex items-center gap-3'>
252+
{getFileIcon(file.type, file.name)}
238253
<div className='min-w-0 flex-1'>
239254
<div className='flex items-center gap-2'>
240255
{isCurrentlyUploading && (
241-
<Loader2 className='h-4 w-4 animate-spin text-blue-500' />
256+
<Loader2 className='h-4 w-4 animate-spin text-[var(--brand-primary-hex)]' />
242257
)}
243258
{isCompleted && <Check className='h-4 w-4 text-green-500' />}
244259
{isFailed && <X className='h-4 w-4 text-red-500' />}
245-
{!isCurrentlyUploading && !isCompleted && !isFailed && (
246-
<div className='h-4 w-4' />
247-
)}
248-
<p className='truncate text-sm'>
249-
<span className='font-medium'>{file.name}</span>
250-
<span className='text-muted-foreground'>
251-
{' '}
252-
{(file.size / 1024 / 1024).toFixed(2)} MB
253-
</span>
260+
<p className='truncate font-medium text-sm'>{file.name}</p>
261+
</div>
262+
<div className='flex items-center gap-2'>
263+
<p className='text-muted-foreground text-xs'>
264+
{formatFileSize(file.size)}
254265
</p>
266+
{isCurrentlyUploading && (
267+
<div className='min-w-0 max-w-32 flex-1'>
268+
<Progress value={fileStatus?.progress || 0} className='h-1' />
269+
</div>
270+
)}
255271
</div>
272+
{isFailed && fileStatus?.error && (
273+
<p className='mt-1 text-red-500 text-xs'>{fileStatus.error}</p>
274+
)}
256275
</div>
257276
<Button
258277
type='button'
259278
variant='ghost'
260279
size='sm'
261280
onClick={() => removeFile(index)}
262281
disabled={isUploading}
263-
className='h-8 w-8 p-0'
282+
className='h-8 w-8 p-0 text-muted-foreground hover:text-destructive'
264283
>
265284
<X className='h-4 w-4' />
266285
</Button>
267286
</div>
268-
{isCurrentlyUploading && (
269-
<Progress value={fileStatus?.progress || 0} className='h-1' />
270-
)}
271-
{isFailed && fileStatus?.error && (
272-
<p className='text-red-500 text-xs'>{fileStatus.error}</p>
273-
)}
274287
</div>
275288
)
276289
})}
@@ -287,7 +300,11 @@ export function UploadModal({
287300
<Button variant='outline' onClick={handleClose} disabled={isUploading}>
288301
Cancel
289302
</Button>
290-
<Button onClick={handleUpload} disabled={files.length === 0 || isUploading}>
303+
<Button
304+
onClick={handleUpload}
305+
disabled={files.length === 0 || isUploading}
306+
className='bg-[var(--brand-primary-hex)] font-[480] text-primary-foreground shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-primary-hover-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
307+
>
291308
{isUploading
292309
? uploadProgress.stage === 'uploading'
293310
? `Uploading ${uploadProgress.filesCompleted + 1}/${uploadProgress.totalFiles}...`

apps/sim/app/workspace/[workspaceId]/knowledge/components/create-modal/create-modal.tsx

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useEffect, useRef, useState } from 'react'
44
import { zodResolver } from '@hookform/resolvers/zod'
5-
import { AlertCircle, CheckCircle2, X } from 'lucide-react'
5+
import { AlertCircle, X } from 'lucide-react'
66
import { useParams } from 'next/navigation'
77
import { useForm } from 'react-hook-form'
88
import { z } from 'zod'
@@ -109,6 +109,7 @@ export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: Crea
109109
register,
110110
handleSubmit,
111111
reset,
112+
watch,
112113
formState: { errors },
113114
} = useForm<FormValues>({
114115
resolver: zodResolver(FormSchema),
@@ -119,9 +120,32 @@ export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: Crea
119120
maxChunkSize: 1024,
120121
overlapSize: 200,
121122
},
122-
mode: 'onChange',
123+
mode: 'onSubmit',
123124
})
124125

126+
// Watch the name field to enable/disable the submit button
127+
const nameValue = watch('name')
128+
129+
// Reset state when modal opens/closes
130+
useEffect(() => {
131+
if (open) {
132+
// Reset states when modal opens
133+
setSubmitStatus(null)
134+
setFileError(null)
135+
setFiles([])
136+
setIsDragging(false)
137+
setDragCounter(0)
138+
// Reset form to default values
139+
reset({
140+
name: '',
141+
description: '',
142+
minChunkSize: 1,
143+
maxChunkSize: 1024,
144+
overlapSize: 200,
145+
})
146+
}
147+
}, [open, reset])
148+
125149
const processFiles = async (fileList: FileList | File[]) => {
126150
setFileError(null)
127151

@@ -292,18 +316,6 @@ export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: Crea
292316
logger.info(`Started processing ${uploadedFiles.length} documents in the background`)
293317
}
294318

295-
setSubmitStatus({
296-
type: 'success',
297-
message: 'Your knowledge base has been created successfully!',
298-
})
299-
reset({
300-
name: '',
301-
description: '',
302-
minChunkSize: 1,
303-
maxChunkSize: 1024,
304-
overlapSize: 200,
305-
})
306-
307319
// Clean up file previews
308320
files.forEach((file) => URL.revokeObjectURL(file.preview))
309321
setFiles([])
@@ -313,10 +325,8 @@ export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: Crea
313325
onKnowledgeBaseCreated(newKnowledgeBase)
314326
}
315327

316-
// Close modal after a short delay to show success message
317-
setTimeout(() => {
318-
onOpenChange(false)
319-
}, 1500)
328+
// Close modal immediately - no need for success message
329+
onOpenChange(false)
320330
} catch (error) {
321331
logger.error('Error creating knowledge base:', error)
322332
setSubmitStatus({
@@ -357,31 +367,13 @@ export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: Crea
357367
className='scrollbar-thin scrollbar-thumb-muted-foreground/20 hover:scrollbar-thumb-muted-foreground/25 scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto px-6'
358368
>
359369
<div className='flex min-h-full flex-col py-4'>
360-
{submitStatus && submitStatus.type === 'success' ? (
361-
<Alert className='mb-6 border-border border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950/30'>
362-
<div className='flex items-start gap-4 py-1'>
363-
<div className='mt-[-1.5px] flex-shrink-0'>
364-
<CheckCircle2 className='h-4 w-4 text-green-600 dark:text-green-400' />
365-
</div>
366-
<div className='mr-4 flex-1 space-y-2'>
367-
<AlertTitle className='-mt-0.5 flex items-center justify-between'>
368-
<span className='font-medium text-green-600 dark:text-green-400'>
369-
Success
370-
</span>
371-
</AlertTitle>
372-
<AlertDescription className='text-green-600 dark:text-green-400'>
373-
{submitStatus.message}
374-
</AlertDescription>
375-
</div>
376-
</div>
377-
</Alert>
378-
) : submitStatus && submitStatus.type === 'error' ? (
370+
{submitStatus && submitStatus.type === 'error' && (
379371
<Alert variant='destructive' className='mb-6'>
380372
<AlertCircle className='h-4 w-4' />
381373
<AlertTitle>Error</AlertTitle>
382374
<AlertDescription>{submitStatus.message}</AlertDescription>
383375
</Alert>
384-
) : null}
376+
)}
385377

386378
{/* Form Fields Section - Fixed at top */}
387379
<div className='flex-shrink-0 space-y-4'>
@@ -611,8 +603,8 @@ export function CreateModal({ open, onOpenChange, onKnowledgeBaseCreated }: Crea
611603
</Button>
612604
<Button
613605
type='submit'
614-
disabled={isSubmitting}
615-
className='bg-[var(--brand-primary-hex)] font-[480] text-primary-foreground shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-primary-hover-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
606+
disabled={isSubmitting || !nameValue?.trim()}
607+
className='bg-[var(--brand-primary-hex)] font-[480] text-primary-foreground shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-primary-hover-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:opacity-50 disabled:hover:shadow-none'
616608
>
617609
{isSubmitting ? 'Creating...' : 'Create Knowledge Base'}
618610
</Button>

0 commit comments

Comments
 (0)