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;