| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- <script setup lang="ts">
- import { computed, ref } from 'vue'
- import { RouterLink } from 'vue-router'
- type StageTone = 'indigo' | 'blue' | 'cyan' | 'amber' | 'green' | 'violet'
- interface FlowStage {
- id: string
- index: string
- title: string
- subtitle: string
- summary: string
- input: string
- output: string
- tools: string[]
- constraints: string[]
- tone: StageTone
- }
- const stages: FlowStage[] = [
- {
- id: 'context',
- index: '01',
- title: '上下文就绪',
- subtitle: 'CONTEXT READY',
- summary: '调度器装配需求、参考视频和拓展点位,并在模型运行前预创建唯一 run_id。',
- input: 'S / A 级需求、业务日期、参考视频、目的点 / 关键点 / 灵感点',
- output: 'FindDemandContext、输入快照、status=running 的运行记录',
- tools: ['调度器预创建(非 Agent Tool)'],
- constraints: ['没有有效点位的需求不进入运行', '同一业务日与需求只自动执行一次', '后续写库必须复用预创建 run_id'],
- tone: 'indigo',
- },
- {
- id: 'search',
- index: '02',
- title: '搜索召回',
- subtitle: 'SEARCH & RECALL',
- summary: '模型规划多个语义不同的搜索词,按关键词、翻页、标签或作者扩展候选。',
- input: '需求意图、参考标题、拓展点位、既有搜索前沿',
- output: '搜索页、原始搜索证据、带 candidate_id 的候选记录',
- tools: ['batch_search_and_record', 'douyin_search', 'douyin_search_tikhub', 'douyin_user_videos'],
- constraints: ['批量搜索最多 6 个任务、每项最多 2 页', '每页立即落库,不覆盖历史搜索', '同一视频被不同搜索命中时保留独立候选'],
- tone: 'blue',
- },
- {
- id: 'evidence',
- index: '03',
- title: '证据补全',
- subtitle: 'EVIDENCE',
- summary: '对高潜候选获取详情、内容点赞画像与作者粉丝画像,并统一标准化年龄证据。',
- input: 'run_id、候选 candidate_ids、搜索阶段已有的基础信息',
- output: '详情证据、双侧画像、年龄标准化结果、刷新后的门禁状态',
- tools: ['douyin_detail', 'batch_fetch_portraits', 'get_content_fans_portrait', 'get_account_fans_portrait', 'normalize_age_portraits'],
- constraints: ['详情和批量画像单次最多 8 条', '优先使用 run_id + candidate_ids 新模式', '数据缺失是未知,不直接等于负证据'],
- tone: 'cyan',
- },
- {
- id: 'decision',
- index: '04',
- title: '评分与门禁',
- subtitle: 'DECISION GATE',
- summary: '模型判断 R / E / S / V,代码用冻结的 P0 规则快照决定候选能否进入主推荐。',
- input: '相关性、受众倾向、分享信号、时效、时长与互动证据',
- output: 'primary / rejected / pending_evaluation 与版本化门禁结果',
- tools: ['batch_update_video_discovery_candidates'],
- constraints: ['primary 必须通过 P0 门禁', '未补齐证据时自动回到 pending_evaluation', '门禁失败的 primary 自动重分类为 rejected'],
- tone: 'amber',
- },
- {
- id: 'verify',
- index: '05',
- title: '状态核对',
- subtitle: 'STATE VERIFY',
- summary: '重新从数据库读取运行、搜索和候选,判断继续搜索是否还能改变结果。',
- input: '当前 run_id 与已经持久化的全量执行轨迹',
- output: 'run / searches / candidates 的一致性快照',
- tools: ['query_video_discovery_state'],
- constraints: ['最终判断以数据库状态为准', '需要区分 primary_count 与 valid_primary_count', '剩余前沿无信息价值时才停止搜索'],
- tone: 'green',
- },
- {
- id: 'finish',
- index: '06',
- title: '完成与输出',
- subtitle: 'FINALIZE',
- summary: '归档未评估候选、计算业务结果,再由模型输出面向用户的执行摘要。',
- input: '最终候选池、意图摘要、停止原因',
- output: 'finished / failed 与 goal_met / partial / no_match / failed',
- tools: ['update_video_discovery_run_status'],
- constraints: ['finished 会归档全部待评估候选', '有效 primary ≥ 5 才是 goal_met', '最终文本不能替代 outcome_status'],
- tone: 'violet',
- },
- ]
- const activeStageId = ref('search')
- const activeStage = computed(() => stages.find((stage) => stage.id === activeStageId.value) ?? stages[0])
- const dataStores = [
- { name: 'RUN', table: 'video_discovery_run', detail: '运行状态 · 业务结果 · 规则快照' },
- { name: 'SEARCH', table: 'video_discovery_search', detail: '搜索词 · 父子关系 · 分页状态' },
- { name: 'CANDIDATE', table: 'video_discovery_candidate', detail: '候选证据 · R/E/S/V · 最终分池' },
- { name: 'EVIDENCE', table: 'video_discovery_evidence', detail: '搜索 / 详情 / 画像原始响应' },
- { name: 'GATE', table: 'video_discovery_gate_evaluation', detail: '规则版本 · 证据版本 · 失败原因' },
- ]
- </script>
- <template>
- <div class="framework-page">
- <header class="page-hero">
- <div class="hero-copy">
- <span class="eyebrow">FIND AGENT · SYSTEM MAP</span>
- <h1>寻找 Agent 框架流程</h1>
- <p>从标准需求输入到可追溯候选输出,展示模型循环、工具编排、证据门禁和持久化状态之间的关系。</p>
- <div class="hero-actions">
- <RouterLink to="/find-agent-records" class="primary-link">查看运行记录 <span>→</span></RouterLink>
- <a href="#stage-flow" class="secondary-link">浏览执行阶段</a>
- </div>
- </div>
- <div class="runtime-boundary" aria-label="运行边界">
- <span class="boundary-label">RUNTIME BOUNDARY</span>
- <div class="boundary-grid">
- <div><small>MODEL</small><strong>Gemini 3 Flash</strong></div>
- <div><small>REACT</small><strong>最多 60 轮</strong></div>
- <div><small>TIMEOUT</small><strong>默认 600 秒</strong></div>
- <div><small>TOOLS</small><strong>13 个注册工具</strong></div>
- </div>
- </div>
- </header>
- <section class="architecture-map" aria-labelledby="architecture-title">
- <div class="section-heading">
- <div>
- <span>01 · ORCHESTRATION</span>
- <h2 id="architecture-title">框架总览</h2>
- </div>
- <p>模型负责决定下一步,工具负责读取与写入事实,完成守卫负责阻止没有业务结果的提前结束。</p>
- </div>
- <div class="kernel-flow">
- <article class="boundary-node input-node">
- <div class="node-icon">IN</div>
- <div>
- <span>标准任务输入</span>
- <strong>需求 + 参考视频 + 拓展点位</strong>
- <small>调度器预创建 run_id</small>
- </div>
- </article>
- <span class="flow-connector" aria-hidden="true"><i /><b>→</b></span>
- <article class="agent-kernel">
- <header>
- <div>
- <span>FIND_AGENT KERNEL</span>
- <strong>LLM ReAct 控制器</strong>
- </div>
- <em>异步顺序工具调用</em>
- </header>
- <div class="react-loop" aria-label="ReAct 循环">
- <div><b>01</b><span>Reason</span><small>理解状态</small></div>
- <i>→</i>
- <div><b>02</b><span>Act</span><small>选择工具</small></div>
- <i>→</i>
- <div><b>03</b><span>Observe</span><small>解析结果</small></div>
- <i>↺</i>
- </div>
- <footer>
- <span><i class="dot model" />系统 Prompt</span>
- <span><i class="dot tool" />Tool Registry</span>
- <span><i class="dot guard" />Completion Guard</span>
- </footer>
- </article>
- <span class="flow-connector" aria-hidden="true"><i /><b>→</b></span>
- <article class="boundary-node output-node">
- <div class="node-icon">OUT</div>
- <div>
- <span>结构化业务结果</span>
- <strong>候选池 + 证据 + 结果状态</strong>
- <small>goal_met / partial / no_match</small>
- </div>
- </article>
- </div>
- <div class="guard-rail">
- <span>完成守卫</span>
- <i />
- <p><strong>status=finished</strong> 且存在业务结果时,模型的无工具回答才可成为最终输出。</p>
- </div>
- </section>
- <section id="stage-flow" class="stage-section" aria-labelledby="stage-title">
- <div class="section-heading">
- <div>
- <span>02 · BUSINESS FLOW</span>
- <h2 id="stage-title">六阶段执行链</h2>
- </div>
- <p>选择阶段查看真实输入、输出、注册工具与代码硬约束。</p>
- </div>
- <div class="stage-flow" role="tablist" aria-label="寻找 Agent 执行阶段">
- <template v-for="(stage, index) in stages" :key="stage.id">
- <button
- type="button"
- role="tab"
- :aria-selected="activeStageId === stage.id"
- :class="['stage-node', stage.tone, { active: activeStageId === stage.id }]"
- @click="activeStageId = stage.id"
- >
- <span class="stage-index">{{ stage.index }}</span>
- <small>{{ stage.subtitle }}</small>
- <strong>{{ stage.title }}</strong>
- <i>{{ stage.id === 'context' ? '调度入口' : `${stage.tools.length} 工具` }}</i>
- </button>
- <span v-if="index < stages.length - 1" class="stage-arrow" aria-hidden="true"><i />→</span>
- </template>
- </div>
- <article class="stage-detail" :class="activeStage.tone">
- <div class="detail-lead">
- <span>{{ activeStage.index }} / {{ activeStage.subtitle }}</span>
- <h3>{{ activeStage.title }}</h3>
- <p>{{ activeStage.summary }}</p>
- </div>
- <dl class="io-grid">
- <div>
- <dt><span>IN</span> 阶段输入</dt>
- <dd>{{ activeStage.input }}</dd>
- </div>
- <div>
- <dt><span>OUT</span> 阶段输出</dt>
- <dd>{{ activeStage.output }}</dd>
- </div>
- </dl>
- <div class="detail-columns">
- <section>
- <span class="detail-label">CALLABLE TOOLS</span>
- <div class="tool-list">
- <code v-for="tool in activeStage.tools" :key="tool">{{ tool }}</code>
- </div>
- </section>
- <section>
- <span class="detail-label">HARD CONSTRAINTS</span>
- <ul>
- <li v-for="constraint in activeStage.constraints" :key="constraint">{{ constraint }}</li>
- </ul>
- </section>
- </div>
- </article>
- </section>
- <section class="data-plane" aria-labelledby="data-title">
- <div class="section-heading compact">
- <div>
- <span>03 · DATA PLANE</span>
- <h2 id="data-title">证据与状态底座</h2>
- </div>
- <p>Agent Tools 不直接写 SQL,统一经过 Service 与 Repository 操作持久化实体。</p>
- </div>
- <div class="data-pipeline">
- <div class="service-gateway">
- <span>SERVICE GATEWAY</span>
- <strong>VideoDiscoveryService</strong>
- <small>事务 · 序列化 · 规则快照</small>
- </div>
- <span class="data-arrow">↓</span>
- <div class="store-grid">
- <article v-for="store in dataStores" :key="store.name">
- <span>{{ store.name }}</span>
- <strong>{{ store.table }}</strong>
- <small>{{ store.detail }}</small>
- </article>
- </div>
- </div>
- </section>
- <footer class="page-footnote">
- <span><i class="legend-dot model" />模型决策</span>
- <span><i class="legend-dot tool" />工具执行</span>
- <span><i class="legend-dot guard" />代码门禁</span>
- <p>当前实现 · 2026-08-04</p>
- </footer>
- </div>
- </template>
- <style scoped>
- .framework-page {
- --ink: #182033;
- --muted: #6d7586;
- --line: #e3e6ed;
- display: flex;
- max-width: 1480px;
- margin: 0 auto;
- padding: 0 4px 38px;
- flex-direction: column;
- gap: 22px;
- color: var(--ink);
- }
- .page-hero,
- .architecture-map,
- .stage-section,
- .data-plane {
- border: 1px solid var(--line);
- border-radius: 20px;
- background: rgba(255, 255, 255, .94);
- box-shadow: 0 14px 42px rgba(35, 43, 64, .055);
- }
- .page-hero {
- position: relative;
- display: grid;
- min-height: 260px;
- padding: 38px 42px;
- overflow: hidden;
- grid-template-columns: minmax(0, 1.15fr) minmax(420px, .85fr);
- align-items: center;
- gap: 38px;
- background:
- radial-gradient(circle at 88% 12%, rgba(103, 98, 207, .14), transparent 31%),
- linear-gradient(135deg, #fff 0 57%, #f8f8fc 57%);
- }
- .page-hero::after {
- position: absolute;
- inset: 0;
- background-image:
- linear-gradient(rgba(89, 98, 127, .045) 1px, transparent 1px),
- linear-gradient(90deg, rgba(89, 98, 127, .045) 1px, transparent 1px);
- background-size: 28px 28px;
- mask-image: linear-gradient(90deg, transparent 46%, #000);
- content: '';
- pointer-events: none;
- }
- .hero-copy,
- .runtime-boundary { position: relative; z-index: 1; }
- .eyebrow,
- .section-heading div > span,
- .boundary-label,
- .detail-label {
- color: #6c6bd0;
- font-size: 11px;
- font-weight: 800;
- letter-spacing: .12em;
- }
- .hero-copy h1 {
- margin: 8px 0 10px;
- font-size: clamp(30px, 3vw, 45px);
- line-height: 1.1;
- letter-spacing: -.035em;
- }
- .hero-copy > p {
- max-width: 650px;
- margin: 0;
- color: var(--muted);
- font-size: 15px;
- line-height: 1.8;
- }
- .hero-actions { display: flex; margin-top: 22px; align-items: center; gap: 12px; }
- .hero-actions a {
- display: inline-flex;
- height: 38px;
- padding: 0 15px;
- align-items: center;
- gap: 14px;
- border-radius: 9px;
- font-size: 13px;
- font-weight: 750;
- text-decoration: none;
- }
- .primary-link { background: #5e5dc8; color: #fff; }
- .primary-link:hover { background: #5352bb; }
- .secondary-link { border: 1px solid #dfe1e8; background: #fff; color: #697184; }
- .secondary-link:hover { border-color: #c9c9eb; color: #5555b7; }
- .runtime-boundary {
- padding: 20px;
- border: 1px solid rgba(98, 96, 194, .16);
- border-radius: 16px;
- background: rgba(255, 255, 255, .78);
- backdrop-filter: blur(9px);
- }
- .boundary-grid { display: grid; margin-top: 12px; grid-template-columns: 1fr 1fr; gap: 8px; }
- .boundary-grid div { padding: 12px; border-radius: 10px; background: rgba(244, 244, 250, .88); }
- .boundary-grid small,
- .boundary-grid strong { display: block; }
- .boundary-grid small { color: #9a9fac; font-size: 10px; font-weight: 800; letter-spacing: .08em; }
- .boundary-grid strong { margin-top: 4px; font-size: 14px; }
- .architecture-map,
- .stage-section,
- .data-plane { padding: 28px 30px; }
- .section-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
- .section-heading h2 { margin: 3px 0 0; font-size: 22px; letter-spacing: -.02em; }
- .section-heading > p { max-width: 610px; margin: 0; color: var(--muted); font-size: 13px; text-align: right; }
- .section-heading.compact { align-items: center; }
- .kernel-flow {
- display: grid;
- margin-top: 28px;
- grid-template-columns: minmax(190px, .8fr) 58px minmax(430px, 1.55fr) 58px minmax(200px, .8fr);
- align-items: center;
- }
- .boundary-node {
- display: flex;
- min-height: 126px;
- padding: 18px;
- align-items: center;
- gap: 13px;
- border: 1px solid #e4e6ed;
- border-radius: 14px;
- background: #fafbfc;
- }
- .boundary-node .node-icon {
- display: grid;
- width: 38px;
- height: 38px;
- flex: 0 0 auto;
- place-items: center;
- border-radius: 10px;
- background: #ececfa;
- color: #6261c5;
- font-size: 10px;
- font-weight: 900;
- }
- .boundary-node span,
- .boundary-node strong,
- .boundary-node small { display: block; }
- .boundary-node span { color: #8e95a3; font-size: 11px; font-weight: 800; }
- .boundary-node strong { margin-top: 5px; font-size: 14px; line-height: 1.45; }
- .boundary-node small { margin-top: 6px; color: #969dab; font-size: 11px; }
- .flow-connector { display: flex; align-items: center; color: #8d91b7; }
- .flow-connector i { height: 1px; flex: 1; background: #d9dbe6; }
- .flow-connector b { font-size: 18px; font-weight: 500; }
- .agent-kernel {
- overflow: hidden;
- border: 1px solid #cbcaee;
- border-radius: 16px;
- background: linear-gradient(145deg, #fbfbff, #f3f3fb);
- box-shadow: 0 10px 30px rgba(78, 76, 165, .08);
- }
- .agent-kernel > header { display: flex; padding: 15px 18px; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddddef; }
- .agent-kernel header span,
- .agent-kernel header strong { display: block; }
- .agent-kernel header span { color: #7170cd; font-size: 10px; font-weight: 850; letter-spacing: .1em; }
- .agent-kernel header strong { margin-top: 3px; font-size: 16px; }
- .agent-kernel header em { padding: 4px 8px; border-radius: 999px; background: #e8e8f8; color: #6665bd; font-size: 10px; font-style: normal; font-weight: 750; }
- .react-loop { display: grid; padding: 18px; grid-template-columns: 1fr 24px 1fr 24px 1fr 28px; align-items: center; }
- .react-loop > div { display: flex; min-height: 70px; padding: 10px 8px; flex-direction: column; align-items: center; justify-content: center; border: 1px solid #dfdfed; border-radius: 11px; background: #fff; }
- .react-loop > div b { color: #8f8ed5; font-size: 10px; }
- .react-loop > div span { margin-top: 2px; font-size: 13px; font-weight: 800; }
- .react-loop > div small { color: #969cab; font-size: 10px; }
- .react-loop > i { color: #9494bc; font-size: 17px; font-style: normal; text-align: center; }
- .agent-kernel > footer { display: flex; padding: 10px 18px; flex-wrap: wrap; gap: 15px; border-top: 1px solid #ddddef; color: #747b8a; font-size: 10px; }
- .agent-kernel footer span { display: inline-flex; align-items: center; gap: 5px; }
- .dot,
- .legend-dot { width: 7px; height: 7px; border-radius: 50%; }
- .model { background: #6665ce; }
- .tool { background: #2795be; }
- .guard { background: #df9c28; }
- .guard-rail { display: grid; margin-top: 14px; padding: 11px 15px; grid-template-columns: auto minmax(30px, 1fr) auto; align-items: center; gap: 12px; border-radius: 10px; background: #fff8ea; color: #8c651d; font-size: 12px; }
- .guard-rail > span { font-weight: 850; }
- .guard-rail > i { height: 1px; background: #ead7ad; }
- .guard-rail p { margin: 0; }
- .stage-flow { display: grid; margin-top: 28px; grid-template-columns: 1fr 22px 1fr 22px 1fr 22px 1fr 22px 1fr 22px 1fr; align-items: center; }
- .stage-node {
- position: relative;
- display: flex;
- min-width: 0;
- min-height: 116px;
- padding: 15px 13px;
- flex-direction: column;
- align-items: flex-start;
- border: 1px solid #e2e4ea;
- border-radius: 13px;
- background: #fafbfc;
- color: var(--ink);
- text-align: left;
- cursor: pointer;
- transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
- }
- .stage-node:hover { transform: translateY(-2px); border-color: #cfd2de; box-shadow: 0 8px 22px rgba(40, 48, 69, .06); }
- .stage-node.active { border-color: var(--tone); background: var(--tone-soft); box-shadow: 0 8px 24px var(--tone-shadow); }
- .stage-node.indigo { --tone: #6665ca; --tone-soft: #f1f1ff; --tone-shadow: rgba(89, 88, 192, .12); }
- .stage-node.blue { --tone: #497dcc; --tone-soft: #eff6ff; --tone-shadow: rgba(67, 117, 193, .12); }
- .stage-node.cyan { --tone: #2997ae; --tone-soft: #effafb; --tone-shadow: rgba(40, 143, 164, .12); }
- .stage-node.amber { --tone: #cf8b25; --tone-soft: #fff8ea; --tone-shadow: rgba(198, 132, 32, .12); }
- .stage-node.green { --tone: #369873; --tone-soft: #effaf6; --tone-shadow: rgba(49, 145, 108, .12); }
- .stage-node.violet { --tone: #845dbd; --tone-soft: #f7f1fd; --tone-shadow: rgba(121, 80, 174, .12); }
- .stage-index { display: grid; width: 25px; height: 21px; place-items: center; border-radius: 6px; background: #e9eaf0; color: #737b8a; font-size: 10px; font-weight: 900; }
- .stage-node.active .stage-index { background: var(--tone); color: #fff; }
- .stage-node small { margin-top: 12px; overflow: hidden; color: #9a9fac; font-size: 9px; font-weight: 800; letter-spacing: .08em; text-overflow: ellipsis; white-space: nowrap; }
- .stage-node strong { margin-top: 2px; font-size: 14px; }
- .stage-node > i { margin-top: auto; color: #9ca2ae; font-size: 10px; font-style: normal; }
- .stage-arrow { display: flex; align-items: center; color: #aaaeb9; font-size: 12px; }
- .stage-arrow i { height: 1px; flex: 1; background: #dadde4; }
- .stage-detail {
- --tone: #497dcc;
- --tone-soft: #eff6ff;
- display: grid;
- margin-top: 18px;
- padding: 22px;
- grid-template-columns: minmax(190px, .8fr) minmax(260px, 1.2fr) minmax(0, 1.35fr);
- gap: 22px;
- border: 1px solid color-mix(in srgb, var(--tone) 28%, #e2e4ea);
- border-radius: 15px;
- background: linear-gradient(135deg, var(--tone-soft), #fff 55%);
- }
- .stage-detail.indigo { --tone: #6665ca; --tone-soft: #f1f1ff; }
- .stage-detail.blue { --tone: #497dcc; --tone-soft: #eff6ff; }
- .stage-detail.cyan { --tone: #2997ae; --tone-soft: #effafb; }
- .stage-detail.amber { --tone: #cf8b25; --tone-soft: #fff8ea; }
- .stage-detail.green { --tone: #369873; --tone-soft: #effaf6; }
- .stage-detail.violet { --tone: #845dbd; --tone-soft: #f7f1fd; }
- .detail-lead > span { color: var(--tone); font-size: 10px; font-weight: 850; letter-spacing: .1em; }
- .detail-lead h3 { margin: 5px 0 6px; font-size: 20px; }
- .detail-lead p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.7; }
- .io-grid { display: grid; margin: 0; gap: 9px; }
- .io-grid div { padding: 11px 12px; border-left: 2px solid var(--tone); background: rgba(255, 255, 255, .72); }
- .io-grid dt { color: #7f8796; font-size: 10px; font-weight: 800; }
- .io-grid dt span { margin-right: 5px; color: var(--tone); }
- .io-grid dd { margin: 4px 0 0; font-size: 12px; line-height: 1.55; }
- .detail-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
- .detail-columns section { min-width: 0; }
- .tool-list { display: flex; margin-top: 9px; flex-wrap: wrap; gap: 6px; }
- .tool-list code { padding: 5px 7px; border: 1px solid #e0e2e9; border-radius: 6px; background: #fff; color: #4d5566; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; overflow-wrap: anywhere; }
- .detail-columns ul { display: grid; margin: 9px 0 0; padding: 0; gap: 7px; list-style: none; }
- .detail-columns li { position: relative; padding-left: 13px; color: #626b7b; font-size: 11px; line-height: 1.45; }
- .detail-columns li::before { position: absolute; top: 6px; left: 0; width: 5px; height: 5px; border-radius: 50%; background: var(--tone); content: ''; }
- .data-pipeline { display: flex; margin-top: 24px; flex-direction: column; align-items: center; }
- .service-gateway { width: min(420px, 100%); padding: 13px 18px; border: 1px solid #d9dbea; border-radius: 12px; background: #f7f7fc; text-align: center; }
- .service-gateway span,
- .service-gateway strong,
- .service-gateway small { display: block; }
- .service-gateway span { color: #7675cc; font-size: 9px; font-weight: 850; letter-spacing: .12em; }
- .service-gateway strong { margin-top: 3px; font-size: 14px; }
- .service-gateway small { margin-top: 2px; color: #9298a5; font-size: 10px; }
- .data-arrow { color: #a4a8b4; line-height: 28px; }
- .store-grid { display: grid; width: 100%; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
- .store-grid article { min-width: 0; padding: 13px; border-top: 2px solid #6d6ccb; background: #f8f9fb; }
- .store-grid span,
- .store-grid strong,
- .store-grid small { display: block; }
- .store-grid span { color: #7372ca; font-size: 9px; font-weight: 900; letter-spacing: .09em; }
- .store-grid strong { margin-top: 5px; overflow: hidden; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
- .store-grid small { margin-top: 5px; color: #8b92a0; font-size: 10px; line-height: 1.4; }
- .page-footnote { display: flex; padding: 0 5px; align-items: center; gap: 17px; color: #8a91a0; font-size: 10px; }
- .page-footnote > span { display: inline-flex; align-items: center; gap: 6px; }
- .page-footnote p { margin: 0 0 0 auto; }
- @media (max-width: 1180px) {
- .kernel-flow { grid-template-columns: 1fr 35px minmax(390px, 1.4fr) 35px 1fr; }
- .stage-flow { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
- .stage-arrow { display: none; }
- .stage-detail { grid-template-columns: 1fr 1.4fr; }
- .detail-columns { grid-column: 1 / -1; }
- .store-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
- }
- @media (max-width: 900px) {
- .page-hero { padding: 30px; grid-template-columns: 1fr; }
- .runtime-boundary { max-width: 620px; }
- .section-heading { align-items: flex-start; flex-direction: column; gap: 8px; }
- .section-heading > p { max-width: none; text-align: left; }
- .kernel-flow { grid-template-columns: 1fr; gap: 10px; }
- .flow-connector { width: 1px; height: 24px; margin: 0 auto; flex-direction: column; }
- .flow-connector i { width: 1px; }
- .flow-connector b { transform: rotate(90deg); }
- .guard-rail { grid-template-columns: auto 1fr; }
- .guard-rail p { grid-column: 1 / -1; }
- }
- @media (max-width: 700px) {
- .framework-page { gap: 14px; }
- .page-hero,
- .architecture-map,
- .stage-section,
- .data-plane { border-radius: 15px; }
- .page-hero,
- .architecture-map,
- .stage-section,
- .data-plane { padding: 22px 18px; }
- .hero-actions { align-items: stretch; flex-direction: column; }
- .hero-actions a { justify-content: center; }
- .boundary-grid { grid-template-columns: 1fr; }
- .react-loop { grid-template-columns: 1fr; gap: 6px; }
- .react-loop > i { transform: rotate(90deg); }
- .stage-flow { grid-template-columns: 1fr 1fr; }
- .stage-detail { padding: 17px; grid-template-columns: 1fr; }
- .detail-columns { grid-column: auto; grid-template-columns: 1fr; }
- .store-grid { grid-template-columns: 1fr 1fr; }
- .page-footnote { flex-wrap: wrap; }
- .page-footnote p { width: 100%; margin-left: 0; }
- }
- @media (max-width: 430px) {
- .stage-flow,
- .store-grid { grid-template-columns: 1fr; }
- }
- </style>
|