diff --git a/.pnpm-store/v11/.pnpm-needs-build-marker b/.pnpm-store/v11/.pnpm-needs-build-marker new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.pnpm-store/v11/index.db b/.pnpm-store/v11/index.db new file mode 100644 index 0000000000..044636a65f Binary files /dev/null and b/.pnpm-store/v11/index.db differ diff --git a/apps/web/src/components/MemorySection.tsx b/apps/web/src/components/MemorySection.tsx index 5284a439b3..f7cb071f98 100644 --- a/apps/web/src/components/MemorySection.tsx +++ b/apps/web/src/components/MemorySection.tsx @@ -727,29 +727,8 @@ export function MemorySection({ // fetch on mount + live SSE updates merged by id so phase transitions // (running → success) replace the row in place. const [extractions, setExtractions] = useState([]); - const [connectors, setConnectors] = useState([]); - const [connectorStatuses, setConnectorStatuses] = useState({}); - const [connectorsLoading, setConnectorsLoading] = useState(true); - const [selectedConnectorIds, setSelectedConnectorIds] = useState>( - () => new Set(), - ); - const [connectorExtracting, setConnectorExtracting] = useState(false); - const [connectorSaving, setConnectorSaving] = useState(false); - const [connectorSuggestions, setConnectorSuggestions] = useState([]); - const [selectedSuggestionIds, setSelectedSuggestionIds] = useState>( - () => new Set(), - ); - const [connectorAttempts, setConnectorAttempts] = useState([]); - const [connectorContextBytes, setConnectorContextBytes] = useState(0); - const [connectorStatus, setConnectorStatus] = useState(null); - const [connectorError, setConnectorError] = useState(null); - const [connectingConnectorIds, setConnectingConnectorIds] = useState>( - () => new Set(), - ); - const [pendingConnectorAuthIds, setPendingConnectorAuthIds] = useState>( - readPendingConnectorAuthIds, - ); - const [connectorConnectErrors, setConnectorConnectErrors] = useState>({}); + const [pendingExtractionDeleteId, setPendingExtractionDeleteId] = useState(null); + const [isDeletingExtraction, setIsDeletingExtraction] = useState(false); const fireFlash = useCallback((kind: FlashKind) => { setFlash({ kind, key: Date.now() }); @@ -1375,6 +1354,17 @@ export function MemorySection({ } }, [reloadExtractions]); + const onConfirmDeleteExtraction = useCallback(async () => { + if (!pendingExtractionDeleteId) return; + setIsDeletingExtraction(true); + try { + await onDeleteExtraction(pendingExtractionDeleteId); + setPendingExtractionDeleteId(null); + } finally { + setIsDeletingExtraction(false); + } + }, [onDeleteExtraction, pendingExtractionDeleteId]); + const onClearExtractions = useCallback(async () => { if (!window.confirm(t('settings.memoryExtractionsClearConfirm'))) return; setExtractions([]); @@ -2418,8 +2408,283 @@ export function MemorySection({ {t('settings.memoryIndexSave')} + ))} + + )) + )} + + +
+ + + {t('settings.memoryExtractions')} + + {extractions.length > 0 ? ( + {extractions.length} + ) : null} + {extractions.some((r) => r.phase === 'running') ? ( + + {t('settings.memoryExtractionPhaseRunning')} + + ) : null} + +
+ + {t('settings.memoryExtractionsHint')} + +
+ {extractions.length > 0 ? ( + + ) : null} + +
{/* end buttons */} +
{/* end hint+buttons row */} + {extractions.length === 0 ? ( +

{t('settings.memoryExtractionsEmpty')}

+ ) : ( +
    + {extractions.map((record) => { + const desc = describeRecord(record, t); + const duration = formatDuration(record); + return ( +
  • +
    + + {desc.phaseLabel} + + + {desc.kindLabel} + + {record.provider ? ( + + {record.provider.kind} · {record.provider.model} ·{' '} + {record.provider.credentialSource === 'env' + ? t('settings.memoryExtractionProviderEnv') + : record.provider.credentialSource === 'memory-config' + ? t('settings.memoryExtractionProviderOverride') + : t('settings.memoryExtractionProviderMediaConfig')} + + ) : null} + + {formatAbsoluteTime(record.startedAt, nowClock)} ·{' '} + {formatRelativeTime(record.startedAt, nowClock)} + + {duration ? ( + + {t('settings.memoryExtractionDuration')} {duration} + + ) : null} + +
    +
    + {record.userMessagePreview || '—'} +
    + {desc.reasonLabel ? ( +
    + {desc.reasonLabel} +
    + ) : null} + {record.phase === 'failed' && record.error ? ( +
    + {record.error} +
    + ) : null} + {record.phase === 'success' && + typeof record.writtenCount === 'number' ? ( +
    + {typeof record.proposedCount === 'number' ? ( + + {record.proposedCount}{' '} + {t('settings.memoryExtractionProposed')} + + ) : null} + + {record.writtenCount}{' '} + {t('settings.memoryExtractionWritten')} + + {Array.isArray(record.writtenIds) && + record.writtenIds.length > 0 ? ( + + {record.writtenIds.map((id: string) => ( + + ))} + + ) : null} +
    + ) : null} +
  • + ); + })} +
+ )} + {pendingExtractionDeleteId ? ( +
{ + if (isDeletingExtraction) return; + setPendingExtractionDeleteId(null); + }} + > +
{ + if (e.key !== 'Escape' || isDeletingExtraction) return; + setPendingExtractionDeleteId(null); + }} + onClick={(e) => e.stopPropagation()} + > +

+ {t('settings.memoryExtractionDeleteConfirmTitle')} +

+

+ {t('settings.memoryExtractionDeleteConfirmBody')} +

+
+ +
-
+ + + ) : null} + + +
+ + + {t('settings.memoryIndex')} + + +