mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-03 14:19:01 +02:00
bb5d90a332
* feat: adding usage rate limiting to workbench and aligning editor context values for suggestionsLeft * feat: prepend word token to headers of token rate limiter to prevent confusion with usage rate limiter * Shared AI paywalls (#31948) * feat: renaming hasPremiumSuggestion and adding token limits to editor context and project load * feat: adding new ai features paywall component * feat: rename getRemainingFeatureUses to token based naming for token based limiter, removed checking for feature usage on anonymous users, and removed guard on null userId since we shouldnt be calling getRemainingFeatureUses on a nonexistent user * feat: using token rate limit headers to set token rate values in editor context * feat: update workbench to be available without refreshing if rate limit reset occurs within session * fix: move paywall out of inert section * Hide new paywalls behind FF and open plans page on upgrade attempt (#32023) * feat: hide new paywalls behind FF * feat: update ai paywall buttons to navigate to plans page post quota plans change release * feat: showing a fair limit notificaiton pre-quota change, and updating paywall to not fire if user has premium already (#32056) GitOrigin-RevId: 565fb128d55543fea34c383bc4abeaa3dd148d09
178 lines
5.9 KiB
TypeScript
178 lines
5.9 KiB
TypeScript
import { useTranslation } from 'react-i18next'
|
|
import { ElementType, memo, useCallback, useMemo, useState } from 'react'
|
|
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
|
|
import StopOnFirstErrorPrompt from '@/features/pdf-preview/components/stop-on-first-error-prompt'
|
|
import PdfPreviewError from '@/features/pdf-preview/components/pdf-preview-error'
|
|
import PdfValidationIssue from '@/features/pdf-preview/components/pdf-validation-issue'
|
|
import PdfLogsEntries from '@/features/pdf-preview/components/pdf-logs-entries'
|
|
import PdfPreviewErrorBoundaryFallback from '@/features/pdf-preview/components/pdf-preview-error-boundary-fallback'
|
|
import withErrorBoundary from '@/infrastructure/error-boundary'
|
|
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
|
import { Nav, NavLink, TabContainer, TabContent } from 'react-bootstrap'
|
|
import { LogEntry as LogEntryData } from '@/features/pdf-preview/util/types'
|
|
import LogEntry from './log-entry'
|
|
import TimeoutUpgradePromptNew from '@/features/pdf-preview/components/timeout-upgrade-prompt-new'
|
|
import getMeta from '@/utils/meta'
|
|
import PdfClearCacheButton from '@/features/pdf-preview/components/pdf-clear-cache-button'
|
|
import PdfDownloadFilesButton from '@/features/pdf-preview/components/pdf-download-files-button'
|
|
import RollingBuildSelectedReminder from './rolling-build-selected-reminder'
|
|
import AiPaywallNotification from '@/shared/components/ai-paywall-notification'
|
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
|
|
|
// todo: quota clean-up remove unneeded old paywall component
|
|
const logsComponents: Array<{
|
|
import: { default: ElementType }
|
|
path: string
|
|
}> = importOverleafModules('errorLogsComponents')
|
|
|
|
type ErrorLogTab = {
|
|
key: string
|
|
label: string
|
|
entries: LogEntryData[] | undefined
|
|
}
|
|
|
|
function ErrorLogs({
|
|
includeActionButtons,
|
|
}: {
|
|
includeActionButtons?: boolean
|
|
}) {
|
|
const { error, logEntries, rawLog, validationIssues, stoppedOnFirstError } =
|
|
useCompileContext()
|
|
const { compileTimeout } = getMeta('ol-compileSettings')
|
|
const { t } = useTranslation()
|
|
|
|
const tabs = useMemo(() => {
|
|
return [
|
|
{
|
|
key: 'all',
|
|
label: t('all_logs'),
|
|
entries: logEntries?.all,
|
|
},
|
|
{ key: 'errors', label: t('errors'), entries: logEntries?.errors },
|
|
{ key: 'warnings', label: t('warnings'), entries: logEntries?.warnings },
|
|
{ key: 'info', label: t('info'), entries: logEntries?.typesetting },
|
|
]
|
|
}, [logEntries, t])
|
|
|
|
const { loadingError } = usePdfPreviewContext()
|
|
|
|
const [activeTab, setActiveTab] = useState<string | null>('all')
|
|
|
|
const changeTab = useCallback(
|
|
(key: string | null) => {
|
|
if (tabs.some(tab => tab.key === key)) {
|
|
setActiveTab(key)
|
|
}
|
|
},
|
|
[tabs]
|
|
)
|
|
|
|
const entries = useMemo(() => {
|
|
return tabs.find(tab => tab.key === activeTab)?.entries || []
|
|
}, [activeTab, tabs])
|
|
|
|
const includeErrors = activeTab === 'all' || activeTab === 'errors'
|
|
const includeWarnings = activeTab === 'all' || activeTab === 'warnings'
|
|
|
|
return (
|
|
<TabContainer onSelect={changeTab} defaultActiveKey={activeTab ?? 'all'}>
|
|
<Nav defaultActiveKey="all" className="error-logs-tabs">
|
|
{tabs.map(tab => (
|
|
<TabHeader key={tab.key} tab={tab} active={activeTab === tab.key} />
|
|
))}
|
|
</Nav>
|
|
{logsComponents.map(({ import: { default: Component }, path }) => (
|
|
<Component key={path} />
|
|
))}
|
|
<AiPaywallNotification featureLocation="errorAssist" />
|
|
<TabContent className="error-logs new-error-logs">
|
|
<div className="logs-pane-content">
|
|
<RollingBuildSelectedReminder />
|
|
{stoppedOnFirstError && includeErrors && <StopOnFirstErrorPrompt />}
|
|
|
|
{loadingError && (
|
|
<PdfPreviewError
|
|
error="pdf-viewer-loading-error"
|
|
includeErrors={includeErrors}
|
|
includeWarnings={includeWarnings}
|
|
/>
|
|
)}
|
|
|
|
{compileTimeout < 60 && error === 'timedout' ? (
|
|
<TimeoutUpgradePromptNew />
|
|
) : (
|
|
<>{error && <PdfPreviewError error={error} />}</>
|
|
)}
|
|
|
|
{includeErrors &&
|
|
validationIssues &&
|
|
Object.entries(validationIssues).map(([name, issue]) => (
|
|
<PdfValidationIssue key={name} name={name} issue={issue} />
|
|
))}
|
|
|
|
{entries && (
|
|
<PdfLogsEntries
|
|
entries={entries}
|
|
hasErrors={
|
|
includeErrors &&
|
|
logEntries?.errors &&
|
|
logEntries?.errors.length > 0
|
|
}
|
|
/>
|
|
)}
|
|
|
|
{rawLog && activeTab === 'all' && (
|
|
<LogEntry
|
|
headerTitle={t('raw_logs')}
|
|
rawContent={rawLog}
|
|
entryAriaLabel={t('raw_logs_description')}
|
|
level="raw"
|
|
alwaysExpandRawContent
|
|
showSourceLocationLink={false}
|
|
/>
|
|
)}
|
|
|
|
{includeActionButtons && (
|
|
<div className="logs-pane-actions">
|
|
<PdfClearCacheButton />
|
|
<PdfDownloadFilesButton />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabContent>
|
|
</TabContainer>
|
|
)
|
|
}
|
|
|
|
function formatErrorNumber(num: number | undefined) {
|
|
if (num === undefined) {
|
|
return undefined
|
|
}
|
|
|
|
if (num > 99) {
|
|
return '99+'
|
|
}
|
|
|
|
return Math.floor(num).toString()
|
|
}
|
|
|
|
const TabHeader = ({ tab, active }: { tab: ErrorLogTab; active: boolean }) => {
|
|
return (
|
|
<NavLink
|
|
eventKey={tab.key}
|
|
className="error-logs-tab-header"
|
|
active={active}
|
|
>
|
|
{tab.label}
|
|
<div className="error-logs-tab-count">
|
|
{/* TODO: it would be nice if this number included custom errors */}
|
|
{formatErrorNumber(tab.entries?.length)}
|
|
</div>
|
|
</NavLink>
|
|
)
|
|
}
|
|
|
|
export default withErrorBoundary(memo(ErrorLogs), () => (
|
|
<PdfPreviewErrorBoundaryFallback type="logs" />
|
|
))
|