/* ===== Screens Part 1: Dashboard, Memory, Sensing ===== */ // ========== 00 · DASHBOARD ========== const dashboardApiUrl = (pathname) => (window.location.protocol === 'file:' ? `http://localhost:4173${pathname}` : pathname); const dashboardNumber = (value) => Number(value || 0); const dashboardCompactNumber = (value) => { const n = dashboardNumber(value); if (n >= 100000000) return `${(n / 100000000).toFixed(1)}亿`; if (n >= 10000) return `${(n / 10000).toFixed(1)}万`; return n.toLocaleString(); }; const dashboardDateValue = (value = '') => { if (!value) return 0; const parsed = new Date(String(value).replace(/\//g, '-')); return Number.isNaN(parsed.getTime()) ? 0 : parsed.getTime(); }; const dashboardFormatDate = (value, fallback = '未记录') => { const time = dashboardDateValue(value); if (!time) return fallback; return new Date(time).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false, }); }; const dashboardFormatRange = (start, end) => { const s = dashboardDateValue(start); const e = dashboardDateValue(end); if (!s && !e) return '未设置周期'; const fmt = time => new Date(time).toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }); return `${s ? fmt(s) : '未设开始'} - ${e ? fmt(e) : '未设结束'}`; }; const dashboardMetric = (row = {}, keys = []) => { for (const key of keys) { const value = key.split('.').reduce((acc, part) => acc?.[part], row); if (value !== undefined && value !== null && value !== '') return dashboardNumber(value); } return 0; }; const dashboardPostViews = (post = {}) => dashboardMetric(post, ['views', 'play_count', 'plays', 'read_count', 'reads', 'organic_impressions', 'raw.views', 'raw.play_count', 'raw.read_count']); const dashboardPostInteraction = (post = {}) => dashboardMetric(post, ['likes', 'like_count', 'raw.likes', 'raw.like_count']) + dashboardMetric(post, ['comments', 'comment_count', 'raw.comments', 'raw.comment_count']) + dashboardMetric(post, ['shares', 'share_count', 'reposts', 'raw.shares', 'raw.share_count']) + dashboardMetric(post, ['saves', 'collects', 'raw.saves', 'raw.collect_count']); const dashboardIsExternalPost = (post = {}) => { const type = `${post.source_type || ''} ${post.asset_type || ''} ${post.source_post_type || ''} ${post.relationship_status || ''}`.toLowerCase(); return Boolean(post.is_official_post === false || /earned|ugc|kol|external/.test(type)); }; const dashboardIsRiskComment = (comment = {}) => { const level = String(comment.pr_risk_level || comment.risk_level || '').toLowerCase(); const sentiment = Number(comment.sentiment || 0); const action = String(comment.action_status || '').toLowerCase(); return ['high', 'medium'].includes(level) || sentiment < -0.25 || /needs|review/.test(action); }; const dashboardMergeRows = (baseRows = [], extraRows = [], idKey) => { const map = new Map(); [...baseRows, ...extraRows].forEach(row => { if (!row?.[idKey]) return; map.set(row[idKey], { ...(map.get(row[idKey]) || {}), ...row }); }); return Array.from(map.values()); }; const dashboardBrandMatches = (brand = {}, brandId = '') => { const brandNames = [brandId, brand?.id, brand?.name, brand?.cn].filter(Boolean).map(item => String(item).toLowerCase()); return (row = {}) => { const rowBrand = String(row.brand_id || row.brandId || '').toLowerCase(); if (rowBrand && brandNames.includes(rowBrand)) return true; const text = [row.account_id, row.account_name, row.name, row.handle, row.title, row.caption].filter(Boolean).join(' ').toLowerCase(); return brandNames.some(name => name && text.includes(name)); }; }; const dashboardActiveConstraints = (brand) => { if (!brand) return []; if (window.BrandMemoryStore?.getActiveConstraints) return window.BrandMemoryStore.getActiveConstraints(brand); return Array.isArray(brand.constraints) ? brand.constraints.filter(item => item.enabled !== false) : []; }; const dashboardLatestPostTime = (post = {}) => dashboardDateValue(post.published_at || post.publish_time || post.created_at || post.updated_at); const dashboardInitiativeTypeLabel = (type = 'campaign') => ({ campaign: 'Campaign', pillar: 'Pillar', series: 'Series', standalone: 'Content', social_care: 'Care', }[type] || 'Object'); const Dashboard = ({ onNav, workspaceContext }) => { const brandStoreReady = Boolean(window.BrandMemoryStore?.useBrandMemoryProfiles); const brandState = brandStoreReady ? window.BrandMemoryStore.useBrandMemoryProfiles() : { brands: [] }; const brandId = workspaceContext?.brandId || BRAND?.id || 'montx'; const workspaceBrand = (WORKSPACE_BRANDS || []).find(item => item.id === brandId) || WORKSPACE_BRANDS?.[0] || BRAND; const brand = brandStoreReady ? window.BrandMemoryStore.getBrandProfile(brandId, brandState.brands) : workspaceBrand; const brandMatches = dashboardBrandMatches(brand || workspaceBrand, brandId); const [syncState, setSyncState] = React.useState({ accounts: [], posts: [], comments: [], status: 'idle', lastSync: '', errors: [] }); const [initiatives, setInitiatives] = React.useState([]); const [marketState, setMarketState] = React.useState({ signals: [], reviewQueue: [], status: 'idle', errors: [] }); React.useEffect(() => { let cancelled = false; const loadDashboardData = async () => { const [socialResult, initiativeResult, signalResult, reviewResult] = await Promise.allSettled([ fetch(dashboardApiUrl('/api/social/sync/status')).then(res => res.json()), fetch(dashboardApiUrl('/api/initiatives')).then(res => res.json()), fetch(dashboardApiUrl('/api/market/signals')).then(res => res.json()), fetch(dashboardApiUrl('/api/market/review-queue')).then(res => res.json()), ]); if (cancelled) return; if (socialResult.status === 'fulfilled') { const data = socialResult.value || {}; setSyncState({ accounts: data.accounts || [], posts: data.posts || [], comments: data.comments || [], status: data.status || 'idle', lastSync: data.lastFinishedAt || '', nextRun: data.nextRunAt || '', errors: data.errors || [], }); } else { setSyncState(prev => ({ ...prev, status: 'offline', errors: [{ message: '同步接口未连接' }] })); } if (initiativeResult.status === 'fulfilled') setInitiatives(initiativeResult.value?.initiatives || initiativeResult.value?.campaigns || []); if (signalResult.status === 'fulfilled' || reviewResult.status === 'fulfilled') { setMarketState({ signals: signalResult.status === 'fulfilled' ? (signalResult.value?.signals || []) : [], reviewQueue: reviewResult.status === 'fulfilled' ? (reviewResult.value?.reviewQueue || reviewResult.value?.tasks || []) : [], status: 'ok', errors: [], }); } else { setMarketState({ signals: [], reviewQueue: [], status: 'offline', errors: [{ message: '市场感知接口未连接' }] }); } }; loadDashboardData(); const timer = setInterval(loadDashboardData, 30000); return () => { cancelled = true; clearInterval(timer); }; }, [brandId]); const accounts = React.useMemo(() => dashboardMergeRows(window.SOCIAL_ACCOUNTS || [], syncState.accounts || [], 'account_id').filter(brandMatches), [syncState.accounts, brandId, brand?.id]); const accountIds = React.useMemo(() => new Set(accounts.map(account => account.account_id)), [accounts]); const posts = React.useMemo(() => dashboardMergeRows(window.SOCIAL_POSTS || [], syncState.posts || [], 'post_id').filter(post => brandMatches(post) || accountIds.has(post.account_id)), [syncState.posts, accountIds, brandId, brand?.id]); const comments = React.useMemo(() => { const postIds = new Set(posts.map(post => post.post_id)); return dashboardMergeRows(window.SOCIAL_COMMENTS || [], syncState.comments || [], 'comment_id').filter(comment => postIds.has(comment.post_id)); }, [syncState.comments, posts]); const ownedPosts = posts.filter(post => !dashboardIsExternalPost(post)); const brandInitiatives = initiatives.filter(item => (item.brandId || item.brand_id || brandId) === brandId && !['archived', 'done', 'completed'].includes(String(item.status || '').toLowerCase())); const activeInitiatives = brandInitiatives.filter(item => ['active', 'running', 'live'].includes(String(item.status || '').toLowerCase())); const activeCampaigns = activeInitiatives.filter(item => (item.type || 'campaign') === 'campaign'); const activeInitiative = activeInitiatives[0] || brandInitiatives[0] || null; const activeConstraints = dashboardActiveConstraints(brand); const productCount = Array.isArray(brand?.products) ? brand.products.length : Array.isArray(workspaceBrand?.products) ? workspaceBrand.products.length : 0; const totalViews = ownedPosts.reduce((sum, post) => sum + dashboardPostViews(post), 0); const totalInteraction = ownedPosts.reduce((sum, post) => sum + dashboardPostInteraction(post), 0); const riskComments = comments.filter(dashboardIsRiskComment); const openRiskComments = riskComments.filter(comment => !['resolved', 'closed'].includes(String(comment.action_status || '').toLowerCase())); const marketSignals = marketState.signals || []; const pendingReviews = marketState.reviewQueue || []; const pendingEvents = [ ...openRiskComments.map(comment => ({ type: 'risk', title: `风险评论待复核:${String(comment.text || comment.keyword || '未命名评论').slice(0, 26)}`, source: '发布监控 · 评论', meta: comment.pr_risk_level || comment.risk_level || 'needs-review', time: dashboardFormatDate(comment.created_at || comment.published_at, '待处理') })), ...pendingReviews.map(item => ({ type: 'risk', title: item.title || item.reason || '市场感知待复核', source: '市场感知 · 事实/风险待确认', meta: item.reason || item.risk_level || item.status || 'pending', time: dashboardFormatDate(item.created_at || item.updated_at, '待处理') })), ]; const latestPosts = [...ownedPosts].sort((a, b) => dashboardLatestPostTime(b) - dashboardLatestPostTime(a)).slice(0, 2); const events = [ ...pendingEvents.slice(0, 3), ...latestPosts.map(post => ({ type: 'ok', title: `已同步内容:${String(post.title || post.caption || '未命名内容').slice(0, 30)}`, source: `社媒资产 · ${post.platform || '未标记平台'}`, meta: `阅读/播放 ${dashboardCompactNumber(dashboardPostViews(post))}`, time: dashboardFormatDate(post.published_at || post.publish_time || post.created_at, '已同步') })), marketSignals.length ? { type: 'opp', title: `市场感知输出 ${marketSignals.length} 条判断素材`, source: 'Market Sensing', meta: '机会 / 风险 / 变化 / 异常', time: marketState.status === 'ok' ? '已更新' : '待连接' } : null, syncState.errors?.length ? { type: 'risk', title: '社媒同步存在错误', source: 'Social Sync', meta: syncState.errors.map(item => item.message || item.platform).filter(Boolean).join(' / ') || '查看同步日志', time: '需处理' } : null, !posts.length ? { type: 'info', title: '暂无同步内容表现数据', source: '社媒资产', meta: '连接同步后自动生成首页指标', time: '待同步' } : null, ].filter(Boolean).slice(0, 5); const lastSyncText = syncState.lastSync ? `上次同步 ${dashboardFormatDate(syncState.lastSync)}` : '等待社媒同步'; return (

{brand?.name || workspaceBrand?.name || 'Workspace'} · 社媒营销操作台

{openRiskComments.length || pendingReviews.length ? `你有 ${openRiskComments.length + pendingReviews.length} 条待处理判断` : '当前没有待处理风险判断'} · 首页只读取品牌记忆、社媒同步、市场感知和运营对象数据。

item.type === 'series').length} Series`} accent/>
); }; const MetricCard = ({ label, value, delta, accent }) => (
{label}
{value}
{delta}
); const dashboardPostIds = (post = {}) => [post.post_id, post.content_id, post.id].filter(Boolean); const dashboardPostBriefIds = (post = {}) => [ post.primaryMasterBriefId, post.briefMapping?.primaryMasterBriefId, ...(post.masterBriefIds || []), ...(post.briefMapping?.masterBriefIds || []), ].filter(Boolean); const dashboardInitiativeMatchesPost = (initiative = {}, post = {}) => { const ids = new Set(initiative.assetPostIds || []); if (dashboardPostIds(post).some(id => ids.has(id))) return true; if (ids.size) return false; const briefIds = new Set(dashboardPostBriefIds(post)); if (initiative.id && briefIds.has(initiative.id)) return true; const parentIds = [post.parentCampaignId, post.campaignId, post.briefMapping?.parentCampaignId].filter(Boolean); if (initiative.id && parentIds.includes(initiative.id)) return true; if (ids.size || briefIds.size || parentIds.length) return false; return (initiative.assetCampaignLabels || []).some(label => label && [post.campaign, post.product, post.content_type, post.title, post.caption].join(' ').includes(label)); }; const CurrentCampaignCard = ({ initiative, posts, comments, totalViews, totalInteraction, onNav }) => { if (!initiative) return (

当前运营对象

未创建
尚未读取到当前品牌的运营对象。创建 Campaign、Series 或 Content 后,首页会按 PRD 的中枢闭环展示进度和表现。 数据来源:campaign-state / Brand Memory / Social Sync
); const linkedPosts = posts.filter(post => dashboardInitiativeMatchesPost(initiative, post)); const linkedViews = linkedPosts.reduce((sum, post) => sum + dashboardPostViews(post), 0); const linkedInteraction = linkedPosts.reduce((sum, post) => sum + dashboardPostInteraction(post), 0); const linkedComments = new Set(linkedPosts.map(post => post.post_id)); const linkedRisk = comments.filter(comment => linkedComments.has(comment.post_id) && dashboardIsRiskComment(comment)).length; const steps = [ { label: '品牌记忆', status: 'done', note: '已加载' }, { label: '市场感知', status: linkedPosts.length ? 'done' : 'active', note: linkedPosts.length ? `${linkedPosts.length} 内容证据` : '等待证据' }, { label: '策略推演', status: initiative.coreMessage || initiative.goals?.length ? 'done' : 'active', note: initiative.stage || dashboardInitiativeTypeLabel(initiative.type) }, { label: 'Brief 编排', status: initiative.masterBrief ? 'done' : 'pending', note: initiative.masterBrief?.version || '待生成' }, { label: '发布监控', status: linkedPosts.length ? 'active' : 'pending', note: `${linkedPosts.length} 已关联` }, { label: '复盘学习', status: linkedRisk ? 'active' : (linkedPosts.length ? 'pending' : 'pending'), note: linkedRisk ? `${linkedRisk} 风险待处理` : '待写回' }, ]; const engagementRate = linkedViews ? `${((linkedInteraction / linkedViews) * 100).toFixed(2)}%` : '待计算'; return (

当前运营对象

{initiative.name || '未命名对象'}
{dashboardInitiativeTypeLabel(initiative.type)}
{initiative.coreMessage || initiative.masterBrief?.objective || '当前对象缺少核心命题,需要在策略推演里补齐目标、受众和内容边界。'} {dashboardFormatRange(initiative.startDate, initiative.endDate)} · {initiative.masterBrief?.title || 'Master Brief 未生成'}
); }; function PipelineBar({ steps = [], label = 'PIPELINE' }) { const fallbackSteps = [ { label: '品牌记忆', status: 'pending', note: '待加载' }, { label: '市场感知', status: 'pending', note: '待同步' }, { label: '策略推演', status: 'pending', note: '待创建' }, { label: 'Brief 编排', status: 'pending', note: '待生成' }, { label: '发布监控', status: 'pending', note: '待关联' }, { label: '复盘学习', status: 'pending', note: '待触发' }, ]; const rows = steps.length ? steps : fallbackSteps; const doneCount = rows.filter(s => s.status === 'done').length; const activeCount = rows.filter(s => s.status === 'active').length; const donePct = rows.length ? (doneCount / rows.length) * 100 : 0; const activePct = rows.length ? (activeCount / rows.length) * 100 : 0; return (
{label} {doneCount}/{rows.length} 已完成 · {activeCount} 进行中
{rows.map((s, i) => (
{s.label}
{s.note}
))}
); } const SubMetric = ({ label, value, target, pct, accent }) => (
{label}
{value}
{target} · {pct}%
); const EventFeedCard = ({ events = [], pendingCount = 0, syncState = {} }) => (

事件流 · Live

{events.length} 条
{pendingCount ? `${pendingCount} pending` : (syncState.status || 'idle')}
{events.map((e, i) => (
{{risk:'风险', opp:'机会', ok:'完成', info:'提示'}[e.type]}
{e.title}
{e.source} · {e.meta}
{e.time}
))} {!events.length &&
暂无事件。连接社媒同步或市场感知后会自动出现风险、机会和异常判断。
}
); const SystemHealthCard = ({ brand, syncState = {}, initiatives = [], posts = [], marketState = {}, riskCount = 0 }) => { const modules = [ ['brand-memory', '品牌记忆', brand ? 'ok' : 'idle', brand ? 'profile loaded' : 'no profile'], ['social-sync', '社媒同步', syncState.status === 'ok' || posts.length ? 'ok' : syncState.errors?.length ? 'warn' : 'idle', `${posts.length} posts`], ['market-sensing', '市场感知', marketState.signals?.length ? 'ok' : marketState.status === 'offline' ? 'warn' : 'idle', `${marketState.signals?.length || 0} signals`], ['campaign-sim', '策略推演', initiatives.length ? 'ok' : 'idle', `${initiatives.length} objects`], ['brief-engine', 'Brief 编排', initiatives.some(item => item.masterBrief) ? 'ok' : 'idle', `${initiatives.filter(item => item.masterBrief).length} briefs`], ['publishing', '发布监控', posts.length ? 'ok' : 'idle', `${posts.length} assets`], ['risk-mon', '风险监听', riskCount ? 'warn' : 'ok', `${riskCount} open`], ['learning', '学习闭环', posts.length ? 'idle' : 'idle', posts.length ? 'ready for review' : 'waiting data'], ]; return (

Agent 集群 · 健康度

{modules.length} modules
{modules.map(([id, name, s, note]) => (
{name}
{note}
))}
); }; const BrandPulseCard = ({ posts = [] }) => { const dailyRows = React.useMemo(() => { const map = new Map(); posts.forEach(post => { const time = dashboardLatestPostTime(post); if (!time) return; const key = new Date(time).toISOString().slice(5, 10); const row = map.get(key) || { label: key, views: 0, interaction: 0 }; row.views += dashboardPostViews(post); row.interaction += dashboardPostInteraction(post); map.set(key, row); }); return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label)).slice(-14); }, [posts]); const maxViews = Math.max(...dailyRows.map(row => row.views), 1); const maxInteraction = Math.max(...dailyRows.map(row => row.interaction), 1); const toPoints = (rows, key, max, minY = 22, maxY = 126) => rows.map((row, i) => { const x = rows.length <= 1 ? 0 : (i / (rows.length - 1)) * 400; const y = maxY - (row[key] / max) * (maxY - minY); return `${x.toFixed(1)},${y.toFixed(1)}`; }).join(' '); const viewPoints = toPoints(dailyRows, 'views', maxViews); const interactionPoints = toPoints(dailyRows, 'interaction', maxInteraction, 56, 126); return (

品牌脉搏 · 近 14 天

{dailyRows.length ? `${dailyRows.length} 天` : '待同步'}
{[20,40,60,80,100,120].map(y => )} {dailyRows.length > 1 && } {dailyRows.length > 1 && } {dailyRows.length > 1 && } {!dailyRows.length && 暂无同步内容表现数据}
{dailyRows.length ? dailyRows.filter((_, i) => i === 0 || i === dailyRows.length - 1 || i % Math.ceil(dailyRows.length / 4) === 0).map(row => {row.label}) : 等待 social-sync-state}
); }; const Legend = ({ color, label, dashed }) => (
{label}
); // ========== 01 · BRAND MEMORY ========== const MEMORY_DOCS = [ { name: '领越品牌核心讯息-0420-V1.pdf', size: '504 KB', status: 'parsed', facts: 42, time: '04-20' }, { name: '广汽领程新品牌品牌策略方案-0418_final.pptx', size: '32 MB', status: 'parsed', facts: 58, time: '04-18' }, { name: 'MONTX_GUIDELINES_V1.1.pptx', size: '14 MB', status: 'parsed', facts: 31, time: '04-2026' }, { name: '全域跨界新物种降临—MONTX发布两款概念车.docx', size: '28 KB', status: 'parsed', facts: 76, time: '04-23' }, { name: '2026 北京车展・杜培源讲话稿.docx', size: '28 KB', status: 'parsed', facts: 64, time: '04-20' }, { name: '牛马野逍遥-0415.pptx', size: '31 MB', status: 'parsed', facts: 49, time: '04-15' }, { name: 'TVC 广汽新物种创意传播内容0414.pptx', size: '208 MB', status: 'parsed', facts: 18, time: '04-14' }, { name: 'MONTX 社媒平台账号主页品牌头像.png/jpg', size: '1080px', status: 'parsed', facts: 3, time: 'asset' }, ]; const MONTX_MEMORY_SOURCE = { brand: [ { label: '品牌名', value: 'MONTX / 领越', meta: '广汽领程新品牌' }, { label: '品牌定位', value: '全域跨界新物种', meta: 'New Breed. No Boundaries.' }, { label: '品牌口号', value: '领现在,越未来', meta: 'New Breed. No Boundaries.' }, { label: '品牌愿景', value: '共创更自由美好的事业与生活', meta: '事业与生活双场景' }, { label: '品牌使命', value: '为用户打造一车多能的全域场景新体验', meta: '一车多能 / 自由切换' }, { label: '目标用户', value: '注重跨界多用途的“尝鲜型”用户', meta: '商业装载 + 生活方式 + 探索需求' }, ], values: [ { title: '全域智能', text: '智能科技与新能源驱动模式加持,覆盖城市通勤、越野脱困、重载运输、智能座舱等复合场景。' }, { title: '全域适应', text: '越级性能和跨界功能带来全域地形、商业运载、休旅生活与移动办公的适应能力。' }, { title: '全域美学', text: '以功能为本,探索跨界先锋设计理念,让实用属性与先锋美感自然共生。' }, ], products: [ { name: 'MONTX P10', tag: '概念车 · 全域移动地空母舰', text: '未来先锋皮卡,从单一装载工具升维为支持立体出行的未来装备。核心叙事包含地空双栖、四轮分布式驱动电机、智能底盘、车机协同、机能美学与战术副手。', source: '讲话稿 / 产品稿' }, { name: 'MONTX V10', tag: '概念车 · 全域移动整备舱', text: '面向数字游民、移动办公与长周期旅居的概念 VAN。核心叙事包含智能双舱、模块化座舱、工作/社交/睡眠切换、四开式尾门、自循环基地与隐形管家。', source: '讲话稿 / 产品稿' }, ], visual: [ { label: 'Logo 含义', value: 'Brandmark 融合字母 M 与山峰/车辙意象,表达 rugged capability、adventure、freedom to explore。' }, { label: 'Brandmark 安全区', value: '四周保留 50% X,确保图形清晰突出。' }, { label: 'Vertical Lockup', value: 'Wordmark 高度为 brandmark 的 33%,两者间距为 brandmark 的 66%,整体数学居中。' }, { label: 'Horizontal Bilingual', value: '仅用于中文市场;wordmark 高度为 brandmark 的 75%,横向中英组合安全区为 150% X。' }, { label: '产品 Logo 工艺', value: 'Brushed metal / chrome / internal illuminated,用于车标、尾标和产品背部发光标识。' }, { label: '社媒头像', value: 'MONTX 社媒平台账号主页品牌头像 PNG/JPG,1080 x 1080。' }, ], campaigns: [ { title: '品牌视频 / TVC', text: '从用户“不想妥协”的洞察切入,讲品牌由来与实力,再呈现多场景体验,最终回到定位与口号。' }, { title: 'Hyper 预热视频', text: '关键词:Hyper Performance / Hyper Intelligence / Hyper Design / Hyper Capability / Hyper Safety。' }, { title: '牛马野逍遥共创计划', text: '真开源 + 真共创 + 真定制;用户从 0 到 1 主导整车设计,KOL 从代言升级为联合发起人。' }, { title: '共创传播矩阵', text: '头部 KOL FREEROAD 自由公路主导共创流程,腰部数字游民/旅行/科技达人扩散,官方账号承接直播与切片。' }, ], guardrails: [ { level: 'P0', rule: 'P10/V10 当前均为概念车叙事,载人飞行器、线控滑移方向盘、时间感知自动进化等不得写成量产承诺。' }, { level: 'P0', rule: '涉及自动识别、自动接管、主动调整时,禁止暗示完全自动驾驶、无人驾驶、替代驾驶员或绝对安全。' }, { level: 'P1', rule: '“牛马”只允许在“牛马野逍遥”共创计划语境内出现,不进入品牌常规口吻,不替用户自嘲。' }, { level: 'P1', rule: '“财富自由”只作为历史讲话素材引用,常规生成中避免金融收益或商业成功的夸大承诺。' }, { level: 'P1', rule: '品牌语气可以开拓、全域、机能、先锋、共创,但不能轻浮玩梗、竞品碾压或过度硬广。' }, ], }; const BRAND_MEMORY_TABS = [ { id: 'brand', label: '品牌主档' }, { id: 'product', label: '产品 Profile' }, { id: 'visual', label: '视觉规范' }, { id: 'campaign', label: '策略对象记忆' }, { id: 'operation', label: '运营阶段' }, { id: 'social', label: '社媒内容' }, { id: 'preference', label: '偏好决策' }, { id: 'evidence', label: '证据写回' }, { id: 'guardrail', label: '护栏规则' }, ]; const memoryNumber = (value) => Number(value || 0); const formatMemoryNumber = (value) => { const n = memoryNumber(value); if (n >= 100000000) return `${(n / 100000000).toFixed(1)}亿`; if (n >= 10000) return `${(n / 10000).toFixed(1)}万`; return String(Math.round(n)); }; const mergeMemoryRows = (baseRows = [], extraRows = [], idKey) => { const map = new Map(); [...baseRows, ...extraRows].forEach(row => { if (!row?.[idKey]) return; map.set(row[idKey], { ...(map.get(row[idKey]) || {}), ...row }); }); return Array.from(map.values()); }; const getMemoryPostUrl = (post = {}) => post.url || post.manual_metrics?.url || post.raw?.share_url || ''; const BrandMemory = ({ workspaceContext }) => { const brandStoreReady = Boolean(window.BrandMemoryStore?.useBrandMemoryProfiles); const brandToolsReady = Boolean(brandStoreReady && window.BrandProductMaintenancePanel && window.GuardrailMaintenancePanel); const brandState = brandStoreReady ? window.BrandMemoryStore.useBrandMemoryProfiles() : { brands: [], setBrands: () => {}, ready: false }; const [syncedAssets, setSyncedAssets] = React.useState({ accounts: [], posts: [], comments: [], lastSync: '', status: 'idle', errors: [] }); const [socialWriteStatus, setSocialWriteStatus] = React.useState(''); const [memoryTab, setMemoryTab] = React.useState('brand'); const brands = brandState.brands || []; const brand = brandStoreReady ? window.BrandMemoryStore.getBrandProfile(workspaceContext?.brandId, brands) : null; const BrandProductMaintenance = brandToolsReady ? window.BrandProductMaintenancePanel : null; const GuardrailMaintenance = brandToolsReady ? window.GuardrailMaintenancePanel : null; const updateBrand = (brandId, nextBrand) => { if (!brandStoreReady) return; brandState.setBrands(prev => prev.map(item => item.id === brandId ? window.BrandMemoryStore.normalizeBrandProfiles([nextBrand])[0] : item)); }; const activeConstraints = brandStoreReady && brand ? window.BrandMemoryStore.getActiveConstraints(brand) : []; React.useEffect(() => { let cancelled = false; const load = async () => { try { const response = await fetch('/api/social/sync/status'); const data = await response.json(); if (!cancelled && response.ok) { setSyncedAssets({ accounts: data.accounts || [], posts: data.posts || [], comments: data.comments || [], lastSync: data.lastFinishedAt || '', status: data.status || 'idle', errors: data.errors || [], }); } } catch (error) { if (!cancelled) setSyncedAssets(prev => ({ ...prev, errors: [{ platform: 'local', message: error.message || '读取社媒同步状态失败' }] })); } }; load(); const timer = setInterval(load, 30000); return () => { cancelled = true; clearInterval(timer); }; }, []); const socialMemory = React.useMemo(() => { if (!brandStoreReady || !window.BrandMemoryStore?.buildSocialMemorySnapshot) return null; const accounts = mergeMemoryRows(window.SOCIAL_ACCOUNTS || [], syncedAssets.accounts || [], 'account_id'); const posts = mergeMemoryRows(window.SOCIAL_POSTS || [], syncedAssets.posts || [], 'post_id'); const comments = mergeMemoryRows(window.SOCIAL_COMMENTS || [], syncedAssets.comments || [], 'comment_id'); return window.BrandMemoryStore.buildSocialMemorySnapshot({ brandId: brand?.id || workspaceContext?.brandId || BRAND.id, accounts, posts, comments, lastSync: syncedAssets.lastSync, }); }, [brandStoreReady, brand?.id, workspaceContext?.brandId, syncedAssets]); const writeSocialMemory = () => { if (!brandStoreReady || !brand || !socialMemory || !window.BrandMemoryStore?.applySocialMemorySnapshot) return; const nextBrand = window.BrandMemoryStore.applySocialMemorySnapshot(brand, socialMemory); updateBrand(brand.id, nextBrand); setSocialWriteStatus(`已写入 ${socialMemory.accounts.length} 个账号 / ${socialMemory.approvedPosts.length} 条客户认可内容`); }; return (

品牌记忆 · 长期认知构建中

上传的每一份资料都会被解析为事实、偏好与决策,写入可被策略对象调用的品牌图谱。

Workspace · {brand?.name || BRAND.name}
brand && updateBrand(brand.id, nextBrand)} /> {brandToolsReady && brandState.ready && brand && (

品牌治理 · Brand Governance

{brand.name} · {activeConstraints.length} active rules · {(brand.products || []).length} products
source: Brand Memory
product.name).join(' / ') || '未配置'}/>
updateBrand(brand.id, nextBrand)} /> updateBrand(brand.id, nextBrand)} />
)}

已入库资料

{MEMORY_DOCS.length}
{MEMORY_DOCS.map((d, i) => (
{d.name}
{d.size} · {d.time}
{d.status === 'parsed' && {d.facts} facts} {d.status === 'parsing' && 解析中} {d.status === 'queued' && queued}
))}

品牌图谱 · Brand Memory Graph

1,284 节点 · 3,820 关系
layout: radial
); }; const getBrandLogoAssets = visualSystem => (visualSystem?.logoAssets || []).filter(asset => asset?.src); const MEMORY_FACT_STATUS_LABELS = { confirmed: { label: '原文确定', tone: 'confirmed' }, documented: { label: '原文确定', tone: 'confirmed' }, 'documented-draft': { label: '初稿确定', tone: 'draft' }, 'superseded-draft': { label: '历史初稿', tone: 'draft' }, inferred: { label: '推测提取', tone: 'inferred' }, pending: { label: '待确认', tone: 'pending' }, 'pending-confirmation': { label: '待确认', tone: 'pending' }, 'conflict-adjusted': { label: '冲突修订', tone: 'conflict' }, resolved: { label: '冲突已处理', tone: 'confirmed' }, archived: { label: '已归档', tone: 'draft' }, rejected: { label: '已拒绝', tone: 'draft' }, }; const memoryList = value => Array.isArray(value) ? value.filter(Boolean) : (value ? [value] : []); const memoryUniq = items => Array.from(new Set((items || []).filter(Boolean))); const inferMemoryFactStatus = (item = {}, fallback = {}) => { const raw = item.factStatus || item.traceStatus || fallback.factStatus || fallback.traceStatus || item.status || fallback.status; if (raw === 'active') return 'confirmed'; if (raw === 'confirmed') return 'confirmed'; if (raw === 'pending') return 'pending-confirmation'; if (raw === 'superseded-draft') return 'superseded-draft'; if (raw) return raw; const provenance = item.provenance || fallback.provenance || ''; if (String(provenance).includes('conflict')) return 'conflict-adjusted'; if (String(provenance).includes('inferred')) return 'inferred'; return 'confirmed'; }; const getMemoryFactStatus = status => MEMORY_FACT_STATUS_LABELS[status] || { label: status || '未标注', tone: 'neutral' }; const getMemoryTrace = (item = {}, fallback = {}) => ({ factStatus: inferMemoryFactStatus(item, fallback), provenance: item.provenance || fallback.provenance || 'documented', source: item.source || item.sourceEvidenceId || fallback.source || fallback.sourceEvidenceId || 'brand-memory', sourceRefs: memoryUniq([ ...memoryList(fallback.sourceRefs), ...memoryList(item.sourceRefs), ]), conflicts: memoryUniq([ ...memoryList(fallback.conflicts), ...memoryList(item.conflicts), ...memoryList(item.conflictId), ]), }); const MemoryFactChip = ({ status }) => { const meta = getMemoryFactStatus(status); const colorMap = { confirmed: { color: 'var(--accent)' }, draft: { color: 'var(--ink-3)' }, inferred: { color: '#7c5a00' }, pending: { color: '#8a4b00' }, conflict: { color: '#580018' }, }; return {meta.label}; }; const MemoryTraceMeta = ({ item = {}, fallback = {}, compact = false }) => { const trace = getMemoryTrace(item, fallback); return (
{trace.provenance && {trace.provenance}} {trace.source && source: {trace.source}} {trace.sourceRefs.slice(0, compact ? 2 : 6).map(ref => ref: {ref})} {trace.conflicts.map(ref => conflict: {ref})}
); }; const OperatingStageMemoryPanel = ({ brand = {} }) => { const stages = brand.projectOperatingStages || []; const confirmedCount = stages.filter(item => item.factStatus === 'confirmed').length; const inferredCount = stages.filter(item => item.factStatus === 'inferred').length; const adjustedCount = stages.filter(item => item.factStatus === 'conflict-adjusted' || (item.operatingPreferences || []).some(pref => pref.factStatus === 'conflict-adjusted')).length; const pendingCount = stages.filter(item => item.factStatus === 'pending-confirmation' || (item.operatingPreferences || []).some(pref => pref.factStatus === 'pending-confirmation')).length; const renderTagList = (items = [], prefix = '') => (
{items.map(item => {prefix}{item})}
); return (
0}/>
统一溯源规则

品牌记忆每个模块都使用同一套标识:原文确定可直接进入报表;推测提取只能作为待校验摘要;待确认进入写回队列;冲突修订会保留原始来源与冲突 ID,方便回溯。

{stages.map(stage => (
{stage.period || '阶段周期待确认'}
{stage.name}
阶段目标 {renderTagList(stage.stageGoals || [], '')}
阶段任务 {renderTagList(stage.stageObjectives || [], '')}
策略边界 {renderTagList(stage.brandStrategyBoundaries || [], '')}
运营偏好 {(stage.operatingPreferences || []).map(pref => (

{pref.label}

))}
平台 / 下游依赖 {stage.platformPlan?.overseas?.length ? renderTagList(stage.platformPlan.overseas, '海外: ') : null} {stage.platformPlan?.domestic?.length ? renderTagList(stage.platformPlan.domestic, '国内: ') : null} {stage.downstream?.length ? renderTagList(stage.downstream, 'downstream: ') : null} {stage.conflicts?.length ? renderTagList(stage.conflicts, 'conflict: ') : null}
{stage.sourceRefs?.length ? (
{stage.sourceRefs.map(ref => source: {ref})}
) : null}
))}
); }; const PreferenceDecisionMemoryPanel = ({ brand = {} }) => { const preferences = brand.preferences || []; const decisions = brand.decisions || []; const preferCount = preferences.filter(item => item.polarity === 'prefer').length; const avoidCount = preferences.filter(item => item.polarity === 'avoid').length; return (
item.status === 'confirmed').length} confirmed`}/> item.stability === 'long-term').length)} delta="不随短期数据变化"/>
客户偏好池
{preferences.map(item => (
{item.polarity === 'avoid' ? 'Avoid' : 'Prefer'} {item.stability || 'medium'}
{item.label}

{item.description}

))}
历史决策摘要
{decisions.map(item => (
{item.status || 'pending'} {item.type || 'decision'}
{item.title}

{item.rationale || item.text}

{(item.impact || []).map(scope => {scope})}
))}
); }; const EvidenceWritebackMemoryPanel = ({ brand = {}, onUpdateBrand }) => { const evidenceSources = brand.evidenceSources || []; const learnings = brand.learnings || []; const conflicts = brand.memoryConflicts || []; const pendingConflicts = conflicts.filter(item => item.status === 'pending'); const pendingLearnings = learnings.filter(item => item.status === 'pending'); const conflictActionText = { accept_current: '以当前事实为准', keep_as_draft: '仅保留初稿', needs_more_review: '稍后处理', }; const resolveConflict = (conflictId, resolution) => { if (!onUpdateBrand || !window.BrandMemoryStore?.applyMemoryConflictResolution) return; onUpdateBrand(window.BrandMemoryStore.applyMemoryConflictResolution(brand, conflictId, resolution)); }; const updateLearningStatus = (learningId, status) => { if (!onUpdateBrand) return; const stampedAt = new Date().toLocaleString('zh-CN', { hour12: false }); const nextLearnings = learnings.map(item => item.id === learningId ? { ...item, status, [status === 'confirmed' ? 'confirmedAt' : 'rejectedAt']: stampedAt, } : item); const nextEvidenceSources = evidenceSources.map(source => ( source.id === 'source-official-social' && status === 'confirmed' ? { ...source, status: 'confirmed', updatedAt: stampedAt } : source )); onUpdateBrand({ ...brand, learnings: nextLearnings, evidenceSources: nextEvidenceSources, updatedAt: stampedAt }); }; return (
item.status === 'confirmed').length} confirmed`}/> 0}/> item.status === 'confirmed').length)} delta="confirmed learning"/>
冲突通知
{pendingConflicts.length ? pendingConflicts.map(conflict => (
Conflict {conflict.severity || 'needs-confirmation'}
{conflict.title}

{conflict.message}

初稿信息

{conflict.draftClaim}

当前事实

{conflict.currentFact}

{(conflict.downstream || []).map(item => {item})}
{(conflict.options || ['accept_current', 'keep_as_draft', 'needs_more_review']).map(option => ( ))}
)) : (
Conflict 暂无冲突待确认

当新资料与当前执行事实、官方账号数据或已确认记忆冲突时,会先进入这里等待用户明确。

)}
待确认写回
{pendingLearnings.length ? pendingLearnings.map(item => (
{item.type || 'learning'} {item.status}
{item.title}

{item.text}

{item.evidencePostIds?.length ? evidence posts: {item.evidencePostIds.length} : null}
)) : (
Queue 暂无待确认写回

线下导入、社媒内容归档、复盘学习都会先进入这里,确认后再成为稳定记忆。

)}
来源证据链
{evidenceSources.map(source => (
{source.type} {source.status} {source.stability}
{source.title}

{(source.facts || []).join(';')}

{source.confidence || 'medium'} confidence
))}
); }; const GuardrailRulesMemoryPanel = ({ rules = [] }) => { const requiredCount = rules.filter(rule => (rule.severity || rule.level) === 'required' || rule.level === 'P0').length; const categories = Array.from(new Set(rules.map(rule => rule.category).filter(Boolean))); return (
{rules.map(item => { const isRequired = (item.severity || item.level) === 'required' || item.level === 'P0'; const scopes = item.scope || []; const terms = item.forbiddenTerms || []; const levelLabel = item.level || (isRequired ? 'P0' : 'P1'); return (
{levelLabel} {item.label || `${item.level} · 品牌护栏`}

{item.description || item.rule}

{(scopes.length || terms.length) ? (
{scopes.length ? (
{scopes.map(scope => {scope})}
) : null} {terms.length ? (
{terms.slice(0, 8).map(term => 禁:{term})}
) : null}
) : null}
{item.category || 'guardrail'}
); })}
); }; const BrandLogoMemoryPreview = ({ visualSystem = {} }) => { const logoAssets = getBrandLogoAssets(visualSystem); if (!logoAssets.length) return null; const brandmark = logoAssets.find(asset => asset.type === 'brandmark') || logoAssets[0]; const wordmark = logoAssets.find(asset => asset.id === 'wordmark-ink') || logoAssets.find(asset => asset.type === 'wordmark') || logoAssets[0]; const previewItems = [ { ...brandmark, label: 'Primary Brandmark', boxStyle: { maxWidth: 118, maxHeight: 118 } }, { ...wordmark, label: 'Primary Wordmark', boxStyle: { maxWidth: 360, maxHeight: 82 } }, ]; return (
{previewItems.map(asset => (
{asset.name
{asset.label} {asset.name}

{asset.usage}

))}
); }; const StructuredBrandMemoryPanel = ({ activeTab, onTab, brand, activeConstraints = [], socialMemory, socialWriteStatus, onWriteSocialMemory, onUpdateBrand }) => { const source = MONTX_MEMORY_SOURCE; const products = brand?.id === 'montx' && brand.products?.length ? brand.products : []; const pendingConflicts = (brand?.memoryConflicts || []).filter(item => item.status === 'pending'); return (

结构化品牌记忆 · Structured Memory

MONTX / 领越 · 8 source files · {activeConstraints.length || source.guardrails.length} active rules
source: /Downloads/广汽品牌资产
{pendingConflicts.length > 0 && (
发现 {pendingConflicts.length} 个品牌记忆冲突,需要人工确认

新资料不会直接覆盖当前事实;请在证据写回里选择以当前事实为准、仅保留初稿或稍后处理。

)}
全局来源标识

本页所有品牌记忆条目都必须带确定性与来源。推测、待确认、冲突修订不会静默覆盖原始文档,会保留来源和冲突 ID 供后续回溯。

{BRAND_MEMORY_TABS.map(tab => ( ))}
{activeTab === 'brand' && (
{source.brand.map(item => (
{item.label} {item.value}

{item.meta}

))}
{source.values.map(item => (
{item.title} {item.text}
))}
)} {activeTab === 'product' && (
{(products.length ? products : source.products).map(product => (
{product.name} {product.narrative?.positioning || product.text || product.positioning}
{(product.narrative?.coreCapabilities || [product.tag, product.source]).filter(Boolean).slice(0, 7).map(item => {item})}
))}
)} {activeTab === 'visual' && ( )} {activeTab === 'campaign' && (
{source.campaigns.map(item => (
Campaign {item.title}

{item.text}

))}
)} {activeTab === 'operation' && ( )} {activeTab === 'social' && ( )} {activeTab === 'preference' && ( )} {activeTab === 'evidence' && ( )} {activeTab === 'guardrail' && ( )}
); }; const VisualSystemMemoryPanel = ({ visualSystem = {}, sourceVisual = [] }) => { const logoAssets = getBrandLogoAssets(visualSystem); const colorTokens = visualSystem.colorTokens || []; const semanticColors = visualSystem.semanticColors || {}; const viGuidelines = visualSystem.viGuidelines || []; const logoLockups = visualSystem.logoLockups || []; const typography = visualSystem.typography || []; const applicationRules = visualSystem.applicationRules || []; const groupedRules = [ { label: 'Color Rules', items: visualSystem.colorRules || [] }, { label: 'Logo Rules', items: visualSystem.logoRules || [] }, { label: 'Layout Rules', items: visualSystem.layoutRules || [] }, { label: 'Image Rules', items: visualSystem.imageRules || [] }, ].filter(group => group.items.length); return (
{viGuidelines.length > 0 && (
VI Source · {viGuidelines[0].name} {viGuidelines[0].file} · {viGuidelines[0].pages} pages · {viGuidelines[0].source}
)} {logoAssets.length > 0 && (
{logoAssets.map(asset => (
{asset.name}
{asset.type || 'logo'} {asset.name}

{asset.usage}

))}
)} {colorTokens.length > 0 && (
{colorTokens.map(token => { const swatch = token.alpha ? `rgba(${token.rgb}, ${token.alpha})` : token.hex; const darkText = ['#E1E0D8', '#FFFFFF'].includes(String(token.hex || '').toUpperCase()) || token.alpha; return (
{token.hex}{token.alpha ? ` / ${Math.round(token.alpha * 100)}%` : ''}
{token.role} {token.name}

{token.usage}

{token.rgb && RGB {token.rgb}} {token.cmyk && {token.cmyk}} {token.pms && PMS {token.pms}}
); })}
)} {Object.keys(semanticColors).length > 0 && (
Semantic Color Tokens 供长图文、内容生成、视觉检查共用的语义色值。
{Object.entries(semanticColors).map(([key, value]) => {key}: {value})}
)} {logoLockups.length > 0 && (
{logoLockups.map(lockup => (
Logo Lockup {lockup.name}

{lockup.usage}

{[lockup.construction, lockup.isolation].filter(Boolean).join(';')}

))}
)} {typography.length > 0 && (
{typography.map(type => (
{type.role} {type.name}

{type.usage}

))}
)} {groupedRules.length > 0 && (
{groupedRules.map(group => (
{group.label} {group.items.length} 条结构化规则

{group.items.join(';')}

))}
)} {applicationRules.length > 0 && (
{applicationRules.map(rule => (
VI Application {rule.name}

{rule.usage}

))}
)}
{sourceVisual.map(item => (
Source Visual {item.label}

{item.value}

))}
); }; const SocialContentMemoryPanel = ({ socialMemory, savedSocialAssets, writeStatus, onWrite }) => { const accounts = socialMemory?.accounts || []; const posts = socialMemory?.approvedPosts || []; const comments = socialMemory?.commentsSummary || {}; const savedAt = savedSocialAssets?.updatedAt || ''; const accountPanelStyle = {display:'grid', gap:8, alignContent:'start'}; const socialCardStyle = {background:'var(--bg-2)', border:'1px solid var(--hairline)', borderRadius:8, padding:10, minWidth:0}; return (
account.platform).filter(Boolean).slice(0, 4).join(' / ') || '待同步'}/>
客户认可内容 · Approved Social Evidence
官方账号已发布内容作为品牌表达证据;写入后进入 pending learning,后续再人工提升为规则或案例。
{writeStatus && {writeStatus}}
官方账号
{accounts.length ? accounts.map(account => (
{account.platform} · {account.account_name || account.handle} {account.positioning || account.owner || account.handle || '官方账号资产'}
{account.status || 'active'} {account.follower_delta_period ? +{formatMemoryNumber(account.follower_delta_period)} followers : null}
)) : (
暂无账号同步社媒监测同步后,这里会显示官方账号资产。
)}
认可内容证据池
{posts.length} posts
{posts.length ? posts.map(post => (
{post.cover_url ? ( ) : ( )}
{post.title} {post.platform} · {post.content_type || '内容'} · {post.campaign || '未标记策略对象'}
阅读/播放 {formatMemoryNumber(post.metrics?.views || 0)} 赞 {formatMemoryNumber(post.metrics?.likes || 0)} 评 {formatMemoryNumber(post.metrics?.comments || 0)} 转 {formatMemoryNumber(post.metrics?.shares || 0)}
{post.url && 打开}
)) : (
暂无已发布内容 同步官方账号内容后,会按互动与发布时间沉淀为客户认可样本。
)}
); }; const UploadZone = () => (
拖入文档 · 会议纪要 · 图片
PDF · DOCX · MD · TXT · PNG
); const BrandGraph = () => { // Radial network viz — hand-composed positions const cx = 320, cy = 220; const center = { x: cx, y: cy, label: 'MONTX\n领越', type: 'brand', r: 32 }; const hubs = [ { x: cx + 140, y: cy - 120, label: '品牌定位', cat: 'identity', r: 20 }, { x: cx - 160, y: cy - 90, label: '产品矩阵', cat: 'product', r: 20 }, { x: cx - 200, y: cy + 60, label: '语言边界', cat: 'boundary', r: 20 }, { x: cx - 80, y: cy + 150, label: '历史决策', cat: 'decision', r: 20 }, { x: cx + 100, y: cy + 160, label: '卖点池', cat: 'selling', r: 20 }, { x: cx + 220, y: cy + 30, label: '风险词', cat: 'risk', r: 20 }, { x: cx + 60, y: cy - 180, label: '客户偏好', cat: 'preference', r: 20 }, ]; const leaves = [ { hub: 0, offX: 90, offY: -30, label: '全域跨界' }, { hub: 0, offX: 110, offY: 20, label: '新物种' }, { hub: 1, offX: -90, offY: 10, label: 'P10 概念车' }, { hub: 1, offX: -80, offY: -40, label: 'V10 概念车' }, { hub: 1, offX: -100, offY: 55, label: 'GAIA 平台' }, { hub: 2, offX: -80, offY: 20, label: '禁完全自动驾驶' }, { hub: 2, offX: -60, offY: 60, label: '禁量产承诺' }, { hub: 3, offX: -40, offY: 60, label: '用户共创' }, { hub: 3, offX: 60, offY: 40, label: '场景定义品牌' }, { hub: 4, offX: 40, offY: 70, label: '全域智能' }, { hub: 4, offX: 100, offY: 30, label: '全域适应' }, { hub: 4, offX: 80, offY: -20, label: '全域美学' }, { hub: 5, offX: 70, offY: 20, label: '牛马语境' }, { hub: 5, offX: 60, offY: -30, label: '财富自由' }, { hub: 6, offX: 50, offY: -40, label: '一车多能' }, { hub: 6, offX: 20, offY: -60, label: '自由切换' }, ]; const catColor = { identity: 'oklch(0.45 0.12 155)', product: 'oklch(0.5 0.08 245)', boundary: 'oklch(0.55 0.1 25)', decision: 'oklch(0.5 0.08 280)', selling: 'oklch(0.55 0.12 60)', risk: 'oklch(0.55 0.18 25)', preference: 'oklch(0.5 0.1 200)', }; return ( {/* grid dots */} {Array.from({length: 20}).map((_, r) => Array.from({length: 32}).map((_, c) => ( )) )} {/* hub lines */} {hubs.map((h, i) => ( ))} {/* leaf lines */} {leaves.map((l, i) => { const h = hubs[l.hub]; return ; })} {/* hubs */} {hubs.map((h, i) => ( {h.label} ))} {/* leaves */} {leaves.map((l, i) => { const h = hubs[l.hub]; return ( {l.label} ); })} {/* center */} MONTX BRAND CORE {/* legend */} {Object.entries(catColor).slice(0, 7).map(([k, c], i) => ( {({identity:'身份',product:'产品',boundary:'边界',decision:'决策',selling:'卖点',risk:'风险',preference:'偏好'})[k]} ))} ); }; const getMemoryFacetTrace = title => { if (title.includes('历史')) return { factStatus: 'confirmed', provenance: 'documented', source: 'gac-brand-assets-2026-04 / source-official-social' }; if (title.includes('决策')) return { factStatus: 'confirmed', provenance: 'manual-confirmed', source: '品牌决策记忆' }; if (title.includes('风险') || title.includes('语言')) return { factStatus: 'confirmed', provenance: 'documented', source: 'brand-policy / source-news-product-speech' }; return { factStatus: 'confirmed', provenance: 'documented', source: '领越品牌核心讯息-0420-V1.pdf' }; }; const MemoryFacet = ({ title, items, accent }) => (

{title}

{items.map((it, i) => (
· {it}
))}
); // ========== 02 · MARKET SENSING ========== const MARKET_TIME_WINDOWS = [ { id: '24h', label: '近 24h', days: 1 }, { id: '7d', label: '近 7d', days: 7 }, { id: '30d', label: '近 30d', days: 30 }, ]; const MARKET_SIGNAL_TYPES = [ { id: 'all', label: '全部信号' }, { id: 'opportunity', label: '机会' }, { id: 'risk', label: '风险' }, { id: 'trend', label: '趋势' }, { id: 'competitor', label: '竞品' }, { id: 'comparison', label: '对比' }, ]; const MARKET_VIEW_TABS = [ { id: 'overview', label: '总览 Overview' }, { id: 'evidence', label: '原文证据 Evidence' }, { id: 'learning', label: '学习地图 Learning' }, { id: 'memory', label: '记忆摘要 Memory' }, ]; const MARKET_CONTENT_FOCUS_FILTERS = [ { id: 'all', label: '全部内容' }, { id: 'product_model', label: '车型 / 产品' }, { id: 'brand', label: '品牌' }, { id: 'mixed', label: '混合' }, ]; const MARKET_PLATFORMS = ['全部平台', '小红书', '抖音', 'B站', 'Instagram', 'YouTube', '公众号', '竞品', '品类']; const marketApiUrl = (pathname) => (window.location.protocol === 'file:' ? `http://localhost:4173${pathname}` : pathname); const SENSE_SIGNALS = [ { id: 'sig-001', time: '14:22', freshness: 1, plat: '品类', source: '搜索聚合', type: 'trend', title: '「数字游民 车」跨平台搜索量 +36%', detail: '知乎、B站和小红书均出现增长,关键词与移动办公、睡眠舱、长周期旅居绑定。', tag: 'opportunity', product: 'V10 对应场景', score: 85, volume: 312, confidence: 0.82, sentiment: 0.36, owner: '市场感知', action: '记录为品类需求变化,供下游按任务查询数字游民与移动办公场景。', evidence: ['知乎提问 +29%', 'B站弹幕共现 64 次', '小红书收藏率 6.8%'], themes: ['数字游民', '移动办公', '旅居'] }, { id: 'sig-002', time: '14:08', freshness: 1, plat: '抖音', source: '竞品达人视频', type: 'competitor', title: '竞品皮卡把「可玩改装」作为主命题放量', detail: '3 位腰部汽车达人连续发布同类话题,评论区集中追问模块化货箱与外放电能力。', tag: 'risk', product: 'P10 对应场景', score: 88, volume: 183, confidence: 0.79, sentiment: 0.18, owner: '市场感知', action: '记录为竞品内容范式与评论关注点,不直接生成应对动作。', evidence: ['互动率 4.1%', '改装评论占比 31%', '外放电提问 47 条'], themes: ['共创改装', '外放电', '货箱'] }, { id: 'sig-003', time: '13:42', freshness: 1, plat: '行业媒体', source: '品类文章', type: 'trend', title: '“商用 + 生活方式”混合车主画像被行业媒体放大', detail: '多篇行业文章讨论一车多能、长途穿越、露营装备和移动办公的组合需求。', tag: 'opportunity', product: 'P10 对应场景', score: 81, volume: 44, confidence: 0.76, sentiment: 0.31, owner: '市场感知', action: '记录为品类叙事变化,沉淀到市场经验库供策略查询。', evidence: ['行业文章 7 篇', '一车多能共现 39 次', '长文转发率高'], themes: ['事业生活', '一车多能', 'PR'] }, { id: 'sig-004', time: '13:15', freshness: 1, plat: '竞品', source: '价格与配置', type: 'competitor', title: '同级新能源皮卡开始用价格锚点抢认知', detail: '经销商短视频把“低门槛越野生活”作为第一卖点,弱化高端机能与共创叙事。', tag: 'risk', product: 'P10 对应场景', score: 79, volume: 127, confidence: 0.68, sentiment: 0.05, owner: '市场感知', action: '记录为竞品价格话术样本,供下游比较定位时按需读取。', evidence: ['低价话术 34 条', '配置对比 12 条', '评论关注预算'], themes: ['价格', '竞品', '差异化'] }, { id: 'sig-005', time: '12:58', freshness: 1, plat: 'B站', source: '品类评论异常', type: 'risk', title: '“飞行汽车 / 地空车”量产误读在品类讨论中聚集', detail: '外部讨论把概念能力、短期量产和真实交付混在一起,容易影响概念车传播边界。', tag: 'risk', product: 'P10 风险边界', score: 93, volume: 61, confidence: 0.9, sentiment: -0.08, owner: '市场感知', action: '记录为概念边界风险样本,供审核或内容模块查询护栏依据。', evidence: ['疑问占比 18%', '负向情绪 -0.08', 'P0 护栏命中'], themes: ['概念边界', 'P10', '风险'] }, { id: 'sig-006', time: '12:41', freshness: 1, plat: '竞品', source: '对比归因', type: 'comparison', title: '外部讨论显示“系统能力”比“低价改装”更能承接高意向人群', detail: '同样讨论皮卡生活方式时,高意向评论更关注能力边界、场景完整性和可靠证据。', tag: 'info', product: 'P10 对应场景', score: 82, volume: 156, confidence: 0.74, sentiment: 0.22, owner: '市场感知', action: '记录为对比型市场洞察,供下游在策略推演时主动查询。', evidence: ['场景词占比 44%', '竞品价格词占比 38%', '高意向评论 +21%'], themes: ['对比信号', 'P10', '策略差异'] }, { id: 'sig-007', time: '11:40', freshness: 7, plat: '海外搜索', source: '长尾词', type: 'trend', title: '“concept van office”长尾词稳定爬升', detail: '英文长尾词与 remote work、modular cabin、sleep mode 同步增长。', tag: 'info', product: 'V10 对应场景', score: 67, volume: 58, confidence: 0.64, sentiment: 0.28, owner: '市场感知', action: '记录为海外长尾需求样本,等待更多证据后进入模式抽象。', evidence: ['长尾词 +14%', 'remote work 共现', '点击率 3.2%'], themes: ['V10', '海外搜索', '移动办公'] }, ]; const MARKET_SENSING_OBJECTS = [ { id: 'competitor', title: 'A. 竞品信号', count: 5, text: '竞品卖点、达人组合、评论区表扬/吐槽与市场教育动作。', examples: ['低价改装话术', '闪充 / 智驾叙事', '达人视频放量'] }, { id: 'category', title: 'B. 品类 / 市场信号', count: 7, text: '需求热点、使用场景、审美、价格敏感度和平台语法变化。', examples: ['数字游民车', '商用 + 生活方式', '移动办公'] }, { id: 'media', title: 'C. 媒体 / 达人 / 用户声量', count: 4, text: '外部媒体、达人、用户自然讨论,不把自有账号内容算入主信号。', examples: ['行业文章', '搜索聚合', 'UGC 讨论'] }, { id: 'risk', title: 'D. 风险信号', count: 3, text: '负面词聚集、舆情苗头、达人外部风险和平台规则变化。', examples: ['量产误读', '话题风险', '达人环境变化'] }, ]; const MARKET_OUTPUTS = [ { id: 'daily', title: '日市场摘要', cadence: '每日', status: 'ready', text: '24h 内高优先级信号已压缩为可查询摘要,下游模块按任务主动读取。' }, { id: 'weekly', title: '周市场摘要', cadence: '每周', status: 'draft', text: '近 7d 品类讨论变化保留为经验记录,不直接转成内容任务。' }, { id: 'competitor', title: '竞品异动报告', cadence: '实时', status: 'ready', text: '竞品话术、评论问题和传播节奏已沉淀为对标记忆。' }, { id: 'risk', title: '风险观察记录', cadence: '实时', status: 'urgent', text: 'P10 飞行器量产误读已记录为风险观察,供下游任务按需读取。' }, { id: 'category', title: '品类讨论趋势摘要', cadence: '每日', status: 'ready', text: '数字游民、移动办公、旅居场景保留为趋势查询条件。' }, { id: 'brand-anomaly', title: '概念边界异常记录', cadence: '实时', status: 'watch', text: '外部讨论中的概念车/量产车混淆被保留为风险证据。' }, { id: 'creator-risk', title: '达人环境变化记录', cadence: '每日', status: 'watch', text: '改装类达人内容放量被记录为平台内容环境变化。' }, ]; const marketTagText = { opportunity: '机会', risk: '风险', info: '提示' }; const marketTypeText = { brand: '品牌', trend: '趋势', risk: '风险', competitor: '竞品', comparison: '对比' }; const marketFocusText = { product_model: '车型/产品向', brand: '品牌向', mixed: '车型+品牌', unclear: '未明确' }; const marketStatusText = { ready: '已就绪', draft: '草稿', urgent: '紧急', watch: '观察中' }; const marketSourceText = { request: '外部请求数据', 'social-sync-state': '同步数据', 'fallback-fixtures': '示例数据', static: '静态示例', 'static-fallback': '静态兜底', api: '接口数据', }; const marketPriorityText = { core: '核心候选', benchmark: '标杆参照', 'narrative-reference': '叙事参照' }; const marketCadenceText = { live: '实时', daily: '每日', weekly: '每周' }; const marketLearningPriorityText = { P1: '类似阶段', P2: '同产品功能', P3: '同用户场景', P4: '内容切角', P5: '风险案例', }; const marketLearningRelationText = { path_case: '路径案例', method_case: '方法案例', product_competitor: '产品竞品', audience_case: '人群案例', risk_case: '风险案例', content_angle_case: '切角案例', }; const localizeMarketText = (text = '') => String(text || '') .replace(/Strategy Input/g, '策略输入') .replace(/Market Sensing/g, '市场感知') .replace(/Context Pack/g, '上下文包'); const localizeMarketEvidence = (text = '') => { const value = String(text || ''); if (value === 'competitor source detected from structured brand fields') return '已根据结构化品牌字段识别为竞品来源'; if (value === 'high engagement competitor content detected') return '检测到高互动竞品内容'; if (value === 'external same-industry source detected; competitor status not confirmed') return '检测到同业外部来源,但竞品身份尚未确认'; if (value === 'high engagement external industry content detected') return '检测到高互动同业外部内容'; if (value === 'competitor or comparison monitoring language detected') return '检测到竞品或对比监控语义'; if (value === 'launch / time-bound CTA language detected') return '检测到上市/节点型行动语义'; if (value === 'product superiority claim without explicit proof') return '产品优势表达缺少明确证据'; if (value === 'competitor product advantage claim captured as unverified market claim') return '竞品优势主张已记录为未验证市场话术'; if (value === 'recurring series cadence or column language detected') return '检测到固定栏目或周期内容语义'; if (value.startsWith('hashtags detected:')) return value.replace('hashtags detected:', '检测到话题标签:'); return value; }; const getMarketChipClass = tag => tag === 'opportunity' ? 'accent' : tag === 'risk' ? 'danger' : 'info'; const getMarketWindow = id => MARKET_TIME_WINDOWS.find(item => item.id === id) || MARKET_TIME_WINDOWS[0]; const marketSeverityScore = { critical: 98, high: 92, medium: 78, low: 64 }; const marketSignalTypeLabel = { brand_or_claim_risk: '品牌 / 主张风险', competitor_activity_spike: '竞品异动', external_industry_signal: '外部行业信号', recurring_education_pattern: '长期栏目机会', time_bound_campaign_candidate: '限时 Campaign 候选', }; const marketBrandLogoText = { byd: 'BYD', 'fang-cheng-bao': '豹', 'geely-radar-riddara': 'R', 'gwm-pao-tank': 'GWM', 'saic-maxus-ldv': 'MAX', 'deepal-g318': '深', 'chery-icar-jetour-jaecoo': 'iCAR', 'dongfeng-m-hero': '猛', rivian: 'RIV', 'tesla-cybertruck': 'T', 'kia-pbv-pv5': 'KIA', 'volkswagen-id-buzz': 'VW', 'zeekr-li-xpeng-nio': 'EV', }; const marketBrandAccent = { byd: 'red', 'fang-cheng-bao': 'bronze', 'geely-radar-riddara': 'green', 'gwm-pao-tank': 'bronze', 'saic-maxus-ldv': 'blue', 'deepal-g318': 'blue', 'chery-icar-jetour-jaecoo': 'green', 'dongfeng-m-hero': 'dark', rivian: 'green', 'tesla-cybertruck': 'dark', 'kia-pbv-pv5': 'blue', 'volkswagen-id-buzz': 'blue', 'zeekr-li-xpeng-nio': 'dark', }; const normalizeBrandId = value => String(value || '').toLowerCase().trim(); const getMarketBrandMeta = (signal = {}, competitors = []) => { const sourceBrandId = normalizeBrandId(signal.brand_id || signal.source_context?.source_brand_id); const candidate = competitors.find(item => { const ids = [item.brand_id, item.brand_name].map(normalizeBrandId); return ids.includes(sourceBrandId); }); const brandId = sourceBrandId || normalizeBrandId(candidate?.brand_id) || 'external'; return { brandId, brandName: candidate?.brand_name || (brandId === 'external' ? '外部来源' : brandId.toUpperCase()), logoText: candidate?.logo_text || marketBrandLogoText[brandId] || (brandId === 'external' ? '外' : brandId.slice(0, 3).toUpperCase()), accent: candidate?.logo_accent || marketBrandAccent[brandId] || 'neutral', priority: candidate?.priority || '', }; }; const inferMarketTag = signal => { if (signal.category === 'risk' || /risk|claim|guardrail/i.test(signal.signal_type || '')) return 'risk'; if (signal.category === 'opportunity' || /pattern|education/i.test(signal.signal_type || '')) return 'opportunity'; return 'info'; }; const inferMarketType = signal => { if (signal.category === 'competitor') return 'competitor'; if (signal.category === 'risk') return 'risk'; if (/comparison/i.test(signal.signal_type || '')) return 'comparison'; return signal.category === 'opportunity' ? 'trend' : (signal.category || 'brand'); }; const formatMarketSignalTime = value => { const date = value ? new Date(value) : null; if (!date || Number.isNaN(date.getTime())) return 'live'; return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }); }; const normalizeApiMarketSignal = (signal = {}, index = 0, competitors = []) => { const confidence = Number(signal.confidence || 0); const tag = inferMarketTag(signal); const type = inferMarketType(signal); const evidence = (signal.evidence || []).filter(Boolean).map(localizeMarketEvidence); const severityBase = marketSeverityScore[signal.severity] || 70; const score = Math.max(50, Math.min(99, Math.round(Math.max(confidence * 100, severityBase)))); const brand = getMarketBrandMeta(signal, competitors); const focus = signal.content_focus || {}; const focusType = focus.focus_type || 'unclear'; const models = signal.models?.length ? signal.models : (focus.model_names || []); return { id: signal.signal_id || `api-signal-${index}`, time: formatMarketSignalTime(signal.detected_at), freshness: 1, plat: signal.platform || (signal.category === 'competitor' ? '竞品' : signal.category === 'risk' ? '风险' : '市场'), source: marketSignalTypeLabel[signal.signal_type] || signal.signal_type || 'market sensing', brand, brandId: brand.brandId, brandName: brand.brandName, focusType, focusLabel: marketFocusText[focusType] || focus.focus_label || '未明确', models, sourceContentType: signal.source_content_type || focus.source_content_type || '', rawText: localizeMarketText(signal.raw_text || signal.summary || ''), postDate: signal.post_date || '', accountName: signal.source_meta?.account_name || signal.source_meta?.username || '', sourceUrl: signal.source_meta?.url || '', signalType: signal.signal_type || '', sourceScope: signal.source_context?.source_scope || '', competitorStatus: signal.source_context?.competitor_status || '', contentIds: signal.content_ids || [], type, title: signal.title || '市场感知信号', detail: localizeMarketText(signal.summary || ''), tag, product: (signal.content_ids || []).join(' / ') || signal.brand_id || '外部来源', score, volume: evidence.length || (signal.content_ids || []).length || 1, confidence: confidence || 0.72, sentiment: tag === 'risk' ? -0.08 : 0, owner: signal.owner || '市场感知', action: localizeMarketText(signal.recommended_action || '保留在市场洞察库,供下游任务按需读取。'), evidence, themes: [ marketFocusText[focusType] || focus.focus_label, ...(models || []).slice(0, 2), signal.source_content_type || focus.source_content_type, signal.category, signal.signal_type, signal.severity, ...(evidence.slice(0, 2)), ].filter(Boolean).slice(0, 4), apiSignal: signal, }; }; const normalizeApiMarketEvidence = (item = {}, index = 0, competitors = []) => { const signal = item.signal || {}; const confidence = Number(item.confidence || signal.confidence || 0.72); const brand = getMarketBrandMeta({ brand_id: item.brand_id, source_context: item.source_context, }, competitors); const focus = item.content_focus || {}; const focusType = focus.focus_type || 'unclear'; const models = item.models?.length ? item.models : (focus.model_names || []); const evidence = (item.evidence || signal.evidence || []).filter(Boolean).map(localizeMarketEvidence); return { id: item.content_id || `market-evidence-${index}`, time: item.post_date || 'raw', freshness: 1, plat: item.platform || '外部来源', source: signal.signal_type ? (marketSignalTypeLabel[signal.signal_type] || signal.signal_type) : '原文证据', brand, brandId: brand.brandId, brandName: brand.brandName, focusType, focusLabel: marketFocusText[focusType] || focus.focus_label || '未明确', models, sourceContentType: item.source_content_type || focus.source_content_type || '', rawText: localizeMarketText(item.raw_text || ''), postDate: item.post_date || '', accountName: item.source_meta?.account_name || item.source_meta?.username || '', sourceUrl: item.source_meta?.url || '', signalType: signal.signal_type || '', sourceScope: item.source_context?.source_scope || '', competitorStatus: item.source_context?.competitor_status || '', contentIds: [item.content_id].filter(Boolean), type: item.source_context?.source_scope === 'external_competitor' ? 'competitor' : 'trend', title: signal.signal_type ? '竞品原文命中信号候选' : '竞品原文证据', detail: localizeMarketText(item.raw_text || ''), tag: signal.signal_type ? inferMarketTag({ signal_type: signal.signal_type, category: 'competitor' }) : 'info', product: item.content_id || '外部来源', score: Math.max(50, Math.min(99, Math.round(confidence * 100))), volume: evidence.length || 1, confidence, sentiment: 0, owner: '市场感知', action: signal.signal_type ? '这条原文已命中市场信号候选,保留为推导证据。' : '保留为竞品原文证据,供后续聚类、信号压缩和策略推演查询。', evidence, themes: [ marketFocusText[focusType] || focus.focus_label, ...(models || []).slice(0, 2), item.source_content_type || focus.source_content_type, signal.signal_type, ...(evidence.slice(0, 1)), ].filter(Boolean).slice(0, 4), apiEvidence: item, }; }; const buildMarketContextOutputs = (contextPack, reviewQueue = [], patterns = []) => { if (!contextPack) return MARKET_OUTPUTS; const snapshot = contextPack.market_snapshot || {}; const memory = contextPack.brand_memory || {}; return [ { id: 'api-context-pack', title: '市场洞察沉淀', cadence: 'live', status: 'ready', text: snapshot.summary ? `${snapshot.summary} 下游模块按任务主动读取,不主动推送。` : '已生成市场总结,供策略、内容和审核模块按任务主动读取。', }, { id: 'api-risk-notes', title: '事实/风险待确认', cadence: 'live', status: reviewQueue.length ? 'urgent' : 'watch', text: reviewQueue.length ? `${reviewQueue.length} 个内容对象需要人工复核,优先确认产品事实、证据和概念边界。` : '当前没有高优先级人工复核项。', }, { id: 'api-brand-memory', title: '可沉淀经验', cadence: 'daily', status: 'ready', text: `已识别 ${memory.known_campaigns?.length || 0} 个 campaign 候选、${memory.known_series?.length || 0} 个栏目候选,保留为市场感知经验库。`, }, { id: 'api-patterns', title: '经验模式', cadence: 'weekly', status: patterns.length ? 'draft' : 'watch', text: patterns[0]?.summary || '等待更多历史内容后抽象可复用经验模式。', }, ]; }; const getSignalCounters = signals => { const competitorSignals = signals.filter(item => item.type === 'competitor'); const riskSignals = signals.filter(item => item.tag === 'risk'); const opportunitySignals = signals.filter(item => item.tag === 'opportunity'); const anomalySignals = signals.filter(item => item.score >= 90 || item.type === 'risk'); const changes = signals.filter(item => ['trend', 'brand', 'competitor', 'comparison'].includes(item.type)); const comparisonSignals = signals.filter(item => item.type === 'comparison'); return [ { label: '竞品信号', value: competitorSignals.length, delta: `${competitorSignals.filter(item => item.score >= 75).length} 个高置信观察`, color: 'var(--accent)' }, { label: '风险信号', value: riskSignals.length, delta: `${riskSignals.filter(item => item.score >= 85).length} 个高优先级证据`, color: 'var(--danger)' }, { label: '变化信号', value: changes.length, delta: `${signals.reduce((sum, item) => sum + item.volume, 0)} 条证据`, color: 'var(--info)' }, { label: '异常信号', value: anomalySignals.length, delta: anomalySignals[0]?.title.slice(0, 12) || '无异常', color: 'var(--warn)' }, { label: '对比信号', value: comparisonSignals.length, delta: comparisonSignals[0]?.themes?.[2] || '等待归因', color: 'var(--ink)' }, ]; }; const getMarketFocusCounters = signals => { const count = focusType => signals.filter(item => item.focusType === focusType).length; const modelSignals = signals.filter(item => item.focusType === 'product_model' || item.focusType === 'mixed'); const topModels = Array.from(new Set(modelSignals.flatMap(item => item.models || []).filter(Boolean))).slice(0, 4); return [ { id: 'product_model', title: '车型 / 产品内容', value: count('product_model'), text: topModels.length ? `主要车系:${topModels.join(' / ')}` : '围绕车型、配置、技术、试驾和使用场景。' }, { id: 'brand', title: '品牌内容', value: count('brand'), text: '围绕品牌文化、全球合作、销量里程碑、用户关系和社会议题。' }, { id: 'mixed', title: '混合内容', value: count('mixed'), text: '车型发布、技术发布和品牌背书同时出现,需要拆开看策略作用。' }, ]; }; const MarketSensing = ({ workspaceContext }) => { const [windowId, setWindowId] = React.useState('24h'); const [activeTab, setActiveTab] = React.useState('overview'); const [typeFilter, setTypeFilter] = React.useState('all'); const [focusFilter, setFocusFilter] = React.useState('all'); const [platform, setPlatform] = React.useState('全部平台'); const [query, setQuery] = React.useState(''); const [selectedId, setSelectedId] = React.useState(SENSE_SIGNALS[0].id); const [writtenIds, setWrittenIds] = React.useState([]); const [marketState, setMarketState] = React.useState({ loading: true, error: '', source: 'static', updatedAt: '', signals: SENSE_SIGNALS, reviewQueue: [], contextPack: null, patterns: [], competitors: [], learningMap: null, evidence: [], evidenceTotal: 0, evidenceLoading: false, }); const activeWindow = getMarketWindow(windowId); const brandName = (WORKSPACE_BRANDS.find(item => item.id === workspaceContext?.brandId) || WORKSPACE_BRANDS[0])?.name || BRAND.name; const brandId = workspaceContext?.brandId || 'montx'; const signals = marketState.signals?.length ? marketState.signals : SENSE_SIGNALS; const evidenceRows = marketState.evidence?.length ? marketState.evidence : signals; const platforms = React.useMemo(() => { const livePlatforms = Array.from(new Set([...signals, ...evidenceRows].map(item => item.plat).filter(Boolean))); return Array.from(new Set([...MARKET_PLATFORMS, ...livePlatforms])); }, [signals, evidenceRows]); const loadMarketData = React.useCallback(async (forceRefresh = false) => { setMarketState(prev => ({ ...prev, loading: true, error: '' })); try { const refreshQuery = forceRefresh ? '&refresh=1' : ''; const [signalsResponse, reviewResponse, patternsResponse, contextResponse, competitorsResponse, learningResponse, evidenceResponse] = await Promise.all([ fetch(marketApiUrl(`/api/market/signals?limit=50${refreshQuery}`)), fetch(marketApiUrl(`/api/market/review-queue?limit=50${refreshQuery}`)), fetch(marketApiUrl(`/api/market/patterns?limit=50${refreshQuery}`)), fetch(marketApiUrl('/api/market/context-pack'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ brand_id: brandId, topic: `${brandName} 市场感知`, refresh: forceRefresh }), }), fetch(marketApiUrl('/api/market/competitors')), fetch(marketApiUrl(`/api/market/learning-map${forceRefresh ? '?refresh=1' : ''}`)), fetch(marketApiUrl(`/api/market/evidence?limit=200&sort=date${forceRefresh ? '&offset=0' : ''}`)), ]); if (!signalsResponse.ok || !reviewResponse.ok || !patternsResponse.ok || !contextResponse.ok || !competitorsResponse.ok || !learningResponse.ok || !evidenceResponse.ok) { throw new Error('市场感知接口暂不可用'); } const [signalsData, reviewData, patternsData, contextData, competitorsData, learningData, evidenceData] = await Promise.all([ signalsResponse.json(), reviewResponse.json(), patternsResponse.json(), contextResponse.json(), competitorsResponse.json(), learningResponse.json(), evidenceResponse.json(), ]); const competitorCandidates = competitorsData.competitorCandidates || []; const apiSignals = (signalsData.signals || []).map((signal, index) => normalizeApiMarketSignal(signal, index, competitorCandidates)); const apiEvidence = (evidenceData.evidence || []).map((item, index) => normalizeApiMarketEvidence(item, index, competitorCandidates)); setMarketState({ loading: false, error: '', source: signalsData.source || (signalsData.versioning ? 'api' : 'static'), updatedAt: signalsData.updatedAt || contextData.contextPack?.context_pack_id || '', signals: apiSignals.length ? apiSignals : SENSE_SIGNALS, reviewQueue: reviewData.reviewQueue || [], contextPack: contextData.contextPack || null, patterns: patternsData.patterns || [], competitors: competitorCandidates, learningMap: learningData.learning_map || learningData || null, evidence: apiEvidence, evidenceTotal: evidenceData.total || apiEvidence.length, evidenceLoading: false, }); } catch (error) { setMarketState(prev => ({ ...prev, loading: false, error: error.message || '市场感知接口暂不可用', source: 'static-fallback', signals: prev.signals?.length ? prev.signals : SENSE_SIGNALS, evidence: prev.evidence?.length ? prev.evidence : (prev.signals?.length ? prev.signals : SENSE_SIGNALS), })); } }, [brandId, brandName]); React.useEffect(() => { loadMarketData(); }, [loadMarketData]); const loadMoreEvidence = React.useCallback(async () => { if (marketState.evidenceLoading) return; setMarketState(prev => ({ ...prev, evidenceLoading: true })); try { const offset = marketState.evidence?.length || 0; const response = await fetch(marketApiUrl(`/api/market/evidence?limit=200&offset=${offset}&sort=date`)); const data = await response.json(); if (!response.ok || !data.ok) throw new Error(data.error || '原文证据接口暂不可用'); const nextRows = (data.evidence || []).map((item, index) => normalizeApiMarketEvidence(item, offset + index, marketState.competitors || [])); setMarketState(prev => { const byId = new Map((prev.evidence || []).map(item => [item.id, item])); nextRows.forEach(item => byId.set(item.id, item)); return { ...prev, evidence: Array.from(byId.values()), evidenceTotal: data.total || prev.evidenceTotal || byId.size, evidenceLoading: false, }; }); } catch (error) { setMarketState(prev => ({ ...prev, evidenceLoading: false, error: error.message || '原文证据接口暂不可用' })); } }, [marketState.evidence?.length, marketState.evidenceLoading, marketState.competitors]); const filteredSignals = React.useMemo(() => { const q = query.trim().toLowerCase(); return signals.filter(item => { const inWindow = item.freshness <= activeWindow.days; const inType = typeFilter === 'all' || item.tag === typeFilter || item.type === typeFilter; const inFocus = focusFilter === 'all' || item.focusType === focusFilter; const inPlatform = platform === '全部平台' || item.plat === platform; const inQuery = !q || [item.title, item.detail, item.rawText, item.product, item.source, item.brandName, item.accountName, item.focusLabel, item.sourceContentType, item.postDate, item.signalType, ...(item.models || []), ...(item.themes || [])].join(' ').toLowerCase().includes(q); return inWindow && inType && inFocus && inPlatform && inQuery; }).sort((a, b) => b.score - a.score); }, [signals, activeWindow.days, typeFilter, focusFilter, platform, query]); const filteredEvidence = React.useMemo(() => { const q = query.trim().toLowerCase(); return evidenceRows.filter(item => { const inType = typeFilter === 'all' || item.tag === typeFilter || item.type === typeFilter; const inFocus = focusFilter === 'all' || item.focusType === focusFilter; const inPlatform = platform === '全部平台' || item.plat === platform; const inQuery = !q || [item.title, item.detail, item.rawText, item.product, item.source, item.brandName, item.accountName, item.focusLabel, item.sourceContentType, item.postDate, item.signalType, ...(item.models || []), ...(item.themes || [])].join(' ').toLowerCase().includes(q); return inType && inFocus && inPlatform && inQuery; }); }, [evidenceRows, typeFilter, focusFilter, platform, query]); const selectedSignal = filteredSignals.find(item => item.id === selectedId) || filteredSignals[0] || signals[0] || SENSE_SIGNALS[0]; const selectedEvidence = filteredEvidence.find(item => item.id === selectedId) || filteredEvidence[0] || evidenceRows[0] || selectedSignal; const activeSelected = activeTab === 'evidence' ? selectedEvidence : selectedSignal; const counters = getSignalCounters(filteredSignals); const focusCounters = React.useMemo(() => getMarketFocusCounters(filteredSignals), [filteredSignals]); const outputRows = React.useMemo( () => buildMarketContextOutputs(marketState.contextPack, marketState.reviewQueue, marketState.patterns), [marketState.contextPack, marketState.reviewQueue, marketState.patterns] ); const writeSelected = () => { if (!activeSelected) return; setWrittenIds(prev => prev.includes(activeSelected.id) ? prev : [...prev, activeSelected.id]); }; React.useEffect(() => { if (activeSelected && activeSelected.id !== selectedId) setSelectedId(activeSelected.id); }, [activeSelected?.id, activeTab]); return (

市场感知 · {brandName} 外部信号工作台

以同行业外部品牌、品类趋势、媒体达人和用户自然讨论为主信号;MONTX 自有内容只作为对照基线和护栏输入。

{MARKET_TIME_WINDOWS.map(item => ( ))}
{counters.map(item => )}
{focusCounters.map(item => ( ))}
{MARKET_VIEW_TABS.map(tab => ( ))}
{MARKET_SIGNAL_TYPES.map(item => ( ))}
{MARKET_CONTENT_FOCUS_FILTERS.map(item => ( ))}
setQuery(event.target.value)} placeholder="搜索原文 / 场景 / 产品 / 主题"/>
{filteredSignals.length} 条信号 · {marketSourceText[marketState.source] || marketState.source}
{activeTab === 'overview' && ( <>

外部市场声量 vs 自有基线

覆盖示意 · 外部优先

平台 × 主题 · 热度矩阵

示意数据

信号流 · Compressed

{filteredSignals.length} / {signals.length}
{marketState.error && 静态兜底} 按优先级排序
{filteredSignals.length ? filteredSignals.map((signal, i) => ( setSelectedId(signal.id)} /> )) : (
当前筛选没有匹配信号。调整时间窗、平台或关键词后重试。
)}
)} {activeTab === 'evidence' && ( )} {activeTab === 'learning' && } {activeTab === 'memory' && (
)}
); }; const MarketSignalRow = ({ signal, active, written, isLast, onSelect }) => ( ); const MarketEvidencePanel = ({ signals = [], loaded = 0, total = 0, selectedSignal, written, onSelect, onWrite, onLoadMore, loadingMore = false }) => { const activeSignal = signals.find(item => item.id === selectedSignal?.id) || signals[0] || null; const canLoadMore = loaded < total; return (

竞品原文 · Evidence Feed

筛选 {signals.length} · 已载入 {loaded || signals.length} / 原始库 {total || signals.length}
{canLoadMore && ( )} 原文 → 分类 → 信号
{signals.length ? signals.map((signal, index) => ( )) : (
当前筛选没有匹配原文。调整平台、内容方向或关键词后重试。
)}
); }; const MarketRawEvidenceDetail = ({ signal }) => { if (!signal) { return (

原文推导 · Derivation

等待选择

选择一条竞品原文后,这里会展示原文、分类字段和推导证据。

); } const derivationSteps = [ { label: '原文', value: signal.rawText || signal.detail }, { label: '内容分类', value: `${signal.focusLabel || '未明确'} / ${signal.sourceContentType || '未标注'}` }, { label: '信号类型', value: marketSignalTypeLabel[signal.signalType] || signal.signalType || signal.source }, { label: '沉淀方式', value: signal.action }, ]; return (

原文推导 · Derivation

{signal.postDate || signal.time}
{signal.brand?.logoText || 'EXT'}
{signal.brandName || '外部来源'}

{[signal.plat, signal.accountName, signal.postDate || '未标注日期', (signal.contentIds || []).join(' / ') || signal.product].filter(Boolean).join(' · ')}

{signal.sourceUrl && ( 打开原帖链接 )}
原文内容

{signal.rawText || signal.detail}

{derivationSteps.map(item => (
{item.label}

{item.value}

))}
{(signal.evidence || []).map(item => ( {item} ))}
); }; const MarketLearningMap = ({ learningMap, loading = false }) => { const priorities = learningMap?.learning_priorities || []; const profile = learningMap?.brand_learning_profile || {}; const strategyNotes = learningMap?.recommended_for_strategy || []; const [selectedCase, setSelectedCase] = React.useState(null); const [caseState, setCaseState] = React.useState({ loading: false, error: '', caseStudy: null }); const openCaseStudy = React.useCallback(async item => { setSelectedCase(item); setCaseState({ loading: true, error: '', caseStudy: null }); try { const response = await fetch(marketApiUrl(`/api/market/case-studies/${encodeURIComponent(item.case_id)}`)); const data = await response.json(); if (!response.ok || !data.ok) throw new Error(data.error || data.message || '案例详情暂未生成'); setCaseState({ loading: false, error: '', caseStudy: data.caseStudy || null }); } catch (error) { setCaseState({ loading: false, error: error.message || '案例详情暂未生成', caseStudy: null }); } }, []); if (!learningMap && loading) { return (

学习地图 · Learning Map

品牌记忆推导中
); } if (!learningMap) return null; return ( <>

学习地图 · Brand Memory Learning Map

{profile.product_stage || 'stage'} · {profile.launch_window || 'launch window'}
{profile.brand_name || 'MONTX'} 当前学习任务

{learningMap.learning_principle || '按品牌当前战略问题选择学习对象。'}

{(profile.strategic_tasks || []).slice(0, 5).map(item => {item})}
{priorities.map(priority => (
{priority.priority} {marketLearningPriorityText[priority.priority] || priority.title}

{priority.intent?.why || priority.description}

{(priority.cases || []).slice(0, 3).map(item => ( ))}
))}
{strategyNotes.slice(0, 4).map(note => (
{note}
))}
{selectedCase && ( setSelectedCase(null)} /> )} ); }; const MarketCaseStudyDrawer = ({ caseItem, caseStudy, loading, error, onClose }) => { const scope = caseStudy?.data_scope || {}; const breakdown = caseStudy?.content_breakdown || {}; const patterns = caseStudy?.transferable_patterns || []; const risks = caseStudy?.risk_patterns || []; const implications = caseStudy?.montx_implications || []; const audience = caseStudy?.audience_insights || []; return (
{caseItem?.case_id || caseStudy?.case_id}

{caseItem?.case_brand || caseStudy?.brand} · 案例详情

{caseItem?.case_product || '长周期预热案例'}

{loading ? (
) : error ? (
{error} 当前案例可能还没有生成结构化 case study。先运行对应分析脚本后再打开。
) : (
{caseStudy?.diagnostic_summary?.one_sentence || '案例结论待补充'}

{caseStudy?.diagnostic_summary?.why_it_matters_to_montx}

内容生命周期

{(breakdown.lifecycle_stages || []).slice(0, 6).map(item => )}

车型拆分

{(breakdown.model_focus || []).map(item => {item.key} · {item.count})}

可迁移内容范式

{patterns.map(item => (
{item.name}

{item.montx_transfer}

))}

评论洞察

{audience.slice(0, 5).map(item => (
{item.label} · {item.count}

{item.insight}

))}

风险模式

{risks.map(item => (
{item.risk}

{item.implication}

))}

沉淀给 MONTX 的启示

{implications.map(item => (
{item}
))}
)}
); }; const MarketCaseStat = ({ label, value }) => (
{label} {value}
); const MarketCaseBar = ({ item, total }) => { const width = total ? Math.max(6, Math.round((item.count / total) * 100)) : 0; return (
{item.key}{item.count}
); }; const MarketSignalDetail = ({ signal, written, onWrite }) => { if (!signal) return null; return (

信号详情 · 洞察记录

{signal.brandName || signal.product}
{signal.action} 记录方 · {signal.owner} · 置信度 {(signal.confidence * 100).toFixed(0)}%
= 85 ? '高置信' : '观察'}/> 0 ? '+' : ''}${signal.sentiment}`} sub={marketTypeText[signal.type] || signal.type}/>
{(signal.evidence || []).map(item => (
{item}
))}
); }; const MarketObjectMap = ({ reviewQueue = [], signals = [] }) => { const counts = { competitor: signals.filter(item => item.type === 'competitor').length, category: signals.filter(item => item.type === 'trend' || item.tag === 'opportunity').length, media: signals.filter(item => ['小红书', '抖音', 'B站', 'Instagram', 'YouTube', '行业媒体', '海外搜索'].includes(item.plat)).length, risk: reviewQueue.length || signals.filter(item => item.tag === 'risk').length, }; return (

外部感知对象 · Four Signal Objects

PRD 3.2
{MARKET_SENSING_OBJECTS.map(item => (
{item.title} {counts[item.id] || item.count}

{item.text}

{item.examples.map(example => {example})}
))}
); }; const MarketMemorySummary = ({ outputs = MARKET_OUTPUTS, loading = false }) => (

市场记忆摘要 · Query Outputs

{outputs.length} 条可查询记录
{loading ? '同步中' : '按需读取'}
{outputs.map(item => (
{marketCadenceText[item.cadence] || item.cadence} {item.title}

{item.text}

{marketStatusText[item.status] || item.status}
))}
); const SignalCounter = ({ label, value, delta, color }) => (
{label}
{value}
{delta}
); const SensingChart = () => ( {[20,50,80,110,140].map(y => )} {[[0,122],[30,118],[60,111],[90,105],[120,96],[150,90],[180,82],[210,76],[240,62],[270,54],[300,48],[330,39],[360,31],[400,26]].map(([x,y], i) => ( ))} ); const HeatMatrix = () => { const rows = ['小红书', '抖音', '微博', 'B站', '知乎', '汽车之家', '公众号']; const cols = ['全域', 'P10', 'V10', '办公', '旅居', '共创', '边界']; const data = [ [9,8,7,8,9,7,5], [8,9,6,6,7,8,6], [6,6,5,5,5,6,4], [5,8,8,7,8,6,9], [4,6,7,8,7,5,8], [3,8,4,4,5,7,6], [7,6,7,8,7,6,8], ]; return (
{cols.map(c => )} {rows.map((r, i) => ( {data[i].map((v, j) => ( ))} ))}
{c}
{r} 6 ? 'white' : 'var(--ink-2)', borderRadius:3, }}>{v}
); }; const MarketSourceCoverage = ({ competitors = [] }) => (

外部数据源覆盖 · Source Coverage

{competitors.length} 个候选竞品
{[ ['外部自然声量', 'Instagram / YouTube 二创与转发', 74, 'ok'], ['品类趋势', '小红书 / B站 / 知乎 / 公众号聚合', 81, 'ok'], ['竞品监听', competitors.length ? competitors.map(item => item.brand_name || item.brand_id).join(' / ') : '等待确认竞品种子', competitors.length ? 76 : 42, competitors.length ? 'ok' : 'warn'], ['风险异常', '护栏命中、评论聚集、概念误读', 86, 'ok'], ['自有声量基线', 'MONTX 官方社媒仅用于对照,不进入主信号流', 92, 'ok'], ].map(([name, desc, pct, status]) => (
{name} {desc}
{pct}%
))}
); const SensingPipeline = () => (

感知链路 · From Signal To Memory

查询服务
{[ ['采集', '平台/竞品/评论进入信号池', 'done'], ['标准化', '实体、车型、平台、证据与置信度归一', 'done'], ['判定', '信号类型、竞品关系、风险边界归因', 'active'], ['沉淀', '生成 Context Pack / Pattern / Case Study', 'active'], ['服务', '下游模块按任务主动查询,不主动推送', 'pending'], ].map(([title, text, status], index) => (
{String(index + 1).padStart(2, '0')} {title}

{text}

))}
); const MiniStat = ({ label, value, sub }) => (
{label}
{value}
{sub}
); Object.assign(window, { Dashboard, BrandMemory, MarketSensing });