dsh-mega-chat-nav 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/LICENSE +201 -0
  2. package/README.en.md +149 -0
  3. package/README.md +149 -0
  4. package/cordis.patch.yml +4 -0
  5. package/lib/client/NavOverlay.js +35 -0
  6. package/lib/client/NavRail.js +12 -0
  7. package/lib/client/QuestionNavSettingsTab.js +26 -0
  8. package/lib/client/QuestionNavStrip.js +555 -0
  9. package/lib/client/components/ConfigPanel.js +14 -0
  10. package/lib/client/components/DotRail.js +62 -0
  11. package/lib/client/components/NavOverlay.js +121 -0
  12. package/lib/client/components/NavRail.js +97 -0
  13. package/lib/client/components/RailDispatch.js +22 -0
  14. package/lib/client/components/RailHost.js +79 -0
  15. package/lib/client/components/SearchBox.js +66 -0
  16. package/lib/client/components/SettingsPage.js +46 -0
  17. package/lib/client/components/StandaloneSettings.js +16 -0
  18. package/lib/client/components/codex/CodexRail.js +139 -0
  19. package/lib/client/components/deepseek/DeepseekRail.js +264 -0
  20. package/lib/client/components/minimal/MinimalRail.js +135 -0
  21. package/lib/client/components/minimal/cards.js +32 -0
  22. package/lib/client/components/minimal/dot.js +11 -0
  23. package/lib/client/components/minimal/hint.js +30 -0
  24. package/lib/client/components/minimal/paging.js +5 -0
  25. package/lib/client/components/minimal/quick-settings.js +28 -0
  26. package/lib/client/components/minimal/search.js +67 -0
  27. package/lib/client/components/minimal/useRail.js +716 -0
  28. package/lib/client/components/rail/cards.js +32 -0
  29. package/lib/client/components/rail/hint.js +30 -0
  30. package/lib/client/components/rail/paging.js +5 -0
  31. package/lib/client/components/rail/quick-settings.js +28 -0
  32. package/lib/client/components/rail/search.js +76 -0
  33. package/lib/client/components/rail/useRail.js +845 -0
  34. package/lib/client/components/settings/SettingsPage.js +46 -0
  35. package/lib/client/components/settings/SettingsTab.js +17 -0
  36. package/lib/client/components/settings/StandaloneSettings.js +16 -0
  37. package/lib/client/components/shared/Cascade.js +26 -0
  38. package/lib/client/components/shared/ConfigPanel.js +27 -0
  39. package/lib/client/components/shared/Dot.js +8 -0
  40. package/lib/client/components/shared/Hint.js +5 -0
  41. package/lib/client/components/shared/PagingButton.js +5 -0
  42. package/lib/client/components/shared/RailShell.js +78 -0
  43. package/lib/client/components/shared/SearchBox.js +63 -0
  44. package/lib/client/components/shared/hooks.js +427 -0
  45. package/lib/client/components/shared/types.js +1 -0
  46. package/lib/client/config.js +1 -0
  47. package/lib/client/engines/jump.js +34 -0
  48. package/lib/client/engines/useAtBottom.js +25 -0
  49. package/lib/client/engines/useJump.js +40 -0
  50. package/lib/client/engines/useScrollSpy.js +88 -0
  51. package/lib/client/hooks.js +11 -0
  52. package/lib/client/index.js +312 -0
  53. package/lib/client/jump.js +255 -0
  54. package/lib/client/locales.js +142 -0
  55. package/lib/client/pure.js +61 -0
  56. package/lib/client/settings.js +369 -0
  57. package/lib/client/virtual/VirtualList.js +21 -0
  58. package/lib/client.js +3213 -0
  59. package/lib/compat.js +145 -0
  60. package/lib/config.js +37 -0
  61. package/lib/core/active-dot.js +17 -0
  62. package/lib/core/align.js +9 -0
  63. package/lib/core/config.js +37 -0
  64. package/lib/core/focus.js +38 -0
  65. package/lib/core/jump.js +98 -0
  66. package/lib/core/logic/active-dot.js +16 -0
  67. package/lib/core/logic/focus.js +34 -0
  68. package/lib/core/logic/jump.js +188 -0
  69. package/lib/core/logic/nodes.js +59 -0
  70. package/lib/core/model/nav-item.js +1 -0
  71. package/lib/core/model/turns.js +73 -0
  72. package/lib/core/nav-item.js +1 -0
  73. package/lib/core/nodes.js +57 -0
  74. package/lib/core/page-size.js +7 -0
  75. package/lib/core/question-entry.js +1 -0
  76. package/lib/core/time.js +23 -0
  77. package/lib/core/turn-dots.js +85 -0
  78. package/lib/core/utils/measure.js +24 -0
  79. package/lib/core/utils/time.js +26 -0
  80. package/lib/index.js +64 -0
  81. package/lib/nav-item.js +1 -0
  82. package/lib/nodes.js +59 -0
  83. package/lib/projection.js +126 -0
  84. package/lib/schema.js +26 -0
  85. package/lib/search.js +285 -0
  86. package/lib/settings.js +89 -0
  87. package/lib/types/client/NavOverlay.d.ts +18 -0
  88. package/lib/types/client/NavRail.d.ts +7 -0
  89. package/lib/types/client/QuestionNavSettingsTab.d.ts +16 -0
  90. package/lib/types/client/QuestionNavStrip.d.ts +48 -0
  91. package/lib/types/client/components/ConfigPanel.d.ts +14 -0
  92. package/lib/types/client/components/DotRail.d.ts +15 -0
  93. package/lib/types/client/components/NavOverlay.d.ts +30 -0
  94. package/lib/types/client/components/NavRail.d.ts +16 -0
  95. package/lib/types/client/components/RailDispatch.d.ts +8 -0
  96. package/lib/types/client/components/RailHost.d.ts +27 -0
  97. package/lib/types/client/components/SearchBox.d.ts +9 -0
  98. package/lib/types/client/components/SettingsPage.d.ts +9 -0
  99. package/lib/types/client/components/StandaloneSettings.d.ts +13 -0
  100. package/lib/types/client/components/codex/CodexRail.d.ts +3 -0
  101. package/lib/types/client/components/deepseek/DeepseekRail.d.ts +3 -0
  102. package/lib/types/client/components/minimal/MinimalRail.d.ts +8 -0
  103. package/lib/types/client/components/minimal/cards.d.ts +19 -0
  104. package/lib/types/client/components/minimal/dot.d.ts +14 -0
  105. package/lib/types/client/components/minimal/hint.d.ts +11 -0
  106. package/lib/types/client/components/minimal/paging.d.ts +10 -0
  107. package/lib/types/client/components/minimal/quick-settings.d.ts +8 -0
  108. package/lib/types/client/components/minimal/search.d.ts +27 -0
  109. package/lib/types/client/components/minimal/useRail.d.ts +90 -0
  110. package/lib/types/client/components/rail/cards.d.ts +19 -0
  111. package/lib/types/client/components/rail/hint.d.ts +11 -0
  112. package/lib/types/client/components/rail/paging.d.ts +10 -0
  113. package/lib/types/client/components/rail/quick-settings.d.ts +8 -0
  114. package/lib/types/client/components/rail/search.d.ts +34 -0
  115. package/lib/types/client/components/rail/useRail.d.ts +132 -0
  116. package/lib/types/client/components/settings/SettingsPage.d.ts +9 -0
  117. package/lib/types/client/components/settings/SettingsTab.d.ts +6 -0
  118. package/lib/types/client/components/settings/StandaloneSettings.d.ts +13 -0
  119. package/lib/types/client/components/shared/Cascade.d.ts +14 -0
  120. package/lib/types/client/components/shared/ConfigPanel.d.ts +8 -0
  121. package/lib/types/client/components/shared/Dot.d.ts +17 -0
  122. package/lib/types/client/components/shared/Hint.d.ts +6 -0
  123. package/lib/types/client/components/shared/PagingButton.d.ts +10 -0
  124. package/lib/types/client/components/shared/RailShell.d.ts +43 -0
  125. package/lib/types/client/components/shared/SearchBox.d.ts +18 -0
  126. package/lib/types/client/components/shared/hooks.d.ts +64 -0
  127. package/lib/types/client/components/shared/types.d.ts +101 -0
  128. package/lib/types/client/config.d.ts +9 -0
  129. package/lib/types/client/engines/jump.d.ts +17 -0
  130. package/lib/types/client/engines/useAtBottom.d.ts +1 -0
  131. package/lib/types/client/engines/useJump.d.ts +16 -0
  132. package/lib/types/client/engines/useScrollSpy.d.ts +12 -0
  133. package/lib/types/client/hooks.d.ts +7 -0
  134. package/lib/types/client/index.d.ts +31 -0
  135. package/lib/types/client/jump.d.ts +47 -0
  136. package/lib/types/client/locales.d.ts +142 -0
  137. package/lib/types/client/pure.d.ts +21 -0
  138. package/lib/types/client/settings.d.ts +173 -0
  139. package/lib/types/client/virtual/VirtualList.d.ts +15 -0
  140. package/lib/types/compat.d.ts +44 -0
  141. package/lib/types/config.d.ts +44 -0
  142. package/lib/types/core/active-dot.d.ts +15 -0
  143. package/lib/types/core/align.d.ts +10 -0
  144. package/lib/types/core/config.d.ts +44 -0
  145. package/lib/types/core/focus.d.ts +24 -0
  146. package/lib/types/core/jump.d.ts +39 -0
  147. package/lib/types/core/logic/active-dot.d.ts +15 -0
  148. package/lib/types/core/logic/focus.d.ts +22 -0
  149. package/lib/types/core/logic/jump.d.ts +39 -0
  150. package/lib/types/core/logic/nodes.d.ts +48 -0
  151. package/lib/types/core/model/nav-item.d.ts +12 -0
  152. package/lib/types/core/model/turns.d.ts +30 -0
  153. package/lib/types/core/nav-item.d.ts +12 -0
  154. package/lib/types/core/nodes.d.ts +43 -0
  155. package/lib/types/core/page-size.d.ts +8 -0
  156. package/lib/types/core/question-entry.d.ts +12 -0
  157. package/lib/types/core/time.d.ts +4 -0
  158. package/lib/types/core/turn-dots.d.ts +29 -0
  159. package/lib/types/core/utils/measure.d.ts +12 -0
  160. package/lib/types/core/utils/time.d.ts +4 -0
  161. package/lib/types/index.d.ts +4 -0
  162. package/lib/types/nav-item.d.ts +12 -0
  163. package/lib/types/nodes.d.ts +48 -0
  164. package/lib/types/projection.d.ts +71 -0
  165. package/lib/types/schema.d.ts +37 -0
  166. package/lib/types/search.d.ts +42 -0
  167. package/lib/types/settings.d.ts +99 -0
  168. package/package.json +71 -0
  169. package/src/client/components/RailDispatch.tsx +27 -0
  170. package/src/client/components/codex/CodexRail.tsx +348 -0
  171. package/src/client/components/deepseek/DeepseekRail.tsx +522 -0
  172. package/src/client/components/minimal/MinimalRail.tsx +324 -0
  173. package/src/client/components/minimal/dot.tsx +32 -0
  174. package/src/client/components/rail/cards.tsx +102 -0
  175. package/src/client/components/rail/hint.tsx +50 -0
  176. package/src/client/components/rail/paging.tsx +26 -0
  177. package/src/client/components/rail/quick-settings.tsx +130 -0
  178. package/src/client/components/rail/search.tsx +147 -0
  179. package/src/client/components/rail/useRail.ts +906 -0
  180. package/src/client/components/settings/SettingsPage.tsx +198 -0
  181. package/src/client/components/settings/StandaloneSettings.tsx +28 -0
  182. package/src/client/components/shared/types.ts +99 -0
  183. package/src/client/index.ts +388 -0
  184. package/src/client/jump.ts +293 -0
  185. package/src/client/locales.ts +143 -0
  186. package/src/client/settings.ts +482 -0
  187. package/src/client/styles.css +1580 -0
  188. package/src/compat.ts +161 -0
  189. package/src/index.ts +66 -0
  190. package/src/projection.ts +174 -0
  191. package/src/search.ts +291 -0
  192. package/src/settings.ts +169 -0
package/lib/client.js ADDED
@@ -0,0 +1,3213 @@
1
+ window.__ModuleLoader__.load({
2
+ id: 'dsh-mega-chat-nav',
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ if (typeof document !== "undefined") {
7
+ try {
8
+ if (!document.getElementById('mgcn-nav-styles')) {
9
+ var mgcnStyle = document.createElement('style');
10
+ mgcnStyle.id = 'mgcn-nav-styles';
11
+ mgcnStyle.setAttribute('data-plugin', 'dsh-mega-chat-nav');
12
+ mgcnStyle.textContent = ".mgcn-rail {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n width: 44px;\n display: flex;\n flex-direction: column;\n align-items: center;\n box-sizing: border-box;\n background: transparent;\n /* Overlay: let pointer events pass through to the conversation beyond the\n dots, so the embedded minimap never blocks the chat. */\n pointer-events: none;\n}\n\n/* Right-anchored rail: release the CSS `left: 0` so the inline `right`\n (written by the layout-correction loop) owns the position. */\n.mgcn-railRight {\n left: auto;\n}\n\n/* The paging queue: holds a single .mgcn-list (the dot band plus the floating\n action controls as its children). Clamped to 60% of the conversation height\n and vertically centered (auto margins). The band re-enables pointer events;\n the wrapper stays pass-through. All action controls (search/settings/count\n + ▲/▼) float relative to the list via .mgcn-actions/.mgcn-pageUp/\n .mgcn-pageDown — never mixed into the dot flow. */\n.mgcn-queue {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 60%;\n margin: auto 0;\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n box-sizing: border-box;\n pointer-events: none;\n}\n\n/* The middle dot band: the only scrollable part of the queue. Scrolls from the\n top (overflow collapses the auto margins to 0). The native scrollbar is\n always hidden: this is a minimap paged by the ▲/▼ buttons and by\n wheel/trackpad, and overflow is signalled by the ▲/▼ triangles themselves\n (each appears only while its direction has more dots to reveal). */\n.mgcn-list {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n position: relative; /* 操作区定位上下文;无 overflow,悬浮件不被裁剪 */\n max-height: min(var(--mgcn-band-height, 210px), 60vh);\n display: flex;\n flex-direction: column;\n align-items: center;\n pointer-events: auto;\n}\n\n/* ---- 操作区:.mgcn-list 子级,相对条带悬浮(列表上方外侧竖排,不占列表高度) ---- */\n.mgcn-actions {\n position: absolute;\n bottom: calc(100% + 22px); /* 操作区:条带上缘外侧 22px(紧凑让位 ▲ 翻页位) */\n left: 50%;\n transform: translateX(-50%);\n z-index: 60;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n pointer-events: none; /* 容器穿透:事件只落在按钮层(隐藏态不挡 rail) */\n}\n.mgcn-actions .mgcn-gear-wrap {\n pointer-events: auto;\n}\n.mgcn-actions .mgcn-config-panel,\n.mgcn-actions .mgcn-search {\n pointer-events: auto; /* 浮层面板仍需接收 */\n}\n/* 翻页标记隐藏(配置 hide)或不可滚动时,▲/▼ 完全不渲染:\n 操作区不再预留翻页空位,改为紧贴条带上缘(6px 间隙) */\n.mgcn-listNoPage .mgcn-actions {\n bottom: calc(100% + 6px);\n}\n\n/* ▲/▼ 翻页:贴列表上下边缘外侧 */\n.mgcn-pageUp {\n position: absolute;\n top: -18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 60;\n pointer-events: auto;\n}\n.mgcn-pageDown {\n position: absolute;\n bottom: -18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 60;\n pointer-events: auto;\n}\n\n/* 圆点:默认实心灰点;被 hover 时变空心(品牌描边 + 透明底);聚焦/激活为实心品牌圆 */\n.mgcn-dot {\n flex: none;\n pointer-events: auto;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n padding: 0;\n background: var(--dsw-alias-border-l3, var(--dsw-alias-label-tertiary)); /* 实心 */\n border: 1.5px solid transparent;\n cursor: pointer;\n transition: transform 120ms ease, background 120ms ease, border-color 120ms ease;\n}\n/* 被 hover / 聚焦(级联选中):空心(品牌描边),配合 1.5 倍放大 */\n.mgcn-dot:hover,\n.mgcn-dot.mgcn-focused {\n background: transparent;\n border-color: var(--dsw-alias-brand-primary);\n}\n.mgcn-dot.mgcn-active {\n background: var(--dsw-alias-brand-primary);\n border-color: var(--dsw-alias-brand-primary);\n}\n/* 激活(阅读位置)节点:实心品牌圆 */\n.mgcn-dot.mgcn-current {\n background: var(--dsw-alias-brand-primary);\n border-color: var(--dsw-alias-brand-primary);\n}\n\n/* Click-paging feedback: dots revealed by a ▲/▼ click pop in with a staggered\n fade + rise/drop (per-dot animation-delay is set inline, stagger order *\n 35ms), traveling in the paging direction so the newly revealed batch reads\n as \"the click brought these in\". `both` fill holds the hidden `from` state\n during each dot's delay. */\n@keyframes mgcn-dotEnterFromBottom {\n from {\n opacity: 0;\n transform: translateY(7px) scale(0.4);\n }\n 60% {\n opacity: 1;\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n@keyframes mgcn-dotEnterFromTop {\n from {\n opacity: 0;\n transform: translateY(-7px) scale(0.4);\n }\n 60% {\n opacity: 1;\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.mgcn-dotEnterFromBottom {\n animation: mgcn-dotEnterFromBottom 320ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both;\n}\n.mgcn-dotEnterFromTop {\n animation: mgcn-dotEnterFromTop 320ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both;\n}\n@media (prefers-reduced-motion: reduce) {\n .mgcn-dotEnterFromBottom,\n .mgcn-dotEnterFromTop {\n animation: none;\n }\n}\n\n/* Question count, rendered just above the first dot (sits in the list gap). */\n.mgcn-count {\n flex: none;\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n color: var(--dsw-alias-label-tertiary);\n user-select: none;\n}\n\n/* The centered group: dot column, centered together (the list's auto\n margins center it when short; it scrolls as a unit when tall). */\n/* 滚动容器:圆点列在此滚动(.mgcn-list 只负责条带几何与操作区锚定) */\n.mgcn-dots {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 8px 0;\n pointer-events: auto;\n}\n.mgcn-dots::-webkit-scrollbar {\n width: 0;\n height: 0;\n display: none;\n}\n\n/* Paging triangles (▲ above the dot queue, ▼ below), styled like the dots:\n a plain triangle in the dot's quiet border color that lights up brand on\n hover. Clicking pages the band by DOT_PAGE_ROWS dots. The triangle is a\n pure CSS border triangle so it stays crisp at any DPI; the button box is a\n slightly larger transparent hit target. */\n.mgcn-navBtn {\n flex: none;\n pointer-events: auto;\n width: 14px;\n height: 12px;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.mgcn-navBtn::before {\n content: '';\n display: block;\n width: 0;\n height: 0;\n border-left: 5px solid transparent;\n border-right: 5px solid transparent;\n transition: border-color 120ms ease;\n}\n.mgcn-navUp::before {\n border-bottom: 7px solid var(--dsw-alias-border-l3);\n}\n.mgcn-navDown::before {\n border-top: 7px solid var(--dsw-alias-border-l3);\n}\n.mgcn-navUp:hover::before {\n border-bottom-color: var(--dsw-alias-brand-primary);\n}\n.mgcn-navDown:hover::before {\n border-top-color: var(--dsw-alias-brand-primary);\n}\n\n.mgcn-empty {\n padding: 10px 4px;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n text-align: center;\n word-break: break-word;\n}\n\n/* Transient jump-failure hint (portal-rendered near the rail top, so it never\n pushes the centered dot band or clips in the 44px-wide rail). */\n.mgcn-hint {\n position: fixed;\n z-index: 30;\n max-width: 260px;\n background: var(--dsw-alias-bg-layer-3);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);\n pointer-events: none;\n}\n.mgcn-hint--bottom {\n top: auto;\n bottom: 150px; /* 对话输入框上方 */\n display: flex;\n align-items: center;\n gap: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);\n}\n/* 加载点动画 */\n.mgcn-hint-dots {\n display: inline-flex;\n gap: 3px;\n flex-shrink: 0;\n}\n.mgcn-hint-dots i {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--dsw-alias-brand-primary);\n opacity: 0.35;\n animation: mgcnHintDot 900ms ease-in-out infinite;\n}\n.mgcn-hint-dots i:nth-child(2) { animation-delay: 150ms; }\n.mgcn-hint-dots i:nth-child(3) { animation-delay: 300ms; }\n@keyframes mgcnHintDot {\n 0%, 100% { opacity: 0.25; transform: translateY(0); }\n 50% { opacity: 1; transform: translateY(-2px); }\n}\n\n/* The vertical focus cascade: one crisp question card per window dot, stacked\n top-to-bottom without overlap, centered on the selected dot. The container\n stays pass-through; only the cards are interactive. */\n.mgcn-cascade {\n position: fixed;\n z-index: 20;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n pointer-events: none;\n}\n\n/* Right-aligned rail: the cascade opens to the LEFT of the rail, so the\n column hugs the rail by stacking its cards against the right edge. */\n.mgcn-cascadeRight {\n align-items: flex-end;\n}\n\n/* One focus question card (portal-rendered in the cascade). The four context\n cards use the base style: a clean solid surface, a quiet neutral border and\n a low shadow — crisp and readable, one visual step behind the focus card.\n The surface is the theme's TOP elevation layer (bg-layer-3, the same token\n DSH menus and popups use): these cards float above the conversation, and in\n dark themes the layer ramp gives them the raised-surface tint. Clicking\n jumps to the question, like the dot; hover raises the border to signal the\n card is interactive. */\n.mgcn-card {\n width: 380px;\n background: var(--dsw-alias-bg-layer-3);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n padding: 8px 12px 10px;\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n box-shadow: 0 1px 6px rgba(0, 0, 0, 0.10);\n white-space: normal;\n word-break: break-word;\n pointer-events: auto;\n cursor: pointer;\n transition: border-color 120ms ease, box-shadow 120ms ease;\n}\n.mgcn-card:hover {\n border-color: var(--dsw-alias-brand-primary);\n box-shadow: 0 3px 12px rgba(0, 0, 0, 0.18);\n color: var(--dsw-alias-label-primary);\n}\n\n/* The selected (center) card — the focus of the cascade: full-contrast text,\n a brand left accent bar (inset, so it follows the rounded corners) and a\n raised shadow lift it above the four context cards. */\n.mgcn-cardSelected {\n border-color: var(--dsw-alias-border-l1); /* 四边统一淡边框(原方案) */\n color: var(--dsw-alias-label-primary);\n box-shadow:\n inset 3px 0 0 0 var(--dsw-alias-brand-primary),\n 0 6px 20px rgba(0, 0, 0, 0.22);\n}\n.mgcn-cardSelected:hover {\n border-color: var(--dsw-alias-brand-primary);\n box-shadow:\n inset 3px 0 0 0 var(--dsw-alias-brand-primary),\n 0 6px 22px rgba(0, 0, 0, 0.26);\n}\n\n/* Right-aligned rail: the focus card's accent bar mirrors to the right edge\n (the side facing the rail). Declared after .mgcn-cardSelected:hover so the\n mirrored bar wins at equal specificity. */\n.mgcn-cardSelectedRight {\n border-color: var(--dsw-alias-border-l1); /* 镜像同:四边统一淡边框 */\n box-shadow:\n inset -3px 0 0 0 var(--dsw-alias-brand-primary),\n 0 6px 20px rgba(0, 0, 0, 0.22);\n}\n.mgcn-cardSelectedRight:hover {\n border-color: var(--dsw-alias-brand-primary);\n box-shadow:\n inset -3px 0 0 0 var(--dsw-alias-brand-primary),\n 0 6px 22px rgba(0, 0, 0, 0.26);\n}\n\n/* Turn label line at the top of the card (\"Turn 32\"). */\n.mgcn-cardHead {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.mgcn-cardFav {\n flex: none;\n border: none;\n background: transparent;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n cursor: pointer;\n padding: 0;\n line-height: 1;\n transition: color 120ms ease;\n}\n.mgcn-cardFav:hover { color: var(--dsw-alias-brand-primary); }\n.mgcn-cardFavOn { color: var(--dsw-alias-brand-primary); }\n.mgcn-cardMetrics {\n font-size: 11px;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n margin-bottom: 4px;\n}\n.mgcn-cardTitle {\n font-size: 11px;\n font-weight: 600;\n color: var(--dsw-alias-label-tertiary);\n margin: 0;\n}\n\n/* Question body; long multi-question turns scroll inside the card. The\n scrollbar stays themed: NO `scrollbar-width` here — a non-auto value makes\n Chromium/Safari drop the GUI's `::-webkit-scrollbar*` skin for this element\n (see ui-theme styles/scrollbar.mgcn-css). Firefox still gets a thin themed bar\n from the theme's `body *` rule, which reads the rebound tokens below. */\n.mgcn-cardBody {\n max-height: 96px;\n overflow-y: auto;\n /* Elevated surface (bg-layer-3): the scrollbar thumb takes the l2 elevation\n tokens, matching DSH menus/popovers. */\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\n /* 悬浮卡滚动条细一倍(8px → 4px):只覆盖 webkit 宽度,保留全局 thumb 皮肤;\n 不可设 scrollbar-width(非 auto 会使 Chromium/Safari 丢弃 ::-webkit-scrollbar 皮肤) */\n}\n.mgcn-cardBody::-webkit-scrollbar {\n width: 4px;\n height: 4px;\n}\n.mgcn-cardBody::-webkit-scrollbar-thumb {\n border-radius: 2px;\n}\n\n/* Clamped cascade-card text (the two neighbors on each side): N lines with\n ellipsis via -webkit-line-clamp (set inline). Inherits the card's text\n color (secondary on context cards, primary on the focus card). */\n.mgcn-cardClamp {\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n overflow: hidden;\n word-break: break-word;\n}\n\n/* Question sent-time line (HH:MM / MM-DD HH:MM / YYYY-MM-DD HH:MM). */\n.mgcn-cardTime {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Settings page (settings.mgcn-plugins.mgcn-tab). */\n.mgcn-settings {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 4px 0;\n}\n/* 配置项行:左右结构——左(名称+说明,左对齐)右(控件,垂直居中、右对齐) */\n.mgcn-settingsRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n width: 100%;\n}\n.mgcn-settingsLabel {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start; /* 左侧文本左对齐 */\n gap: 2px;\n text-align: left;\n}\n.mgcn-settingsTitle {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n margin: 0;\n}\n.mgcn-settingsDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n}\n/* 右侧控件区:垂直居中、贴右 */\n.mgcn-settingsControl {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n}\n.mgcn-settingsControl .mgcn-segmented {\n align-self: center; /* 覆盖全局 flex-start,保证垂直居中 */\n}\n.mgcn-segmented {\n display: inline-flex;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n overflow: hidden;\n align-self: flex-start;\n}\n.mgcn-segment {\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n line-height: 1;\n padding: 8px 16px;\n cursor: pointer;\n transition: background 120ms ease, color 120ms ease;\n}\n.mgcn-segment + .mgcn-segment {\n border-left: 1px solid var(--dsw-alias-border-l1);\n}\n.mgcn-segment:hover {\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n.mgcn-segmentActive,\n.mgcn-segmentActive:hover {\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-label-primary-inverted);\n}\n\n/* ---- ⚙ 组件内面板(高频配置) ---- */\n.mgcn-config-gear {\n pointer-events: auto;\n border: none;\n background: transparent;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n cursor: pointer;\n font-size: 12px;\n line-height: 1;\n padding: 2px 4px;\n border-radius: 6px;\n transition: color 120ms ease, background 120ms ease;\n}\n.mgcn-config-gear:hover {\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n.mgcn-gear-wrap {\n position: relative;\n display: inline-flex;\n}\n/* 面板打开方向随停靠侧:右对齐向左弹,左对齐向右弹 */\n.mgcn-gear-wrap--right .mgcn-config-panel {\n right: calc(100% + 8px);\n left: auto;\n}\n.mgcn-gear-wrap--left .mgcn-config-panel {\n left: calc(100% + 8px);\n right: auto;\n}\n.mgcn-config-panel {\n position: absolute;\n top: 0; /* 顶边与齿轮按钮对齐 */\n z-index: 62;\n width: max-content; /* 自适应宽度 */\n min-width: 200px;\n max-width: 320px;\n white-space: nowrap;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n border-radius: 12px;\n border: 1px solid var(--mgcn-border, var(--dsw-alias-border-l2));\n background: var(--mgcn-bg-raised, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--mgcn-shadow, var(--dsw-shadow-lv2));\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n font-size: 12px;\n pointer-events: auto;\n}\n.mgcn-config-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.mgcn-config-title {\n font-weight: 600;\n}\n.mgcn-config-close {\n border: none;\n background: transparent;\n color: var(--mgcn-fg-secondary, var(--dsw-alias-label-secondary));\n cursor: pointer;\n padding: 2px 4px;\n border-radius: 6px;\n font-size: 12px;\n}\n.mgcn-config-close:hover {\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n.mgcn-config-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.mgcn-config-label {\n color: var(--mgcn-fg-secondary, var(--dsw-alias-label-secondary));\n flex-shrink: 0;\n}\n/* ---- 快捷设置面板:mini 设计(紧凑尺寸) ---- */\n.mgcn-config-panel {\n min-width: 180px;\n max-width: 280px;\n gap: 6px;\n padding: 8px;\n border-radius: 10px;\n font-size: 11px;\n}\n.mgcn-config-panel .mgcn-config-title {\n font-size: 11px;\n}\n.mgcn-config-panel .mgcn-config-close {\n font-size: 10px;\n padding: 1px 3px;\n border-radius: 4px;\n}\n.mgcn-config-panel .mgcn-config-row {\n gap: 6px;\n}\n.mgcn-config-panel .mgcn-config-label {\n font-size: 11px;\n}\n.mgcn-config-panel .mgcn-segmented {\n border-radius: 6px;\n}\n.mgcn-config-panel .mgcn-segment {\n font-size: 11px;\n line-height: 1;\n padding: 4px 8px;\n}\n.mgcn-config-panel .mgcn-config-sep {\n margin: 1px 0;\n}\n\n/* ---- 浮现态(JS 状态驱动:进入 rail 立即显示、移出后延迟 1s 隐藏) ----\n 过渡设计:浮现时立即可见再淡入(基类 visibility 0s 即时切换);\n 隐藏时先淡出、160ms 后才 visibility:hidden(.mgcn-peek 侧延迟切换)。\n 元素仍占布局位(absolute/inline-flex),隐藏期间不可交互(visibility) */\n.mgcn-gear-wrap,\n.mgcn-count,\n.mgcn-pageUp,\n.mgcn-pageDown {\n transition: opacity 160ms ease, visibility 0s;\n}\n.mgcn-peek {\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: opacity 160ms ease, visibility 0s linear 160ms;\n}\n@media (prefers-reduced-motion: reduce) {\n .mgcn-gear-wrap,\n .mgcn-count,\n .mgcn-pageUp,\n .mgcn-pageDown {\n transition: none;\n }\n}\n\n/* ---- 搜索浮层 ---- */\n.mgcn-search {\n position: absolute;\n top: -18px; /* 面板与触发按钮垂直居中对齐 */\n z-index: 62;\n width: 300px;\n box-sizing: border-box; /* padding 计入宽度 */\n\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n border-radius: 12px;\n border: 1px solid var(--mgcn-border, var(--dsw-alias-border-l2));\n background: var(--mgcn-bg-raised, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--mgcn-shadow, var(--dsw-shadow-lv2));\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n font-size: 12px;\n pointer-events: auto;\n white-space: nowrap;\n}\n.mgcn-gear-wrap--right .mgcn-search {\n right: calc(100% + 8px);\n left: auto;\n}\n.mgcn-gear-wrap--left .mgcn-search {\n left: calc(100% + 8px);\n right: auto;\n}\n.mgcn-search-input {\n width: 100%;\n box-sizing: border-box; /* 100% + padding/border 不再溢出面板 */\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--mgcn-border, var(--dsw-alias-border-l2));\n background: var(--mgcn-bg, var(--dsw-alias-bg-layer-2));\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n font-size: 12px;\n outline: none;\n}\n.mgcn-search-input:focus {\n border-color: var(--mgcn-highlight, var(--dsw-alias-brand-primary));\n}\n.mgcn-search-busy {\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n font-size: 11px;\n}\n.mgcn-search-results {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 280px;\n overflow-y: auto;\n}\n.mgcn-search-hit {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 6px 8px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--mgcn-fg-secondary, var(--dsw-alias-label-secondary));\n text-align: left;\n font-size: 12px;\n cursor: pointer;\n}\n.mgcn-search-hit:hover {\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n/* 标题行:轮次 + 时间 */\n.mgcn-search-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 11px;\n font-weight: 600;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-search-titleLeft {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.mgcn-search-turn {\n flex-shrink: 0;\n}\n.mgcn-search-titleTime {\n font-weight: 400;\n}\n/* 正文行:角色 tag + 两行预览 */\n.mgcn-search-body {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n min-width: 0;\n}\n.mgcn-search-tag {\n flex-shrink: 0;\n margin-top: 1px;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 16px;\n font-weight: 600;\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-search-tag--user { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 22%, transparent); color: var(--dsw-alias-state-business-primary); }\n.mgcn-search-tag--assistant { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent); color: var(--dsw-alias-brand-primary); }\n.mgcn-search-tag--tool { background: color-mix(in srgb, var(--dsw-alias-state-warning) 20%, transparent); color: var(--dsw-alias-state-warning); }\n.mgcn-search-text {\n flex: 1;\n min-width: 0;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n white-space: normal;\n line-height: 17px;\n}\n.mgcn-search-mark {\n background: color-mix(in srgb, var(--mgcn-highlight, var(--dsw-alias-brand-primary)) 30%, transparent);\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n border-radius: 2px;\n padding: 0 1px;\n}\n/* 更早历史命中提示 */\n.mgcn-search-historic {\n padding: 4px 4px 0;\n font-size: 11px;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n/* 按钮图标(currentColor 随主题) */\n.mgcn-btn-icon {\n display: block;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-config-gear:hover .mgcn-btn-icon {\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n}\n/* 配置面板分隔 */\n.mgcn-config-sep {\n height: 1px;\n background: var(--mgcn-border, var(--dsw-alias-border-l2));\n margin: 2px 0;\n}\n/* 多选组(悬浮卡功能) */\n.mgcn-checkGroup {\n display: flex;\n gap: 10px;\n}\n.mgcn-checkItem {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n cursor: pointer;\n user-select: none;\n}\n.mgcn-checkItem input {\n accent-color: var(--dsw-alias-brand-primary);\n}\n.mgcn-favFilterOn {\n color: var(--dsw-alias-brand-primary) !important;\n}\n/* 滑块行(导航栏偏移) */\n.mgcn-sliderRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.mgcn-sliderRow input[type=\"range\"] {\n width: 120px;\n accent-color: var(--dsw-alias-brand-primary);\n}\n.mgcn-sliderValue {\n font-size: 11px;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n min-width: 28px;\n}\n/* ---- 手机模式:底部半宽搜索抽屉 ---- */\n.mgcn-drawerMask {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.35);\n z-index: 90;\n}\n.mgcn-searchDrawer {\n position: fixed;\n left: 0;\n bottom: 0;\n width: 100%;\n height: 70vh;\n background: var(--mgcn-bg-raised, var(--dsw-alias-bg-layer-3));\n border-radius: 14px 14px 0 0;\n box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.18);\n padding: 12px;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n z-index: 91;\n animation: mgcnDrawerUp 240ms ease-out;\n}\n/* 全部轮次列表(不搜索时显示) */\n.mgcn-drawerList {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin-top: 8px;\n}\n/* 抽屉 item:结构与样式对齐桌面搜索面板 item(.mgcn-search-hit) */\n.mgcn-drawerItem {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 6px 8px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--mgcn-fg-secondary, var(--dsw-alias-label-secondary));\n text-align: left;\n font-size: 12px;\n cursor: pointer;\n}\n.mgcn-drawerItem:hover {\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n.mgcn-drawerItemTitle {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 11px;\n font-weight: 600;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-drawerItemTurn {\n flex-shrink: 0;\n}\n.mgcn-drawerItemTime {\n font-weight: 400;\n}\n.mgcn-drawerItemText {\n min-width: 0;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n white-space: normal;\n line-height: 17px;\n}\n@keyframes mgcnDrawerUp {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n}\n.mgcn-searchDrawer .mgcn-search {\n position: static;\n width: 100%;\n padding: 0;\n border: none;\n background: transparent;\n box-shadow: none;\n flex: 1 1 auto; /* 撑满抽屉,形成受限高度链 */\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.mgcn-searchDrawer .mgcn-search-results {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n margin-top: 4px;\n}\n/* ---- 手机模式:搜索按钮(注入滚动容器,inline sticky 定位;无 padding、带边框) ---- */\n.mgcn-mobileSearch {\n padding: 3px;\n border: 1px solid var(--mgcn-border, var(--dsw-alias-border-l2));\n border-radius: 6px;\n background: var(--mgcn-bg, var(--dsw-alias-bg-layer-2));\n z-index: 60;\n pointer-events: auto;\n}\n\n/* ==================== codex 风格:刻度轨(时间线式) ====================\n 轨道语言:每轮一个短横刻度(mark),锚定在轨道外侧(远离对话的一侧)\n 并朝对话方向展开;刻度静止短灰、hover 波纹本尊展开为长亮条、\n 向两侧逐级收短(±1/±2/±3);阅读位置常亮、跳转目标品牌脉冲。\n 刻度色调 data-tone:soft=柔和(低对比)/ deep=深邃(高对比,默认)。\n 命名/变量全为 mgcn 体系内化。 */\n.mgcn-cxRail {\n width: 46px; /* 略宽于圆点轨:容纳刻度波纹展开 */\n}\n.mgcn-cxList {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n position: relative;\n padding: 8px 0;\n pointer-events: auto;\n}\n.mgcn-cxList::-webkit-scrollbar { width: 0; height: 0; display: none; }\n\n/* ---- 操作区与翻页提示对齐刻度列(刻度常态在 rail 外侧缘,锚点距边 7px + 半条 ≈ 13px;\n 操作按钮若居中于 rail 中轴会与短横刻度脱节、显得突兀——整体平移到刻度列中心。\n 左 rail:rail 中心 23px → 目标 13px(左移 10px);右 rail 镜像 → 33px(右移 10px) ---- */\n.mgcn-cxRail .mgcn-actions,\n.mgcn-cxRail .mgcn-pageUp,\n.mgcn-cxRail .mgcn-pageDown {\n margin-left: -10px;\n}\n.mgcn-cxRail.mgcn-railRight .mgcn-actions,\n.mgcn-cxRail.mgcn-railRight .mgcn-pageUp,\n.mgcn-cxRail.mgcn-railRight .mgcn-pageDown {\n margin-left: 10px;\n}\n.mgcn-cxRail .mgcn-config-gear {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n padding: 0;\n}\n\n\n/* 刻度槽:整行可点(14px 行高,与圆点轨行距一致——翻页/跟读共用几何) */\n.mgcn-cxSlot {\n display: block;\n position: relative;\n width: 100%;\n height: 14px;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n pointer-events: auto;\n border-radius: 3px;\n}\n.mgcn-cxSlot:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* 刻度几何(公共层,与色调无关):静止 12px / 阅读位 18px / hover 波纹 38px */\n.mgcn-cxRail {\n --mgcn-cx-width-idle: 12px;\n --mgcn-cx-width-current: 18px;\n --mgcn-cx-width-hover: 38px;\n}\n/* 色调变量:仅颜色与不透明度按 data-tone 切换(deep 高对比,为默认) */\n.mgcn-cxRail[data-tone='soft'] {\n --mgcn-cx-mark-bg: var(--dsw-alias-label-tertiary);\n --mgcn-cx-mark-idle-op: 0.4;\n --mgcn-cx-current-bg: var(--dsw-alias-label-primary);\n --mgcn-cx-current-op: 0.7;\n --mgcn-cx-ripple-bg: var(--dsw-alias-label-secondary);\n --mgcn-cx-ripple-op: 0.75;\n}\n.mgcn-cxRail[data-tone='deep'] {\n --mgcn-cx-mark-bg: var(--dsw-alias-label-secondary);\n --mgcn-cx-mark-idle-op: 0.62;\n --mgcn-cx-current-bg: var(--dsw-alias-brand-primary);\n --mgcn-cx-current-op: 0.92;\n --mgcn-cx-ripple-bg: var(--dsw-alias-label-primary);\n --mgcn-cx-ripple-op: 0.88;\n}\n/* 兼容缺省(未设置 data-tone):走深邃档 */\n.mgcn-cxRail:not([data-tone='soft']) {\n --mgcn-cx-mark-bg: var(--dsw-alias-label-secondary);\n --mgcn-cx-mark-idle-op: 0.62;\n --mgcn-cx-current-bg: var(--dsw-alias-brand-primary);\n --mgcn-cx-current-op: 0.92;\n --mgcn-cx-ripple-bg: var(--dsw-alias-label-primary);\n --mgcn-cx-ripple-op: 0.88;\n}\n\n/* 刻度条:锚定在外侧缘(rail 在对话左→锚左缘;右→锚右缘),朝对话方向展开 */\n.mgcn-cxMark {\n position: absolute;\n top: 50%;\n width: var(--mgcn-cx-mark-w, var(--mgcn-cx-width-idle, 12px));\n height: 3px;\n margin-top: -1.5px;\n border-radius: 2px;\n background: var(--mgcn-cx-mark-bg, var(--dsw-alias-label-secondary));\n opacity: var(--mgcn-cx-mark-o, var(--mgcn-cx-mark-idle-op, 0.62));\n transition: width 160ms cubic-bezier(0.22, 0.75, 0.18, 1), opacity 90ms ease-out, background 120ms ease-out;\n}\n.mgcn-rail:not(.mgcn-railRight) .mgcn-cxMark {\n left: 7px; /* rail 在对话左侧:刻度从左侧缘向右展开(朝对话) */\n transform-origin: left center;\n}\n.mgcn-railRight .mgcn-cxMark {\n right: 7px; /* rail 在对话右侧:刻度从右侧缘向左展开(朝对话) */\n transform-origin: right center;\n}\n/* 波纹邻居:较本尊淡一档(hover 级联为纯 CSS,JS 不再逐槽驱动) */\n.mgcn-cxSlot .mgcn-cxMark {\n background: var(--mgcn-cx-mark-bg, var(--dsw-alias-label-secondary));\n}\n/* 阅读位置(current):常亮品牌色、略长 */\n.mgcn-cxSlot.mgcn-cxCurrent .mgcn-cxMark {\n width: var(--mgcn-cx-width-current, 18px);\n background: var(--mgcn-cx-current-bg, var(--dsw-alias-brand-primary));\n opacity: var(--mgcn-cx-current-op, 0.92);\n}\n/* hover/focus 波纹:本尊 38px 品牌实色;邻居逐级收短 30/21/15px——\n 纯 CSS 级联(:has 现代浏览器),hover 零 React 状态更新 */\n.mgcn-cxSlot:hover .mgcn-cxMark,\n.mgcn-cxSlot:focus-visible .mgcn-cxMark {\n width: 38px;\n background: var(--dsw-alias-brand-primary);\n opacity: 1;\n}\n.mgcn-cxSlot:has(+ .mgcn-cxSlot:hover) .mgcn-cxMark,\n.mgcn-cxSlot:hover + .mgcn-cxSlot .mgcn-cxMark {\n width: 30px;\n}\n.mgcn-cxSlot:has(+ .mgcn-cxSlot + .mgcn-cxSlot:hover) .mgcn-cxMark,\n.mgcn-cxSlot:hover + .mgcn-cxSlot + .mgcn-cxSlot .mgcn-cxMark {\n width: 21px;\n}\n.mgcn-cxSlot:has(+ .mgcn-cxSlot + .mgcn-cxSlot + .mgcn-cxSlot:hover) .mgcn-cxMark,\n.mgcn-cxSlot:hover + .mgcn-cxSlot + .mgcn-cxSlot + .mgcn-cxSlot .mgcn-cxMark {\n width: 15px;\n}\n/* 悬浮卡聚焦中的目标槽(JS focus 保活):不随鼠标离开收回 */\n.mgcn-cxSlot.mgcn-cxFocused .mgcn-cxMark {\n width: 38px;\n background: var(--dsw-alias-brand-primary);\n opacity: 1;\n}\n/* 跳转目标:品牌色脉冲闪光(600ms 后还原为阅读高亮) */\n.mgcn-cxSlot.mgcn-cxJump .mgcn-cxMark {\n background: var(--dsw-alias-brand-primary);\n opacity: 1;\n animation: mgcn-cxPulse 600ms ease-in-out;\n}\n@keyframes mgcn-cxPulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n\n/* 翻页入场的错峰滑入(与圆点轨同节奏,key 内联 delay 由列表组件给) */\n@keyframes mgcn-cxEnterFromBottom {\n from {\n opacity: 0;\n transform: translateY(7px);\n }\n 60% { opacity: 1; }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@keyframes mgcn-cxEnterFromTop {\n from {\n opacity: 0;\n transform: translateY(-7px);\n }\n 60% { opacity: 1; }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.mgcn-cxEnterFromBottom { animation: mgcn-cxEnterFromBottom 320ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both; }\n.mgcn-cxEnterFromTop { animation: mgcn-cxEnterFromTop 320ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both; }\n@media (prefers-reduced-motion: reduce) {\n .mgcn-cxEnterFromBottom,\n .mgcn-cxEnterFromTop {\n animation: none;\n }\n}\n\n/* ==================== deepseek 风格:用户消息面板(JS 驱动展开) ====================\n 形态:常态 30px 窄轨(每轮行首 8×2 短横把手);mouseenter/leave 由 JS 切换\n .mgcn-dsOpen → rail 加宽到 280px 并浮现面板外观(不再用 CSS :hover)。\n 面板高度 = band 高度(queue 60% 居中 / band 档),不铺满 rail 全高。\n 面板头操作区独立一行;行 hover(JS)横线变收藏按钮。\n 交互守则:搜索就地态/设置浮层开启时,移出**不收起**——只有点击外部才关闭;\n ⚙ 点击 → rail 收起窄轨(.mgcn-dsSnap 无过渡)并弹通用设置浮层(band 角锚点)。 */\n.mgcn-dsRail {\n width: 30px; /* 常态:窄把手轨 */\n background: transparent;\n border: 1px solid transparent;\n border-radius: 12px;\n box-shadow: none;\n /* 收拢方向延迟 140ms:先等面板头淡出完毕再收窄,窄轨布局变化不可见 */\n transition: width 200ms cubic-bezier(0.22, 0.75, 0.18, 1) 140ms;\n}\n/* 展开方向不延迟:宽度先到位,面板头在其后(200ms 处)淡入 */\n.mgcn-dsRail.mgcn-dsOpen {\n transition: width 200ms cubic-bezier(0.22, 0.75, 0.18, 1);\n}\n/* JS 展开态:宽度 280 + 层级抬升(背景画在 band 面板,避免整高背景) */\n.mgcn-dsRail.mgcn-dsOpen {\n width: 280px;\n z-index: 20;\n /* (transition 见上方展开规则:不延迟宽度动画) */\n}\n/* band 布局:queue 60% 居中、list band 高 */\n.mgcn-dsRail .mgcn-queue {\n max-height: 60%;\n margin: auto 0;\n width: 100%;\n}\n.mgcn-dsRail .mgcn-list {\n max-height: min(var(--mgcn-band-height, 220px), 60vh);\n width: 100%;\n}\n\n/* ---- 面板容器:head(操作区行)+ body 纵向排列 ---- */\n.mgcn-dsPanel {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n pointer-events: auto;\n background: transparent !important; /* 折叠底子:防主题覆盖 */\n border: 1px solid transparent !important;\n border-radius: 12px;\n box-shadow: none !important;\n transition: background 160ms ease-out, border-color 160ms ease-out, box-shadow 160ms ease-out;\n}\n.mgcn-dsOpen .mgcn-dsPanel {\n background: var(--mgcn-bg-raised, var(--dsw-alias-bg-layer-3)) !important;\n border-color: var(--mgcn-border, var(--dsw-alias-border-l2)) !important;\n box-shadow: var(--mgcn-shadow, var(--dsw-shadow-lv2)) !important;\n}\n\n/* ---- 面板头:操作区独立一行(搜索 → 设置 → 收藏 → 计数) ---- */\n.mgcn-dsHead {\n flex: none;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 6px 8px;\n border-bottom: 1px solid var(--mgcn-border, var(--dsw-alias-border-l2));\n opacity: 0;\n visibility: hidden;\n transition: opacity 140ms ease-out, visibility 0s linear 140ms;\n}\n.mgcn-dsOpen .mgcn-dsHead {\n opacity: 1;\n visibility: visible;\n /* 等 rail 宽度动画(200ms)完成后再淡入:窄轨展开初期 flex 无自由空间,\n 计数/操作钮被压向左侧;若立即显现会随宽度增长从左飞向右 */\n transition: opacity 140ms ease-out 200ms, visibility 0s 200ms;\n}\n.mgcn-dsHead .mgcn-config-gear {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border-radius: 6px;\n}\n.mgcn-dsHead .mgcn-count {\n margin-left: auto;\n font-size: 12px;\n}\n.mgcn-dsHead .mgcn-dsModeOn {\n color: var(--dsw-alias-brand-primary);\n}\n.mgcn-dsHead .mgcn-dsModeOn .mgcn-btn-icon {\n color: var(--dsw-alias-brand-primary);\n}\n/* 搜索态:头部输入框占满操作区剩余空间 */\n.mgcn-dsSearchInput {\n flex: 1 1 auto;\n min-width: 0;\n box-sizing: border-box;\n height: 22px;\n padding: 0 8px;\n border-radius: 6px;\n border: 1px solid var(--mgcn-border, var(--dsw-alias-border-l2));\n background: var(--mgcn-bg, var(--dsw-alias-bg-layer-2));\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n font-size: 12px;\n outline: none;\n}\n.mgcn-dsSearchInput:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n/* ---- 就地搜索模式容器:纵向 flex 约束(min-height:0 + overflow:hidden),\n 列表超长只在内层 .mgcn-dsList 就地滚动,不外溢 band ---- */\n.mgcn-dsMode {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* ---- 面板体:消息列表(行高 30px / 文字 14px) ---- */\n.mgcn-dsList {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n padding: 4px 0 8px;\n pointer-events: auto;\n}\n.mgcn-dsList::-webkit-scrollbar { width: 0; height: 0; display: none; }\n.mgcn-dsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n height: 30px;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n pointer-events: auto;\n border-radius: 6px;\n white-space: nowrap;\n overflow: hidden;\n}\n/* 折叠态(rail 未展开):行内仅 mark,居中于窄轨 */\n.mgcn-dsRail:not(.mgcn-dsOpen) .mgcn-dsRow {\n justify-content: center;\n padding: 0;\n}\n/* JS 行 hover 高亮 */\n.mgcn-dsOpen .mgcn-dsRow.mgcn-dsRowHover {\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n.mgcn-dsMark {\n flex: none;\n width: 8px;\n height: 2px;\n margin-left: 8px;\n margin-right: 4px;\n border-radius: 1px;\n background: var(--dsw-alias-label-tertiary);\n opacity: 0.9;\n transition: background 120ms ease-out, opacity 120ms ease-out, width 120ms ease-out;\n}\n/* 行首收藏按钮(hover 行出现,替换 mark) */\n.mgcn-dsFav {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n margin-left: 4px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n transition: color 120ms ease-out, background 120ms ease-out;\n}\n.mgcn-dsFav:hover {\n color: var(--dsw-alias-brand-primary);\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n.mgcn-dsFavOn {\n color: var(--dsw-alias-state-warn-primary, var(--dsw-alias-brand-primary));\n}\n/* 行文本按钮:占满剩余、可跳转 */\n.mgcn-dsText {\n flex: 1 1 auto;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n}\n.mgcn-dsText:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n border-radius: 6px;\n}\n.mgcn-dsTextInner {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 14px;\n line-height: 30px;\n margin-right: 8px;\n color: var(--mgcn-fg-secondary, var(--dsw-alias-label-secondary));\n opacity: 0;\n visibility: hidden;\n transition: opacity 140ms ease-out, visibility 0s linear 140ms;\n}\n.mgcn-dsOpen .mgcn-dsTextInner {\n opacity: 1;\n visibility: visible;\n transition: opacity 140ms ease-out, visibility 0s;\n}\n.mgcn-dsOpen .mgcn-dsRow.mgcn-dsRowHover .mgcn-dsTextInner {\n color: var(--mgcn-fg, var(--dsw-alias-label-primary));\n}\n/* 阅读位置行:短横业务蓝且略长(deepseek 用户消息语义色)\n 长横线去掉靠文本一侧的 margin-right(外缘 ml 沿用短横线 8px),\n 加长方向贴近文本、文本列位置不额外偏移 */\n.mgcn-dsRow.mgcn-dsCurrent .mgcn-dsMark {\n background: var(--dsw-alias-state-business-primary);\n opacity: 1;\n width: 12px;\n margin-right: 0;\n}\n/* 跳转目标:短横品牌脉冲 */\n.mgcn-dsRow.mgcn-dsJump .mgcn-dsMark {\n background: var(--dsw-alias-brand-primary);\n opacity: 1;\n animation: mgcn-dsPulse 600ms ease-in-out;\n}\n@keyframes mgcn-dsPulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n/* ---- 就地搜索结果行 ---- */\n.mgcn-dsHit {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n width: 100%;\n min-height: 30px;\n padding: 6px 10px;\n border: none;\n background: transparent;\n cursor: pointer;\n text-align: left;\n border-radius: 6px;\n}\n.mgcn-dsHit:hover {\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n}\n.mgcn-dsHit .mgcn-dsMark {\n margin-top: 9px;\n}\n.mgcn-dsHitBody {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.mgcn-dsHitMeta {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n font-weight: 600;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-dsHitTag {\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 16px;\n font-weight: 600;\n background: var(--mgcn-hover-bg, var(--dsw-alias-interactive-bg-hover));\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-dsHitTag--user { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 22%, transparent); color: var(--dsw-alias-state-business-primary); }\n.mgcn-dsHitTag--assistant { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent); color: var(--dsw-alias-brand-primary); }\n.mgcn-dsHitTag--tool { background: color-mix(in srgb, var(--dsw-alias-state-warning) 20%, transparent); color: var(--dsw-alias-state-warning); }\n.mgcn-dsHitText {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n white-space: normal;\n font-size: 13px;\n line-height: 18px;\n color: var(--mgcn-fg-secondary, var(--dsw-alias-label-secondary));\n}\n.mgcn-dsNoHit {\n padding: 14px 10px;\n font-size: 12px;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n text-align: center;\n}\n\n/* ---- 搜索进行中:列表 loading 态(三点点动) ---- */\n.mgcn-dsLoadingRow {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n font-size: 12px;\n color: var(--mgcn-fg-dim, var(--dsw-alias-label-tertiary));\n}\n.mgcn-dsLoadingRow .mgcn-dsLoadingDots {\n display: inline-flex;\n gap: 3px;\n}\n.mgcn-dsLoadingRow .mgcn-dsLoadingDots i {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--dsw-alias-brand-primary);\n opacity: 0.35;\n animation: mgcnHintDot 900ms ease-in-out infinite;\n}\n.mgcn-dsLoadingRow .mgcn-dsLoadingDots i:nth-child(2) { animation-delay: 150ms; }\n.mgcn-dsLoadingRow .mgcn-dsLoadingDots i:nth-child(3) { animation-delay: 300ms; }\n\n/* ---- 右停靠镜像:rail 在对话右侧 → 短横把手贴面板右缘、操作区镜像 ----\n 左停靠常态:行 = [短横 | 文本],操作区靠面板左缘(与 rail 同侧);\n 右停靠镜像:行 = [文本 | 短横](flex-direction 反转),操作区靠右缘。 */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsRow,\n.mgcn-dsRail.mgcn-railRight .mgcn-dsHit {\n flex-direction: row-reverse;\n}\n/* 把手/收藏钮由 margin-left 改 margin-right(反转后贴右缘) */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsMark {\n margin-left: 4px;\n margin-right: 8px;\n}\n/* 右停靠长横线(当前行):去掉靠文本一侧的 margin-left,外缘 mr8 沿用 */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsRow.mgcn-dsCurrent .mgcn-dsMark {\n margin-left: 0;\n}\n.mgcn-dsRail.mgcn-railRight .mgcn-dsFav {\n margin-left: 0;\n margin-right: 4px;\n}\n/* 文本呼吸边同步镜像:文本内容区左缘内缩(原 margin-right 反转) */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsTextInner {\n margin-right: 0;\n margin-left: 8px;\n}\n/* 命中行 mark 垂直偏移保留(行首 mark 现居右,对齐仍按行高) */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsHit .mgcn-dsMark {\n margin-top: 9px;\n}\n/* 面板头操作区镜像:搜索 → ⚙ → ⭐ 序列贴右缘,计数贴左缘 */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsHead {\n flex-direction: row-reverse;\n}\n.mgcn-dsRail.mgcn-railRight .mgcn-dsHead .mgcn-count {\n margin-left: 0;\n margin-right: auto;\n}\n\n/* ---- 设置浮层(通用快捷设置面板,与 minimal/codex 同一实现) ----\n 点 ⚙ → rail 立即收起窄轨(.mgcn-dsSnap 关过渡、抬层级),设置面板挂在 band 盒\n 角上的 0×0 锚点(.mgcn-dsFloat),按停靠侧向对话区弹出;开启期间 hover 不展开,\n 只有点击外部 / 面板 ✕ 才关闭。 */\n.mgcn-dsRail.mgcn-dsSnap {\n transition: none;\n z-index: 30;\n}\n.mgcn-dsRail .mgcn-dsFloat {\n position: absolute; /* 0×0 锚点:.mgcn-dsPanel 同盒(band 顶角) */\n top: 0;\n width: 0;\n height: 0;\n pointer-events: none; /* 锚点本身不接事件;面板内按钮照常 */\n}\n/* 停靠侧:右停靠 → 面板向左(对话侧)弹;左停靠 → 向右弹 */\n.mgcn-dsRail.mgcn-railRight .mgcn-dsFloat {\n left: 0;\n right: auto;\n}\n.mgcn-dsRail:not(.mgcn-railRight) .mgcn-dsFloat {\n left: 100%;\n right: auto;\n}\n.mgcn-dsFloat .mgcn-config-panel {\n pointer-events: auto;\n}\n\n/* 手机抽屉顶部输入行 */\n.mgcn-drawerInput {\n flex: none;\n padding: 0 0 6px;\n}\n@media (prefers-reduced-motion: reduce) {\n .mgcn-dsRail {\n transition: none;\n }\n}";
13
+ document.head.appendChild(mgcnStyle);
14
+ }
15
+ } catch (e) { }
16
+ }
17
+ const PKG_VERSION = '0.1.1';
18
+ "use strict";
19
+ var __defProp = Object.defineProperty;
20
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
21
+ var __getOwnPropNames = Object.getOwnPropertyNames;
22
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (let key of __getOwnPropNames(from))
30
+ if (!__hasOwnProp.call(to, key) && key !== except)
31
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ }
33
+ return to;
34
+ };
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // src/client/index.ts
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ apply: () => apply,
41
+ inject: () => inject
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/client/components/RailDispatch.tsx
46
+ var import_react9 = require("react");
47
+
48
+ // src/client/settings.ts
49
+ var STYLE_OPTIONS = ["minimal", "codex", "deepseek"];
50
+ var ALIGN_OPTIONS = ["left", "right"];
51
+ var BAND_OPTIONS = ["compact", "standard", "tall"];
52
+ var SCROLL_OPTIONS = ["smooth", "instant"];
53
+ var SHOW_OPTIONS = ["show", "peek", "hide"];
54
+ var DEFAULT_PAGING = {
55
+ minimal: "show",
56
+ codex: "hide",
57
+ deepseek: "peek"
58
+ };
59
+ var SEARCH_SCOPE_OPTIONS = ["user", "assistant", "tool"];
60
+ var DEFAULT_SEARCH_SCOPES = ["user", "assistant", "tool"];
61
+ var CARD_OPTIONS = [1, 3, 5];
62
+ var DEFAULT_CARD_COUNT = {
63
+ minimal: 3,
64
+ codex: 1,
65
+ deepseek: 1
66
+ };
67
+ var CARD_ITEM_OPTIONS = ["turn", "duration", "favorite"];
68
+ var DEFAULT_CARD_ITEMS = ["turn", "duration", "favorite"];
69
+ var MARK_TONE_OPTIONS = ["soft", "deep"];
70
+ var DEFAULT_MARK_TONE = "deep";
71
+ var DEFAULT_STYLE = "minimal";
72
+ var DEFAULT_ALIGN = "left";
73
+ var DEFAULT_BAND = "standard";
74
+ var DEFAULT_SCROLL = "smooth";
75
+ var DEFAULT_SHOW = "show";
76
+ var BAND_HEIGHT_PX = {
77
+ minimal: { compact: 144, standard: 204, tall: 274 },
78
+ codex: { compact: 140, standard: 210, tall: 280 },
79
+ deepseek: { compact: 160, standard: 220, tall: 280 }
80
+ };
81
+ var STYLE_CAPABILITIES = {
82
+ minimal: { hoverCards: true, paging: true },
83
+ codex: { hoverCards: true, paging: true },
84
+ deepseek: { hoverCards: false, paging: false }
85
+ };
86
+ var MGCN_NS = "mega-chat-nav";
87
+ var RAIL_OFFSET_MIN = 0;
88
+ var RAIL_OFFSET_MAX = 32;
89
+ var DEFAULT_RAIL_OFFSET = 8;
90
+ function isRailAlign(value) {
91
+ return ALIGN_OPTIONS.includes(value);
92
+ }
93
+ function isNavStyle(value) {
94
+ return STYLE_OPTIONS.includes(value);
95
+ }
96
+ function coerceShowMode(value) {
97
+ if (typeof value === "boolean") return value ? "show" : "hide";
98
+ return isShowMode(value) ? value : DEFAULT_SHOW;
99
+ }
100
+ function coercePagingMode(value, fallback) {
101
+ if (typeof value === "boolean") return value ? "show" : "hide";
102
+ return isShowMode(value) ? value : fallback;
103
+ }
104
+ function isShowMode(value) {
105
+ return SHOW_OPTIONS.includes(value);
106
+ }
107
+ function isCardCount(value) {
108
+ return CARD_OPTIONS.includes(value);
109
+ }
110
+ function isBandHeight(value) {
111
+ return BAND_OPTIONS.includes(value);
112
+ }
113
+ function isSearchScopes(value) {
114
+ return Array.isArray(value) && value.every((s) => SEARCH_SCOPE_OPTIONS.includes(s));
115
+ }
116
+ function isCardItems(value) {
117
+ return Array.isArray(value) && value.every((item) => CARD_ITEM_OPTIONS.includes(item));
118
+ }
119
+ function isMarkTone(value) {
120
+ return MARK_TONE_OPTIONS.includes(value);
121
+ }
122
+ function isScrollMode(value) {
123
+ return SCROLL_OPTIONS.includes(value);
124
+ }
125
+ var NavSettingsController = class {
126
+ scope;
127
+ listeners = /* @__PURE__ */ new Set();
128
+ unsubscribe = () => {
129
+ };
130
+ state = {
131
+ style: DEFAULT_STYLE,
132
+ align: DEFAULT_ALIGN,
133
+ bandHeight: DEFAULT_BAND,
134
+ scrollBehavior: DEFAULT_SCROLL,
135
+ showCount: DEFAULT_SHOW,
136
+ showSearch: DEFAULT_SHOW,
137
+ showQuickSettings: DEFAULT_SHOW,
138
+ showFavorites: DEFAULT_SHOW,
139
+ showPaging: DEFAULT_PAGING.minimal,
140
+ searchScopes: [...DEFAULT_SEARCH_SCOPES],
141
+ railOffset: DEFAULT_RAIL_OFFSET,
142
+ cardCount: DEFAULT_CARD_COUNT[DEFAULT_STYLE],
143
+ cardItems: [...DEFAULT_CARD_ITEMS],
144
+ markTone: DEFAULT_MARK_TONE,
145
+ overridden: false
146
+ };
147
+ /**
148
+ * Bind the namespace scope once the settings surface is present.
149
+ * A no-op after the first bind.
150
+ */
151
+ attach(binder) {
152
+ if (this.scope !== void 0) return;
153
+ this.scope = binder.bind({ namespace: MGCN_NS });
154
+ this.state = this.derive(this.scope.getSnapshot());
155
+ this.unsubscribe = this.scope.subscribe(() => {
156
+ if (this.scope === void 0) return;
157
+ const next = this.derive(this.scope.getSnapshot());
158
+ if (next.style === this.state.style && next.align === this.state.align && next.bandHeight === this.state.bandHeight && next.cardCount === this.state.cardCount && next.scrollBehavior === this.state.scrollBehavior && next.showCount === this.state.showCount && next.showSearch === this.state.showSearch && next.showQuickSettings === this.state.showQuickSettings && next.overridden === this.state.overridden) return;
159
+ this.state = next;
160
+ for (const listener of this.listeners) listener();
161
+ });
162
+ }
163
+ /** 当前生效风格的 styles.<style> 段(快照内读取,供逐风格独立取值) */
164
+ styleSection(snapshot, style) {
165
+ return snapshot.value?.styles?.[style];
166
+ }
167
+ derive(snapshot) {
168
+ const user = snapshot.user;
169
+ const general = snapshot.value?.general;
170
+ const ready = snapshot.status === "ready";
171
+ const style = ready && isNavStyle(general?.style) ? general.style : DEFAULT_STYLE;
172
+ const active = this.styleSection(snapshot, style);
173
+ return {
174
+ style,
175
+ align: ready && isRailAlign(general?.align) ? general.align : DEFAULT_ALIGN,
176
+ bandHeight: ready && isBandHeight(active?.bandHeight) ? active.bandHeight : DEFAULT_BAND,
177
+ cardCount: ready && isCardCount(active?.cardCount) ? active.cardCount : DEFAULT_CARD_COUNT[style],
178
+ cardItems: ready && isCardItems(active?.cardItems) ? active.cardItems : [...DEFAULT_CARD_ITEMS],
179
+ markTone: ready && isMarkTone(active?.markTone) ? active.markTone : DEFAULT_MARK_TONE,
180
+ scrollBehavior: ready && isScrollMode(general?.scrollBehavior) ? general.scrollBehavior : DEFAULT_SCROLL,
181
+ showCount: ready ? coerceShowMode(general?.showCount) : DEFAULT_SHOW,
182
+ showSearch: ready ? coerceShowMode(general?.showSearch) : DEFAULT_SHOW,
183
+ showQuickSettings: ready ? coerceShowMode(general?.showQuickSettings) : DEFAULT_SHOW,
184
+ showFavorites: ready ? coerceShowMode(general?.showFavorites) : DEFAULT_SHOW,
185
+ showPaging: ready ? coercePagingMode(active?.showPaging, DEFAULT_PAGING[style]) : DEFAULT_PAGING[style],
186
+ searchScopes: ready && isSearchScopes(general?.searchScopes) ? general.searchScopes : [...DEFAULT_SEARCH_SCOPES],
187
+ railOffset: ready && typeof general?.railOffset === "number" && general.railOffset >= 0 && general.railOffset <= 32 ? Math.round(general.railOffset) : DEFAULT_RAIL_OFFSET,
188
+ overridden: user !== void 0 && (user.general !== void 0 || user.styles !== void 0)
189
+ };
190
+ }
191
+ /** Release the scope subscription. */
192
+ dispose() {
193
+ this.unsubscribe();
194
+ this.listeners.clear();
195
+ }
196
+ /** 乐观提交:更新状态并立即通知(UI 即时反映) */
197
+ commit(next) {
198
+ this.state = next;
199
+ for (const listener of this.listeners) listener();
200
+ }
201
+ /** 当前 general 段(setter 整段写回用) */
202
+ get general() {
203
+ return {
204
+ style: this.state.style,
205
+ align: this.state.align,
206
+ scrollBehavior: this.state.scrollBehavior,
207
+ showCount: this.state.showCount,
208
+ showSearch: this.state.showSearch,
209
+ showQuickSettings: this.state.showQuickSettings,
210
+ showFavorites: this.state.showFavorites,
211
+ searchScopes: this.state.searchScopes,
212
+ railOffset: this.state.railOffset
213
+ };
214
+ }
215
+ /** 当前风格段的完整值(setter 局部 patch 的基础) */
216
+ get styleValues() {
217
+ return {
218
+ bandHeight: this.state.bandHeight,
219
+ cardCount: this.state.cardCount,
220
+ cardItems: this.state.cardItems,
221
+ markTone: this.state.markTone,
222
+ showPaging: this.state.showPaging
223
+ };
224
+ }
225
+ /** 按当前风格构造 write 的 styles 段(只含当前风格键,绝不染指其他风格段) */
226
+ stylePatch(extra) {
227
+ const section = { ...this.styleValues, ...extra };
228
+ const patch = {};
229
+ patch[this.state.style] = section;
230
+ return patch;
231
+ }
232
+ /** @returns the current state (stable reference until the next change). */
233
+ getSnapshot() {
234
+ return this.state;
235
+ }
236
+ /** Observe state replacements; returns the disposer. */
237
+ subscribe(listener) {
238
+ this.listeners.add(listener);
239
+ return () => {
240
+ this.listeners.delete(listener);
241
+ };
242
+ }
243
+ /**
244
+ * 写回宿主:scope.set 契约只接受 section 内**标量**字段,嵌套 section
245
+ * (general/styles 对象)无法经它持久化,所以走 host 桥
246
+ * (POST /mega-chat-nav/settings → settings.update 深层 merge + revision 围栏)。
247
+ */
248
+ write(patch) {
249
+ if (this.scope === void 0) return;
250
+ const revision = this.scope.getSnapshot().revision;
251
+ void fetch("/mega-chat-nav/settings", {
252
+ method: "POST",
253
+ headers: { "Content-Type": "application/json" },
254
+ body: JSON.stringify({ patch, revision: revision ?? void 0 })
255
+ }).then((resp) => resp.json().catch(() => ({}))).then((data) => {
256
+ if (!data.ok) console.error("[mega-chat-nav] settings write rejected");
257
+ }).catch((e) => console.error("[mega-chat-nav] settings write failed:", e));
258
+ }
259
+ /** Route the user's anchor-edge choice to the Host document. */
260
+ setAlign(align) {
261
+ if (this.scope === void 0) return;
262
+ this.commit({ ...this.state, align });
263
+ this.write({ general: { ...this.general, align } });
264
+ }
265
+ /** Route the user's style choice to the Host document(切换后随即载入该风格自己的独立配置) */
266
+ setStyle(style) {
267
+ if (this.scope === void 0) return;
268
+ const snapshot = this.scope.getSnapshot();
269
+ const next = this.derive({ ...snapshot, value: { ...snapshot.value, general: { ...snapshot.value?.general, style } } });
270
+ this.commit({ ...next, overridden: this.state.overridden });
271
+ this.write({ general: { ...this.general, style } });
272
+ }
273
+ /** Route the user's band-height choice to the Host document(写入当前风格的独立段) */
274
+ setBandHeight(band) {
275
+ if (this.scope === void 0) return;
276
+ this.commit({ ...this.state, bandHeight: band });
277
+ this.write({ styles: this.stylePatch({ bandHeight: band }) });
278
+ }
279
+ /** Route the user's scroll-mode choice to the Host document. */
280
+ setScrollMode(mode) {
281
+ if (this.scope === void 0) return;
282
+ this.commit({ ...this.state, scrollBehavior: mode });
283
+ this.write({ general: { ...this.general, scrollBehavior: mode } });
284
+ }
285
+ /** Route the user's count-badge display mode to the Host document. */
286
+ setShowCount(mode) {
287
+ if (this.scope === void 0) return;
288
+ this.commit({ ...this.state, showCount: mode });
289
+ this.write({ general: { ...this.general, showCount: mode } });
290
+ }
291
+ /** Route the user's search-button display mode to the Host document. */
292
+ setShowSearch(mode) {
293
+ if (this.scope === void 0) return;
294
+ this.commit({ ...this.state, showSearch: mode });
295
+ this.write({ general: { ...this.general, showSearch: mode } });
296
+ }
297
+ /** Route the user's quick-settings display mode to the Host document. */
298
+ setShowQuickSettings(mode) {
299
+ if (this.scope === void 0) return;
300
+ this.commit({ ...this.state, showQuickSettings: mode });
301
+ this.write({ general: { ...this.general, showQuickSettings: mode } });
302
+ }
303
+ /** Route the user's favorite-button display mode to the Host document. */
304
+ setShowFavorites(mode) {
305
+ if (this.scope === void 0) return;
306
+ this.commit({ ...this.state, showFavorites: mode });
307
+ this.write({ general: { ...this.general, showFavorites: mode } });
308
+ }
309
+ /** Route the user's paging-marker display mode to the Host document(写入当前风格独立段) */
310
+ setShowPaging(mode) {
311
+ if (this.scope === void 0) return;
312
+ this.commit({ ...this.state, showPaging: mode });
313
+ this.write({ styles: this.stylePatch({ showPaging: mode }) });
314
+ }
315
+ /** Route the user's search-scope selection to the Host document(用户必选,强制保留且置于首位) */
316
+ setSearchScopes(scopes) {
317
+ if (this.scope === void 0) return;
318
+ const normalized = ["user", ...scopes.filter((s) => s !== "user")];
319
+ this.commit({ ...this.state, searchScopes: normalized });
320
+ this.write({ general: { ...this.general, searchScopes: normalized } });
321
+ }
322
+ /** Route the user's rail-offset choice to the Host document(0-32px 取整钳制) */
323
+ setRailOffset(offset) {
324
+ if (this.scope === void 0) return;
325
+ const clamped = Math.max(RAIL_OFFSET_MIN, Math.min(RAIL_OFFSET_MAX, Math.round(offset)));
326
+ this.commit({ ...this.state, railOffset: clamped });
327
+ this.write({ general: { ...this.general, railOffset: clamped } });
328
+ }
329
+ /** Route the user's cascade-card count to the Host document(写入当前风格的独立段) */
330
+ setCardCount(count) {
331
+ if (this.scope === void 0) return;
332
+ this.commit({ ...this.state, cardCount: count });
333
+ this.write({ styles: this.stylePatch({ cardCount: count }) });
334
+ }
335
+ /** Route the user's card-content selection to the Host document(写入当前风格的独立段) */
336
+ setCardItems(items) {
337
+ if (this.scope === void 0) return;
338
+ this.commit({ ...this.state, cardItems: items });
339
+ this.write({ styles: this.stylePatch({ cardItems: items }) });
340
+ }
341
+ /** Route the user's mark-tone choice to the Host document(codex 刻度色调;写入当前风格段) */
342
+ setMarkTone(tone) {
343
+ if (this.scope === void 0) return;
344
+ this.commit({ ...this.state, markTone: tone });
345
+ this.write({ styles: this.stylePatch({ markTone: tone }) });
346
+ }
347
+ };
348
+
349
+ // src/client/components/minimal/MinimalRail.tsx
350
+ var import_react6 = require("react");
351
+
352
+ // src/client/components/rail/useRail.ts
353
+ var import_react = require("react");
354
+ function anchorKey(id) {
355
+ return "13:input-message" + String(id);
356
+ }
357
+ function projectedEntries(face) {
358
+ const value = face?.getSnapshot();
359
+ if (!Array.isArray(value)) return [];
360
+ return value.filter((item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.seq === "number" && typeof item.turn === "number");
361
+ }
362
+ function sameMarkers(a, b) {
363
+ if (a.length !== b.length) return false;
364
+ for (let i = 0; i < a.length; i++) {
365
+ if (a[i].key !== b[i].key || a[i].members.length !== b[i].members.length) return false;
366
+ for (let j = 0; j < a[i].members.length; j++) {
367
+ if (a[i].members[j] !== b[i].members[j]) return false;
368
+ }
369
+ }
370
+ return true;
371
+ }
372
+ function buildMarkers(projected, live) {
373
+ const seen = /* @__PURE__ */ new Set();
374
+ const merged = [];
375
+ for (const p of projected) {
376
+ const key = anchorKey(p.id);
377
+ if (seen.has(key)) continue;
378
+ seen.add(key);
379
+ merged.push({ turn: p.turn, seq: p.seq, key, time: p.time, text: p.text, metrics: p.metrics });
380
+ }
381
+ for (const l of live) {
382
+ if (seen.has(l.key)) continue;
383
+ seen.add(l.key);
384
+ merged.push({ turn: null, seq: l.anchorSeq, key: l.key, time: l.time, text: l.text });
385
+ }
386
+ merged.sort((a, b) => a.seq - b.seq);
387
+ const markers = [];
388
+ for (const m of merged) {
389
+ const tail = markers[markers.length - 1];
390
+ if (tail !== void 0 && tail.turn !== null && tail.turn === m.turn) {
391
+ markers[markers.length - 1] = { ...tail, texts: [...tail.texts, m.text], members: [...tail.members, m.key] };
392
+ } else {
393
+ markers.push({ turn: m.turn, key: m.key, seq: m.seq, time: m.time, texts: [m.text], members: [m.key], metrics: m.metrics });
394
+ }
395
+ }
396
+ return markers;
397
+ }
398
+ function useMarkerData(injected, useSessions) {
399
+ const current = useSessions?.((s) => s.current);
400
+ const summary = useSessions?.((s) => current === void 0 ? void 0 : s.byId?.[String(current)]);
401
+ const [viewOk, setViewOk] = (0, import_react.useState)(() => document.querySelector("[data-chat-flow]") !== null);
402
+ (0, import_react.useEffect)(() => {
403
+ const check = () => setViewOk(document.querySelector("[data-chat-flow]") !== null);
404
+ check();
405
+ const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(check);
406
+ observer?.observe(document.body, { childList: true, subtree: true });
407
+ return () => {
408
+ observer?.disconnect();
409
+ };
410
+ }, []);
411
+ const visible = current !== void 0 && summary !== void 0 && summary.blank !== true && viewOk;
412
+ const [markers, setMarkers] = (0, import_react.useState)([]);
413
+ const cacheRef = (0, import_react.useRef)([]);
414
+ (0, import_react.useEffect)(() => {
415
+ if (!visible || current === void 0 || injected === void 0) {
416
+ cacheRef.current = [];
417
+ setMarkers([]);
418
+ return;
419
+ }
420
+ const face = injected.questionProjection(current);
421
+ let raf = 0;
422
+ const refresh = () => {
423
+ if (raf !== 0) return;
424
+ raf = requestAnimationFrame(() => {
425
+ raf = 0;
426
+ const next = buildMarkers(projectedEntries(face), injected.readQuestions(current));
427
+ if (sameMarkers(next, cacheRef.current)) {
428
+ setMarkers(cacheRef.current);
429
+ return;
430
+ }
431
+ cacheRef.current = next;
432
+ setMarkers(next);
433
+ });
434
+ };
435
+ refresh();
436
+ const offProjection = face?.subscribe(refresh) ?? (() => {
437
+ });
438
+ const offContent = injected.subscribeContent(current, refresh);
439
+ return () => {
440
+ if (raf !== 0) cancelAnimationFrame(raf);
441
+ offProjection();
442
+ offContent();
443
+ };
444
+ }, [injected, current, visible]);
445
+ return { sessionId: current, visible, markers };
446
+ }
447
+ function conversationRoot() {
448
+ return document.querySelector('[data-slot="conversation"] > div[data-phase]');
449
+ }
450
+ function mobileSearchHost() {
451
+ return document.querySelector("[data-conversation-scroll]");
452
+ }
453
+ function desiredGeometry(panel, align, offset) {
454
+ const frame = panel.closest("[data-shell-overlay]")?.parentElement ?? null;
455
+ const root = conversationRoot();
456
+ if (frame === null || root === null) return null;
457
+ const fr = frame.getBoundingClientRect();
458
+ const rr = root.getBoundingClientRect();
459
+ if (rr.height <= 0 || rr.width <= 0) return null;
460
+ const top = Math.round(rr.top - fr.top);
461
+ const height = Math.round(rr.height);
462
+ return align === "right" ? { top: top + "px", height: height + "px", edge: Math.round(fr.right - rr.right + offset) + "px", side: "right" } : { top: top + "px", height: height + "px", edge: Math.round(rr.left - fr.left + offset) + "px", side: "left" };
463
+ }
464
+ function usePinning(panelRef, visible, align, offset = 0, sessionId) {
465
+ (0, import_react.useEffect)(() => {
466
+ let alive = true;
467
+ let observer = null;
468
+ let domObserver = null;
469
+ let raf = 0;
470
+ let parked = true;
471
+ let stableFrames = 0;
472
+ let retry = null;
473
+ let watchedRoot = null;
474
+ const calibrate = () => {
475
+ const panel = panelRef.current;
476
+ if (panel === null) return "missing";
477
+ const want = desiredGeometry(panel, align, offset);
478
+ if (want === null) return "missing";
479
+ const other = want.side === "right" ? "left" : "right";
480
+ const dirty = panel.style.top !== want.top || panel.style.height !== want.height || panel.style[want.side] !== want.edge || panel.style[other] !== "";
481
+ if (!dirty) return "stable";
482
+ panel.style.top = want.top;
483
+ panel.style.height = want.height;
484
+ panel.style[want.side] = want.edge;
485
+ panel.style[other] = "";
486
+ return "changed";
487
+ };
488
+ const ensureWatched = () => {
489
+ const root = conversationRoot();
490
+ if (root !== watchedRoot) bindResize();
491
+ };
492
+ const loop = () => {
493
+ if (!alive) return;
494
+ raf = 0;
495
+ const result = calibrate();
496
+ if (result === "missing") {
497
+ if (retry === null) retry = setTimeout(() => {
498
+ retry = null;
499
+ loop();
500
+ }, 60);
501
+ return;
502
+ }
503
+ ensureWatched();
504
+ if (result === "changed") stableFrames = 0;
505
+ else if (result === "stable") stableFrames += 1;
506
+ if (stableFrames >= 3) {
507
+ parked = true;
508
+ return;
509
+ }
510
+ raf = requestAnimationFrame(loop);
511
+ };
512
+ const run = () => {
513
+ if (!alive || !parked) return;
514
+ parked = false;
515
+ stableFrames = 0;
516
+ if (retry !== null) {
517
+ clearTimeout(retry);
518
+ retry = null;
519
+ }
520
+ if (raf === 0) raf = requestAnimationFrame(loop);
521
+ };
522
+ const bindResize = () => {
523
+ observer?.disconnect();
524
+ if (typeof ResizeObserver === "undefined") return;
525
+ observer = new ResizeObserver(run);
526
+ const frame = panelRef.current?.closest("[data-shell-overlay]")?.parentElement ?? null;
527
+ if (frame !== null) observer.observe(frame, { box: "border-box" });
528
+ const root = conversationRoot();
529
+ if (root !== null) {
530
+ observer.observe(root, { box: "border-box" });
531
+ watchedRoot = root;
532
+ }
533
+ };
534
+ if (typeof MutationObserver !== "undefined") {
535
+ domObserver = new MutationObserver(() => {
536
+ if (watchedRoot !== null && !watchedRoot.isConnected) {
537
+ bindResize();
538
+ run();
539
+ }
540
+ });
541
+ domObserver.observe(document.body, { childList: true, subtree: true });
542
+ }
543
+ bindResize();
544
+ run();
545
+ window.addEventListener("resize", run);
546
+ return () => {
547
+ alive = false;
548
+ if (raf !== 0) cancelAnimationFrame(raf);
549
+ if (retry !== null) clearTimeout(retry);
550
+ observer?.disconnect();
551
+ domObserver?.disconnect();
552
+ window.removeEventListener("resize", run);
553
+ };
554
+ }, [panelRef, visible, align, offset, sessionId]);
555
+ }
556
+ function readingRowKey(flow, portTop, line, markerSet) {
557
+ let lastKey = null;
558
+ for (const row of flow.querySelectorAll("[data-chat-anchor-key]")) {
559
+ const key = row.dataset.chatAnchorKey;
560
+ if (key === void 0) continue;
561
+ if (markerSet.has(key)) {
562
+ const top = row.getBoundingClientRect().top - portTop;
563
+ if (top >= line) return key;
564
+ lastKey = key;
565
+ }
566
+ }
567
+ return lastKey;
568
+ }
569
+ function useReadingSpy(visible, markers) {
570
+ const [currentKey, setCurrentKey] = (0, import_react.useState)(null);
571
+ const currentKeyRef = (0, import_react.useRef)(null);
572
+ const forceKey = (key) => {
573
+ currentKeyRef.current = key;
574
+ setCurrentKey(key);
575
+ };
576
+ const markerSetRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
577
+ markerSetRef.current = new Set(markers.flatMap((m) => m.members));
578
+ (0, import_react.useEffect)(() => {
579
+ if (!visible || markers.length === 0) {
580
+ setCurrentKey(null);
581
+ return;
582
+ }
583
+ let raf = 0;
584
+ let disposed = false;
585
+ let observer = null;
586
+ let trackedFlow = null;
587
+ let lastSample = 0;
588
+ const sample = () => {
589
+ raf = 0;
590
+ const now = performance.now();
591
+ if (now - lastSample < 100 && currentKeyRef.current !== null) {
592
+ schedule();
593
+ return;
594
+ }
595
+ lastSample = now;
596
+ const scrollport = document.querySelector("[data-conversation-scroll]");
597
+ const flow = document.querySelector("[data-chat-flow]");
598
+ if (scrollport === null || flow === null) {
599
+ setCurrentKey(null);
600
+ return;
601
+ }
602
+ if (observer !== null && trackedFlow !== flow) {
603
+ observer.disconnect();
604
+ observer.observe(flow);
605
+ trackedFlow = flow;
606
+ }
607
+ const portRect = scrollport.getBoundingClientRect();
608
+ if (portRect.height <= 0) return;
609
+ const next = readingRowKey(flow, portRect.top, 0, markerSetRef.current);
610
+ currentKeyRef.current = next;
611
+ setCurrentKey((prev) => prev === next ? prev : next);
612
+ };
613
+ const schedule = () => {
614
+ if (!disposed && raf === 0) raf = requestAnimationFrame(sample);
615
+ };
616
+ observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
617
+ sample();
618
+ document.addEventListener("scroll", schedule, true);
619
+ window.addEventListener("resize", schedule);
620
+ return () => {
621
+ disposed = true;
622
+ if (raf !== 0) cancelAnimationFrame(raf);
623
+ observer?.disconnect();
624
+ document.removeEventListener("scroll", schedule, true);
625
+ window.removeEventListener("resize", schedule);
626
+ };
627
+ }, [visible, markers]);
628
+ return [currentKey, forceKey];
629
+ }
630
+ var NODE_SIZE = 8;
631
+ var ROW_GAP = 6;
632
+ var ROW_HEIGHT = NODE_SIZE + ROW_GAP;
633
+ var ROWS_PER_PAGE = 5;
634
+ function pageDistance() {
635
+ return ROWS_PER_PAGE * ROW_HEIGHT;
636
+ }
637
+ function clampScroll(pos, max) {
638
+ const ceiling = Math.max(0, max);
639
+ if (pos < 0) return 0;
640
+ if (pos > ceiling) return ceiling;
641
+ return pos;
642
+ }
643
+ function bandVisibility(list, els) {
644
+ const band = list.getBoundingClientRect();
645
+ return els.map((el) => {
646
+ const r = el.getBoundingClientRect();
647
+ return r.bottom > band.top && r.top < band.bottom;
648
+ });
649
+ }
650
+ function enteringBatch(list, els, before, dir) {
651
+ const band = list.getBoundingClientRect();
652
+ const fresh = [];
653
+ els.forEach((el, i) => {
654
+ if (before[i]) return;
655
+ const r = el.getBoundingClientRect();
656
+ const key = el.dataset.megaChatNavKey;
657
+ if (key !== void 0 && r.bottom > band.top && r.top < band.bottom) fresh.push(key);
658
+ });
659
+ if (dir === 1) fresh.reverse();
660
+ const batch = /* @__PURE__ */ new Map();
661
+ fresh.forEach((key, order) => batch.set(key, { dir, order }));
662
+ return batch;
663
+ }
664
+ function useBandPaging(listRef, markers) {
665
+ const [scrollable, setScrollable] = (0, import_react.useState)(false);
666
+ const [, bumpScroll] = (0, import_react.useState)(0);
667
+ const [revealed, setRevealed] = (0, import_react.useState)(null);
668
+ const revealTimer = (0, import_react.useRef)(null);
669
+ const readBand = () => {
670
+ const list = listRef.current;
671
+ if (list === null) return { top: 0, max: 0 };
672
+ return { top: list.scrollTop, max: Math.max(0, list.scrollHeight - list.clientHeight) };
673
+ };
674
+ const syncScroll = () => {
675
+ bumpScroll((n) => n + 1);
676
+ };
677
+ const pageBy = (dir) => {
678
+ const list = listRef.current;
679
+ if (list === null) return;
680
+ const { max: max2 } = readBand();
681
+ const target = clampScroll(list.scrollTop + dir * pageDistance(), max2);
682
+ if (target === list.scrollTop) return;
683
+ const els = Array.from(list.querySelectorAll("[data-mega-chat-nav-key]"));
684
+ const before = bandVisibility(list, els);
685
+ list.scrollTop = target;
686
+ syncScroll();
687
+ const batch = enteringBatch(list, els, before, dir);
688
+ if (batch.size === 0) return;
689
+ setRevealed(batch);
690
+ if (revealTimer.current !== null) window.clearTimeout(revealTimer.current);
691
+ revealTimer.current = window.setTimeout(() => setRevealed(null), 320 + batch.size * 35 + 120);
692
+ };
693
+ (0, import_react.useEffect)(() => {
694
+ const list = listRef.current;
695
+ if (list === null) return;
696
+ const check = () => {
697
+ setScrollable(list.scrollHeight > list.clientHeight + 1);
698
+ syncScroll();
699
+ };
700
+ check();
701
+ if (typeof ResizeObserver === "undefined") return;
702
+ const observer = new ResizeObserver(check);
703
+ observer.observe(list);
704
+ return () => observer.disconnect();
705
+ }, [markers]);
706
+ (0, import_react.useEffect)(() => () => {
707
+ if (revealTimer.current !== null) window.clearTimeout(revealTimer.current);
708
+ }, []);
709
+ const { top, max } = readBand();
710
+ return {
711
+ scrollable,
712
+ revealed,
713
+ syncScroll,
714
+ pageBy,
715
+ canPageUp: scrollable && top > 0,
716
+ canPageDown: scrollable && top < max - 1
717
+ // 1px 容差:滚动到底不再显示 ▼
718
+ };
719
+ }
720
+ function usePeekState() {
721
+ const [peekVisible, setPeekVisible] = (0, import_react.useState)(false);
722
+ const peekTimer = (0, import_react.useRef)(null);
723
+ const showPeek = () => {
724
+ if (peekTimer.current !== null) {
725
+ window.clearTimeout(peekTimer.current);
726
+ peekTimer.current = null;
727
+ }
728
+ setPeekVisible(true);
729
+ };
730
+ const hidePeek = () => {
731
+ if (peekTimer.current === null) {
732
+ peekTimer.current = window.setTimeout(() => {
733
+ peekTimer.current = null;
734
+ setPeekVisible(false);
735
+ }, 1e3);
736
+ }
737
+ };
738
+ (0, import_react.useEffect)(() => () => {
739
+ if (peekTimer.current !== null) window.clearTimeout(peekTimer.current);
740
+ }, []);
741
+ const peekCls = (mode, open) => {
742
+ if (mode !== "peek" || open) return "";
743
+ return peekVisible ? "" : " mgcn-peek";
744
+ };
745
+ return { peekVisible, showPeek, hidePeek, peekCls };
746
+ }
747
+ function useClosePopoversOnOutside(configOpen, searchOpen, close) {
748
+ (0, import_react.useEffect)(() => {
749
+ if (!configOpen && !searchOpen) return;
750
+ const onDown = (e) => {
751
+ const target = e.target;
752
+ if (target === null || !target.closest(".mgcn-gear-wrap")) close();
753
+ };
754
+ document.addEventListener("pointerdown", onDown, true);
755
+ return () => document.removeEventListener("pointerdown", onDown, true);
756
+ }, [configOpen, searchOpen, close]);
757
+ }
758
+ var CARD_SIZES = [
759
+ { widthPx: 380, fontSize: 13, maxLines: 6 },
760
+ { widthPx: 300, fontSize: 12.5, maxLines: 2 },
761
+ { widthPx: 240, fontSize: 12, maxLines: 1 }
762
+ ];
763
+ function cardMetrics(distance) {
764
+ const magnitude = distance < 0 ? -distance : distance;
765
+ if (magnitude > CARD_SIZES.length - 1) return null;
766
+ return CARD_SIZES[magnitude] ?? CARD_SIZES[CARD_SIZES.length - 1];
767
+ }
768
+ function markerTier(distance) {
769
+ const magnitude = distance < 0 ? -distance : distance;
770
+ return magnitude > CARD_SIZES.length - 1 ? null : magnitude;
771
+ }
772
+ function markerScale(distance) {
773
+ return distance === 0 ? 1.5 : 1;
774
+ }
775
+ function cardSpan(markers, index, radius) {
776
+ const lo = Math.max(0, index - radius);
777
+ const hi = Math.min(markers.length - 1, index + radius);
778
+ const out = [];
779
+ for (let i = lo; i <= hi; i++) {
780
+ out.push({ dot: markers[i], distance: Math.abs(i - index) });
781
+ }
782
+ return out;
783
+ }
784
+ function useCardPreview(markers, align, radius, enabled) {
785
+ const [focus, setFocus] = (0, import_react.useState)(null);
786
+ const clearTimer = (0, import_react.useRef)(null);
787
+ const cancelClear = () => {
788
+ if (clearTimer.current !== null) {
789
+ window.clearTimeout(clearTimer.current);
790
+ clearTimer.current = null;
791
+ }
792
+ };
793
+ const scheduleClear = () => {
794
+ cancelClear();
795
+ clearTimer.current = window.setTimeout(() => setFocus(null), 240);
796
+ };
797
+ const openCards = (marker, target) => {
798
+ if (!enabled) return;
799
+ const index = markers.findIndex((m) => m.key === marker.key);
800
+ if (index < 0) return;
801
+ cancelClear();
802
+ const r = target.getBoundingClientRect();
803
+ const horizontal = align === "right" ? { right: window.innerWidth - r.left + 10 } : { left: r.right + 10 };
804
+ setFocus({ key: marker.key, items: cardSpan(markers, index, radius), centerY: r.top + r.height / 2, ...horizontal });
805
+ };
806
+ (0, import_react.useEffect)(() => {
807
+ if (focus === null || !enabled) return;
808
+ const index = markers.findIndex((m) => m.key === focus.key);
809
+ if (index < 0) return;
810
+ setFocus((prev) => prev !== null && prev.key === focus.key ? { ...prev, items: cardSpan(markers, index, radius) } : prev);
811
+ }, [radius, markers, enabled]);
812
+ (0, import_react.useEffect)(() => () => {
813
+ if (clearTimer.current !== null) window.clearTimeout(clearTimer.current);
814
+ }, []);
815
+ return { focus, openCards, cancelClear, scheduleClear };
816
+ }
817
+ function useFailureNotice(t) {
818
+ const [notice, setNotice] = (0, import_react.useState)(null);
819
+ const timer = (0, import_react.useRef)(null);
820
+ (0, import_react.useEffect)(() => {
821
+ const onFail = (event) => {
822
+ const code = event.detail ?? "TIMEOUT";
823
+ const known = ["jump.inactive", "jump.hidden", "jump.notfound", "jump.timeout"];
824
+ setNotice(t(known.includes(code) ? code : "jump.timeout"));
825
+ if (timer.current !== null) window.clearTimeout(timer.current);
826
+ timer.current = window.setTimeout(() => setNotice(null), 1800);
827
+ };
828
+ window.addEventListener("mega-chat-nav:jump-failed", onFail);
829
+ return () => {
830
+ window.removeEventListener("mega-chat-nav:jump-failed", onFail);
831
+ if (timer.current !== null) window.clearTimeout(timer.current);
832
+ };
833
+ }, [t]);
834
+ return notice;
835
+ }
836
+ function useLoadingNotice(t) {
837
+ const [state, setState] = (0, import_react.useState)({ loading: false, pages: 0 });
838
+ const timer = (0, import_react.useRef)(null);
839
+ (0, import_react.useEffect)(() => {
840
+ const onLoading = (event) => {
841
+ const detail = event.detail;
842
+ const on = detail?.loading === true;
843
+ setState({ loading: on, pages: detail?.pages ?? 0 });
844
+ if (on) {
845
+ if (timer.current !== null) window.clearTimeout(timer.current);
846
+ timer.current = window.setTimeout(() => setState({ loading: false, pages: 0 }), 1e4);
847
+ }
848
+ };
849
+ window.addEventListener("mega-chat-nav:jump-loading", onLoading);
850
+ return () => {
851
+ window.removeEventListener("mega-chat-nav:jump-loading", onLoading);
852
+ if (timer.current !== null) window.clearTimeout(timer.current);
853
+ };
854
+ }, []);
855
+ return state.loading ? t("jump.loading", { n: state.pages }) : null;
856
+ }
857
+ function useNavSettings(injected) {
858
+ const [style, setStyle] = (0, import_react.useState)(() => injected?.style() ?? DEFAULT_STYLE);
859
+ const [align, setAlign] = (0, import_react.useState)(() => injected?.align() ?? DEFAULT_ALIGN);
860
+ const [bandHeight, setBandHeight] = (0, import_react.useState)(() => injected?.bandHeight() ?? DEFAULT_BAND);
861
+ const [cardCount, setCardCount] = (0, import_react.useState)(() => injected?.cardCount() ?? DEFAULT_CARD_COUNT[DEFAULT_STYLE]);
862
+ const [cardItems, setCardItems] = (0, import_react.useState)(() => injected?.cardItems() ?? [...DEFAULT_CARD_ITEMS]);
863
+ const [markTone, setMarkTone] = (0, import_react.useState)(() => injected?.markTone() ?? DEFAULT_MARK_TONE);
864
+ const [scrollBehavior, setScrollBehavior] = (0, import_react.useState)(() => injected?.scrollBehavior() ?? DEFAULT_SCROLL);
865
+ const [showCount, setShowCount] = (0, import_react.useState)(() => injected?.showCount() ?? DEFAULT_SHOW);
866
+ const [showSearch, setShowSearch] = (0, import_react.useState)(() => injected?.showSearch() ?? DEFAULT_SHOW);
867
+ const [showQuickSettings, setShowQuickSettings] = (0, import_react.useState)(() => injected?.showQuickSettings() ?? DEFAULT_SHOW);
868
+ const [showFavorites, setShowFavorites] = (0, import_react.useState)(() => injected?.showFavorites() ?? DEFAULT_SHOW);
869
+ const [showPaging, setShowPaging] = (0, import_react.useState)(() => injected?.showPaging() ?? DEFAULT_PAGING[DEFAULT_STYLE]);
870
+ const [searchScopes, setSearchScopes] = (0, import_react.useState)(() => injected?.searchScopes() ?? [...DEFAULT_SEARCH_SCOPES]);
871
+ const [railOffset, setRailOffset] = (0, import_react.useState)(() => injected?.railOffset() ?? 8);
872
+ (0, import_react.useEffect)(() => {
873
+ if (injected === void 0) return;
874
+ return injected.subscribeSettings(() => {
875
+ setStyle(injected.style());
876
+ setAlign(injected.align());
877
+ setBandHeight(injected.bandHeight());
878
+ setCardCount(injected.cardCount());
879
+ setCardItems(injected.cardItems());
880
+ setMarkTone(injected.markTone());
881
+ setScrollBehavior(injected.scrollBehavior());
882
+ setShowCount(injected.showCount());
883
+ setShowSearch(injected.showSearch());
884
+ setShowQuickSettings(injected.showQuickSettings());
885
+ setShowFavorites(injected.showFavorites());
886
+ setShowPaging(injected.showPaging());
887
+ setSearchScopes(injected.searchScopes());
888
+ setRailOffset(injected.railOffset());
889
+ });
890
+ }, [injected]);
891
+ return { style, align, bandHeight, cardCount, cardItems, markTone, scrollBehavior, showCount, showSearch, showQuickSettings, showFavorites, showPaging, searchScopes, railOffset };
892
+ }
893
+ function clockText(time, now) {
894
+ if (!(time > 0) || !Number.isFinite(time)) return "";
895
+ const stamp = new Date(time);
896
+ const pad = (n) => String(n).padStart(2, "0");
897
+ const hm = pad(stamp.getHours()) + ":" + pad(stamp.getMinutes());
898
+ const ref = new Date(now);
899
+ const sameDay = stamp.getFullYear() === ref.getFullYear() && stamp.getMonth() === ref.getMonth() && stamp.getDate() === ref.getDate();
900
+ if (sameDay) return hm;
901
+ const md = pad(stamp.getMonth() + 1) + "-" + pad(stamp.getDate());
902
+ if (stamp.getFullYear() === ref.getFullYear()) return md + " " + hm;
903
+ return stamp.getFullYear() + "-" + md + " " + hm;
904
+ }
905
+ var FAV_STORAGE_PREFIX = "mega-chat-nav:favorites:";
906
+ function readFavorites(sessionId) {
907
+ if (sessionId === void 0) return /* @__PURE__ */ new Set();
908
+ try {
909
+ const raw = window.localStorage.getItem(FAV_STORAGE_PREFIX + sessionId);
910
+ if (raw === null) return /* @__PURE__ */ new Set();
911
+ const parsed = JSON.parse(raw);
912
+ if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
913
+ return new Set(parsed.filter((k) => typeof k === "string"));
914
+ } catch {
915
+ return /* @__PURE__ */ new Set();
916
+ }
917
+ }
918
+ function writeFavorites(sessionId, keys) {
919
+ try {
920
+ window.localStorage.setItem(FAV_STORAGE_PREFIX + sessionId, JSON.stringify([...keys]));
921
+ } catch {
922
+ }
923
+ }
924
+ function useFavorites(sessionId) {
925
+ const [favorites, setFavorites] = (0, import_react.useState)(() => readFavorites(sessionId));
926
+ (0, import_react.useEffect)(() => {
927
+ setFavorites(readFavorites(sessionId));
928
+ }, [sessionId]);
929
+ const toggle = (key) => {
930
+ setFavorites((prev) => {
931
+ const next = new Set(prev);
932
+ if (next.has(key)) next.delete(key);
933
+ else next.add(key);
934
+ if (sessionId !== void 0) writeFavorites(sessionId, next);
935
+ return next;
936
+ });
937
+ };
938
+ return { favorites, toggle };
939
+ }
940
+ function durationText(ms) {
941
+ const total = Math.max(0, Math.round(ms / 1e3));
942
+ if (total >= 60) {
943
+ const m = Math.floor(total / 60);
944
+ const s = total % 60;
945
+ return m + "\u5206" + s + "\u79D2";
946
+ }
947
+ return total + "\u79D2";
948
+ }
949
+ function metricsLine(metrics) {
950
+ const parts = [];
951
+ parts.push("\u672C\u8F6E\u7528\u65F6 " + durationText(metrics.durationMs));
952
+ if (metrics.firstTokenMs > 0) parts.push("\u9996 token " + durationText(metrics.firstTokenMs));
953
+ if (metrics.tokensPerSec > 0) parts.push(metrics.tokensPerSec + " tok/s");
954
+ return parts.join(" \xB7 ");
955
+ }
956
+ function useMobileMode(breakpoint = 1024) {
957
+ const [mobile, setMobile] = (0, import_react.useState)(() => window.innerWidth < breakpoint);
958
+ (0, import_react.useEffect)(() => {
959
+ const onResize = () => setMobile(window.innerWidth < breakpoint);
960
+ window.addEventListener("resize", onResize);
961
+ return () => window.removeEventListener("resize", onResize);
962
+ }, [breakpoint]);
963
+ return mobile;
964
+ }
965
+ function useMobileSearchButton(onClick, enabled, align, label) {
966
+ (0, import_react.useEffect)(() => {
967
+ if (!enabled) return;
968
+ const host = mobileSearchHost();
969
+ if (host === null) return;
970
+ const wrap = document.createElement("div");
971
+ wrap.style.width = "100%";
972
+ wrap.style.height = "0";
973
+ wrap.style.margin = "0";
974
+ wrap.style.position = "sticky";
975
+ wrap.style.top = "0";
976
+ wrap.style.overflow = "visible";
977
+ wrap.style.zIndex = "60";
978
+ wrap.style.pointerEvents = "none";
979
+ const btn = document.createElement("button");
980
+ btn.type = "button";
981
+ btn.className = "mgcn-config-gear mgcn-mobileSearch";
982
+ btn.setAttribute("aria-label", label);
983
+ btn.style.position = "absolute";
984
+ btn.style.top = "8px";
985
+ btn.style[align === "right" ? "right" : "left"] = "8px";
986
+ btn.style.zIndex = "60";
987
+ btn.style.pointerEvents = "auto";
988
+ btn.innerHTML = '<svg class="mgcn-btn-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><circle cx="7" cy="7" r="4.5" fill="none" stroke="currentColor" stroke-width="1.6"/><line x1="10.4" y1="10.4" x2="14" y2="14" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
989
+ btn.addEventListener("click", onClick);
990
+ wrap.appendChild(btn);
991
+ host.insertBefore(wrap, host.firstChild);
992
+ return () => {
993
+ wrap.remove();
994
+ };
995
+ }, [enabled, onClick, align, label]);
996
+ }
997
+
998
+ // src/client/components/minimal/dot.tsx
999
+ var import_react2 = require("react");
1000
+ var import_jsx_runtime = require("react/jsx-runtime");
1001
+ var Dot = (0, import_react2.memo)(function Dot2(props) {
1002
+ const { scale, className, label, index, keyName, focused, revealed } = props;
1003
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1004
+ "button",
1005
+ {
1006
+ type: "button",
1007
+ className,
1008
+ style: {
1009
+ transform: "scale(" + scale + ")",
1010
+ ...revealed !== void 0 ? { animationDelay: revealed.order * 35 + "ms" } : {}
1011
+ },
1012
+ "data-mega-chat-nav-focused": focused ? "true" : void 0,
1013
+ "data-mega-chat-nav-index": index,
1014
+ "data-mega-chat-nav-key": keyName,
1015
+ "aria-label": label
1016
+ }
1017
+ );
1018
+ });
1019
+
1020
+ // src/client/components/rail/cards.tsx
1021
+ var import_react_dom = require("react-dom");
1022
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1023
+ function favoriteButton(favorited, onClick) {
1024
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ className: favorited ? "mgcn-cardFav mgcn-cardFavOn" : "mgcn-cardFav",
1029
+ "aria-label": favorited ? "unfavorite" : "favorite",
1030
+ onClick: (e) => {
1031
+ e.stopPropagation();
1032
+ onClick();
1033
+ },
1034
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 16 16", width: "11", height: "11", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1035
+ "path",
1036
+ {
1037
+ d: "M8 2.2l1.76 3.56 3.93.57-2.84 2.77.67 3.91L8 11.4l-3.52 1.85.67-3.91-2.84-2.77 3.93-.57z",
1038
+ fill: favorited ? "currentColor" : "none",
1039
+ stroke: "currentColor",
1040
+ strokeWidth: "1.3",
1041
+ strokeLinejoin: "round"
1042
+ }
1043
+ ) })
1044
+ }
1045
+ );
1046
+ }
1047
+ function Cards(props) {
1048
+ const { focus, align, cardItems, favorites, onToggleFavorite, onJump, onEnter, onLeave } = props;
1049
+ const showTurn = cardItems.includes("turn");
1050
+ const showDuration = cardItems.includes("duration");
1051
+ const showFavorite = cardItems.includes("favorite");
1052
+ return (0, import_react_dom.createPortal)(
1053
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1054
+ "div",
1055
+ {
1056
+ className: align === "right" ? "mgcn-cascade mgcn-cascadeRight" : "mgcn-cascade",
1057
+ style: {
1058
+ ...focus.left !== void 0 ? { left: focus.left } : {},
1059
+ ...focus.right !== void 0 ? { right: focus.right } : {},
1060
+ top: focus.centerY,
1061
+ transform: "translateY(-50%)"
1062
+ },
1063
+ onMouseEnter: onEnter,
1064
+ onMouseLeave: onLeave,
1065
+ children: focus.items.map((item) => {
1066
+ const metrics = cardMetrics(item.distance);
1067
+ if (metrics === null) return null;
1068
+ const isSelected = item.distance === 0;
1069
+ const dot = item.dot;
1070
+ const text = dot.texts.join(" \xB7 ");
1071
+ const cls = isSelected ? "mgcn-card mgcn-cardSelected" + (align === "right" ? " mgcn-cardSelectedRight" : "") : "mgcn-card";
1072
+ const favorited = favorites.has(dot.key);
1073
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1074
+ "div",
1075
+ {
1076
+ className: cls,
1077
+ role: "button",
1078
+ tabIndex: -1,
1079
+ style: { width: metrics.widthPx },
1080
+ onClick: () => onJump(dot),
1081
+ children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mgcn-cardHead", children: [
1083
+ showFavorite ? favoriteButton(favorited, () => onToggleFavorite(dot.key)) : null,
1084
+ showTurn && isSelected && dot.turn !== null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mgcn-cardTitle", children: [
1085
+ "Turn ",
1086
+ dot.turn
1087
+ ] }) : null,
1088
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mgcn-cardTime", children: clockText(dot.time, Date.now()) })
1089
+ ] }),
1090
+ showDuration && dot.metrics !== void 0 && isSelected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mgcn-cardMetrics", children: metricsLine(dot.metrics) }) : null,
1091
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1092
+ "div",
1093
+ {
1094
+ className: isSelected ? "mgcn-cardBody" : "mgcn-cardClamp",
1095
+ style: isSelected ? void 0 : { WebkitLineClamp: metrics.maxLines, fontSize: metrics.fontSize },
1096
+ children: text
1097
+ }
1098
+ )
1099
+ ]
1100
+ },
1101
+ dot.key
1102
+ );
1103
+ })
1104
+ }
1105
+ ),
1106
+ document.body
1107
+ );
1108
+ }
1109
+
1110
+ // src/client/components/rail/paging.tsx
1111
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1112
+ function PagingButton(props) {
1113
+ const cls = props.dir === -1 ? "mgcn-navBtn mgcn-navUp" : "mgcn-navBtn mgcn-navDown";
1114
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1115
+ "button",
1116
+ {
1117
+ type: "button",
1118
+ className: cls,
1119
+ "aria-label": props.label,
1120
+ style: { visibility: props.visible ? "visible" : "hidden" },
1121
+ onMouseEnter: props.onEnter,
1122
+ onMouseLeave: props.onLeave,
1123
+ onClick: props.onClick
1124
+ }
1125
+ );
1126
+ }
1127
+
1128
+ // src/client/components/rail/hint.tsx
1129
+ var import_react3 = require("react");
1130
+ var import_react_dom2 = require("react-dom");
1131
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1132
+ function conversationEdge(align) {
1133
+ const root = document.querySelector('[data-slot="conversation"] > div[data-phase]');
1134
+ if (root === null) return null;
1135
+ const r = root.getBoundingClientRect();
1136
+ return align === "right" ? window.innerWidth - r.right : r.left;
1137
+ }
1138
+ function Hint({ text, align, position = "rail", loading = false }) {
1139
+ const [edge, setEdge] = (0, import_react3.useState)(() => conversationEdge(align));
1140
+ (0, import_react3.useEffect)(() => {
1141
+ const measure = () => setEdge(conversationEdge(align));
1142
+ measure();
1143
+ window.addEventListener("resize", measure);
1144
+ return () => window.removeEventListener("resize", measure);
1145
+ }, [align]);
1146
+ const cls = position === "bottom" ? "mgcn-hint mgcn-hint--bottom" : "mgcn-hint";
1147
+ const style = position === "bottom" ? align === "right" ? { right: (edge ?? 0) + 20, bottom: 150 } : { left: (edge ?? 0) + 20, bottom: 150 } : align === "right" ? { right: 52, top: 12 } : { left: 52, top: 12 };
1148
+ return (0, import_react_dom2.createPortal)(
1149
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cls, style, children: [
1150
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mgcn-hint-dots", "aria-hidden": "true", children: [
1151
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", {}),
1152
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", {}),
1153
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", {})
1154
+ ] }) : null,
1155
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: text })
1156
+ ] }),
1157
+ document.body
1158
+ );
1159
+ }
1160
+
1161
+ // src/client/components/rail/search.tsx
1162
+ var import_react4 = require("react");
1163
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1164
+ function fetchHits(sessionId, query, scopes, signal) {
1165
+ const url = "/mega-chat-nav/search?session=" + encodeURIComponent(sessionId ?? "") + "&q=" + encodeURIComponent(query) + "&scopes=" + encodeURIComponent(scopes.join(","));
1166
+ return fetch(url, { signal }).then((resp) => resp.json()).then((data) => {
1167
+ const hits = data.ok === true ? data.hits ?? [] : [];
1168
+ return hits.sort((x, y) => y.turn - x.turn || y.seq - x.seq);
1169
+ });
1170
+ }
1171
+ function roleTag(role, t) {
1172
+ if (role === "assistant") return t("search.tag.assistant");
1173
+ if (role === "tool") return t("search.tag.tool");
1174
+ return t("search.tag.user");
1175
+ }
1176
+ function SearchBox(props) {
1177
+ const [query, setQuery] = (0, import_react4.useState)("");
1178
+ const [results, setResults] = (0, import_react4.useState)([]);
1179
+ const [busy, setBusy] = (0, import_react4.useState)(false);
1180
+ const abortRef = (0, import_react4.useRef)(null);
1181
+ const debounceRef = (0, import_react4.useRef)(null);
1182
+ const inputRef = (0, import_react4.useRef)(null);
1183
+ const run = (0, import_react4.useCallback)((raw) => {
1184
+ abortRef.current?.abort();
1185
+ const ac = new AbortController();
1186
+ abortRef.current = ac;
1187
+ const q = raw.trim();
1188
+ if (q.length < 1) {
1189
+ setResults([]);
1190
+ setBusy(false);
1191
+ return;
1192
+ }
1193
+ setBusy(true);
1194
+ fetchHits(props.sessionId, q, props.scopes, ac.signal).then((hits) => {
1195
+ setResults(hits);
1196
+ }).catch((e) => {
1197
+ if (e.name !== "AbortError") setResults([]);
1198
+ }).finally(() => setBusy(false));
1199
+ }, [props.sessionId, props.scopes]);
1200
+ const onType = (value) => {
1201
+ setQuery(value);
1202
+ if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
1203
+ debounceRef.current = window.setTimeout(() => run(value), 250);
1204
+ };
1205
+ (0, import_react4.useEffect)(() => {
1206
+ inputRef.current?.focus();
1207
+ return () => {
1208
+ if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
1209
+ abortRef.current?.abort();
1210
+ };
1211
+ }, []);
1212
+ const historicCount = results.filter((hit) => hit.historic).length;
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mgcn-search", role: "dialog", "aria-label": props.t("search.placeholder"), children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1215
+ "input",
1216
+ {
1217
+ ref: inputRef,
1218
+ className: "mgcn-search-input",
1219
+ value: query,
1220
+ placeholder: props.t("search.placeholder"),
1221
+ onChange: (e) => onType(e.target.value),
1222
+ onKeyDown: (e) => {
1223
+ if (e.key === "Escape") props.onClose();
1224
+ }
1225
+ }
1226
+ ),
1227
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mgcn-search-busy", children: "\u2026" }) : null,
1228
+ !busy && query.trim() === "" && props.emptyContent !== void 0 ? props.emptyContent : null,
1229
+ !busy && results.length > 0 && historicCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mgcn-search-historic", children: props.t("search.historic", { n: historicCount }) }) : null,
1230
+ results.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mgcn-search-results", children: results.map((hit) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1231
+ "button",
1232
+ {
1233
+ type: "button",
1234
+ className: "mgcn-search-hit",
1235
+ onClick: () => props.onJump(hit.key, hit.seq),
1236
+ children: [
1237
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mgcn-search-title", children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mgcn-search-titleLeft", children: [
1239
+ hit.turn > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mgcn-search-turn", children: props.t("search.turnTitle", { turn: hit.turn }) }) : null,
1240
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mgcn-search-tag mgcn-search-tag--" + hit.role, children: roleTag(hit.role, props.t) })
1241
+ ] }),
1242
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mgcn-search-titleTime", children: clockText(hit.time, Date.now()) })
1243
+ ] }),
1244
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mgcn-search-body", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mgcn-search-text", children: hit.hitStart >= 0 && hit.hitLen > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1245
+ hit.snippet.slice(0, hit.hitStart),
1246
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("mark", { className: "mgcn-search-mark", children: hit.snippet.slice(hit.hitStart, hit.hitStart + hit.hitLen) }),
1247
+ hit.snippet.slice(hit.hitStart + hit.hitLen)
1248
+ ] }) : hit.snippet }) })
1249
+ ]
1250
+ },
1251
+ hit.key + ":" + hit.seq
1252
+ )) }) : null
1253
+ ] });
1254
+ }
1255
+
1256
+ // src/client/components/rail/quick-settings.tsx
1257
+ var import_react5 = require("react");
1258
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1259
+ function Seg(props) {
1260
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mgcn-segmented", role: "radiogroup", children: props.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1261
+ "button",
1262
+ {
1263
+ type: "button",
1264
+ role: "radio",
1265
+ "aria-checked": props.value === o,
1266
+ className: props.value === o ? "mgcn-segment mgcn-segmentActive" : "mgcn-segment",
1267
+ onClick: () => props.onPick(o),
1268
+ children: props.labels[String(o)] ?? String(o)
1269
+ },
1270
+ o
1271
+ )) });
1272
+ }
1273
+ function ModeSeg({ value, onChange, t }) {
1274
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1275
+ Seg,
1276
+ {
1277
+ options: SHOW_OPTIONS,
1278
+ value,
1279
+ labels: {
1280
+ show: t("config.showMode.show"),
1281
+ peek: t("config.showMode.peek"),
1282
+ hide: t("config.showMode.hide")
1283
+ },
1284
+ onPick: (v) => onChange(v)
1285
+ }
1286
+ );
1287
+ }
1288
+ function useSettingsTick(subscribe) {
1289
+ const [, bump] = (0, import_react5.useState)(0);
1290
+ (0, import_react5.useEffect)(() => subscribe(() => bump((n) => n + 1)), [subscribe]);
1291
+ }
1292
+ function QuickSettings(props) {
1293
+ const { injected, t, onClose } = props;
1294
+ useSettingsTick(injected.subscribeSettings);
1295
+ const caps = STYLE_CAPABILITIES[injected.style()];
1296
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-panel", role: "dialog", "aria-label": t("config.title"), children: [
1297
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-head", children: [
1298
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mgcn-config-title", children: [
1299
+ t("config.style." + injected.style()),
1300
+ " \xB7 ",
1301
+ t("config.settings")
1302
+ ] }),
1303
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mgcn-config-close", "aria-label": t("config.close"), onClick: onClose, children: "\u2715" })
1304
+ ] }),
1305
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1306
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.align") }),
1307
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1308
+ Seg,
1309
+ {
1310
+ options: ALIGN_OPTIONS,
1311
+ value: injected.align(),
1312
+ labels: { left: t("settings.align.left"), right: t("settings.align.right") },
1313
+ onPick: (v) => injected.setAlign(v)
1314
+ }
1315
+ )
1316
+ ] }),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.bandHeight") }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1320
+ Seg,
1321
+ {
1322
+ options: BAND_OPTIONS,
1323
+ value: injected.bandHeight(),
1324
+ labels: {
1325
+ compact: t("config.bandHeight.compact"),
1326
+ standard: t("config.bandHeight.standard"),
1327
+ tall: t("config.bandHeight.tall")
1328
+ },
1329
+ onPick: (v) => injected.setBandHeight(v)
1330
+ }
1331
+ )
1332
+ ] }),
1333
+ caps.hoverCards ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1334
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.cardCount") }),
1335
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1336
+ Seg,
1337
+ {
1338
+ options: CARD_OPTIONS,
1339
+ value: injected.cardCount(),
1340
+ labels: { 1: "1", 3: "3", 5: "5" },
1341
+ onPick: (v) => injected.setCardCount(v)
1342
+ }
1343
+ )
1344
+ ] }) : null,
1345
+ injected.style() === "codex" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1346
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.markTone") }),
1347
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1348
+ Seg,
1349
+ {
1350
+ options: MARK_TONE_OPTIONS,
1351
+ value: injected.markTone(),
1352
+ labels: { soft: t("config.markTone.soft"), deep: t("config.markTone.deep") },
1353
+ onPick: (v) => injected.setMarkTone(v)
1354
+ }
1355
+ )
1356
+ ] }) : null,
1357
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mgcn-config-sep" }),
1358
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.showQuickSettings") }),
1360
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModeSeg, { value: injected.showQuickSettings(), onChange: (v) => injected.setShowQuickSettings(v), t })
1361
+ ] }),
1362
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1363
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.showSearch") }),
1364
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModeSeg, { value: injected.showSearch(), onChange: (v) => injected.setShowSearch(v), t })
1365
+ ] }),
1366
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1367
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.showFavorites") }),
1368
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModeSeg, { value: injected.showFavorites(), onChange: (v) => injected.setShowFavorites(v), t })
1369
+ ] }),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.showCount") }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModeSeg, { value: injected.showCount(), onChange: (v) => injected.setShowCount(v), t })
1373
+ ] }),
1374
+ STYLE_CAPABILITIES[injected.style()].paging ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mgcn-config-row", children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mgcn-config-label", children: t("config.showPaging") }),
1376
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModeSeg, { value: injected.showPaging(), onChange: (v) => injected.setShowPaging(v), t })
1377
+ ] }) : null
1378
+ ] });
1379
+ }
1380
+
1381
+ // src/client/components/minimal/MinimalRail.tsx
1382
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1383
+ function MinimalRail(props) {
1384
+ const { injected, useSessions, t } = props;
1385
+ const panelRef = (0, import_react6.useRef)(null);
1386
+ const listRef = (0, import_react6.useRef)(null);
1387
+ const [jumpingKey, setJumpingKey] = (0, import_react6.useState)(null);
1388
+ const [configOpen, setConfigOpen] = (0, import_react6.useState)(false);
1389
+ const [searchOpen, setSearchOpen] = (0, import_react6.useState)(false);
1390
+ const [favFilter, setFavFilter] = (0, import_react6.useState)(false);
1391
+ const [drawerOpen, setDrawerOpen] = (0, import_react6.useState)(false);
1392
+ const [followTick, setFollowTick] = (0, import_react6.useState)(0);
1393
+ const { showPeek, hidePeek, peekCls } = usePeekState();
1394
+ const lastHoverKey = (0, import_react6.useRef)(null);
1395
+ const mobile = useMobileMode();
1396
+ const { sessionId, visible, markers } = useMarkerData(injected, useSessions);
1397
+ const { favorites, toggle: toggleFavorite } = useFavorites(sessionId);
1398
+ const settings = useNavSettings(injected);
1399
+ const { style, align, bandHeight, cardCount, cardItems, showPaging } = settings;
1400
+ useMobileSearchButton(() => setDrawerOpen(true), mobile && markers.length > 0, align, t("search.placeholder"));
1401
+ const effectiveFilter = favFilter && favorites.size > 0;
1402
+ const displayMarkers = effectiveFilter ? markers.filter((m) => m.members.some((k) => favorites.has(k))) : markers;
1403
+ usePinning(panelRef, visible, align, settings.railOffset, sessionId);
1404
+ const [currentKey, forceKey] = useReadingSpy(visible, displayMarkers);
1405
+ const { scrollable, revealed, syncScroll, pageBy, canPageUp, canPageDown } = useBandPaging(listRef, displayMarkers);
1406
+ const caps = STYLE_CAPABILITIES[style];
1407
+ const focusRadius = Math.floor((cardCount - 1) / 2);
1408
+ const { focus, openCards, cancelClear, scheduleClear } = useCardPreview(displayMarkers, align, focusRadius, caps.hoverCards);
1409
+ const hint = useFailureNotice(t);
1410
+ const loading = useLoadingNotice(t);
1411
+ useClosePopoversOnOutside(configOpen, searchOpen, () => {
1412
+ setConfigOpen(false);
1413
+ setSearchOpen(false);
1414
+ });
1415
+ (0, import_react6.useEffect)(() => {
1416
+ const list = listRef.current;
1417
+ if (list === null || currentKey === null) return;
1418
+ const index = displayMarkers.findIndex((d) => d.key === currentKey);
1419
+ if (index < 0) return;
1420
+ const step = ROW_HEIGHT;
1421
+ const dotTop = 8 + index * step;
1422
+ const contentHeight = 16 + displayMarkers.length * step - 6;
1423
+ const maxScroll = Math.max(0, contentHeight - list.clientHeight);
1424
+ const target = Math.max(0, Math.min(dotTop - list.clientHeight / 2 + step / 2, maxScroll));
1425
+ if (Math.abs(target - list.scrollTop) < 2) return;
1426
+ list.scrollTo({ top: target, behavior: "smooth" });
1427
+ }, [currentKey, markers, followTick]);
1428
+ if (!visible || injected === void 0 || markers.length === 0) return null;
1429
+ const onJump = (dot) => {
1430
+ if (sessionId === void 0) return;
1431
+ setJumpingKey(dot.key);
1432
+ forceKey(dot.key);
1433
+ const currentMarker = currentKey === null ? void 0 : displayMarkers.find((m) => m.key === currentKey);
1434
+ injected.jump(sessionId, dot.key, dot.seq, currentMarker?.seq);
1435
+ window.setTimeout(() => setJumpingKey((k) => k === dot.key ? null : k), 600);
1436
+ };
1437
+ const onDotsOver = (e) => {
1438
+ const el = e.target;
1439
+ const hit = el.closest("[data-mega-chat-nav-key]");
1440
+ if (hit === null) return;
1441
+ const key = hit.dataset.megaChatNavKey;
1442
+ if (key === void 0 || key === lastHoverKey.current) return;
1443
+ const dot = displayMarkers.find((d) => d.key === key);
1444
+ if (dot === void 0) return;
1445
+ lastHoverKey.current = key;
1446
+ cancelClear();
1447
+ openCards(dot, hit);
1448
+ };
1449
+ const onDotsClick = (e) => {
1450
+ const el = e.target;
1451
+ const hit = el.closest("[data-mega-chat-nav-key]");
1452
+ if (hit === null) return;
1453
+ const key = hit.dataset.megaChatNavKey;
1454
+ const dot = displayMarkers.find((d) => d.key === key);
1455
+ if (dot !== void 0) onJump(dot);
1456
+ };
1457
+ if (mobile) {
1458
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: drawerOpen ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-drawerMask", onClick: () => setDrawerOpen(false) }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-searchDrawer", role: "dialog", "aria-label": t("search.placeholder"), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1461
+ SearchBox,
1462
+ {
1463
+ sessionId,
1464
+ scopes: settings.searchScopes,
1465
+ t,
1466
+ onJump: (key, seq) => {
1467
+ setDrawerOpen(false);
1468
+ onJump({ key, seq: seq ?? 0, turn: null, time: Date.now(), texts: [], members: [key] });
1469
+ },
1470
+ onClose: () => setDrawerOpen(false),
1471
+ emptyContent: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-drawerList", children: [...markers].reverse().slice(0, 20).map((m) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1472
+ "button",
1473
+ {
1474
+ type: "button",
1475
+ className: "mgcn-drawerItem",
1476
+ onClick: () => {
1477
+ setDrawerOpen(false);
1478
+ onJump(m);
1479
+ },
1480
+ children: [
1481
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mgcn-drawerItemTitle", children: [
1482
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mgcn-drawerItemTurn", children: m.turn !== null ? "Turn " + m.turn : "" }),
1483
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mgcn-drawerItemTime", children: clockText(m.time, Date.now()) })
1484
+ ] }),
1485
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mgcn-drawerItemText", children: m.texts[0] ?? "" })
1486
+ ]
1487
+ },
1488
+ m.key
1489
+ )) })
1490
+ }
1491
+ ) })
1492
+ ] }) : null });
1493
+ }
1494
+ const selectedIndex = focus === null ? -1 : displayMarkers.findIndex((d) => d.key === focus?.key);
1495
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1496
+ "div",
1497
+ {
1498
+ ref: panelRef,
1499
+ className: align === "right" ? "mgcn-rail mgcn-railRight" : "mgcn-rail",
1500
+ "data-mega-chat-nav": "rail",
1501
+ onMouseEnter: showPeek,
1502
+ onMouseLeave: () => {
1503
+ hidePeek();
1504
+ setFollowTick((n) => n + 1);
1505
+ },
1506
+ children: [
1507
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-queue", children: displayMarkers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-empty", children: t("strip.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1508
+ "div",
1509
+ {
1510
+ className: "mgcn-list",
1511
+ style: { "--mgcn-band-height": BAND_HEIGHT_PX.minimal[bandHeight] + "px" },
1512
+ children: [
1513
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mgcn-actions", children: [
1514
+ settings.showQuickSettings !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + peekCls(settings.showQuickSettings, configOpen), children: [
1515
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1516
+ "button",
1517
+ {
1518
+ type: "button",
1519
+ className: "mgcn-config-gear",
1520
+ "aria-label": t("config.title"),
1521
+ onClick: () => {
1522
+ setConfigOpen((v) => !v);
1523
+ setSearchOpen(false);
1524
+ },
1525
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 24 24", width: "12", height: "12", "aria-hidden": "true", children: [
1526
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "3", fill: "none", stroke: "currentColor", strokeWidth: "1.7" }),
1527
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" })
1528
+ ] })
1529
+ }
1530
+ ),
1531
+ configOpen ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(QuickSettings, { injected, t, onClose: () => setConfigOpen(false) }) : null
1532
+ ] }) : null,
1533
+ settings.showFavorites !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + peekCls(settings.showFavorites, false), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1534
+ "button",
1535
+ {
1536
+ type: "button",
1537
+ className: effectiveFilter ? "mgcn-config-gear mgcn-favFilterOn" : "mgcn-config-gear",
1538
+ "aria-label": t("fav.filter"),
1539
+ onClick: () => {
1540
+ if (favorites.size > 0) setFavFilter((v) => !v);
1541
+ },
1542
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1543
+ "path",
1544
+ {
1545
+ d: "M8 2.2l1.76 3.56 3.93.57-2.84 2.77.67 3.91L8 11.4l-3.52 1.85.67-3.91-2.84-2.77 3.93-.57z",
1546
+ fill: favFilter ? "currentColor" : "none",
1547
+ stroke: "currentColor",
1548
+ strokeWidth: "1.3",
1549
+ strokeLinejoin: "round"
1550
+ }
1551
+ ) })
1552
+ }
1553
+ ) }) : null,
1554
+ settings.showSearch !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + peekCls(settings.showSearch, searchOpen), children: [
1555
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1556
+ "button",
1557
+ {
1558
+ type: "button",
1559
+ className: "mgcn-config-gear",
1560
+ "aria-label": t("search.placeholder"),
1561
+ onClick: () => {
1562
+ setSearchOpen((v) => !v);
1563
+ setConfigOpen(false);
1564
+ },
1565
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: [
1566
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "7", cy: "7", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
1567
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "10.4", y1: "10.4", x2: "14", y2: "14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
1568
+ ] })
1569
+ }
1570
+ ),
1571
+ searchOpen ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1572
+ SearchBox,
1573
+ {
1574
+ sessionId,
1575
+ scopes: settings.searchScopes,
1576
+ t,
1577
+ onJump: (key, seq) => onJump({ key, seq: seq ?? 0, turn: null, time: Date.now(), texts: [], members: [key] }),
1578
+ onClose: () => setSearchOpen(false)
1579
+ }
1580
+ ) : null
1581
+ ] }) : null,
1582
+ settings.showCount !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mgcn-count" + peekCls(settings.showCount, false), children: displayMarkers.length }) : null
1583
+ ] }),
1584
+ scrollable && showPaging !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-pageUp" + peekCls(showPaging, false), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1585
+ PagingButton,
1586
+ {
1587
+ dir: -1,
1588
+ visible: canPageUp,
1589
+ label: t("strip.up"),
1590
+ onEnter: cancelClear,
1591
+ onLeave: scheduleClear,
1592
+ onClick: () => pageBy(-1)
1593
+ }
1594
+ ) }) : null,
1595
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1596
+ "div",
1597
+ {
1598
+ ref: listRef,
1599
+ className: "mgcn-dots",
1600
+ onScroll: syncScroll,
1601
+ onMouseLeave: () => {
1602
+ lastHoverKey.current = null;
1603
+ scheduleClear();
1604
+ },
1605
+ onMouseOver: onDotsOver,
1606
+ onClick: onDotsClick,
1607
+ children: displayMarkers.map((dot, index) => {
1608
+ const isFocused = focus !== null && focus.key === dot.key;
1609
+ const tier = selectedIndex < 0 ? null : markerTier(index - selectedIndex);
1610
+ const scale = jumpingKey === dot.key ? 1.6 : tier !== null ? markerScale(tier) : 1;
1611
+ const reveal = revealed?.get(dot.key);
1612
+ const cls = ["mgcn-dot"];
1613
+ if (isFocused) cls.push("mgcn-focused");
1614
+ if (jumpingKey === dot.key) cls.push("mgcn-active");
1615
+ if (currentKey === dot.key) cls.push("mgcn-current");
1616
+ if (reveal !== void 0) {
1617
+ cls.push(reveal.dir === 1 ? "mgcn-dotEnterFromBottom" : "mgcn-dotEnterFromTop");
1618
+ }
1619
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1620
+ Dot,
1621
+ {
1622
+ keyName: dot.key,
1623
+ index,
1624
+ className: cls.join(" "),
1625
+ scale,
1626
+ label: dot.texts[0] ?? "",
1627
+ focused: isFocused,
1628
+ revealed: reveal
1629
+ },
1630
+ dot.key
1631
+ );
1632
+ })
1633
+ }
1634
+ ),
1635
+ scrollable && showPaging !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mgcn-pageDown" + peekCls(showPaging, false), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1636
+ PagingButton,
1637
+ {
1638
+ dir: 1,
1639
+ visible: canPageDown,
1640
+ label: t("strip.down"),
1641
+ onEnter: cancelClear,
1642
+ onLeave: scheduleClear,
1643
+ onClick: () => pageBy(1)
1644
+ }
1645
+ ) }) : null
1646
+ ]
1647
+ }
1648
+ ) }),
1649
+ loading !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Hint, { text: loading, align, position: "bottom", loading: true }) : hint !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Hint, { text: hint, align }) : null,
1650
+ focus !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1651
+ Cards,
1652
+ {
1653
+ focus,
1654
+ align,
1655
+ cardItems,
1656
+ favorites,
1657
+ onToggleFavorite: toggleFavorite,
1658
+ onJump,
1659
+ onEnter: cancelClear,
1660
+ onLeave: scheduleClear
1661
+ }
1662
+ ) : null
1663
+ ]
1664
+ }
1665
+ );
1666
+ }
1667
+
1668
+ // src/client/components/codex/CodexRail.tsx
1669
+ var import_react7 = require("react");
1670
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1671
+ var CodexSlot = (0, import_react7.memo)(function CodexSlot2(props) {
1672
+ const { marker, current, focused, jumping, label, reveal } = props;
1673
+ const cls = ["mgcn-cxSlot"];
1674
+ if (focused) cls.push("mgcn-cxFocused");
1675
+ if (current) cls.push("mgcn-cxCurrent");
1676
+ if (jumping) cls.push("mgcn-cxJump");
1677
+ if (reveal !== void 0) cls.push(reveal.dir === 1 ? "mgcn-cxEnterFromBottom" : "mgcn-cxEnterFromTop");
1678
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1679
+ "button",
1680
+ {
1681
+ type: "button",
1682
+ className: cls.join(" "),
1683
+ style: reveal !== void 0 ? { animationDelay: reveal.order * 35 + "ms" } : void 0,
1684
+ "data-mega-chat-nav-key": marker.key,
1685
+ "aria-label": label,
1686
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("i", { className: "mgcn-cxMark", "aria-hidden": "true" })
1687
+ }
1688
+ );
1689
+ });
1690
+ function useFollowCurrent(listRef, currentKey, markers, resetKey) {
1691
+ (0, import_react7.useEffect)(() => {
1692
+ const list = listRef.current;
1693
+ if (list === null || currentKey === null) return;
1694
+ const index = markers.findIndex((d) => d.key === currentKey);
1695
+ if (index < 0) return;
1696
+ const slotTop = 8 + index * ROW_HEIGHT;
1697
+ const contentHeight = 16 + markers.length * ROW_HEIGHT;
1698
+ const maxScroll = Math.max(0, contentHeight - list.clientHeight);
1699
+ const target = Math.max(0, Math.min(slotTop - list.clientHeight / 2 + ROW_HEIGHT / 2, maxScroll));
1700
+ if (Math.abs(target - list.scrollTop) < 2) return;
1701
+ list.scrollTo({ top: target, behavior: "smooth" });
1702
+ }, [currentKey, markers, resetKey]);
1703
+ }
1704
+ function CodexRail(props) {
1705
+ const { injected, useSessions, t } = props;
1706
+ const panelRef = (0, import_react7.useRef)(null);
1707
+ const listRef = (0, import_react7.useRef)(null);
1708
+ const [jumpingKey, setJumpingKey] = (0, import_react7.useState)(null);
1709
+ const [configOpen, setConfigOpen] = (0, import_react7.useState)(false);
1710
+ const [searchOpen, setSearchOpen] = (0, import_react7.useState)(false);
1711
+ const [favFilter, setFavFilter] = (0, import_react7.useState)(false);
1712
+ const [drawerOpen, setDrawerOpen] = (0, import_react7.useState)(false);
1713
+ const [followTick, setFollowTick] = (0, import_react7.useState)(0);
1714
+ const { showPeek, hidePeek, peekCls } = usePeekState();
1715
+ const lastHoverKey = (0, import_react7.useRef)(null);
1716
+ const mobile = useMobileMode();
1717
+ const { sessionId, visible, markers } = useMarkerData(injected, useSessions);
1718
+ const { favorites, toggle: toggleFavorite } = useFavorites(sessionId);
1719
+ const settings = useNavSettings(injected);
1720
+ const { style, align, bandHeight, cardCount, cardItems, markTone, showPaging } = settings;
1721
+ useMobileSearchButton(() => setDrawerOpen(true), mobile && markers.length > 0, align, t("search.placeholder"));
1722
+ const effectiveFilter = favFilter && favorites.size > 0;
1723
+ const displayMarkers = effectiveFilter ? markers.filter((m) => m.members.some((k) => favorites.has(k))) : markers;
1724
+ usePinning(panelRef, visible, align, settings.railOffset, sessionId);
1725
+ const [currentKey, forceKey] = useReadingSpy(visible, displayMarkers);
1726
+ const { scrollable, revealed, syncScroll, pageBy, canPageUp, canPageDown } = useBandPaging(listRef, displayMarkers);
1727
+ const caps = STYLE_CAPABILITIES[style];
1728
+ const focusRadius = Math.floor((cardCount - 1) / 2);
1729
+ const { focus, openCards, cancelClear, scheduleClear } = useCardPreview(displayMarkers, align, focusRadius, caps.hoverCards);
1730
+ const hint = useFailureNotice(t);
1731
+ const loading = useLoadingNotice(t);
1732
+ useClosePopoversOnOutside(configOpen, searchOpen, () => {
1733
+ setConfigOpen(false);
1734
+ setSearchOpen(false);
1735
+ });
1736
+ useFollowCurrent(listRef, currentKey, displayMarkers, followTick);
1737
+ if (!visible || injected === void 0 || markers.length === 0) return null;
1738
+ const onJump = (slot) => {
1739
+ if (sessionId === void 0) return;
1740
+ setJumpingKey(slot.key);
1741
+ forceKey(slot.key);
1742
+ const currentMarker = currentKey === null ? void 0 : displayMarkers.find((m) => m.key === currentKey);
1743
+ injected.jump(sessionId, slot.key, slot.seq, currentMarker?.seq);
1744
+ window.setTimeout(() => setJumpingKey((k) => k === slot.key ? null : k), 600);
1745
+ };
1746
+ if (mobile) {
1747
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: drawerOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1748
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-drawerMask", onClick: () => setDrawerOpen(false) }),
1749
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-searchDrawer", role: "dialog", "aria-label": t("search.placeholder"), children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1750
+ SearchBox,
1751
+ {
1752
+ sessionId,
1753
+ scopes: settings.searchScopes,
1754
+ t,
1755
+ onJump: (key, seq) => {
1756
+ setDrawerOpen(false);
1757
+ onJump({ key, seq: seq ?? 0, turn: null, time: Date.now(), texts: [], members: [key] });
1758
+ },
1759
+ onClose: () => setDrawerOpen(false),
1760
+ emptyContent: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-drawerList", children: [...markers].reverse().slice(0, 20).map((m) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1761
+ "button",
1762
+ {
1763
+ type: "button",
1764
+ className: "mgcn-drawerItem",
1765
+ onClick: () => {
1766
+ setDrawerOpen(false);
1767
+ onJump(m);
1768
+ },
1769
+ children: [
1770
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "mgcn-drawerItemTitle", children: [
1771
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mgcn-drawerItemTurn", children: m.turn !== null ? "Turn " + m.turn : "" }),
1772
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mgcn-drawerItemTime", children: clockText(m.time, Date.now()) })
1773
+ ] }),
1774
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mgcn-drawerItemText", children: m.texts[0] ?? "" })
1775
+ ]
1776
+ },
1777
+ m.key
1778
+ )) })
1779
+ }
1780
+ ) })
1781
+ ] }) : null });
1782
+ }
1783
+ const hoverOff = () => {
1784
+ lastHoverKey.current = null;
1785
+ scheduleClear();
1786
+ };
1787
+ const onListOver = (e) => {
1788
+ const el = e.target;
1789
+ const slot = el.closest("[data-mega-chat-nav-key]");
1790
+ if (slot === null) return;
1791
+ const key = slot.dataset.megaChatNavKey;
1792
+ if (key === void 0 || key === lastHoverKey.current) return;
1793
+ const marker = displayMarkers.find((m) => m.key === key);
1794
+ if (marker === void 0) return;
1795
+ lastHoverKey.current = key;
1796
+ cancelClear();
1797
+ openCards(marker, slot);
1798
+ };
1799
+ const onListClick = (e) => {
1800
+ const el = e.target;
1801
+ const slot = el.closest("[data-mega-chat-nav-key]");
1802
+ if (slot === null) return;
1803
+ const key = slot.dataset.megaChatNavKey;
1804
+ const marker = displayMarkers.find((m) => m.key === key);
1805
+ if (marker !== void 0) onJump(marker);
1806
+ };
1807
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1808
+ "div",
1809
+ {
1810
+ ref: panelRef,
1811
+ className: align === "right" ? "mgcn-rail mgcn-cxRail mgcn-railRight" : "mgcn-rail mgcn-cxRail",
1812
+ "data-mega-chat-nav": "rail",
1813
+ "data-tone": markTone,
1814
+ onMouseEnter: showPeek,
1815
+ onMouseLeave: () => {
1816
+ hidePeek();
1817
+ setFollowTick((n) => n + 1);
1818
+ },
1819
+ children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-queue", children: displayMarkers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-empty", children: t("strip.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1821
+ "div",
1822
+ {
1823
+ className: "mgcn-list" + (scrollable && showPaging !== "hide" ? "" : " mgcn-listNoPage"),
1824
+ style: { "--mgcn-band-height": BAND_HEIGHT_PX.codex[bandHeight] + "px" },
1825
+ children: [
1826
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mgcn-actions", children: [
1827
+ settings.showQuickSettings !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + peekCls(settings.showQuickSettings, configOpen), children: [
1828
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1829
+ "button",
1830
+ {
1831
+ type: "button",
1832
+ className: "mgcn-config-gear",
1833
+ "aria-label": t("config.title"),
1834
+ onClick: () => {
1835
+ setConfigOpen((v) => !v);
1836
+ setSearchOpen(false);
1837
+ },
1838
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 24 24", width: "12", height: "12", "aria-hidden": "true", children: [
1839
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "3", fill: "none", stroke: "currentColor", strokeWidth: "1.7" }),
1840
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" })
1841
+ ] })
1842
+ }
1843
+ ),
1844
+ configOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(QuickSettings, { injected, t, onClose: () => setConfigOpen(false) }) : null
1845
+ ] }) : null,
1846
+ settings.showFavorites !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + peekCls(settings.showFavorites, false), children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1847
+ "button",
1848
+ {
1849
+ type: "button",
1850
+ className: effectiveFilter ? "mgcn-config-gear mgcn-favFilterOn" : "mgcn-config-gear",
1851
+ "aria-label": t("fav.filter"),
1852
+ onClick: () => {
1853
+ if (favorites.size > 0) setFavFilter((v) => !v);
1854
+ },
1855
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1856
+ "path",
1857
+ {
1858
+ d: "M8 2.2l1.76 3.56 3.93.57-2.84 2.77.67 3.91L8 11.4l-3.52 1.85.67-3.91-2.84-2.77 3.93-.57z",
1859
+ fill: favFilter ? "currentColor" : "none",
1860
+ stroke: "currentColor",
1861
+ strokeWidth: "1.3",
1862
+ strokeLinejoin: "round"
1863
+ }
1864
+ ) })
1865
+ }
1866
+ ) }) : null,
1867
+ settings.showSearch !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + peekCls(settings.showSearch, searchOpen), children: [
1868
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1869
+ "button",
1870
+ {
1871
+ type: "button",
1872
+ className: "mgcn-config-gear",
1873
+ "aria-label": t("search.placeholder"),
1874
+ onClick: () => {
1875
+ setSearchOpen((v) => !v);
1876
+ setConfigOpen(false);
1877
+ },
1878
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: [
1879
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "7", cy: "7", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
1880
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "10.4", y1: "10.4", x2: "14", y2: "14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
1881
+ ] })
1882
+ }
1883
+ ),
1884
+ searchOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1885
+ SearchBox,
1886
+ {
1887
+ sessionId,
1888
+ scopes: settings.searchScopes,
1889
+ t,
1890
+ onJump: (key, seq) => onJump({ key, seq: seq ?? 0, turn: null, time: Date.now(), texts: [], members: [key] }),
1891
+ onClose: () => setSearchOpen(false)
1892
+ }
1893
+ ) : null
1894
+ ] }) : null,
1895
+ settings.showCount !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mgcn-count" + peekCls(settings.showCount, false), children: displayMarkers.length }) : null
1896
+ ] }),
1897
+ scrollable && showPaging !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-pageUp" + peekCls(showPaging, false), children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1898
+ PagingButton,
1899
+ {
1900
+ dir: -1,
1901
+ visible: canPageUp,
1902
+ label: t("strip.up"),
1903
+ onEnter: cancelClear,
1904
+ onLeave: hoverOff,
1905
+ onClick: () => pageBy(-1)
1906
+ }
1907
+ ) }) : null,
1908
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1909
+ "div",
1910
+ {
1911
+ ref: listRef,
1912
+ className: "mgcn-cxList",
1913
+ onScroll: syncScroll,
1914
+ onMouseLeave: hoverOff,
1915
+ onMouseOver: onListOver,
1916
+ onClick: onListClick,
1917
+ children: displayMarkers.map((slot) => {
1918
+ const current = currentKey === slot.key;
1919
+ const focused = focus !== null && focus.key === slot.key;
1920
+ const reveal = revealed?.get(slot.key);
1921
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1922
+ CodexSlot,
1923
+ {
1924
+ marker: slot,
1925
+ current,
1926
+ focused,
1927
+ jumping: jumpingKey === slot.key,
1928
+ label: slot.texts[0] ?? "",
1929
+ reveal
1930
+ },
1931
+ slot.key
1932
+ );
1933
+ })
1934
+ }
1935
+ ),
1936
+ scrollable && showPaging !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mgcn-pageDown" + peekCls(showPaging, false), children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1937
+ PagingButton,
1938
+ {
1939
+ dir: 1,
1940
+ visible: canPageDown,
1941
+ label: t("strip.down"),
1942
+ onEnter: cancelClear,
1943
+ onLeave: hoverOff,
1944
+ onClick: () => pageBy(1)
1945
+ }
1946
+ ) }) : null
1947
+ ]
1948
+ }
1949
+ ) }),
1950
+ loading !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Hint, { text: loading, align, position: "bottom", loading: true }) : hint !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Hint, { text: hint, align }) : null,
1951
+ focus !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1952
+ Cards,
1953
+ {
1954
+ focus,
1955
+ align,
1956
+ cardItems,
1957
+ favorites,
1958
+ onToggleFavorite: toggleFavorite,
1959
+ onJump,
1960
+ onEnter: cancelClear,
1961
+ onLeave: hoverOff
1962
+ }
1963
+ ) : null
1964
+ ]
1965
+ }
1966
+ );
1967
+ }
1968
+
1969
+ // src/client/components/deepseek/DeepseekRail.tsx
1970
+ var import_react8 = require("react");
1971
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1972
+ var DS_ROW_HEIGHT = 30;
1973
+ var DsRow = (0, import_react8.memo)(function DsRow2(props) {
1974
+ const { marker, current, jumping, hovered, favorited, label, onHover, onLeave, onToggleFavorite, onJump } = props;
1975
+ const cls = ["mgcn-dsRow"];
1976
+ if (current) cls.push("mgcn-dsCurrent");
1977
+ if (jumping) cls.push("mgcn-dsJump");
1978
+ if (hovered) cls.push("mgcn-dsRowHover");
1979
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1980
+ "div",
1981
+ {
1982
+ className: cls.join(" "),
1983
+ "data-mega-chat-nav-key": marker.key,
1984
+ onMouseEnter: onHover,
1985
+ onMouseLeave: onLeave,
1986
+ children: [
1987
+ hovered ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1988
+ "button",
1989
+ {
1990
+ type: "button",
1991
+ className: favorited ? "mgcn-dsFav mgcn-dsFavOn" : "mgcn-dsFav",
1992
+ "aria-label": favorited ? "unfavorite" : "favorite",
1993
+ onClick: (e) => {
1994
+ e.stopPropagation();
1995
+ onToggleFavorite();
1996
+ },
1997
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { viewBox: "0 0 16 16", width: "11", height: "11", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1998
+ "path",
1999
+ {
2000
+ d: "M8 2.2l1.76 3.56 3.93.57-2.84 2.77.67 3.91L8 11.4l-3.52 1.85.67-3.91-2.84-2.77 3.93-.57z",
2001
+ fill: favorited ? "currentColor" : "none",
2002
+ stroke: "currentColor",
2003
+ strokeWidth: "1.4",
2004
+ strokeLinejoin: "round"
2005
+ }
2006
+ ) })
2007
+ }
2008
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { className: "mgcn-dsMark", "aria-hidden": "true" }),
2009
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "mgcn-dsText", "aria-label": label, onClick: onJump, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-dsTextInner", children: marker.texts[0] ?? "" }) })
2010
+ ]
2011
+ }
2012
+ );
2013
+ });
2014
+ function SearchHitRow(props) {
2015
+ const { hit, onJump } = props;
2016
+ const roleTag2 = hit.role === "assistant" ? "\u52A9\u624B" : hit.role === "tool" ? "\u5DE5\u5177" : "\u7528\u6237";
2017
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("button", { type: "button", className: "mgcn-dsHit", onClick: onJump, children: [
2018
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { className: "mgcn-dsMark", "aria-hidden": "true" }),
2019
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "mgcn-dsHitBody", children: [
2020
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "mgcn-dsHitMeta", children: [
2021
+ hit.turn > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
2022
+ "\u7B2C ",
2023
+ hit.turn,
2024
+ " \u8F6E"
2025
+ ] }) : null,
2026
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-dsHitTag mgcn-dsHitTag--" + hit.role, children: roleTag2 })
2027
+ ] }),
2028
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-dsHitText", children: hit.hitStart >= 0 && hit.hitLen > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2029
+ hit.snippet.slice(0, hit.hitStart),
2030
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("mark", { className: "mgcn-search-mark", children: hit.snippet.slice(hit.hitStart, hit.hitStart + hit.hitLen) }),
2031
+ hit.snippet.slice(hit.hitStart + hit.hitLen)
2032
+ ] }) : hit.snippet })
2033
+ ] })
2034
+ ] });
2035
+ }
2036
+ function useFollowCurrent2(listRef, currentKey, markers, resetKey) {
2037
+ (0, import_react8.useEffect)(() => {
2038
+ const list = listRef.current;
2039
+ if (list === null || currentKey === null) return;
2040
+ const index = markers.findIndex((d) => d.key === currentKey);
2041
+ if (index < 0) return;
2042
+ const slotTop = index * DS_ROW_HEIGHT;
2043
+ const contentHeight = markers.length * DS_ROW_HEIGHT + 12;
2044
+ const maxScroll = Math.max(0, contentHeight - list.clientHeight);
2045
+ const target = Math.max(0, Math.min(slotTop - list.clientHeight / 2 + DS_ROW_HEIGHT / 2, maxScroll));
2046
+ if (Math.abs(target - list.scrollTop) < 2) return;
2047
+ list.scrollTo({ top: target, behavior: "smooth" });
2048
+ }, [currentKey, markers, resetKey]);
2049
+ }
2050
+ function DeepseekRail(props) {
2051
+ const { injected, useSessions, t } = props;
2052
+ const panelRef = (0, import_react8.useRef)(null);
2053
+ const listRef = (0, import_react8.useRef)(null);
2054
+ const searchInputRef = (0, import_react8.useRef)(null);
2055
+ const [open, setOpen] = (0, import_react8.useState)(false);
2056
+ const [jumpingKey, setJumpingKey] = (0, import_react8.useState)(null);
2057
+ const [searchOpen, setSearchOpen] = (0, import_react8.useState)(false);
2058
+ const [configOpen, setConfigOpen] = (0, import_react8.useState)(false);
2059
+ const [favFilter, setFavFilter] = (0, import_react8.useState)(false);
2060
+ const [hoverKey, setHoverKey] = (0, import_react8.useState)(null);
2061
+ const [drawerOpen, setDrawerOpen] = (0, import_react8.useState)(false);
2062
+ const closeTimer = (0, import_react8.useRef)(null);
2063
+ const [query, setQuery] = (0, import_react8.useState)("");
2064
+ const [results, setResults] = (0, import_react8.useState)([]);
2065
+ const [searching, setSearching] = (0, import_react8.useState)(false);
2066
+ const [followTick, setFollowTick] = (0, import_react8.useState)(0);
2067
+ const abortRef = (0, import_react8.useRef)(null);
2068
+ const debounceRef = (0, import_react8.useRef)(null);
2069
+ const mobile = useMobileMode();
2070
+ const { sessionId, visible, markers } = useMarkerData(injected, useSessions);
2071
+ const { favorites, toggle: toggleFavorite } = useFavorites(sessionId);
2072
+ const settings = useNavSettings(injected);
2073
+ const { align, bandHeight } = settings;
2074
+ useMobileSearchButton(() => setDrawerOpen(true), mobile && markers.length > 0, align, t("search.placeholder"));
2075
+ const effectiveFilter = favFilter && favorites.size > 0;
2076
+ const displayMarkers = effectiveFilter ? markers.filter((m) => m.members.some((k) => favorites.has(k))) : markers;
2077
+ usePinning(panelRef, visible, align, settings.railOffset, sessionId);
2078
+ const [currentKey, forceKey] = useReadingSpy(visible, displayMarkers);
2079
+ const hint = useFailureNotice(t);
2080
+ const loading = useLoadingNotice(t);
2081
+ useFollowCurrent2(listRef, currentKey, displayMarkers, String(searchOpen) + ":" + followTick);
2082
+ const collapseToIdle = () => {
2083
+ cancelClose();
2084
+ setOpen(false);
2085
+ setHoverKey(null);
2086
+ setSearchOpen(false);
2087
+ setQuery("");
2088
+ setResults([]);
2089
+ setSearching(false);
2090
+ if (debounceRef.current !== null) {
2091
+ window.clearTimeout(debounceRef.current);
2092
+ debounceRef.current = null;
2093
+ }
2094
+ abortRef.current?.abort();
2095
+ setConfigOpen(false);
2096
+ setFavFilter(false);
2097
+ };
2098
+ const cancelClose = () => {
2099
+ if (closeTimer.current !== null) {
2100
+ window.clearTimeout(closeTimer.current);
2101
+ closeTimer.current = null;
2102
+ }
2103
+ };
2104
+ const scheduleClose = () => {
2105
+ cancelClose();
2106
+ if (searchOpen || configOpen || effectiveFilter) return;
2107
+ closeTimer.current = window.setTimeout(() => {
2108
+ closeTimer.current = null;
2109
+ collapseToIdle();
2110
+ }, 260);
2111
+ };
2112
+ const collapseToIdleRef = (0, import_react8.useRef)(collapseToIdle);
2113
+ collapseToIdleRef.current = collapseToIdle;
2114
+ (0, import_react8.useEffect)(() => {
2115
+ const onDocPointerDown = (e) => {
2116
+ const panel = panelRef.current;
2117
+ if (panel === null) return;
2118
+ const t2 = e.target;
2119
+ if (t2 instanceof Node && panel.contains(t2)) return;
2120
+ collapseToIdleRef.current?.();
2121
+ };
2122
+ document.addEventListener("pointerdown", onDocPointerDown, true);
2123
+ return () => document.removeEventListener("pointerdown", onDocPointerDown, true);
2124
+ }, []);
2125
+ (0, import_react8.useEffect)(() => () => {
2126
+ if (closeTimer.current !== null) window.clearTimeout(closeTimer.current);
2127
+ if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
2128
+ abortRef.current?.abort();
2129
+ }, []);
2130
+ const runSearch = (raw) => {
2131
+ abortRef.current?.abort();
2132
+ const ac = new AbortController();
2133
+ abortRef.current = ac;
2134
+ const q = raw.trim();
2135
+ if (q.length < 1) {
2136
+ setResults([]);
2137
+ setSearching(false);
2138
+ return;
2139
+ }
2140
+ setSearching(true);
2141
+ fetchHits(sessionId, q, settings.searchScopes, ac.signal).then((hits) => {
2142
+ if (abortRef.current !== ac) return;
2143
+ setResults(hits);
2144
+ setSearching(false);
2145
+ }).catch(() => {
2146
+ if (abortRef.current === ac) setSearching(false);
2147
+ });
2148
+ };
2149
+ const onSearchInput = (value) => {
2150
+ setQuery(value);
2151
+ if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
2152
+ if (value.trim() === "") {
2153
+ debounceRef.current = null;
2154
+ abortRef.current?.abort();
2155
+ setResults([]);
2156
+ setSearching(false);
2157
+ return;
2158
+ }
2159
+ setSearching(true);
2160
+ debounceRef.current = window.setTimeout(() => {
2161
+ debounceRef.current = null;
2162
+ runSearch(value);
2163
+ }, 250);
2164
+ };
2165
+ const openSearch = () => {
2166
+ cancelClose();
2167
+ setConfigOpen(false);
2168
+ setFavFilter(false);
2169
+ setSearchOpen(true);
2170
+ setOpen(true);
2171
+ window.setTimeout(() => searchInputRef.current?.focus(), 30);
2172
+ };
2173
+ const onToggleRowFavorite = (key) => {
2174
+ const removingLast = effectiveFilter && favorites.size === 1 && favorites.has(key);
2175
+ toggleFavorite(key);
2176
+ if (removingLast) setFavFilter(false);
2177
+ };
2178
+ const closeSearch = () => {
2179
+ cancelClose();
2180
+ setSearchOpen(false);
2181
+ setQuery("");
2182
+ setResults([]);
2183
+ setSearching(false);
2184
+ if (debounceRef.current !== null) {
2185
+ window.clearTimeout(debounceRef.current);
2186
+ debounceRef.current = null;
2187
+ }
2188
+ abortRef.current?.abort();
2189
+ if (panelRef.current !== null && !panelRef.current.matches(":hover")) {
2190
+ setOpen(false);
2191
+ setHoverKey(null);
2192
+ }
2193
+ };
2194
+ const toggleConfig = () => {
2195
+ cancelClose();
2196
+ if (configOpen) {
2197
+ setConfigOpen(false);
2198
+ setHoverKey(null);
2199
+ if (panelRef.current !== null && !panelRef.current.matches(":hover")) {
2200
+ setOpen(false);
2201
+ }
2202
+ return;
2203
+ }
2204
+ closeSearch();
2205
+ setOpen(false);
2206
+ setHoverKey(null);
2207
+ setConfigOpen(true);
2208
+ };
2209
+ if (!visible || injected === void 0 || markers.length === 0) return null;
2210
+ const onJump = (row) => {
2211
+ if (sessionId === void 0) return;
2212
+ setJumpingKey(row.key);
2213
+ forceKey(row.key);
2214
+ const currentMarker = currentKey === null ? void 0 : displayMarkers.find((m) => m.key === currentKey);
2215
+ injected.jump(sessionId, row.key, row.seq, currentMarker?.seq);
2216
+ window.setTimeout(() => setJumpingKey((k) => k === row.key ? null : k), 600);
2217
+ };
2218
+ const onJumpHit = (hit) => {
2219
+ if (sessionId === void 0) return;
2220
+ setJumpingKey(hit.key);
2221
+ forceKey(hit.key);
2222
+ injected.jump(sessionId, hit.key, hit.seq);
2223
+ window.setTimeout(() => setJumpingKey((k) => k === hit.key ? null : k), 600);
2224
+ };
2225
+ if (mobile) {
2226
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: drawerOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2227
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-drawerMask", onClick: () => setDrawerOpen(false) }),
2228
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mgcn-searchDrawer", role: "dialog", "aria-label": t("search.placeholder"), children: [
2229
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-drawerInput", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2230
+ "input",
2231
+ {
2232
+ className: "mgcn-search-input",
2233
+ value: query,
2234
+ placeholder: t("search.placeholder"),
2235
+ autoFocus: true,
2236
+ onChange: (e) => onSearchInput(e.target.value),
2237
+ onKeyDown: (e) => {
2238
+ if (e.key === "Escape") setDrawerOpen(false);
2239
+ }
2240
+ }
2241
+ ) }),
2242
+ query.trim() === "" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-drawerList", children: [...markers].reverse().slice(0, 20).map((m) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2243
+ "button",
2244
+ {
2245
+ type: "button",
2246
+ className: "mgcn-drawerItem",
2247
+ onClick: () => {
2248
+ setDrawerOpen(false);
2249
+ onJump(m);
2250
+ },
2251
+ children: [
2252
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "mgcn-drawerItemTitle", children: [
2253
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-drawerItemTurn", children: m.turn !== null ? "Turn " + m.turn : "" }),
2254
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-drawerItemTime", children: clockText(m.time, Date.now()) })
2255
+ ] }),
2256
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-drawerItemText", children: m.texts[0] ?? "" })
2257
+ ]
2258
+ },
2259
+ m.key
2260
+ )) }) : searching ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-drawerList", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mgcn-dsLoadingRow", role: "status", children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "mgcn-dsLoadingDots", "aria-hidden": "true", children: [
2262
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", {}),
2263
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", {}),
2264
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", {})
2265
+ ] }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: t("search.loading") })
2267
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mgcn-drawerList", children: [
2268
+ results.map((hit) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SearchHitRow, { hit, onJump: () => {
2269
+ setDrawerOpen(false);
2270
+ onJumpHit(hit);
2271
+ } }, hit.key + ":" + hit.seq)),
2272
+ results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-dsNoHit", children: t("search.empty") }) : null
2273
+ ] })
2274
+ ] })
2275
+ ] }) : null });
2276
+ }
2277
+ const messagesBody = (scrollable) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ref: scrollable ? listRef : void 0, className: "mgcn-dsList", children: displayMarkers.map((row) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2278
+ DsRow,
2279
+ {
2280
+ marker: row,
2281
+ current: currentKey === row.key,
2282
+ jumping: jumpingKey === row.key,
2283
+ hovered: hoverKey === row.key,
2284
+ favorited: favorites.has(row.key),
2285
+ label: row.texts[0] ?? "",
2286
+ onHover: () => setHoverKey(row.key),
2287
+ onLeave: () => setHoverKey((k) => k === row.key ? null : k),
2288
+ onToggleFavorite: () => onToggleRowFavorite(row.key),
2289
+ onJump: () => onJump(row)
2290
+ },
2291
+ row.key
2292
+ )) });
2293
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2294
+ "div",
2295
+ {
2296
+ ref: panelRef,
2297
+ className: (align === "right" ? "mgcn-rail mgcn-dsRail mgcn-railRight" : "mgcn-rail mgcn-dsRail") + (open ? " mgcn-dsOpen" : "") + (configOpen ? " mgcn-dsSnap" : ""),
2298
+ "data-mega-chat-nav": "rail",
2299
+ onMouseEnter: () => {
2300
+ cancelClose();
2301
+ if (!configOpen) setOpen(true);
2302
+ },
2303
+ onMouseLeave: () => {
2304
+ setFollowTick((n) => n + 1);
2305
+ scheduleClose();
2306
+ },
2307
+ children: [
2308
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-queue", children: displayMarkers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-empty", children: t("strip.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2309
+ "div",
2310
+ {
2311
+ className: "mgcn-list",
2312
+ style: { "--mgcn-band-height": BAND_HEIGHT_PX.deepseek[bandHeight] + "px" },
2313
+ children: [
2314
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { role: "menu", className: "mgcn-dsPanel", children: [
2315
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mgcn-dsHead", children: [
2316
+ settings.showSearch !== "hide" ? searchOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2317
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2318
+ "button",
2319
+ {
2320
+ type: "button",
2321
+ className: "mgcn-config-gear mgcn-dsModeOn",
2322
+ "aria-label": t("search.placeholder"),
2323
+ onClick: closeSearch,
2324
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [
2325
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "7", cy: "7", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
2326
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "10.4", y1: "10.4", x2: "14", y2: "14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
2327
+ ] })
2328
+ }
2329
+ ),
2330
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2331
+ "input",
2332
+ {
2333
+ ref: searchInputRef,
2334
+ className: "mgcn-dsSearchInput",
2335
+ value: query,
2336
+ placeholder: t("search.placeholder"),
2337
+ onChange: (e) => onSearchInput(e.target.value),
2338
+ onKeyDown: (e) => {
2339
+ if (e.key === "Escape") closeSearch();
2340
+ }
2341
+ }
2342
+ )
2343
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2344
+ "button",
2345
+ {
2346
+ type: "button",
2347
+ className: "mgcn-config-gear",
2348
+ "aria-label": t("search.placeholder"),
2349
+ onClick: openSearch,
2350
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [
2351
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "7", cy: "7", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
2352
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "10.4", y1: "10.4", x2: "14", y2: "14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
2353
+ ] })
2354
+ }
2355
+ ) : null,
2356
+ !searchOpen && settings.showQuickSettings !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2357
+ "button",
2358
+ {
2359
+ type: "button",
2360
+ className: configOpen ? "mgcn-config-gear mgcn-dsModeOn" : "mgcn-config-gear",
2361
+ "aria-label": t("config.title"),
2362
+ onClick: toggleConfig,
2363
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 24 24", width: "13", height: "13", "aria-hidden": "true", children: [
2364
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "3", fill: "none", stroke: "currentColor", strokeWidth: "1.7" }),
2365
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" })
2366
+ ] })
2367
+ }
2368
+ ) : null,
2369
+ !searchOpen && settings.showFavorites !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2370
+ "button",
2371
+ {
2372
+ type: "button",
2373
+ className: effectiveFilter ? "mgcn-config-gear mgcn-favFilterOn" : "mgcn-config-gear",
2374
+ "aria-label": t("fav.filter"),
2375
+ onClick: () => {
2376
+ if (favorites.size > 0) setFavFilter((v) => !v);
2377
+ },
2378
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { className: "mgcn-btn-icon", viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2379
+ "path",
2380
+ {
2381
+ d: "M8 2.2l1.76 3.56 3.93.57-2.84 2.77.67 3.91L8 11.4l-3.52 1.85.67-3.91-2.84-2.77 3.93-.57z",
2382
+ fill: favFilter ? "currentColor" : "none",
2383
+ stroke: "currentColor",
2384
+ strokeWidth: "1.3",
2385
+ strokeLinejoin: "round"
2386
+ }
2387
+ ) })
2388
+ }
2389
+ ) : null,
2390
+ !searchOpen && settings.showCount !== "hide" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-count", children: displayMarkers.length }) : null
2391
+ ] }),
2392
+ searchOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-dsMode", children: query.trim() === "" ? messagesBody(false) : searching ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mgcn-dsLoadingRow", role: "status", children: [
2393
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "mgcn-dsLoadingDots", "aria-hidden": "true", children: [
2394
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", {}),
2395
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", {}),
2396
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", {})
2397
+ ] }),
2398
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: t("search.loading") })
2399
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mgcn-dsList", children: [
2400
+ results.map((hit) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SearchHitRow, { hit, onJump: () => onJumpHit(hit) }, hit.key + ":" + hit.seq)),
2401
+ results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mgcn-dsNoHit", children: t("search.empty") }) : null
2402
+ ] }) }) : messagesBody(true)
2403
+ ] }),
2404
+ configOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mgcn-gear-wrap mgcn-gear-wrap--" + align + " mgcn-dsFloat", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(QuickSettings, { injected, t, onClose: () => {
2405
+ cancelClose();
2406
+ setConfigOpen(false);
2407
+ setHoverKey(null);
2408
+ } }) }) : null
2409
+ ]
2410
+ }
2411
+ ) }),
2412
+ loading !== null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Hint, { text: loading, align, position: "bottom", loading: true }) : hint !== null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Hint, { text: hint, align }) : null
2413
+ ]
2414
+ }
2415
+ );
2416
+ }
2417
+
2418
+ // src/client/components/RailDispatch.tsx
2419
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2420
+ function RailDispatch(props) {
2421
+ const [style, setStyle] = (0, import_react9.useState)(() => props.injected?.style() ?? DEFAULT_STYLE);
2422
+ (0, import_react9.useEffect)(() => {
2423
+ if (props.injected === void 0) return;
2424
+ return props.injected.subscribeSettings(() => setStyle(props.injected?.style() ?? DEFAULT_STYLE));
2425
+ }, [props.injected]);
2426
+ const railProps = { injected: props.injected, useSessions: props.useSessions, t: props.t };
2427
+ if (style === "codex") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CodexRail, { ...railProps });
2428
+ if (style === "deepseek") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DeepseekRail, { ...railProps });
2429
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MinimalRail, { ...railProps });
2430
+ }
2431
+
2432
+ // src/client/components/settings/SettingsPage.tsx
2433
+ var import_react10 = require("react");
2434
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2435
+ function Seg2(props) {
2436
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mgcn-segmented", role: "radiogroup", children: props.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2437
+ "button",
2438
+ {
2439
+ type: "button",
2440
+ role: "radio",
2441
+ "aria-checked": props.value === o,
2442
+ className: props.value === o ? "mgcn-segment mgcn-segmentActive" : "mgcn-segment",
2443
+ onClick: () => props.onPick(o),
2444
+ children: props.labels[String(o)] ?? String(o)
2445
+ },
2446
+ String(o)
2447
+ )) });
2448
+ }
2449
+ function Field({ label, desc, children }) {
2450
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mgcn-settingsRow", children: [
2451
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mgcn-settingsLabel", children: [
2452
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mgcn-settingsTitle", children: label }),
2453
+ desc !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mgcn-settingsDesc", children: desc }) : null
2454
+ ] }),
2455
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mgcn-settingsControl", children })
2456
+ ] });
2457
+ }
2458
+ function useSettingsTick2(subscribe) {
2459
+ const [, bump] = (0, import_react10.useState)(0);
2460
+ (0, import_react10.useEffect)(() => subscribe(() => bump((n) => n + 1)), [subscribe]);
2461
+ }
2462
+ function SettingsPage(props) {
2463
+ const { injected, t, hosted } = props;
2464
+ useSettingsTick2(injected.subscribeSettings);
2465
+ if (hosted === true) {
2466
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mgcn-settings", children: t("settings.hosted") });
2467
+ }
2468
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mgcn-settings", children: [
2469
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.style"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2470
+ Seg2,
2471
+ {
2472
+ options: STYLE_OPTIONS,
2473
+ value: injected.style(),
2474
+ labels: { minimal: t("config.style.minimal"), codex: t("config.style.codex"), deepseek: t("config.style.deepseek") },
2475
+ onPick: (v) => injected.setStyle(v)
2476
+ }
2477
+ ) }),
2478
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.railOffset"), desc: t("config.railOffset.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mgcn-sliderRow", children: [
2479
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2480
+ "input",
2481
+ {
2482
+ type: "range",
2483
+ min: "0",
2484
+ max: "32",
2485
+ step: "1",
2486
+ value: injected.railOffset(),
2487
+ onChange: (e) => injected.setRailOffset(Number(e.target.value))
2488
+ }
2489
+ ),
2490
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "mgcn-sliderValue", children: [
2491
+ injected.railOffset(),
2492
+ "px"
2493
+ ] })
2494
+ ] }) }),
2495
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.align"), desc: t("settings.align.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2496
+ Seg2,
2497
+ {
2498
+ options: ALIGN_OPTIONS,
2499
+ value: injected.align(),
2500
+ labels: { left: t("settings.align.left"), right: t("settings.align.right") },
2501
+ onPick: (v) => injected.setAlign(v)
2502
+ }
2503
+ ) }),
2504
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.scrollBehavior"), desc: t("config.scrollBehavior.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2505
+ Seg2,
2506
+ {
2507
+ options: SCROLL_OPTIONS,
2508
+ value: injected.scrollBehavior(),
2509
+ labels: { smooth: t("config.scrollBehavior.smooth"), instant: t("config.scrollBehavior.instant") },
2510
+ onPick: (v) => injected.setScrollMode(v)
2511
+ }
2512
+ ) }),
2513
+ STYLE_CAPABILITIES[injected.style()].hoverCards ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2514
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.cardCount"), desc: t("config.cardCount.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2515
+ Seg2,
2516
+ {
2517
+ options: CARD_OPTIONS,
2518
+ value: injected.cardCount(),
2519
+ labels: { 1: "1", 3: "3", 5: "5" },
2520
+ onPick: (v) => injected.setCardCount(v)
2521
+ }
2522
+ ) }),
2523
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.cardItems"), desc: t("config.cardItems.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mgcn-checkGroup", children: CARD_ITEM_OPTIONS.map((item) => {
2524
+ const on = injected.cardItems().includes(item);
2525
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "mgcn-checkItem", children: [
2526
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2527
+ "input",
2528
+ {
2529
+ type: "checkbox",
2530
+ checked: on,
2531
+ onChange: (e) => {
2532
+ const items = e.target.checked ? [...injected.cardItems(), item] : injected.cardItems().filter((x) => x !== item);
2533
+ injected.setCardItems(items);
2534
+ }
2535
+ }
2536
+ ),
2537
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: t("config.cardItems." + item) })
2538
+ ] }, item);
2539
+ }) }) })
2540
+ ] }) : null,
2541
+ injected.style() === "codex" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.markTone"), desc: t("config.markTone.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2542
+ Seg2,
2543
+ {
2544
+ options: MARK_TONE_OPTIONS,
2545
+ value: injected.markTone(),
2546
+ labels: { soft: t("config.markTone.soft"), deep: t("config.markTone.deep") },
2547
+ onPick: (v) => injected.setMarkTone(v)
2548
+ }
2549
+ ) }) : null,
2550
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.searchScopes"), desc: t("config.searchScopes.desc"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mgcn-checkGroup", children: SEARCH_SCOPE_OPTIONS.map((scope) => {
2551
+ const on = injected.searchScopes().includes(scope);
2552
+ const locked = scope === "user";
2553
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "mgcn-checkItem", children: [
2554
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2555
+ "input",
2556
+ {
2557
+ type: "checkbox",
2558
+ checked: on,
2559
+ disabled: locked,
2560
+ onChange: (e) => {
2561
+ const items = e.target.checked ? [...injected.searchScopes(), scope] : injected.searchScopes().filter((x) => x !== scope);
2562
+ injected.setSearchScopes(items);
2563
+ }
2564
+ }
2565
+ ),
2566
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: t("config.searchScopes." + scope) })
2567
+ ] }, scope);
2568
+ }) }) }),
2569
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.showQuickSettings"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ModeSeg2, { value: injected.showQuickSettings(), onChange: (v) => injected.setShowQuickSettings(v), t }) }),
2570
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.showSearch"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ModeSeg2, { value: injected.showSearch(), onChange: (v) => injected.setShowSearch(v), t }) }),
2571
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.showFavorites"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ModeSeg2, { value: injected.showFavorites(), onChange: (v) => injected.setShowFavorites(v), t }) }),
2572
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.showCount"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ModeSeg2, { value: injected.showCount(), onChange: (v) => injected.setShowCount(v), t }) }),
2573
+ STYLE_CAPABILITIES[injected.style()].paging ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Field, { label: t("config.showPaging"), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ModeSeg2, { value: injected.showPaging(), onChange: (v) => injected.setShowPaging(v), t }) }) : null
2574
+ ] });
2575
+ }
2576
+ function ModeSeg2({ value, onChange, t }) {
2577
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2578
+ Seg2,
2579
+ {
2580
+ options: SHOW_OPTIONS,
2581
+ value,
2582
+ labels: {
2583
+ show: t("config.showMode.show"),
2584
+ peek: t("config.showMode.peek"),
2585
+ hide: t("config.showMode.hide")
2586
+ },
2587
+ onPick: (v) => onChange(v)
2588
+ }
2589
+ );
2590
+ }
2591
+
2592
+ // src/client/components/settings/StandaloneSettings.tsx
2593
+ var import_react11 = require("react");
2594
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2595
+ function StandaloneSettings(props) {
2596
+ const [hosted, setHosted] = (0, import_react11.useState)(false);
2597
+ (0, import_react11.useEffect)(() => {
2598
+ const check = () => {
2599
+ const n = props.slots?.entries?.("mega.settings.member")?.length ?? 0;
2600
+ setHosted(n > 0);
2601
+ };
2602
+ check();
2603
+ return props.slots?.subscribe?.("mega.settings.member", check);
2604
+ }, [props.slots]);
2605
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SettingsPage, { injected: props.injected, t: props.t, hosted });
2606
+ }
2607
+
2608
+ // src/client/locales.ts
2609
+ var zh = {
2610
+ "strip.empty": "\u672C\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u63D0\u95EE",
2611
+ "strip.up": "\u5411\u4E0A\u7FFB\u51FA\u66F4\u591A\u63D0\u95EE\u5706\u70B9",
2612
+ "strip.down": "\u5411\u4E0B\u7FFB\u51FA\u66F4\u591A\u63D0\u95EE\u5706\u70B9",
2613
+ "jump.inactive": "\u804A\u5929\u89C6\u56FE\u672A\u6FC0\u6D3B",
2614
+ "jump.hidden": "\u76EE\u6807\u65E0\u72EC\u7ACB\u6C14\u6CE1\uFF0C\u5DF2\u5B9A\u4F4D\u5230\u90BB\u8FD1\u5185\u5BB9",
2615
+ "jump.notfound": "\u76EE\u6807\u672A\u52A0\u8F7D\u6216\u4E0D\u5B58\u5728\uFF08\u53EF\u80FD\u5DF2\u538B\u7F29\uFF09",
2616
+ "jump.timeout": "\u52A0\u8F7D\u5386\u53F2\u8D85\u65F6\uFF0C\u53EF\u91CD\u8BD5",
2617
+ "jump.loading": "\u6B63\u5728\u52A0\u8F7D\u8F83\u65E9\u8BB0\u5F55\u4EE5\u5B9A\u4F4D\uFF08\u7B2C {n} \u9875\uFF09",
2618
+ "search.historic": "\u53E6\u6709 {n} \u6761\u6765\u81EA\u66F4\u65E9\u5386\u53F2",
2619
+ "search.turnTitle": "\u7B2C {turn} \u8F6E",
2620
+ "search.tag.user": "\u7528\u6237",
2621
+ "search.tag.assistant": "\u52A9\u624B",
2622
+ "search.tag.tool": "\u5DE5\u5177",
2623
+ "settings.tab": "\u63D0\u95EE\u5BFC\u822A",
2624
+ "label": "mega \u5BFC\u822A",
2625
+ "config.align": "\u5BF9\u9F50\u65B9\u5F0F",
2626
+ "config.railOffset": "\u5BFC\u822A\u680F\u504F\u79FB",
2627
+ "config.railOffset.desc": "\u5BFC\u822A\u6761\u4E0E\u5BF9\u8BDD\u5217\u8FB9\u7F18\u7684\u8DDD\u79BB\uFF080-32px\uFF0C\u968F\u5DE6\u53F3\u5BF9\u9F50\u81EA\u9002\u5E94\uFF09\u3002",
2628
+ "settings.align.desc": "\u9009\u62E9\u5706\u70B9\u5BFC\u822A\u6761\u951A\u5B9A\u5728\u5BF9\u8BDD\u680F\u7684\u54EA\u4E00\u4FA7\u3002",
2629
+ "settings.align.left": "\u5DE6\u4FA7",
2630
+ "settings.align.right": "\u53F3\u4FA7",
2631
+ "settings.hosted": "\u914D\u7F6E\u5DF2\u6536\u7EB3\u81F3 mega \u8BBE\u7F6E\u4E2D\u5FC3",
2632
+ "config.bandHeight": "\u8282\u70B9\u680F\u9AD8\u5EA6",
2633
+ "config.bandHeight.desc": "\u8282\u70B9\u680F\u7684\u6700\u5927\u9AD8\u5EA6\u3002",
2634
+ "config.bandHeight.compact": "\u7D27\u51D1",
2635
+ "config.bandHeight.standard": "\u6807\u51C6",
2636
+ "config.bandHeight.tall": "\u9AD8",
2637
+ "config.title": "\u5BFC\u822A\u6761\u8BBE\u7F6E",
2638
+ "config.settings": "\u8BBE\u7F6E",
2639
+ "config.close": "\u5173\u95ED",
2640
+ "config.scrollBehavior": "\u8DF3\u8F6C\u52A8\u753B",
2641
+ "config.scrollBehavior.desc": "\u70B9\u51FB\u8DF3\u8F6C\u65F6\u6D88\u606F\u6EDA\u5165\u7684\u52A8\u753B\u65B9\u5F0F\u3002",
2642
+ "config.scrollBehavior.smooth": "\u5E73\u6ED1",
2643
+ "config.scrollBehavior.instant": "\u77AC\u65F6",
2644
+ "config.showCount": "\u8F6E\u6B21\u603B\u6570",
2645
+ "config.showPaging": "\u7FFB\u9875\u6807\u8BB0",
2646
+ "config.showQuickSettings": "\u8BBE\u7F6E\u6309\u94AE",
2647
+ "config.showFavorites": "\u6536\u85CF\u6309\u94AE",
2648
+ "config.searchScopes": "\u641C\u7D22\u5185\u5BB9",
2649
+ "config.searchScopes.desc": "\u641C\u7D22\u54EA\u4E9B\u6D88\u606F\u7C7B\u578B\uFF08\u591A\u9009\uFF1B\u7528\u6237\u5FC5\u9009\uFF09\u3002",
2650
+ "config.searchScopes.user": "\u7528\u6237",
2651
+ "config.searchScopes.assistant": "\u52A9\u624B",
2652
+ "config.searchScopes.tool": "\u5DE5\u5177",
2653
+ "config.style": "\u98CE\u683C",
2654
+ "config.style.minimal": "\u7B80\u7EA6",
2655
+ "config.style.codex": "Codex",
2656
+ "config.style.deepseek": "Deepseek Chat",
2657
+ "config.cardCount": "\u60AC\u505C\u5361\u7247\u6570",
2658
+ "config.cardCount.desc": "\u60AC\u505C\u8282\u70B9\u65F6\u5F39\u51FA\u7684\u5361\u7247\u6570\u91CF\uFF081/3/5 \u5F20\uFF09\u3002",
2659
+ "config.cardItems": "\u60AC\u6D6E\u5361\u529F\u80FD",
2660
+ "config.cardItems.desc": "\u52FE\u9009\u60AC\u6D6E\u5361\u663E\u793A\u7684\u5185\u5BB9\u5757\uFF08\u591A\u9009\uFF09\u3002",
2661
+ "config.cardItems.turn": "\u8F6E\u6B21",
2662
+ "config.cardItems.duration": "\u7528\u65F6",
2663
+ "config.cardItems.favorite": "\u6536\u85CF",
2664
+ "config.markTone": "\u523B\u5EA6\u8272\u8C03",
2665
+ "config.markTone.desc": "codex \u523B\u5EA6\u6761\u7684\u660E\u6697\u6863\uFF1A\u67D4\u548C\u4F4E\u5BF9\u6BD4 / \u6DF1\u9083\u9AD8\u5BF9\u6BD4\uFF08\u9ED8\u8BA4\uFF09\u3002",
2666
+ "config.markTone.soft": "\u67D4\u548C",
2667
+ "config.markTone.deep": "\u6DF1\u9083",
2668
+ "fav.filter": "\u53EA\u770B\u6536\u85CF",
2669
+ "strip.noFavorites": "\u6682\u65E0\u6536\u85CF",
2670
+ "config.showSearch": "\u641C\u7D22\u6309\u94AE",
2671
+ "config.showMode.show": "\u663E\u793A",
2672
+ "config.showMode.peek": "\u6D6E\u73B0",
2673
+ "config.showMode.hide": "\u9690\u85CF",
2674
+ "search.placeholder": "\u641C\u7D22\u6D88\u606F\u2026",
2675
+ "search.empty": "\u65E0\u5339\u914D\u7ED3\u679C",
2676
+ "search.loading": "\u6B63\u5728\u641C\u7D22\u2026"
2677
+ };
2678
+ var en = {
2679
+ "strip.empty": "No questions in this session yet",
2680
+ "strip.up": "Reveal more question dots above",
2681
+ "strip.down": "Reveal more question dots below",
2682
+ "jump.inactive": "Chat view is not active",
2683
+ "jump.hidden": "No dedicated bubble; landed on nearby content",
2684
+ "jump.notfound": "Target not loaded or missing (maybe compacted)",
2685
+ "jump.timeout": "Timed out loading history; retry",
2686
+ "jump.loading": "Loading earlier records to locate (page {n})",
2687
+ "search.historic": "{n} more from earlier history",
2688
+ "search.turnTitle": "Turn {turn}",
2689
+ "search.tag.user": "User",
2690
+ "search.tag.assistant": "Assistant",
2691
+ "search.tag.tool": "Tool",
2692
+ "settings.tab": "Question Nav",
2693
+ "label": "Mega Nav",
2694
+ "config.align": "Alignment",
2695
+ "config.railOffset": "Rail offset",
2696
+ "config.railOffset.desc": "Distance between the rail and the conversation edge (0-32px, adapts to alignment).",
2697
+ "settings.align.desc": "Choose which edge of the conversation column the dot rail anchors to.",
2698
+ "settings.align.left": "Left",
2699
+ "settings.align.right": "Right",
2700
+ "settings.hosted": "Config is hosted in the mega settings center",
2701
+ "config.bandHeight": "Band height",
2702
+ "config.bandHeight.desc": "Max height of the dot band.",
2703
+ "config.bandHeight.compact": "Compact",
2704
+ "config.bandHeight.standard": "Standard",
2705
+ "config.bandHeight.tall": "Tall",
2706
+ "config.title": "Nav settings",
2707
+ "config.settings": "Settings",
2708
+ "config.close": "Close",
2709
+ "config.scrollBehavior": "Jump animation",
2710
+ "config.scrollBehavior.desc": "How the chat scrolls when jumping to a message.",
2711
+ "config.scrollBehavior.smooth": "Smooth",
2712
+ "config.scrollBehavior.instant": "Instant",
2713
+ "config.showCount": "Turn count",
2714
+ "config.showPaging": "Paging markers",
2715
+ "config.showQuickSettings": "Settings button",
2716
+ "config.showFavorites": "Favorite button",
2717
+ "config.searchScopes": "Search content",
2718
+ "config.searchScopes.desc": "Message types included in search (multi-select; user is required).",
2719
+ "config.searchScopes.user": "User",
2720
+ "config.searchScopes.assistant": "Assistant",
2721
+ "config.searchScopes.tool": "Tool",
2722
+ "config.style": "Style",
2723
+ "config.style.minimal": "Minimal",
2724
+ "config.style.codex": "Codex",
2725
+ "config.style.deepseek": "Deepseek Chat",
2726
+ "config.cardCount": "Card count",
2727
+ "config.cardCount.desc": "Cascade cards shown on hover (1/3/5).",
2728
+ "config.cardItems": "Card contents",
2729
+ "config.cardItems.desc": "Content blocks shown on hover cards (multi-select).",
2730
+ "config.cardItems.turn": "Turn",
2731
+ "config.cardItems.duration": "Duration",
2732
+ "config.cardItems.favorite": "Favorite",
2733
+ "config.markTone": "Mark tone",
2734
+ "config.markTone.desc": "Codex tick contrast: soft / deep (default).",
2735
+ "config.markTone.soft": "Soft",
2736
+ "config.markTone.deep": "Deep",
2737
+ "fav.filter": "Favorites only",
2738
+ "strip.noFavorites": "No favorites yet",
2739
+ "config.showSearch": "Search button",
2740
+ "config.showMode.show": "Show",
2741
+ "config.showMode.peek": "Peek",
2742
+ "config.showMode.hide": "Hide",
2743
+ "search.placeholder": "Search messages\u2026",
2744
+ "search.empty": "No matches",
2745
+ "search.loading": "Searching\u2026"
2746
+ };
2747
+
2748
+ // src/client/jump.ts
2749
+ var BUDGET = {
2750
+ totalTimeoutMs: 3e4,
2751
+ maxPages: 300,
2752
+ rowWaitMs: 8e3,
2753
+ pollMs: 60
2754
+ };
2755
+ function isVisibleRow(row) {
2756
+ return row.visibility !== "hidden";
2757
+ }
2758
+ function hasTarget(rows, key) {
2759
+ for (const row of rows) {
2760
+ if (isVisibleRow(row) && row.key === key) return true;
2761
+ }
2762
+ return false;
2763
+ }
2764
+ function seqRange(rows) {
2765
+ let min = null;
2766
+ let max = null;
2767
+ for (const row of rows) {
2768
+ if (min === null || row.anchorSeq < min) min = row.anchorSeq;
2769
+ if (max === null || row.anchorSeq > max) max = row.anchorSeq;
2770
+ }
2771
+ return min === null || max === null ? null : { min, max };
2772
+ }
2773
+ function smallestSeq(rows) {
2774
+ const range = seqRange(rows);
2775
+ return range === null ? null : range.min;
2776
+ }
2777
+ function retreatTo(rows, excludeKey) {
2778
+ let best = null;
2779
+ for (const row of rows) {
2780
+ if (row.visibility === "hidden") continue;
2781
+ if (row.key === excludeKey) continue;
2782
+ if (best === null || row.anchorSeq > best.anchorSeq) best = { key: row.key, anchorSeq: row.anchorSeq };
2783
+ }
2784
+ return best;
2785
+ }
2786
+ async function waitForRow(ports, rowKey, pollMs, rowWaitMs) {
2787
+ for (let waited = 0; waited <= rowWaitMs; waited += pollMs) {
2788
+ if (!ports.active()) return null;
2789
+ const row = ports.locate(rowKey);
2790
+ if (row !== null) return row;
2791
+ await ports.sleep(pollMs);
2792
+ }
2793
+ return null;
2794
+ }
2795
+ async function scrollLocate(ports, key, dir, maxSteps, stepMs) {
2796
+ const port = ports.scrollport();
2797
+ if (port === null) return null;
2798
+ for (let i = 0; i < maxSteps; i++) {
2799
+ const row = ports.locate(key);
2800
+ if (row !== null) return row;
2801
+ const before = port.scrollTop;
2802
+ const step = Math.max(200, Math.round(port.clientHeight * 0.4));
2803
+ port.scrollBy({ top: dir * step });
2804
+ await ports.sleep(stepMs);
2805
+ const floor = Math.max(0, port.scrollHeight - port.clientHeight);
2806
+ if (Math.abs(port.scrollTop - before) < 2) {
2807
+ if (dir === 1) {
2808
+ for (let i2 = 0; i2 < 10; i2++) {
2809
+ const row2 = ports.locate(key);
2810
+ if (row2 !== null) return row2;
2811
+ port.scrollBy({ top: -Math.round(port.clientHeight * 0.4) });
2812
+ await ports.sleep(stepMs);
2813
+ }
2814
+ }
2815
+ return null;
2816
+ }
2817
+ const snap = ports.snap();
2818
+ if (snap !== void 0 && hasTarget(snap.rows, key)) {
2819
+ const rendered = await waitForRow(ports, key, Math.max(100, stepMs), 2500);
2820
+ if (rendered !== null) return rendered;
2821
+ return null;
2822
+ }
2823
+ }
2824
+ return null;
2825
+ }
2826
+ async function goTo(ports, key, options = {}, mode = "smooth", targetSeq, currentSeq) {
2827
+ const budget = { ...BUDGET, ...options };
2828
+ const fail = (code, fallback2 = false) => {
2829
+ ports.report?.(code, fallback2);
2830
+ return fallback2 ? { ok: false, code, fallback: true } : { ok: false, code };
2831
+ };
2832
+ if (!ports.active()) return fail("VIEW_INACTIVE");
2833
+ const deadline = ports.now() + budget.totalTimeoutMs;
2834
+ let pages = 0;
2835
+ let lastNotified = -1;
2836
+ const notifyLoading = (loading, count) => {
2837
+ const key2 = loading ? count : -1;
2838
+ if (key2 === lastNotified) return;
2839
+ lastNotified = key2;
2840
+ ports.onLoading?.(loading, count);
2841
+ };
2842
+ for (; ; ) {
2843
+ const snap2 = ports.snap();
2844
+ if (snap2 === void 0) {
2845
+ notifyLoading(false, 0);
2846
+ return fail("VIEW_INACTIVE");
2847
+ }
2848
+ if (hasTarget(snap2.rows, key)) {
2849
+ notifyLoading(false, 0);
2850
+ break;
2851
+ }
2852
+ if (ports.now() > deadline) {
2853
+ notifyLoading(false, 0);
2854
+ return fail("TIMEOUT");
2855
+ }
2856
+ if (targetSeq !== void 0) {
2857
+ const range = seqRange(snap2.rows);
2858
+ const below = currentSeq !== void 0 ? targetSeq > currentSeq : range !== null && targetSeq > range.max;
2859
+ if (below) {
2860
+ const found = await scrollLocate(ports, key, 1, 500, budget.pollMs * 2);
2861
+ if (found !== null) {
2862
+ ports.reveal(found, mode);
2863
+ return { ok: true };
2864
+ }
2865
+ notifyLoading(false, 0);
2866
+ return fail("TARGET_HIDDEN");
2867
+ }
2868
+ }
2869
+ if (!snap2.loadingOlder) {
2870
+ const range = seqRange(snap2.rows);
2871
+ const closeEnough = targetSeq === void 0 || range === null || targetSeq >= range.min - 40;
2872
+ if (closeEnough) {
2873
+ const up = await scrollLocate(ports, key, -1, 40, budget.pollMs * 2);
2874
+ if (up !== null) {
2875
+ notifyLoading(false, 0);
2876
+ ports.reveal(up, mode);
2877
+ return { ok: true };
2878
+ }
2879
+ }
2880
+ }
2881
+ if (snap2.openState !== "open") {
2882
+ if (snap2.openState === "error") {
2883
+ notifyLoading(false, 0);
2884
+ return fail("VIEW_INACTIVE");
2885
+ }
2886
+ await ports.sleep(budget.pollMs);
2887
+ continue;
2888
+ }
2889
+ if (snap2.hasMore !== true) {
2890
+ notifyLoading(false, 0);
2891
+ return fail("NOT_FOUND");
2892
+ }
2893
+ if (pages >= budget.maxPages) {
2894
+ notifyLoading(false, 0);
2895
+ return fail("TIMEOUT");
2896
+ }
2897
+ notifyLoading(true, pages + 1);
2898
+ if (snap2.loadingOlder) {
2899
+ await ports.sleep(budget.pollMs);
2900
+ continue;
2901
+ }
2902
+ const before = smallestSeq(snap2.rows);
2903
+ await ports.loadMore();
2904
+ pages += 1;
2905
+ notifyLoading(true, pages);
2906
+ let after = before;
2907
+ for (let waited = 0; waited <= 5e3; waited += budget.pollMs) {
2908
+ const s2 = ports.snap();
2909
+ const cur = smallestSeq(s2 === void 0 ? [] : s2.rows);
2910
+ if (cur !== null && (before === null || cur < before)) {
2911
+ after = cur;
2912
+ break;
2913
+ }
2914
+ await ports.sleep(budget.pollMs);
2915
+ }
2916
+ if (after === null || before !== null && after >= before) {
2917
+ const up = await scrollLocate(ports, key, -1, 40, budget.pollMs * 2);
2918
+ if (up !== null) {
2919
+ notifyLoading(false, 0);
2920
+ ports.reveal(up, mode);
2921
+ return { ok: true };
2922
+ }
2923
+ notifyLoading(false, 0);
2924
+ return fail("NOT_FOUND");
2925
+ }
2926
+ }
2927
+ const row = await waitForRow(ports, key, budget.pollMs, budget.rowWaitMs);
2928
+ if (row !== null) {
2929
+ ports.reveal(row, mode);
2930
+ return { ok: true };
2931
+ }
2932
+ const snap0 = ports.snap();
2933
+ if (snap0 !== void 0 && hasTarget(snap0.rows, key)) {
2934
+ const located = await scrollLocate(ports, key, -1, 60, budget.pollMs * 2) ?? await scrollLocate(ports, key, 1, 60, budget.pollMs * 2);
2935
+ if (located !== null) {
2936
+ ports.reveal(located, mode);
2937
+ return { ok: true };
2938
+ }
2939
+ }
2940
+ const snap = ports.snap();
2941
+ const fallback = retreatTo(snap === void 0 ? [] : snap.rows, key);
2942
+ if (fallback !== null) {
2943
+ const fbRow = await waitForRow(ports, fallback.key, budget.pollMs, budget.rowWaitMs);
2944
+ if (fbRow !== null) {
2945
+ ports.reveal(fbRow, mode);
2946
+ return fail("TARGET_HIDDEN", true);
2947
+ }
2948
+ }
2949
+ return fail("TARGET_HIDDEN", false);
2950
+ }
2951
+
2952
+ // src/client/index.ts
2953
+ var NS = "mega-chat-nav";
2954
+ var inject = ["slots", "locale", "sessions"];
2955
+ function sessionsOf(ctx) {
2956
+ return ctx.sessions;
2957
+ }
2958
+ var QUESTION_KINDS = ["user", "steering"];
2959
+ function firstTextBlock(content) {
2960
+ const block = content?.find((part) => typeof part?.text === "string");
2961
+ return block?.text ?? "";
2962
+ }
2963
+ function extractQuestionRows(nodes) {
2964
+ return [...nodes].filter((node) => QUESTION_KINDS.includes(node.kind ?? "")).map((node) => {
2965
+ const payload = typeof node.data === "object" && node.data !== null ? node.data : {};
2966
+ return {
2967
+ key: node.key,
2968
+ anchorSeq: node.anchorSeq,
2969
+ seq: payload.seq ?? -1,
2970
+ time: payload.time ?? 0,
2971
+ text: firstTextBlock(payload.content)
2972
+ };
2973
+ }).sort((a, b) => a.anchorSeq - b.anchorSeq);
2974
+ }
2975
+ function claimApply() {
2976
+ if (globalThis.__dshMegaChatNavApplied === true) return false;
2977
+ globalThis.__dshMegaChatNavApplied = true;
2978
+ return true;
2979
+ }
2980
+ function releaseApply() {
2981
+ globalThis.__dshMegaChatNavApplied = void 0;
2982
+ }
2983
+ function jumpPortsFor(ctx, sessionId) {
2984
+ const sessions = sessionsOf(ctx);
2985
+ return {
2986
+ snap: () => {
2987
+ const binding = sessions.binding(sessionId);
2988
+ const snap = binding?.session.getSnapshot();
2989
+ if (snap === void 0) return void 0;
2990
+ return {
2991
+ openState: snap.openState ?? "",
2992
+ hasMore: snap.hasMore ?? false,
2993
+ loadingOlder: snap.loadingOlder ?? false,
2994
+ rows: snap.chat?.nodes?.values() ?? []
2995
+ };
2996
+ },
2997
+ loadMore: async () => {
2998
+ const binding = sessions.binding(sessionId);
2999
+ if (binding === void 0) throw new Error("session unavailable");
3000
+ await binding.session.loadOlder();
3001
+ },
3002
+ active: () => document.querySelector("[data-chat-flow]") !== null,
3003
+ locate: (key) => {
3004
+ const rows = Array.from(document.querySelectorAll("[data-chat-anchor-key]"));
3005
+ for (const row of rows) {
3006
+ if (row.dataset.chatAnchorKey === key) return row;
3007
+ }
3008
+ for (const row of rows) {
3009
+ const k = row.dataset.chatAnchorKey;
3010
+ if (k !== void 0 && k.includes(key)) return row;
3011
+ }
3012
+ if (key.startsWith("seq:")) {
3013
+ const n = key.slice(4);
3014
+ let found = null;
3015
+ for (const row of rows) {
3016
+ const k = row.dataset.chatAnchorKey;
3017
+ if (k !== void 0 && k.includes(n)) found = row;
3018
+ }
3019
+ if (found !== null) return found;
3020
+ }
3021
+ return null;
3022
+ },
3023
+ reveal: (row, mode) => {
3024
+ const scrollToRow = () => {
3025
+ const port = document.querySelector("[data-conversation-scroll]");
3026
+ if (port !== null) {
3027
+ const rowTop = row.getBoundingClientRect().top - port.getBoundingClientRect().top + port.scrollTop;
3028
+ port.scrollTo({ top: rowTop - 12, behavior: mode });
3029
+ } else {
3030
+ row.scrollIntoView({ behavior: mode, block: "start" });
3031
+ }
3032
+ };
3033
+ scrollToRow();
3034
+ const ensure = () => {
3035
+ const port = document.querySelector("[data-conversation-scroll]");
3036
+ if (port === null) return;
3037
+ const rowRect = row.getBoundingClientRect();
3038
+ const portRect = port.getBoundingClientRect();
3039
+ const offset = rowRect.top - portRect.top;
3040
+ if (offset < -60 || offset > 120) scrollToRow();
3041
+ };
3042
+ window.setTimeout(ensure, 300);
3043
+ window.setTimeout(ensure, 800);
3044
+ const bubble = row.querySelector('[class*="bubble"]') ?? row.querySelector('[class*="messageBody"], [class*="bubbleBody"]');
3045
+ if (bubble !== null) {
3046
+ const original = bubble.style.background;
3047
+ const themeBg = "color-mix(in srgb, var(--dsw-alias-brand-primary) 38%, transparent)";
3048
+ bubble.style.transition = "background 350ms ease";
3049
+ const paint = (on) => {
3050
+ bubble.style.background = on ? themeBg : original;
3051
+ };
3052
+ const waitVisible = () => new Promise((resolve) => {
3053
+ const port = document.querySelector("[data-conversation-scroll]");
3054
+ let tries = 0;
3055
+ const check = () => {
3056
+ const r = bubble.getBoundingClientRect();
3057
+ const portTop = port !== null ? port.getBoundingClientRect().top : 0;
3058
+ const portBottom = port !== null ? port.getBoundingClientRect().bottom : window.innerHeight;
3059
+ if (r.bottom > portTop && r.top < portBottom || tries > 40) resolve();
3060
+ else {
3061
+ tries += 1;
3062
+ window.setTimeout(check, 100);
3063
+ }
3064
+ };
3065
+ check();
3066
+ });
3067
+ void waitVisible().then(() => {
3068
+ paint(true);
3069
+ window.setTimeout(() => paint(false), 400);
3070
+ window.setTimeout(() => paint(true), 800);
3071
+ window.setTimeout(() => paint(false), 1200);
3072
+ window.setTimeout(() => {
3073
+ bubble.style.transition = "";
3074
+ }, 1500);
3075
+ });
3076
+ }
3077
+ },
3078
+ scrollport: () => document.querySelector("[data-conversation-scroll]"),
3079
+ now: () => Date.now(),
3080
+ sleep: (ms) => new Promise((resolve) => window.setTimeout(resolve, ms))
3081
+ };
3082
+ }
3083
+ function navProjectionOf(ctx, sessionId) {
3084
+ const face = sessionsOf(ctx).binding(sessionId)?.session.projections.faceOf("msgNavMessages");
3085
+ if (face === void 0) return void 0;
3086
+ return {
3087
+ getSnapshot: () => face.getSnapshot(),
3088
+ subscribe: (listener) => face.subscribe(listener)
3089
+ };
3090
+ }
3091
+ function createInject(ctx, settings) {
3092
+ const sessions = sessionsOf(ctx);
3093
+ return {
3094
+ readQuestions: (sessionId) => {
3095
+ const snap = sessions.binding(sessionId)?.session.getSnapshot();
3096
+ if (snap === void 0 || snap.chat?.nodes === void 0) return [];
3097
+ return extractQuestionRows(snap.chat.nodes.values());
3098
+ },
3099
+ subscribeList: (cb) => sessions.list.subscribe(cb),
3100
+ subscribeContent: (sessionId, cb) => {
3101
+ const binding = sessions.binding(sessionId);
3102
+ if (binding === void 0) return () => {
3103
+ };
3104
+ return binding.session.subscribe(cb);
3105
+ },
3106
+ questionProjection: (sessionId) => navProjectionOf(ctx, sessionId),
3107
+ jump: (sessionId, key, seq, currentSeq) => {
3108
+ const ports = jumpPortsFor(ctx, sessionId);
3109
+ ports.report = (code) => {
3110
+ window.dispatchEvent(new CustomEvent("mega-chat-nav:jump-failed", { detail: code }));
3111
+ };
3112
+ ports.onLoading = (loading, pages) => {
3113
+ window.dispatchEvent(new CustomEvent("mega-chat-nav:jump-loading", { detail: { loading, pages } }));
3114
+ };
3115
+ void goTo(ports, key, {}, settings.getSnapshot().scrollBehavior, seq, currentSeq);
3116
+ },
3117
+ style: () => settings.getSnapshot().style,
3118
+ setStyle: (style) => settings.setStyle(style),
3119
+ align: () => settings.getSnapshot().align,
3120
+ subscribeSettings: (cb) => settings.subscribe(cb),
3121
+ setAlign: (align) => settings.setAlign(align),
3122
+ bandHeight: () => settings.getSnapshot().bandHeight,
3123
+ setBandHeight: (band) => settings.setBandHeight(band),
3124
+ scrollBehavior: () => settings.getSnapshot().scrollBehavior,
3125
+ setScrollMode: (mode) => settings.setScrollMode(mode),
3126
+ showCount: () => settings.getSnapshot().showCount,
3127
+ setShowCount: (mode) => settings.setShowCount(mode),
3128
+ showSearch: () => settings.getSnapshot().showSearch,
3129
+ setShowSearch: (mode) => settings.setShowSearch(mode),
3130
+ showQuickSettings: () => settings.getSnapshot().showQuickSettings,
3131
+ setShowQuickSettings: (mode) => settings.setShowQuickSettings(mode),
3132
+ showFavorites: () => settings.getSnapshot().showFavorites,
3133
+ setShowFavorites: (mode) => settings.setShowFavorites(mode),
3134
+ showPaging: () => settings.getSnapshot().showPaging,
3135
+ setShowPaging: (mode) => settings.setShowPaging(mode),
3136
+ searchScopes: () => settings.getSnapshot().searchScopes,
3137
+ setSearchScopes: (scopes) => settings.setSearchScopes(scopes),
3138
+ railOffset: () => settings.getSnapshot().railOffset,
3139
+ setRailOffset: (offset) => settings.setRailOffset(offset),
3140
+ cardCount: () => settings.getSnapshot().cardCount,
3141
+ setCardCount: (count) => settings.setCardCount(count),
3142
+ cardItems: () => settings.getSnapshot().cardItems,
3143
+ setCardItems: (items) => settings.setCardItems(items),
3144
+ markTone: () => settings.getSnapshot().markTone,
3145
+ setMarkTone: (tone) => settings.setMarkTone(tone)
3146
+ };
3147
+ }
3148
+ function apply(ctx) {
3149
+ if (!claimApply()) return;
3150
+ ctx.effect(() => releaseApply, "mega-chat-nav: apply claim");
3151
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "mega-chat-nav: dictionaries");
3152
+ const t = ctx.locale.bind(NS);
3153
+ const settings = new NavSettingsController();
3154
+ const injected = createInject(ctx, settings);
3155
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
3156
+ name: "shell.overlay",
3157
+ id: "mega-chat-nav",
3158
+ order: 900,
3159
+ locale: NS,
3160
+ inject: () => ({ injected })
3161
+ }, RailDispatch));
3162
+ ctx.slots.inject("mega.settings.member", () => ctx.slots.register({
3163
+ name: "mega.settings.member",
3164
+ id: "mega-chat-nav",
3165
+ order: 10,
3166
+ label: () => t("label"),
3167
+ inject: () => ({ description: () => t("desc"), version: PKG_VERSION })
3168
+ }, ListRow));
3169
+ ctx.slots.inject("mega.settings.member.page", () => ctx.slots.register({
3170
+ name: "mega.settings.member.page",
3171
+ key: "mega-chat-nav",
3172
+ order: 10,
3173
+ label: () => t("label"),
3174
+ locale: NS,
3175
+ inject: () => ({ injected })
3176
+ }, SettingsPage));
3177
+ {
3178
+ let tabDispose = null;
3179
+ const ensurePluginsTab = () => {
3180
+ const hosted = (ctx.slots.entries?.("mega.settings.member")?.length ?? 0) > 0;
3181
+ if (hosted) {
3182
+ tabDispose?.();
3183
+ tabDispose = null;
3184
+ } else if (tabDispose === null) {
3185
+ tabDispose = ctx.slots.register({
3186
+ name: "settings.plugins.tab",
3187
+ id: "mega-chat-nav",
3188
+ order: 100,
3189
+ label: () => t("label"),
3190
+ locale: NS,
3191
+ inject: () => ({ injected, slots: ctx.slots })
3192
+ }, StandaloneSettings);
3193
+ }
3194
+ };
3195
+ ensurePluginsTab();
3196
+ const offMember = ctx.slots.subscribe?.("mega.settings.member", ensurePluginsTab);
3197
+ ctx.effect(() => () => {
3198
+ tabDispose?.();
3199
+ tabDispose = null;
3200
+ offMember?.();
3201
+ }, "mega-chat-nav: plugins tab ledger");
3202
+ }
3203
+ ctx.inject(["settingsScope"], (scopeCtx) => {
3204
+ settings.attach(scopeCtx.get("settingsScope"));
3205
+ });
3206
+ }
3207
+ function ListRow() {
3208
+ return null;
3209
+ }
3210
+
3211
+ return module.exports;
3212
+ }
3213
+ });