FindAgentFrameworkView.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <script setup lang="ts">
  2. import { computed, ref } from 'vue'
  3. import { RouterLink } from 'vue-router'
  4. type StageTone = 'indigo' | 'blue' | 'cyan' | 'amber' | 'green' | 'violet'
  5. interface FlowStage {
  6. id: string
  7. index: string
  8. title: string
  9. subtitle: string
  10. summary: string
  11. input: string
  12. output: string
  13. tools: string[]
  14. constraints: string[]
  15. tone: StageTone
  16. }
  17. const stages: FlowStage[] = [
  18. {
  19. id: 'context',
  20. index: '01',
  21. title: '上下文就绪',
  22. subtitle: 'CONTEXT READY',
  23. summary: '调度器装配需求、参考视频和拓展点位,并在模型运行前预创建唯一 run_id。',
  24. input: 'S / A 级需求、业务日期、参考视频、目的点 / 关键点 / 灵感点',
  25. output: 'FindDemandContext、输入快照、status=running 的运行记录',
  26. tools: ['调度器预创建(非 Agent Tool)'],
  27. constraints: ['没有有效点位的需求不进入运行', '同一业务日与需求只自动执行一次', '后续写库必须复用预创建 run_id'],
  28. tone: 'indigo',
  29. },
  30. {
  31. id: 'search',
  32. index: '02',
  33. title: '搜索召回',
  34. subtitle: 'SEARCH & RECALL',
  35. summary: '模型规划多个语义不同的搜索词,按关键词、翻页、标签或作者扩展候选。',
  36. input: '需求意图、参考标题、拓展点位、既有搜索前沿',
  37. output: '搜索页、原始搜索证据、带 candidate_id 的候选记录',
  38. tools: ['batch_search_and_record', 'douyin_search', 'douyin_search_tikhub', 'douyin_user_videos'],
  39. constraints: ['批量搜索最多 6 个任务、每项最多 2 页', '每页立即落库,不覆盖历史搜索', '同一视频被不同搜索命中时保留独立候选'],
  40. tone: 'blue',
  41. },
  42. {
  43. id: 'evidence',
  44. index: '03',
  45. title: '证据补全',
  46. subtitle: 'EVIDENCE',
  47. summary: '对高潜候选获取详情、内容点赞画像与作者粉丝画像,并统一标准化年龄证据。',
  48. input: 'run_id、候选 candidate_ids、搜索阶段已有的基础信息',
  49. output: '详情证据、双侧画像、年龄标准化结果、刷新后的门禁状态',
  50. tools: ['douyin_detail', 'batch_fetch_portraits', 'get_content_fans_portrait', 'get_account_fans_portrait', 'normalize_age_portraits'],
  51. constraints: ['详情和批量画像单次最多 8 条', '优先使用 run_id + candidate_ids 新模式', '数据缺失是未知,不直接等于负证据'],
  52. tone: 'cyan',
  53. },
  54. {
  55. id: 'decision',
  56. index: '04',
  57. title: '评分与门禁',
  58. subtitle: 'DECISION GATE',
  59. summary: '模型判断 R / E / S / V,代码用冻结的 P0 规则快照决定候选能否进入主推荐。',
  60. input: '相关性、受众倾向、分享信号、时效、时长与互动证据',
  61. output: 'primary / rejected / pending_evaluation 与版本化门禁结果',
  62. tools: ['batch_update_video_discovery_candidates'],
  63. constraints: ['primary 必须通过 P0 门禁', '未补齐证据时自动回到 pending_evaluation', '门禁失败的 primary 自动重分类为 rejected'],
  64. tone: 'amber',
  65. },
  66. {
  67. id: 'verify',
  68. index: '05',
  69. title: '状态核对',
  70. subtitle: 'STATE VERIFY',
  71. summary: '重新从数据库读取运行、搜索和候选,判断继续搜索是否还能改变结果。',
  72. input: '当前 run_id 与已经持久化的全量执行轨迹',
  73. output: 'run / searches / candidates 的一致性快照',
  74. tools: ['query_video_discovery_state'],
  75. constraints: ['最终判断以数据库状态为准', '需要区分 primary_count 与 valid_primary_count', '剩余前沿无信息价值时才停止搜索'],
  76. tone: 'green',
  77. },
  78. {
  79. id: 'finish',
  80. index: '06',
  81. title: '完成与输出',
  82. subtitle: 'FINALIZE',
  83. summary: '归档未评估候选、计算业务结果,再由模型输出面向用户的执行摘要。',
  84. input: '最终候选池、意图摘要、停止原因',
  85. output: 'finished / failed 与 goal_met / partial / no_match / failed',
  86. tools: ['update_video_discovery_run_status'],
  87. constraints: ['finished 会归档全部待评估候选', '有效 primary ≥ 5 才是 goal_met', '最终文本不能替代 outcome_status'],
  88. tone: 'violet',
  89. },
  90. ]
  91. const activeStageId = ref('search')
  92. const activeStage = computed(() => stages.find((stage) => stage.id === activeStageId.value) ?? stages[0])
  93. const dataStores = [
  94. { name: 'RUN', table: 'video_discovery_run', detail: '运行状态 · 业务结果 · 规则快照' },
  95. { name: 'SEARCH', table: 'video_discovery_search', detail: '搜索词 · 父子关系 · 分页状态' },
  96. { name: 'CANDIDATE', table: 'video_discovery_candidate', detail: '候选证据 · R/E/S/V · 最终分池' },
  97. { name: 'EVIDENCE', table: 'video_discovery_evidence', detail: '搜索 / 详情 / 画像原始响应' },
  98. { name: 'GATE', table: 'video_discovery_gate_evaluation', detail: '规则版本 · 证据版本 · 失败原因' },
  99. ]
  100. </script>
  101. <template>
  102. <div class="framework-page">
  103. <header class="page-hero">
  104. <div class="hero-copy">
  105. <span class="eyebrow">FIND AGENT · SYSTEM MAP</span>
  106. <h1>寻找 Agent 框架流程</h1>
  107. <p>从标准需求输入到可追溯候选输出,展示模型循环、工具编排、证据门禁和持久化状态之间的关系。</p>
  108. <div class="hero-actions">
  109. <RouterLink to="/find-agent-records" class="primary-link">查看运行记录 <span>→</span></RouterLink>
  110. <a href="#stage-flow" class="secondary-link">浏览执行阶段</a>
  111. </div>
  112. </div>
  113. <div class="runtime-boundary" aria-label="运行边界">
  114. <span class="boundary-label">RUNTIME BOUNDARY</span>
  115. <div class="boundary-grid">
  116. <div><small>MODEL</small><strong>Gemini 3 Flash</strong></div>
  117. <div><small>REACT</small><strong>最多 60 轮</strong></div>
  118. <div><small>TIMEOUT</small><strong>默认 600 秒</strong></div>
  119. <div><small>TOOLS</small><strong>13 个注册工具</strong></div>
  120. </div>
  121. </div>
  122. </header>
  123. <section class="architecture-map" aria-labelledby="architecture-title">
  124. <div class="section-heading">
  125. <div>
  126. <span>01 · ORCHESTRATION</span>
  127. <h2 id="architecture-title">框架总览</h2>
  128. </div>
  129. <p>模型负责决定下一步,工具负责读取与写入事实,完成守卫负责阻止没有业务结果的提前结束。</p>
  130. </div>
  131. <div class="kernel-flow">
  132. <article class="boundary-node input-node">
  133. <div class="node-icon">IN</div>
  134. <div>
  135. <span>标准任务输入</span>
  136. <strong>需求 + 参考视频 + 拓展点位</strong>
  137. <small>调度器预创建 run_id</small>
  138. </div>
  139. </article>
  140. <span class="flow-connector" aria-hidden="true"><i /><b>→</b></span>
  141. <article class="agent-kernel">
  142. <header>
  143. <div>
  144. <span>FIND_AGENT KERNEL</span>
  145. <strong>LLM ReAct 控制器</strong>
  146. </div>
  147. <em>异步顺序工具调用</em>
  148. </header>
  149. <div class="react-loop" aria-label="ReAct 循环">
  150. <div><b>01</b><span>Reason</span><small>理解状态</small></div>
  151. <i>→</i>
  152. <div><b>02</b><span>Act</span><small>选择工具</small></div>
  153. <i>→</i>
  154. <div><b>03</b><span>Observe</span><small>解析结果</small></div>
  155. <i>↺</i>
  156. </div>
  157. <footer>
  158. <span><i class="dot model" />系统 Prompt</span>
  159. <span><i class="dot tool" />Tool Registry</span>
  160. <span><i class="dot guard" />Completion Guard</span>
  161. </footer>
  162. </article>
  163. <span class="flow-connector" aria-hidden="true"><i /><b>→</b></span>
  164. <article class="boundary-node output-node">
  165. <div class="node-icon">OUT</div>
  166. <div>
  167. <span>结构化业务结果</span>
  168. <strong>候选池 + 证据 + 结果状态</strong>
  169. <small>goal_met / partial / no_match</small>
  170. </div>
  171. </article>
  172. </div>
  173. <div class="guard-rail">
  174. <span>完成守卫</span>
  175. <i />
  176. <p><strong>status=finished</strong> 且存在业务结果时,模型的无工具回答才可成为最终输出。</p>
  177. </div>
  178. </section>
  179. <section id="stage-flow" class="stage-section" aria-labelledby="stage-title">
  180. <div class="section-heading">
  181. <div>
  182. <span>02 · BUSINESS FLOW</span>
  183. <h2 id="stage-title">六阶段执行链</h2>
  184. </div>
  185. <p>选择阶段查看真实输入、输出、注册工具与代码硬约束。</p>
  186. </div>
  187. <div class="stage-flow" role="tablist" aria-label="寻找 Agent 执行阶段">
  188. <template v-for="(stage, index) in stages" :key="stage.id">
  189. <button
  190. type="button"
  191. role="tab"
  192. :aria-selected="activeStageId === stage.id"
  193. :class="['stage-node', stage.tone, { active: activeStageId === stage.id }]"
  194. @click="activeStageId = stage.id"
  195. >
  196. <span class="stage-index">{{ stage.index }}</span>
  197. <small>{{ stage.subtitle }}</small>
  198. <strong>{{ stage.title }}</strong>
  199. <i>{{ stage.id === 'context' ? '调度入口' : `${stage.tools.length} 工具` }}</i>
  200. </button>
  201. <span v-if="index < stages.length - 1" class="stage-arrow" aria-hidden="true"><i />→</span>
  202. </template>
  203. </div>
  204. <article class="stage-detail" :class="activeStage.tone">
  205. <div class="detail-lead">
  206. <span>{{ activeStage.index }} / {{ activeStage.subtitle }}</span>
  207. <h3>{{ activeStage.title }}</h3>
  208. <p>{{ activeStage.summary }}</p>
  209. </div>
  210. <dl class="io-grid">
  211. <div>
  212. <dt><span>IN</span> 阶段输入</dt>
  213. <dd>{{ activeStage.input }}</dd>
  214. </div>
  215. <div>
  216. <dt><span>OUT</span> 阶段输出</dt>
  217. <dd>{{ activeStage.output }}</dd>
  218. </div>
  219. </dl>
  220. <div class="detail-columns">
  221. <section>
  222. <span class="detail-label">CALLABLE TOOLS</span>
  223. <div class="tool-list">
  224. <code v-for="tool in activeStage.tools" :key="tool">{{ tool }}</code>
  225. </div>
  226. </section>
  227. <section>
  228. <span class="detail-label">HARD CONSTRAINTS</span>
  229. <ul>
  230. <li v-for="constraint in activeStage.constraints" :key="constraint">{{ constraint }}</li>
  231. </ul>
  232. </section>
  233. </div>
  234. </article>
  235. </section>
  236. <section class="data-plane" aria-labelledby="data-title">
  237. <div class="section-heading compact">
  238. <div>
  239. <span>03 · DATA PLANE</span>
  240. <h2 id="data-title">证据与状态底座</h2>
  241. </div>
  242. <p>Agent Tools 不直接写 SQL,统一经过 Service 与 Repository 操作持久化实体。</p>
  243. </div>
  244. <div class="data-pipeline">
  245. <div class="service-gateway">
  246. <span>SERVICE GATEWAY</span>
  247. <strong>VideoDiscoveryService</strong>
  248. <small>事务 · 序列化 · 规则快照</small>
  249. </div>
  250. <span class="data-arrow">↓</span>
  251. <div class="store-grid">
  252. <article v-for="store in dataStores" :key="store.name">
  253. <span>{{ store.name }}</span>
  254. <strong>{{ store.table }}</strong>
  255. <small>{{ store.detail }}</small>
  256. </article>
  257. </div>
  258. </div>
  259. </section>
  260. <footer class="page-footnote">
  261. <span><i class="legend-dot model" />模型决策</span>
  262. <span><i class="legend-dot tool" />工具执行</span>
  263. <span><i class="legend-dot guard" />代码门禁</span>
  264. <p>当前实现 · 2026-08-04</p>
  265. </footer>
  266. </div>
  267. </template>
  268. <style scoped>
  269. .framework-page {
  270. --ink: #182033;
  271. --muted: #6d7586;
  272. --line: #e3e6ed;
  273. display: flex;
  274. max-width: 1480px;
  275. margin: 0 auto;
  276. padding: 0 4px 38px;
  277. flex-direction: column;
  278. gap: 22px;
  279. color: var(--ink);
  280. }
  281. .page-hero,
  282. .architecture-map,
  283. .stage-section,
  284. .data-plane {
  285. border: 1px solid var(--line);
  286. border-radius: 20px;
  287. background: rgba(255, 255, 255, .94);
  288. box-shadow: 0 14px 42px rgba(35, 43, 64, .055);
  289. }
  290. .page-hero {
  291. position: relative;
  292. display: grid;
  293. min-height: 260px;
  294. padding: 38px 42px;
  295. overflow: hidden;
  296. grid-template-columns: minmax(0, 1.15fr) minmax(420px, .85fr);
  297. align-items: center;
  298. gap: 38px;
  299. background:
  300. radial-gradient(circle at 88% 12%, rgba(103, 98, 207, .14), transparent 31%),
  301. linear-gradient(135deg, #fff 0 57%, #f8f8fc 57%);
  302. }
  303. .page-hero::after {
  304. position: absolute;
  305. inset: 0;
  306. background-image:
  307. linear-gradient(rgba(89, 98, 127, .045) 1px, transparent 1px),
  308. linear-gradient(90deg, rgba(89, 98, 127, .045) 1px, transparent 1px);
  309. background-size: 28px 28px;
  310. mask-image: linear-gradient(90deg, transparent 46%, #000);
  311. content: '';
  312. pointer-events: none;
  313. }
  314. .hero-copy,
  315. .runtime-boundary { position: relative; z-index: 1; }
  316. .eyebrow,
  317. .section-heading div > span,
  318. .boundary-label,
  319. .detail-label {
  320. color: #6c6bd0;
  321. font-size: 11px;
  322. font-weight: 800;
  323. letter-spacing: .12em;
  324. }
  325. .hero-copy h1 {
  326. margin: 8px 0 10px;
  327. font-size: clamp(30px, 3vw, 45px);
  328. line-height: 1.1;
  329. letter-spacing: -.035em;
  330. }
  331. .hero-copy > p {
  332. max-width: 650px;
  333. margin: 0;
  334. color: var(--muted);
  335. font-size: 15px;
  336. line-height: 1.8;
  337. }
  338. .hero-actions { display: flex; margin-top: 22px; align-items: center; gap: 12px; }
  339. .hero-actions a {
  340. display: inline-flex;
  341. height: 38px;
  342. padding: 0 15px;
  343. align-items: center;
  344. gap: 14px;
  345. border-radius: 9px;
  346. font-size: 13px;
  347. font-weight: 750;
  348. text-decoration: none;
  349. }
  350. .primary-link { background: #5e5dc8; color: #fff; }
  351. .primary-link:hover { background: #5352bb; }
  352. .secondary-link { border: 1px solid #dfe1e8; background: #fff; color: #697184; }
  353. .secondary-link:hover { border-color: #c9c9eb; color: #5555b7; }
  354. .runtime-boundary {
  355. padding: 20px;
  356. border: 1px solid rgba(98, 96, 194, .16);
  357. border-radius: 16px;
  358. background: rgba(255, 255, 255, .78);
  359. backdrop-filter: blur(9px);
  360. }
  361. .boundary-grid { display: grid; margin-top: 12px; grid-template-columns: 1fr 1fr; gap: 8px; }
  362. .boundary-grid div { padding: 12px; border-radius: 10px; background: rgba(244, 244, 250, .88); }
  363. .boundary-grid small,
  364. .boundary-grid strong { display: block; }
  365. .boundary-grid small { color: #9a9fac; font-size: 10px; font-weight: 800; letter-spacing: .08em; }
  366. .boundary-grid strong { margin-top: 4px; font-size: 14px; }
  367. .architecture-map,
  368. .stage-section,
  369. .data-plane { padding: 28px 30px; }
  370. .section-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
  371. .section-heading h2 { margin: 3px 0 0; font-size: 22px; letter-spacing: -.02em; }
  372. .section-heading > p { max-width: 610px; margin: 0; color: var(--muted); font-size: 13px; text-align: right; }
  373. .section-heading.compact { align-items: center; }
  374. .kernel-flow {
  375. display: grid;
  376. margin-top: 28px;
  377. grid-template-columns: minmax(190px, .8fr) 58px minmax(430px, 1.55fr) 58px minmax(200px, .8fr);
  378. align-items: center;
  379. }
  380. .boundary-node {
  381. display: flex;
  382. min-height: 126px;
  383. padding: 18px;
  384. align-items: center;
  385. gap: 13px;
  386. border: 1px solid #e4e6ed;
  387. border-radius: 14px;
  388. background: #fafbfc;
  389. }
  390. .boundary-node .node-icon {
  391. display: grid;
  392. width: 38px;
  393. height: 38px;
  394. flex: 0 0 auto;
  395. place-items: center;
  396. border-radius: 10px;
  397. background: #ececfa;
  398. color: #6261c5;
  399. font-size: 10px;
  400. font-weight: 900;
  401. }
  402. .boundary-node span,
  403. .boundary-node strong,
  404. .boundary-node small { display: block; }
  405. .boundary-node span { color: #8e95a3; font-size: 11px; font-weight: 800; }
  406. .boundary-node strong { margin-top: 5px; font-size: 14px; line-height: 1.45; }
  407. .boundary-node small { margin-top: 6px; color: #969dab; font-size: 11px; }
  408. .flow-connector { display: flex; align-items: center; color: #8d91b7; }
  409. .flow-connector i { height: 1px; flex: 1; background: #d9dbe6; }
  410. .flow-connector b { font-size: 18px; font-weight: 500; }
  411. .agent-kernel {
  412. overflow: hidden;
  413. border: 1px solid #cbcaee;
  414. border-radius: 16px;
  415. background: linear-gradient(145deg, #fbfbff, #f3f3fb);
  416. box-shadow: 0 10px 30px rgba(78, 76, 165, .08);
  417. }
  418. .agent-kernel > header { display: flex; padding: 15px 18px; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddddef; }
  419. .agent-kernel header span,
  420. .agent-kernel header strong { display: block; }
  421. .agent-kernel header span { color: #7170cd; font-size: 10px; font-weight: 850; letter-spacing: .1em; }
  422. .agent-kernel header strong { margin-top: 3px; font-size: 16px; }
  423. .agent-kernel header em { padding: 4px 8px; border-radius: 999px; background: #e8e8f8; color: #6665bd; font-size: 10px; font-style: normal; font-weight: 750; }
  424. .react-loop { display: grid; padding: 18px; grid-template-columns: 1fr 24px 1fr 24px 1fr 28px; align-items: center; }
  425. .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; }
  426. .react-loop > div b { color: #8f8ed5; font-size: 10px; }
  427. .react-loop > div span { margin-top: 2px; font-size: 13px; font-weight: 800; }
  428. .react-loop > div small { color: #969cab; font-size: 10px; }
  429. .react-loop > i { color: #9494bc; font-size: 17px; font-style: normal; text-align: center; }
  430. .agent-kernel > footer { display: flex; padding: 10px 18px; flex-wrap: wrap; gap: 15px; border-top: 1px solid #ddddef; color: #747b8a; font-size: 10px; }
  431. .agent-kernel footer span { display: inline-flex; align-items: center; gap: 5px; }
  432. .dot,
  433. .legend-dot { width: 7px; height: 7px; border-radius: 50%; }
  434. .model { background: #6665ce; }
  435. .tool { background: #2795be; }
  436. .guard { background: #df9c28; }
  437. .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; }
  438. .guard-rail > span { font-weight: 850; }
  439. .guard-rail > i { height: 1px; background: #ead7ad; }
  440. .guard-rail p { margin: 0; }
  441. .stage-flow { display: grid; margin-top: 28px; grid-template-columns: 1fr 22px 1fr 22px 1fr 22px 1fr 22px 1fr 22px 1fr; align-items: center; }
  442. .stage-node {
  443. position: relative;
  444. display: flex;
  445. min-width: 0;
  446. min-height: 116px;
  447. padding: 15px 13px;
  448. flex-direction: column;
  449. align-items: flex-start;
  450. border: 1px solid #e2e4ea;
  451. border-radius: 13px;
  452. background: #fafbfc;
  453. color: var(--ink);
  454. text-align: left;
  455. cursor: pointer;
  456. transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
  457. }
  458. .stage-node:hover { transform: translateY(-2px); border-color: #cfd2de; box-shadow: 0 8px 22px rgba(40, 48, 69, .06); }
  459. .stage-node.active { border-color: var(--tone); background: var(--tone-soft); box-shadow: 0 8px 24px var(--tone-shadow); }
  460. .stage-node.indigo { --tone: #6665ca; --tone-soft: #f1f1ff; --tone-shadow: rgba(89, 88, 192, .12); }
  461. .stage-node.blue { --tone: #497dcc; --tone-soft: #eff6ff; --tone-shadow: rgba(67, 117, 193, .12); }
  462. .stage-node.cyan { --tone: #2997ae; --tone-soft: #effafb; --tone-shadow: rgba(40, 143, 164, .12); }
  463. .stage-node.amber { --tone: #cf8b25; --tone-soft: #fff8ea; --tone-shadow: rgba(198, 132, 32, .12); }
  464. .stage-node.green { --tone: #369873; --tone-soft: #effaf6; --tone-shadow: rgba(49, 145, 108, .12); }
  465. .stage-node.violet { --tone: #845dbd; --tone-soft: #f7f1fd; --tone-shadow: rgba(121, 80, 174, .12); }
  466. .stage-index { display: grid; width: 25px; height: 21px; place-items: center; border-radius: 6px; background: #e9eaf0; color: #737b8a; font-size: 10px; font-weight: 900; }
  467. .stage-node.active .stage-index { background: var(--tone); color: #fff; }
  468. .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; }
  469. .stage-node strong { margin-top: 2px; font-size: 14px; }
  470. .stage-node > i { margin-top: auto; color: #9ca2ae; font-size: 10px; font-style: normal; }
  471. .stage-arrow { display: flex; align-items: center; color: #aaaeb9; font-size: 12px; }
  472. .stage-arrow i { height: 1px; flex: 1; background: #dadde4; }
  473. .stage-detail {
  474. --tone: #497dcc;
  475. --tone-soft: #eff6ff;
  476. display: grid;
  477. margin-top: 18px;
  478. padding: 22px;
  479. grid-template-columns: minmax(190px, .8fr) minmax(260px, 1.2fr) minmax(0, 1.35fr);
  480. gap: 22px;
  481. border: 1px solid color-mix(in srgb, var(--tone) 28%, #e2e4ea);
  482. border-radius: 15px;
  483. background: linear-gradient(135deg, var(--tone-soft), #fff 55%);
  484. }
  485. .stage-detail.indigo { --tone: #6665ca; --tone-soft: #f1f1ff; }
  486. .stage-detail.blue { --tone: #497dcc; --tone-soft: #eff6ff; }
  487. .stage-detail.cyan { --tone: #2997ae; --tone-soft: #effafb; }
  488. .stage-detail.amber { --tone: #cf8b25; --tone-soft: #fff8ea; }
  489. .stage-detail.green { --tone: #369873; --tone-soft: #effaf6; }
  490. .stage-detail.violet { --tone: #845dbd; --tone-soft: #f7f1fd; }
  491. .detail-lead > span { color: var(--tone); font-size: 10px; font-weight: 850; letter-spacing: .1em; }
  492. .detail-lead h3 { margin: 5px 0 6px; font-size: 20px; }
  493. .detail-lead p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.7; }
  494. .io-grid { display: grid; margin: 0; gap: 9px; }
  495. .io-grid div { padding: 11px 12px; border-left: 2px solid var(--tone); background: rgba(255, 255, 255, .72); }
  496. .io-grid dt { color: #7f8796; font-size: 10px; font-weight: 800; }
  497. .io-grid dt span { margin-right: 5px; color: var(--tone); }
  498. .io-grid dd { margin: 4px 0 0; font-size: 12px; line-height: 1.55; }
  499. .detail-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
  500. .detail-columns section { min-width: 0; }
  501. .tool-list { display: flex; margin-top: 9px; flex-wrap: wrap; gap: 6px; }
  502. .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; }
  503. .detail-columns ul { display: grid; margin: 9px 0 0; padding: 0; gap: 7px; list-style: none; }
  504. .detail-columns li { position: relative; padding-left: 13px; color: #626b7b; font-size: 11px; line-height: 1.45; }
  505. .detail-columns li::before { position: absolute; top: 6px; left: 0; width: 5px; height: 5px; border-radius: 50%; background: var(--tone); content: ''; }
  506. .data-pipeline { display: flex; margin-top: 24px; flex-direction: column; align-items: center; }
  507. .service-gateway { width: min(420px, 100%); padding: 13px 18px; border: 1px solid #d9dbea; border-radius: 12px; background: #f7f7fc; text-align: center; }
  508. .service-gateway span,
  509. .service-gateway strong,
  510. .service-gateway small { display: block; }
  511. .service-gateway span { color: #7675cc; font-size: 9px; font-weight: 850; letter-spacing: .12em; }
  512. .service-gateway strong { margin-top: 3px; font-size: 14px; }
  513. .service-gateway small { margin-top: 2px; color: #9298a5; font-size: 10px; }
  514. .data-arrow { color: #a4a8b4; line-height: 28px; }
  515. .store-grid { display: grid; width: 100%; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
  516. .store-grid article { min-width: 0; padding: 13px; border-top: 2px solid #6d6ccb; background: #f8f9fb; }
  517. .store-grid span,
  518. .store-grid strong,
  519. .store-grid small { display: block; }
  520. .store-grid span { color: #7372ca; font-size: 9px; font-weight: 900; letter-spacing: .09em; }
  521. .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; }
  522. .store-grid small { margin-top: 5px; color: #8b92a0; font-size: 10px; line-height: 1.4; }
  523. .page-footnote { display: flex; padding: 0 5px; align-items: center; gap: 17px; color: #8a91a0; font-size: 10px; }
  524. .page-footnote > span { display: inline-flex; align-items: center; gap: 6px; }
  525. .page-footnote p { margin: 0 0 0 auto; }
  526. @media (max-width: 1180px) {
  527. .kernel-flow { grid-template-columns: 1fr 35px minmax(390px, 1.4fr) 35px 1fr; }
  528. .stage-flow { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
  529. .stage-arrow { display: none; }
  530. .stage-detail { grid-template-columns: 1fr 1.4fr; }
  531. .detail-columns { grid-column: 1 / -1; }
  532. .store-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  533. }
  534. @media (max-width: 900px) {
  535. .page-hero { padding: 30px; grid-template-columns: 1fr; }
  536. .runtime-boundary { max-width: 620px; }
  537. .section-heading { align-items: flex-start; flex-direction: column; gap: 8px; }
  538. .section-heading > p { max-width: none; text-align: left; }
  539. .kernel-flow { grid-template-columns: 1fr; gap: 10px; }
  540. .flow-connector { width: 1px; height: 24px; margin: 0 auto; flex-direction: column; }
  541. .flow-connector i { width: 1px; }
  542. .flow-connector b { transform: rotate(90deg); }
  543. .guard-rail { grid-template-columns: auto 1fr; }
  544. .guard-rail p { grid-column: 1 / -1; }
  545. }
  546. @media (max-width: 700px) {
  547. .framework-page { gap: 14px; }
  548. .page-hero,
  549. .architecture-map,
  550. .stage-section,
  551. .data-plane { border-radius: 15px; }
  552. .page-hero,
  553. .architecture-map,
  554. .stage-section,
  555. .data-plane { padding: 22px 18px; }
  556. .hero-actions { align-items: stretch; flex-direction: column; }
  557. .hero-actions a { justify-content: center; }
  558. .boundary-grid { grid-template-columns: 1fr; }
  559. .react-loop { grid-template-columns: 1fr; gap: 6px; }
  560. .react-loop > i { transform: rotate(90deg); }
  561. .stage-flow { grid-template-columns: 1fr 1fr; }
  562. .stage-detail { padding: 17px; grid-template-columns: 1fr; }
  563. .detail-columns { grid-column: auto; grid-template-columns: 1fr; }
  564. .store-grid { grid-template-columns: 1fr 1fr; }
  565. .page-footnote { flex-wrap: wrap; }
  566. .page-footnote p { width: 100%; margin-left: 0; }
  567. }
  568. @media (max-width: 430px) {
  569. .stage-flow,
  570. .store-grid { grid-template-columns: 1fr; }
  571. }
  572. </style>