From 3951a9cbc5acecb0fb7cadb622e0d4ae35faa1db Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Sun, 5 Jul 2026 16:17:03 +0300 Subject: [PATCH] Clarify keyword snapshot loading state --- seo_mode/seo_mode/app/src/App.tsx | 75 ++++++++++++++++++++++++---- seo_mode/seo_mode/app/src/styles.css | 21 ++++++++ 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/seo_mode/seo_mode/app/src/App.tsx b/seo_mode/seo_mode/app/src/App.tsx index 5758031..fc4a9b0 100644 --- a/seo_mode/seo_mode/app/src/App.tsx +++ b/seo_mode/seo_mode/app/src/App.tsx @@ -4379,6 +4379,8 @@ function SeoAnchorReviewWorkspace({ actionKey, activeFilter, anchorReview, + contextProfileLabel, + contextProfileStatus, demandCollectionProfile, draftChangeCount, draftDecisionCount, @@ -4400,6 +4402,8 @@ function SeoAnchorReviewWorkspace({ actionKey: string | null; activeFilter: AnchorReviewFilter; anchorReview: AnchorReviewContract; + contextProfileLabel: string; + contextProfileStatus: "current" | "loading" | "snapshot"; demandCollectionProfile: DemandCollectionProfile; draftChangeCount: number; draftDecisionCount: number; @@ -4441,6 +4445,10 @@ function SeoAnchorReviewWorkspace({
Этап 1 · Фиксация семантического базиса Выбираем смысловые якоря и вручную решаем, что отправлять в очередь Wordstat. + + Текущий активный профиль: {contextProfileLabel} + {contextProfileStatus === "loading" ? " · загружается" : contextProfileStatus === "snapshot" ? " · снимок" : ""} + {getAnchorReviewStateLabel(anchorReview.state)} · очередь Wordstat {anchorReview.readiness.wordstatQueueCount} · нормализовано: {anchorReview.readiness.normalizedAnchorCount} · предложено:{" "} @@ -14288,11 +14296,17 @@ export function App() { const activeKeywordContextSnapshotDetail = activeKeywordContextSnapshotKey ? (keywordContextSnapshotDetails[activeKeywordContextSnapshotKey] ?? null) : null; + const isActiveKeywordContextSnapshotReplay = activeKeywordContextSnapshotId !== "current"; + const isActiveKeywordContextSnapshotPending = Boolean( + isActiveKeywordContextSnapshotReplay && !activeKeywordContextSnapshotDetail + ); const isActiveKeywordContextSnapshotLoading = Boolean( - activeKeywordContextSnapshotKey && loadingKeywordContextSnapshotKey === activeKeywordContextSnapshotKey + isActiveKeywordContextSnapshotPending || + (activeKeywordContextSnapshotKey && loadingKeywordContextSnapshotKey === activeKeywordContextSnapshotKey) ); const activeSemanticAnalysis = - activeKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ?? (activeProject ? (semanticAnalyses[activeProject.id] ?? null) : null); + activeKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ?? + (isActiveKeywordContextSnapshotPending ? null : activeProject ? (semanticAnalyses[activeProject.id] ?? null) : null); const activeContextReviewRaw = activeProject ? (contextReviews[activeProject.id] ?? null) : null; const activeContextReview = activeSemanticAnalysis && activeContextReviewRaw?.semanticRunId === activeSemanticAnalysis.runId @@ -14300,10 +14314,11 @@ export function App() { : null; const activeMarketEnrichment = activeKeywordContextSnapshotDetail?.snapshot.marketEnrichment ?? - (activeProject ? (marketEnrichments[activeProject.id] ?? null) : null); + (isActiveKeywordContextSnapshotPending ? null : activeProject ? (marketEnrichments[activeProject.id] ?? null) : null); const activeKeywordAnalysisWorkflow = activeProject ? (keywordAnalysisWorkflows[activeProject.id] ?? null) : null; const activeKeywordMap = - activeKeywordContextSnapshotDetail?.snapshot.keywordMap ?? (activeProject ? (keywordMaps[activeProject.id] ?? null) : null); + activeKeywordContextSnapshotDetail?.snapshot.keywordMap ?? + (isActiveKeywordContextSnapshotPending ? null : activeProject ? (keywordMaps[activeProject.id] ?? null) : null); const activeQuotaProjectId = activeProject?.id ?? null; useEffect(() => { @@ -14884,7 +14899,11 @@ export function App() { !isPersistingActiveKeywordMap ); const isSubmittingActiveKeywordStrategy = isPersistingActiveKeywordMap && persistingKeywordMapMode === "strategy"; - const isActiveKeywordContextSnapshotReplay = activeKeywordContextSnapshotId !== "current"; + const activeKeywordContextSnapshotSummary = + activeKeywordContextSnapshotId !== "current" + ? (activeKeywordContextSnapshotList.find((snapshot) => snapshot.id === activeKeywordContextSnapshotId) ?? null) + : null; + const activeKeywordContextProfileLabel = activeKeywordContextSnapshotSummary?.label ?? "Текущий контекст"; const activeKeywordContextSnapshotOptions: NdcGlassSelectOption[] = [ { label: "Текущий контекст", value: "current" }, ...activeKeywordContextSnapshotList.map((snapshot) => ({ @@ -14933,7 +14952,7 @@ export function App() { value={activeKeywordContextSnapshotId} /> {isActiveKeywordContextSnapshotReplay ? ( - + {isActiveKeywordContextSnapshotLoading ? "загружаю снимок" : "снимок"} ) : null} @@ -16467,8 +16486,26 @@ export function App() { const projectKeywordContextSnapshotDetail = projectKeywordContextSnapshotKey ? (keywordContextSnapshotDetails[projectKeywordContextSnapshotKey] ?? null) : null; + const projectKeywordContextSnapshotSummary = + projectKeywordContextSnapshotId !== "current" + ? ((keywordContextSnapshots[project.id] ?? []).find( + (snapshot) => snapshot.id === projectKeywordContextSnapshotId + ) ?? null) + : null; + const projectKeywordContextSnapshotPending = Boolean( + projectKeywordContextSnapshotId !== "current" && !projectKeywordContextSnapshotDetail + ); + const projectKeywordContextProfileLabel = + projectKeywordContextSnapshotSummary?.label ?? "Текущий контекст"; + const projectKeywordContextProfileStatus: "current" | "loading" | "snapshot" = + projectKeywordContextSnapshotId === "current" + ? "current" + : projectKeywordContextSnapshotPending + ? "loading" + : "snapshot"; const semanticAnalysis = - projectKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ?? (semanticAnalyses[project.id] ?? null); + projectKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ?? + (projectKeywordContextSnapshotPending ? null : semanticAnalyses[project.id] ?? null); const contextReview = contextReviews[project.id] ?? null; const isContextReviewAligned = Boolean( semanticAnalysis && contextReview && contextReview.semanticRunId === semanticAnalysis.runId @@ -16476,10 +16513,14 @@ export function App() { const activeContextReview = isContextReviewAligned ? contextReview : null; const projectOntology = projectOntologies[project.id] ?? null; const marketEnrichment = - projectKeywordContextSnapshotDetail?.snapshot.marketEnrichment ?? (marketEnrichments[project.id] ?? null); + projectKeywordContextSnapshotDetail?.snapshot.marketEnrichment ?? + (projectKeywordContextSnapshotPending ? null : marketEnrichments[project.id] ?? null); const keywordCleaning = - projectKeywordContextSnapshotDetail?.snapshot.keywordCleaning ?? (keywordCleanings[project.id] ?? null); - const keywordMap = projectKeywordContextSnapshotDetail?.snapshot.keywordMap ?? (keywordMaps[project.id] ?? null); + projectKeywordContextSnapshotDetail?.snapshot.keywordCleaning ?? + (projectKeywordContextSnapshotPending ? null : keywordCleanings[project.id] ?? null); + const keywordMap = + projectKeywordContextSnapshotDetail?.snapshot.keywordMap ?? + (projectKeywordContextSnapshotPending ? null : keywordMaps[project.id] ?? null); const seoStrategy = seoStrategies[project.id] ?? null; const yandexEvidence = yandexEvidences[project.id] ?? null; const serpInterpretation = serpInterpretations[project.id] ?? null; @@ -16507,7 +16548,8 @@ export function App() { const isKeywordAnalysisRebuilding = isKeywordAnalysisWorkflowActive(keywordAnalysisWorkflow); const anchorReviewDraft = anchorReviewDraftDecisions[project.id] ?? {}; const rawAnchorReview = - projectKeywordContextSnapshotDetail?.snapshot.anchorReview ?? marketEnrichment?.anchorReview ?? null; + projectKeywordContextSnapshotDetail?.snapshot.anchorReview ?? + (projectKeywordContextSnapshotPending ? null : marketEnrichment?.anchorReview ?? null); const anchorReviewDraftCount = getAnchorReviewDraftDecisionList(rawAnchorReview, anchorReviewDraft).length; const savedDemandCollectionProfile = rawAnchorReview?.demandCollectionProfile ?? "contextual"; const anchorReviewDemandProfile = @@ -19536,6 +19578,15 @@ export function App() { Прошло {keywordBasisElapsedLabel}. Обычно до 8 минут; окно можно оставить открытым.
+ ) : projectKeywordContextSnapshotPending ? ( +
+ + Загружаю сохранённый профиль + {projectKeywordContextProfileLabel} + + Поднимаем сохранённые anchors, Wordstat evidence, cleaning и карту ключей из snapshot. + +
) : ( <> {semanticAnalysis && !activeContextReview ? ( @@ -19566,6 +19617,8 @@ export function App() { actionKey={anchorReviewActionKey} activeFilter={activeAnchorReviewFilter} anchorReview={anchorReview} + contextProfileLabel={projectKeywordContextProfileLabel} + contextProfileStatus={projectKeywordContextProfileStatus} draftDecisionCount={anchorReviewDraftCount} demandCollectionProfile={anchorReviewDemandProfile} draftChangeCount={anchorReviewDraftChangeCount} diff --git a/seo_mode/seo_mode/app/src/styles.css b/seo_mode/seo_mode/app/src/styles.css index b324b2f..834014b 100644 --- a/seo_mode/seo_mode/app/src/styles.css +++ b/seo_mode/seo_mode/app/src/styles.css @@ -2985,6 +2985,18 @@ input { color: var(--ink); } +.keyword-stage-heading .keyword-stage-profile-line { + color: rgba(10, 12, 14, 0.72); +} + +.keyword-stage-heading .keyword-stage-profile-line strong { + display: inline; + color: var(--ink); + font-size: inherit; + font-weight: 900; + line-height: inherit; +} + .keyword-stage-actions { display: inline-flex; flex: 0 0 auto; @@ -3117,6 +3129,15 @@ input { overflow-wrap: anywhere; } +.keyword-context-snapshot-loading { + min-height: 14rem; +} + +.keyword-context-snapshot-loading span { + max-width: min(48rem, 100%); + overflow-wrap: anywhere; +} + .keyword-workflow-step-list { display: grid; gap: 7px;