|
1 | 1 | 'use client' |
2 | 2 |
|
3 | 3 | import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' |
4 | | -import { Badge, Button, Checkbox, ChipConfirmModal, cn, Loader, Tooltip } from '@sim/emcn' |
| 4 | +import { |
| 5 | + Badge, |
| 6 | + Button, |
| 7 | + Checkbox, |
| 8 | + ChipConfirmModal, |
| 9 | + cn, |
| 10 | + DropdownMenu, |
| 11 | + DropdownMenuContent, |
| 12 | + DropdownMenuItem, |
| 13 | + DropdownMenuTrigger, |
| 14 | + Loader, |
| 15 | + Tooltip, |
| 16 | +} from '@sim/emcn' |
5 | 17 | import { createLogger } from '@sim/logger' |
6 | 18 | import { format, formatDistanceToNow, isPast } from 'date-fns' |
7 | 19 | import { |
@@ -115,14 +127,14 @@ export function ConnectorsSection({ |
115 | 127 | }, []) |
116 | 128 |
|
117 | 129 | const handleSync = useCallback( |
118 | | - (connectorId: string) => { |
| 130 | + (connectorId: string, rehydrate = false) => { |
119 | 131 | if (isSyncOnCooldown(connectorId)) return |
120 | 132 |
|
121 | 133 | syncTriggeredAt.current[connectorId] = Date.now() |
122 | 134 | addToSet(setSyncingIds, connectorId) |
123 | 135 |
|
124 | 136 | triggerSync( |
125 | | - { knowledgeBaseId, connectorId }, |
| 137 | + { knowledgeBaseId, connectorId, rehydrate }, |
126 | 138 | { |
127 | 139 | onSuccess: () => { |
128 | 140 | setError(null) |
@@ -214,7 +226,7 @@ export function ConnectorsSection({ |
214 | 226 | isSyncPending={syncingIds.has(connector.id)} |
215 | 227 | isUpdating={updatingIds.has(connector.id)} |
216 | 228 | syncCooldown={isSyncOnCooldown(connector.id)} |
217 | | - onSync={() => handleSync(connector.id)} |
| 229 | + onSync={(rehydrate) => handleSync(connector.id, rehydrate)} |
218 | 230 | onTogglePause={() => handleTogglePause(connector)} |
219 | 231 | onEdit={() => setEditingConnector(connector)} |
220 | 232 | onDelete={() => setDeleteTarget(connector.id)} |
@@ -273,7 +285,7 @@ interface ConnectorCardProps { |
273 | 285 | isSyncPending: boolean |
274 | 286 | isUpdating: boolean |
275 | 287 | syncCooldown: boolean |
276 | | - onSync: () => void |
| 288 | + onSync: (rehydrate?: boolean) => void |
277 | 289 | onEdit: () => void |
278 | 290 | onTogglePause: () => void |
279 | 291 | onDelete: () => void |
@@ -327,6 +339,14 @@ function ConnectorCard({ |
327 | 339 | ) |
328 | 340 | const syncLogs = detail?.syncLogs ?? [] |
329 | 341 |
|
| 342 | + const canFullResync = Boolean(connectorDef?.rehydrateOnFullSync) |
| 343 | + const syncDisabled = |
| 344 | + connector.status === 'syncing' || |
| 345 | + connector.status === 'disabled' || |
| 346 | + isSyncPending || |
| 347 | + syncCooldown |
| 348 | + const syncTooltip = syncCooldown ? 'Sync recently triggered' : canFullResync ? 'Sync' : 'Sync now' |
| 349 | + |
330 | 350 | return ( |
331 | 351 | <div |
332 | 352 | className={cn( |
@@ -409,28 +429,60 @@ function ConnectorCard({ |
409 | 429 | <div className='flex flex-shrink-0 items-center gap-0.5'> |
410 | 430 | {canEdit && ( |
411 | 431 | <> |
412 | | - <Tooltip.Root> |
413 | | - <Tooltip.Trigger asChild> |
414 | | - <Button |
415 | | - variant='ghost' |
416 | | - className={CONNECTOR_ACTION_BUTTON_CLASSES} |
417 | | - onClick={onSync} |
418 | | - disabled={ |
419 | | - connector.status === 'syncing' || |
420 | | - connector.status === 'disabled' || |
421 | | - isSyncPending || |
422 | | - syncCooldown |
423 | | - } |
424 | | - > |
425 | | - <RefreshCw |
426 | | - className={cn('size-3.5', connector.status === 'syncing' && 'animate-spin')} |
427 | | - /> |
428 | | - </Button> |
429 | | - </Tooltip.Trigger> |
430 | | - <Tooltip.Content> |
431 | | - {syncCooldown ? 'Sync recently triggered' : 'Sync now'} |
432 | | - </Tooltip.Content> |
433 | | - </Tooltip.Root> |
| 432 | + {canFullResync ? ( |
| 433 | + <DropdownMenu> |
| 434 | + <Tooltip.Root> |
| 435 | + <Tooltip.Trigger asChild> |
| 436 | + {/* span keeps the tooltip hoverable while the trigger button is disabled */} |
| 437 | + <span className='inline-flex'> |
| 438 | + <DropdownMenuTrigger asChild> |
| 439 | + <Button |
| 440 | + variant='ghost' |
| 441 | + aria-label='Sync options' |
| 442 | + className={CONNECTOR_ACTION_BUTTON_CLASSES} |
| 443 | + disabled={syncDisabled} |
| 444 | + > |
| 445 | + <RefreshCw |
| 446 | + className={cn( |
| 447 | + 'size-3.5', |
| 448 | + connector.status === 'syncing' && 'animate-spin' |
| 449 | + )} |
| 450 | + /> |
| 451 | + </Button> |
| 452 | + </DropdownMenuTrigger> |
| 453 | + </span> |
| 454 | + </Tooltip.Trigger> |
| 455 | + <Tooltip.Content>{syncTooltip}</Tooltip.Content> |
| 456 | + </Tooltip.Root> |
| 457 | + <DropdownMenuContent align='end'> |
| 458 | + <DropdownMenuItem onSelect={() => onSync(false)}>Sync now</DropdownMenuItem> |
| 459 | + <DropdownMenuItem onSelect={() => onSync(true)}>Full resync</DropdownMenuItem> |
| 460 | + </DropdownMenuContent> |
| 461 | + </DropdownMenu> |
| 462 | + ) : ( |
| 463 | + <Tooltip.Root> |
| 464 | + <Tooltip.Trigger asChild> |
| 465 | + {/* span keeps the tooltip hoverable while the button is disabled */} |
| 466 | + <span className='inline-flex'> |
| 467 | + <Button |
| 468 | + variant='ghost' |
| 469 | + aria-label='Sync now' |
| 470 | + className={CONNECTOR_ACTION_BUTTON_CLASSES} |
| 471 | + disabled={syncDisabled} |
| 472 | + onClick={() => onSync(false)} |
| 473 | + > |
| 474 | + <RefreshCw |
| 475 | + className={cn( |
| 476 | + 'size-3.5', |
| 477 | + connector.status === 'syncing' && 'animate-spin' |
| 478 | + )} |
| 479 | + /> |
| 480 | + </Button> |
| 481 | + </span> |
| 482 | + </Tooltip.Trigger> |
| 483 | + <Tooltip.Content>{syncTooltip}</Tooltip.Content> |
| 484 | + </Tooltip.Root> |
| 485 | + )} |
434 | 486 |
|
435 | 487 | <Tooltip.Root> |
436 | 488 | <Tooltip.Trigger asChild> |
|
0 commit comments