@lobehub/lobehub 2.0.0-next.71 → 2.0.0-next.73

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 (338) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/next.config.ts +5 -5
  4. package/package.json +1 -1
  5. package/packages/model-bank/src/aiModels/qiniu.ts +126 -0
  6. package/scripts/prebuild.mts +1 -1
  7. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +6 -6
  8. package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +7 -4
  9. package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +4 -4
  10. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/UserBanner.tsx +5 -6
  11. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/useCategory.tsx +5 -8
  12. package/src/app/[variants]/(main)/(mobile)/me/(home)/index.tsx +25 -0
  13. package/src/app/[variants]/(main)/(mobile)/me/(home)/layout.tsx +13 -16
  14. package/src/app/[variants]/(main)/(mobile)/me/profile/features/Category.tsx +6 -6
  15. package/src/app/[variants]/(main)/(mobile)/me/profile/features/Header.tsx +3 -3
  16. package/src/app/[variants]/(main)/(mobile)/me/profile/index.tsx +16 -0
  17. package/src/app/[variants]/(main)/(mobile)/me/profile/layout.tsx +9 -11
  18. package/src/app/[variants]/(main)/(mobile)/me/settings/features/Header.tsx +3 -3
  19. package/src/app/[variants]/(main)/(mobile)/me/settings/features/useCategory.tsx +3 -4
  20. package/src/app/[variants]/(main)/(mobile)/me/settings/index.tsx +16 -0
  21. package/src/app/[variants]/(main)/(mobile)/me/settings/layout.tsx +15 -13
  22. package/src/app/[variants]/(main)/changelog/_layout/Desktop/index.tsx +6 -5
  23. package/src/app/[variants]/(main)/changelog/_layout/Mobile/Header.tsx +3 -3
  24. package/src/app/[variants]/(main)/changelog/_layout/Mobile/index.tsx +5 -5
  25. package/src/app/[variants]/(main)/changelog/features/Post.tsx +7 -4
  26. package/src/app/[variants]/(main)/changelog/index.tsx +55 -0
  27. package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +6 -7
  28. package/src/app/[variants]/(main)/chat/_layout/Mobile.tsx +3 -3
  29. package/src/app/[variants]/(main)/chat/components/WorkspaceLayout.tsx +1 -14
  30. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatHydration/index.tsx +2 -2
  31. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/MessageFromUrl.tsx +11 -13
  32. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/index.tsx +0 -1
  33. package/src/app/[variants]/(main)/chat/components/conversation/features/ThreadHydration.tsx +2 -2
  34. package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +20 -11
  35. package/src/app/[variants]/(main)/chat/features/PageTitle/index.tsx +1 -2
  36. package/src/app/[variants]/(main)/chat/index.tsx +29 -0
  37. package/src/app/[variants]/(main)/chat/session/features/SessionHydration.tsx +8 -6
  38. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/Inbox/index.tsx +2 -4
  39. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/index.tsx +1 -1
  40. package/src/app/[variants]/(main)/chat/session/layout/Mobile/SessionHeader.tsx +3 -3
  41. package/src/app/[variants]/(main)/chat/settings/_layout/Desktop/Header.tsx +3 -3
  42. package/src/app/[variants]/(main)/chat/settings/_layout/Mobile/Header.tsx +3 -3
  43. package/src/app/[variants]/(main)/chat/settings/features/PublishResultModal/index.tsx +3 -3
  44. package/src/app/[variants]/(main)/chat/{components/SettingsPage.tsx → settings/index.tsx} +6 -35
  45. package/src/app/[variants]/(main)/components/Link.tsx +21 -0
  46. package/src/app/[variants]/(main)/discover/(detail)/_layout/{Desktop.tsx → Desktop/index.tsx} +15 -7
  47. package/src/app/[variants]/(main)/discover/(detail)/_layout/Mobile/index.tsx +3 -3
  48. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Capabilities/Block.tsx +1 -1
  49. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Overview/index.tsx +1 -1
  50. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Related/index.tsx +2 -2
  51. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/SystemRole/index.tsx +3 -3
  52. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Versions/index.tsx +4 -4
  53. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/index.tsx +1 -1
  54. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Header.tsx +2 -2
  55. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/ActionButton/AddAgent.tsx +3 -3
  56. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/ActionButton/index.tsx +1 -1
  57. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/Related/index.tsx +1 -1
  58. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/TocList/index.tsx +2 -2
  59. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/StatusPage/index.tsx +3 -3
  60. package/src/app/[variants]/(main)/discover/(detail)/assistant/{AssistantDetailPage.tsx → index.tsx} +18 -11
  61. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +1 -1
  62. package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Details/Related/index.tsx +2 -2
  63. package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Details/Versions/index.tsx +5 -5
  64. package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Details/index.tsx +1 -1
  65. package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/Related/index.tsx +2 -2
  66. package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/ServerConfig.tsx +4 -4
  67. package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/TocList/index.tsx +2 -2
  68. package/src/app/[variants]/(main)/discover/(detail)/mcp/{McpDetailPage.tsx → index.tsx} +15 -7
  69. package/src/app/[variants]/(main)/discover/(detail)/mcp/loading.tsx +1 -0
  70. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/Overview/index.tsx +1 -1
  71. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/Parameter/ParameterItem.tsx +1 -1
  72. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/Related/index.tsx +2 -2
  73. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/index.tsx +1 -1
  74. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Header.tsx +1 -1
  75. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/ActionButton/ChatWithModel.tsx +3 -3
  76. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/ActionButton/index.tsx +1 -1
  77. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/Related/index.tsx +2 -2
  78. package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/RelatedProviders/index.tsx +1 -1
  79. package/src/app/[variants]/(main)/discover/(detail)/model/{ModelDetailPage.tsx → index.tsx} +17 -10
  80. package/src/app/[variants]/(main)/discover/(detail)/model/loading.tsx +1 -0
  81. package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Details/Related/index.tsx +2 -2
  82. package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Details/index.tsx +1 -1
  83. package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/ActionButton/ProviderConfig.tsx +5 -4
  84. package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/ActionButton/index.tsx +1 -1
  85. package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/Related/index.tsx +2 -2
  86. package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/RelatedModels/index.tsx +1 -1
  87. package/src/app/[variants]/(main)/discover/(detail)/provider/{ProviderDetailPage.tsx → index.tsx} +17 -10
  88. package/src/app/[variants]/(main)/discover/(detail)/provider/loading.tsx +1 -0
  89. package/src/app/[variants]/(main)/discover/(list)/(home)/{HomePage.tsx → index.tsx} +14 -0
  90. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +2 -3
  91. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/index.tsx +4 -3
  92. package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/Nav.tsx +3 -4
  93. package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/index.tsx +4 -3
  94. package/src/app/[variants]/(main)/discover/(list)/assistant/_layout/Desktop.tsx +3 -4
  95. package/src/app/[variants]/(main)/discover/(list)/assistant/_layout/Mobile.tsx +3 -3
  96. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +2 -2
  97. package/src/app/[variants]/(main)/discover/(list)/assistant/{AssistantPage.tsx → index.tsx} +18 -4
  98. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +2 -2
  99. package/src/app/[variants]/(main)/discover/(list)/mcp/_layout/Desktop.tsx +3 -3
  100. package/src/app/[variants]/(main)/discover/(list)/mcp/_layout/Mobile.tsx +3 -3
  101. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +1 -1
  102. package/src/app/[variants]/(main)/discover/(list)/mcp/{McpPage.tsx → index.tsx} +9 -2
  103. package/src/app/[variants]/(main)/discover/(list)/model/_layout/Desktop.tsx +3 -3
  104. package/src/app/[variants]/(main)/discover/(list)/model/_layout/Mobile.tsx +3 -3
  105. package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +1 -1
  106. package/src/app/[variants]/(main)/discover/(list)/model/{ModelPage.tsx → index.tsx} +9 -2
  107. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +1 -1
  108. package/src/app/[variants]/(main)/discover/(list)/provider/{Client.tsx → index.tsx} +9 -2
  109. package/src/app/[variants]/(main)/discover/_layout/Desktop/index.tsx +4 -4
  110. package/src/app/[variants]/(main)/discover/_layout/Mobile/index.tsx +3 -3
  111. package/src/app/[variants]/(main)/discover/components/Title.tsx +1 -1
  112. package/src/app/[variants]/(main)/discover/features/Search.tsx +2 -2
  113. package/src/app/[variants]/(main)/discover/features/useNav.tsx +11 -12
  114. package/src/app/[variants]/(main)/hooks/useActiveTabKey.ts +40 -0
  115. package/src/app/[variants]/(main)/hooks/usePathname.ts +10 -0
  116. package/src/app/[variants]/(main)/hooks/useQuery.ts +12 -0
  117. package/src/app/[variants]/(main)/hooks/useRouter.ts +22 -0
  118. package/src/app/[variants]/(main)/hooks/useSearchParams.ts +11 -0
  119. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect/index.tsx +5 -5
  120. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicUrlSync.tsx +1 -1
  121. package/src/app/[variants]/(main)/image/ComingSoon.tsx +15 -0
  122. package/src/app/[variants]/(main)/image/_layout/Desktop/index.tsx +5 -2
  123. package/src/app/[variants]/(main)/image/_layout/DesktopWrapper.tsx +15 -0
  124. package/src/app/[variants]/(main)/image/_layout/Mobile/index.tsx +3 -1
  125. package/src/app/[variants]/(main)/image/features/ImageWorkspace/index.tsx +1 -2
  126. package/src/app/[variants]/(main)/image/index.tsx +18 -0
  127. package/src/app/[variants]/(main)/knowledge/_layout/Desktop.tsx +17 -0
  128. package/src/app/[variants]/(main)/knowledge/_layout/Mobile.tsx +17 -0
  129. package/src/app/[variants]/(main)/knowledge/components/KnowledgeBaseItem/index.tsx +1 -1
  130. package/src/app/[variants]/(main)/knowledge/components/modal/ModalPageClient.tsx +4 -4
  131. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/index.tsx +5 -8
  132. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/Head.tsx +1 -1
  133. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/CategoryMenu.tsx +2 -2
  134. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/KnowledgeBase.tsx +1 -1
  135. package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/DesktopLayoutContainer.tsx +3 -2
  136. package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/SideBar/BottomActions.tsx +4 -4
  137. package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/SideBar/TopActions.test.tsx +9 -9
  138. package/src/app/[variants]/(main)/layouts/desktop/SideBar/TopActions.tsx +117 -0
  139. package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/index.tsx +9 -4
  140. package/src/app/[variants]/(main)/layouts/index.tsx +11 -0
  141. package/src/app/[variants]/(main)/{_layout/Mobile → layouts/mobile}/NavBar.tsx +5 -5
  142. package/src/app/[variants]/(main)/{_layout/Mobile → layouts/mobile}/index.tsx +10 -7
  143. package/src/app/[variants]/(main)/profile/(home)/desktop.tsx +26 -0
  144. package/src/app/[variants]/(main)/profile/(home)/index.tsx +26 -0
  145. package/src/app/[variants]/(main)/profile/@category/features/CategoryContent.tsx +5 -7
  146. package/src/app/[variants]/(main)/profile/_layout/Desktop/index.tsx +3 -2
  147. package/src/app/[variants]/(main)/profile/_layout/DesktopWrapper.tsx +14 -0
  148. package/src/app/[variants]/(main)/profile/_layout/Mobile/Header.tsx +3 -3
  149. package/src/app/[variants]/(main)/profile/_layout/Mobile/index.tsx +4 -3
  150. package/src/app/[variants]/(main)/profile/apikey/index.tsx +18 -0
  151. package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +6 -6
  152. package/src/app/[variants]/(main)/profile/security/index.tsx +29 -0
  153. package/src/app/[variants]/(main)/profile/stats/features/AssistantsRank.tsx +4 -4
  154. package/src/app/[variants]/(main)/profile/stats/features/TopicsRank.tsx +4 -4
  155. package/src/app/[variants]/(main)/profile/stats/index.tsx +20 -0
  156. package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +7 -9
  157. package/src/app/[variants]/(main)/profile/usage/index.tsx +13 -0
  158. package/src/app/[variants]/(main)/settings/_layout/Desktop/Header.tsx +2 -2
  159. package/src/app/[variants]/(main)/settings/_layout/Desktop/index.tsx +2 -2
  160. package/src/app/[variants]/(main)/settings/_layout/DesktopWrapper.tsx +23 -0
  161. package/src/app/[variants]/(main)/settings/_layout/Mobile/Header.tsx +8 -7
  162. package/src/app/[variants]/(main)/settings/_layout/Mobile/index.tsx +2 -5
  163. package/src/app/[variants]/(main)/settings/_layout/MobileWrapper.tsx +23 -0
  164. package/src/app/[variants]/(main)/settings/agent/AgentMenu/Menu.tsx +2 -4
  165. package/src/app/[variants]/(main)/settings/agent/index.tsx +2 -4
  166. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +2 -3
  167. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/All.tsx +2 -2
  168. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Item.tsx +38 -37
  169. package/src/app/[variants]/(main)/settings/provider/_layout/Mobile.tsx +2 -2
  170. package/src/app/[variants]/(main)/settings/provider/features/CreateNewProvider/index.tsx +3 -3
  171. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/UpdateProviderInfo/SettingModal.tsx +3 -3
  172. package/src/app/[variants]/DesktopRouter.tsx +40 -0
  173. package/src/app/[variants]/MobileRouter.tsx +41 -0
  174. package/src/app/[variants]/desktopRouter.config.tsx +381 -0
  175. package/src/app/[variants]/layout.tsx +1 -3
  176. package/src/app/[variants]/loaders/routeParams.ts +45 -0
  177. package/src/app/[variants]/loading/Server/Redirect.tsx +1 -1
  178. package/src/app/[variants]/mobileRouter.config.tsx +412 -0
  179. package/src/app/[variants]/page.tsx +16 -6
  180. package/src/app/desktop/devtools/page.tsx +1 -1
  181. package/src/app/desktop/layout.tsx +1 -1
  182. package/src/components/BootErrorBoundary/index.tsx +129 -0
  183. package/src/components/mdx/constants.ts +1 -0
  184. package/src/features/ChangelogModal/index.tsx +3 -3
  185. package/src/features/KnowledgeManager/FileExplorer/index.tsx +4 -4
  186. package/src/features/KnowledgeManager/Header/FilesSearchBar.tsx +1 -1
  187. package/src/features/User/UserPanel/PanelContent.tsx +4 -3
  188. package/src/features/User/UserPanel/useMenu.tsx +20 -14
  189. package/src/features/User/__tests__/PanelContent.test.tsx +13 -7
  190. package/src/hooks/useDiscoverTab.ts +2 -2
  191. package/src/hooks/useInterceptingRoutes.test.ts +23 -26
  192. package/src/hooks/useInterceptingRoutes.ts +5 -4
  193. package/src/hooks/usePinnedAgentState.ts +6 -6
  194. package/src/hooks/useQuery.ts +5 -0
  195. package/src/hooks/useQueryParam.ts +322 -0
  196. package/src/hooks/useQueryRoute.test.ts +2 -12
  197. package/src/hooks/useQueryRoute.ts +5 -5
  198. package/src/hooks/useShowMobileWorkspace.ts +1 -1
  199. package/src/hooks/useSwitchSession.ts +4 -3
  200. package/src/layout/GlobalProvider/ImportSettings.tsx +22 -9
  201. package/src/layout/GlobalProvider/StoreInitialization.tsx +9 -1
  202. package/src/proxy.ts +13 -1
  203. package/src/services/message/index.ts +11 -2
  204. package/src/store/chat/agents/createAgentExecutors.ts +31 -16
  205. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +218 -0
  206. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +4 -0
  207. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +9 -3
  208. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +142 -61
  209. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +204 -10
  210. package/src/store/chat/slices/builtinTool/actions/search.ts +51 -26
  211. package/src/store/chat/slices/message/action.test.ts +182 -33
  212. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +79 -36
  213. package/src/store/chat/slices/message/actions/query.ts +7 -5
  214. package/src/store/chat/slices/message/selectors/dbMessage.ts +11 -4
  215. package/src/store/chat/slices/plugin/action.test.ts +257 -54
  216. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +63 -26
  217. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +52 -19
  218. package/src/store/chat/slices/plugin/actions/publicApi.ts +6 -1
  219. package/src/store/chat/slices/plugin/actions/workflow.ts +17 -6
  220. package/src/store/chat/slices/thread/action.ts +2 -0
  221. package/src/store/global/action.test.ts +3 -3
  222. package/src/store/global/actions/workspacePane.ts +2 -1
  223. package/src/store/global/initialState.ts +10 -2
  224. package/src/store/user/slices/common/action.ts +4 -0
  225. package/src/app/[variants]/(main)/(mobile)/me/(home)/loading.tsx +0 -38
  226. package/src/app/[variants]/(main)/(mobile)/me/(home)/page.tsx +0 -40
  227. package/src/app/[variants]/(main)/(mobile)/me/profile/loading.tsx +0 -5
  228. package/src/app/[variants]/(main)/(mobile)/me/profile/page.tsx +0 -30
  229. package/src/app/[variants]/(main)/(mobile)/me/settings/loading.tsx +0 -5
  230. package/src/app/[variants]/(main)/(mobile)/me/settings/page.tsx +0 -30
  231. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.tsx +0 -106
  232. package/src/app/[variants]/(main)/changelog/layout.tsx +0 -10
  233. package/src/app/[variants]/(main)/changelog/modal/page.tsx +0 -23
  234. package/src/app/[variants]/(main)/changelog/page.tsx +0 -78
  235. package/src/app/[variants]/(main)/chat/ChatRouter.tsx +0 -83
  236. package/src/app/[variants]/(main)/chat/_layout/ChatLayout.tsx +0 -22
  237. package/src/app/[variants]/(main)/chat/components/MainChatPage.tsx +0 -25
  238. package/src/app/[variants]/(main)/chat/error.tsx +0 -3
  239. package/src/app/[variants]/(main)/chat/layout.tsx +0 -10
  240. package/src/app/[variants]/(main)/chat/loading.tsx +0 -3
  241. package/src/app/[variants]/(main)/chat/not-found.tsx +0 -1
  242. package/src/app/[variants]/(main)/chat/page.tsx +0 -12
  243. package/src/app/[variants]/(main)/chat/settings/error.tsx +0 -3
  244. package/src/app/[variants]/(main)/chat/settings/loading.tsx +0 -3
  245. package/src/app/[variants]/(main)/chat/settings/not-found.tsx +0 -1
  246. package/src/app/[variants]/(main)/discover/(detail)/_layout/DetailLayout.tsx +0 -22
  247. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/Client.tsx +0 -51
  248. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/Client.tsx +0 -43
  249. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/loading.tsx +0 -1
  250. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/Client.tsx +0 -40
  251. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/loading.tsx +0 -1
  252. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/Client.tsx +0 -40
  253. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/loading.tsx +0 -1
  254. package/src/app/[variants]/(main)/discover/(list)/_layout/ListLayout.tsx +0 -22
  255. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantLayout.tsx +0 -21
  256. package/src/app/[variants]/(main)/discover/(list)/mcp/Client.tsx +0 -44
  257. package/src/app/[variants]/(main)/discover/(list)/mcp/McpLayout.tsx +0 -21
  258. package/src/app/[variants]/(main)/discover/(list)/model/Client.tsx +0 -44
  259. package/src/app/[variants]/(main)/discover/(list)/model/ModelLayout.tsx +0 -21
  260. package/src/app/[variants]/(main)/discover/(list)/provider/ProviderPage.tsx +0 -43
  261. package/src/app/[variants]/(main)/discover/DiscoverRouter.tsx +0 -170
  262. package/src/app/[variants]/(main)/discover/[[...path]]/page.tsx +0 -12
  263. package/src/app/[variants]/(main)/discover/_layout/DiscoverLayout.tsx +0 -22
  264. package/src/app/[variants]/(main)/discover/error.tsx +0 -3
  265. package/src/app/[variants]/(main)/discover/not-found.tsx +0 -1
  266. package/src/app/[variants]/(main)/error.tsx +0 -3
  267. package/src/app/[variants]/(main)/image/layout.tsx +0 -15
  268. package/src/app/[variants]/(main)/image/page.tsx +0 -45
  269. package/src/app/[variants]/(main)/knowledge/KnowledgeRouter.tsx +0 -74
  270. package/src/app/[variants]/(main)/knowledge/[[...path]]/page.tsx +0 -12
  271. package/src/app/[variants]/(main)/knowledge/components/modal/page.tsx +0 -13
  272. package/src/app/[variants]/(main)/knowledge/layout.tsx +0 -12
  273. package/src/app/[variants]/(main)/layout.tsx +0 -10
  274. package/src/app/[variants]/(main)/not-found.tsx +0 -1
  275. package/src/app/[variants]/(main)/profile/apikey/page.tsx +0 -32
  276. package/src/app/[variants]/(main)/profile/error.tsx +0 -3
  277. package/src/app/[variants]/(main)/profile/layout.tsx +0 -11
  278. package/src/app/[variants]/(main)/profile/loading.tsx +0 -3
  279. package/src/app/[variants]/(main)/profile/not-found.tsx +0 -1
  280. package/src/app/[variants]/(main)/profile/security/page.tsx +0 -28
  281. package/src/app/[variants]/(main)/profile/stats/page.tsx +0 -23
  282. package/src/app/[variants]/(main)/profile/usage/page.tsx +0 -23
  283. package/src/app/[variants]/@modal/(.)changelog/modal/features/Cover.tsx +0 -48
  284. package/src/app/[variants]/@modal/(.)changelog/modal/features/Hero.tsx +0 -29
  285. package/src/app/[variants]/@modal/(.)changelog/modal/features/Post.tsx +0 -57
  286. package/src/app/[variants]/@modal/(.)changelog/modal/features/PublishedTime.tsx +0 -50
  287. package/src/app/[variants]/@modal/(.)changelog/modal/features/ReadDetail.tsx +0 -72
  288. package/src/app/[variants]/@modal/(.)changelog/modal/features/VersionTag.tsx +0 -26
  289. package/src/app/[variants]/@modal/(.)changelog/modal/layout.tsx +0 -41
  290. package/src/app/[variants]/@modal/(.)changelog/modal/loading.tsx +0 -10
  291. package/src/app/[variants]/@modal/(.)changelog/modal/page.tsx +0 -38
  292. package/src/app/[variants]/@modal/_layout/ModalLayout.tsx +0 -63
  293. package/src/app/[variants]/@modal/_layout/SettingModalLayout.tsx +0 -71
  294. package/src/app/[variants]/@modal/default.tsx +0 -3
  295. package/src/app/[variants]/@modal/error.tsx +0 -3
  296. package/src/app/[variants]/@modal/layout.tsx +0 -7
  297. package/src/app/[variants]/@modal/loading.tsx +0 -5
  298. /package/src/app/[variants]/{@modal/(.)changelog/modal → (main)/changelog}/features/Pagination.tsx +0 -0
  299. /package/src/app/[variants]/{@modal/(.)changelog/modal → (main)/changelog}/features/UpdateChangelogStatus.tsx +0 -0
  300. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/DetailProvider.tsx +0 -0
  301. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Capabilities/Knowledge.tsx +0 -0
  302. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Capabilities/KnowledgeItem.tsx +0 -0
  303. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Capabilities/PluginItem.tsx +0 -0
  304. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Capabilities/Plugins.tsx +0 -0
  305. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Capabilities/index.tsx +0 -0
  306. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Nav.tsx +0 -0
  307. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/Overview/TagList.tsx +0 -0
  308. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Details/SystemRole/TagList.tsx +0 -0
  309. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/Related/Item.tsx +0 -0
  310. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/Summary/index.tsx +0 -0
  311. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/features → features}/Sidebar/index.tsx +0 -0
  312. /package/src/app/[variants]/(main)/discover/(detail)/assistant/{[...slugs]/loading.tsx → loading.tsx} +0 -0
  313. /package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/ActionButton/index.tsx +0 -0
  314. /package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/ConnectionTypeAlert.tsx +0 -0
  315. /package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/Related/Item.tsx +0 -0
  316. /package/src/app/[variants]/(main)/discover/(detail)/mcp/{[slug]/features → features}/Sidebar/index.tsx +0 -0
  317. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/DetailProvider.tsx +0 -0
  318. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/Nav.tsx +0 -0
  319. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/Overview/ProviderList/index.tsx +0 -0
  320. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Details/Parameter/index.tsx +0 -0
  321. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/Related/Item.tsx +0 -0
  322. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/RelatedProviders/Item.tsx +0 -0
  323. /package/src/app/[variants]/(main)/discover/(detail)/model/{[...slugs]/features → features}/Sidebar/index.tsx +0 -0
  324. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/DetailProvider.tsx +0 -0
  325. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Details/Guide/index.tsx +0 -0
  326. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Details/Nav.tsx +0 -0
  327. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Details/Overview/ModelList/index.tsx +0 -0
  328. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Details/Overview/index.tsx +0 -0
  329. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Header.tsx +0 -0
  330. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/Related/Item.tsx +0 -0
  331. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/RelatedModels/Item.tsx +0 -0
  332. /package/src/app/[variants]/(main)/discover/(detail)/provider/{[...slugs]/features → features}/Sidebar/index.tsx +0 -0
  333. /package/src/app/[variants]/(main)/labs/{page.tsx → index.tsx} +0 -0
  334. /package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/RegisterHotkeys.tsx +0 -0
  335. /package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/SideBar/Avatar.test.tsx +0 -0
  336. /package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/SideBar/Avatar.tsx +0 -0
  337. /package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/SideBar/PinList/index.tsx +0 -0
  338. /package/src/app/[variants]/(main)/{_layout/Desktop → layouts/desktop}/SideBar/index.tsx +0 -0
@@ -15,9 +15,9 @@ import {
15
15
  Mail,
16
16
  Settings2,
17
17
  } from 'lucide-react';
18
- import Link from 'next/link';
19
18
  import { PropsWithChildren, memo } from 'react';
20
19
  import { useTranslation } from 'react-i18next';
20
+ import { Link } from 'react-router-dom';
21
21
  import { Flexbox } from 'react-layout-kit';
22
22
 
23
23
  import type { MenuProps } from '@/components/Menu';
@@ -77,7 +77,7 @@ export const useMenu = () => {
77
77
  {
78
78
  icon: <Icon icon={CircleUserRound} />,
79
79
  key: 'profile',
80
- label: <Link href={'/profile'}>{t('userPanel.profile')}</Link>,
80
+ label: <Link to="/profile">{t('userPanel.profile')}</Link>,
81
81
  },
82
82
  ];
83
83
 
@@ -91,7 +91,7 @@ export const useMenu = () => {
91
91
  icon: <Icon icon={Settings2} />,
92
92
  key: 'setting',
93
93
  label: (
94
- <Link href={'/settings'}>
94
+ <Link to="/settings">
95
95
  <NewVersionBadge showBadge={hasNewVersion}>{t('userPanel.setting')}</NewVersionBadge>
96
96
  </Link>
97
97
  ),
@@ -135,15 +135,21 @@ export const useMenu = () => {
135
135
  icon: <Icon icon={Cloudy} />,
136
136
  key: 'cloud',
137
137
  label: (
138
- <Link href={`${OFFICIAL_URL}?utm_source=${UTM_SOURCE}`} target={'_blank'}>
138
+ <a href={`${OFFICIAL_URL}?utm_source=${UTM_SOURCE}`} rel="noopener noreferrer" target="_blank">
139
139
  {t('userPanel.cloud', { name: LOBE_CHAT_CLOUD })}
140
- </Link>
140
+ </a>
141
141
  ),
142
142
  },
143
143
  {
144
144
  icon: <Icon icon={FileClockIcon} />,
145
145
  key: 'changelog',
146
- label: <Link href={isDesktop ? CHANGELOG : '/changelog/modal'}>{t('changelog')}</Link>,
146
+ label: isDesktop ? (
147
+ <a href={CHANGELOG} rel="noopener noreferrer" target="_blank">
148
+ {t('changelog')}
149
+ </a>
150
+ ) : (
151
+ <Link to="/changelog/modal">{t('changelog')}</Link>
152
+ ),
147
153
  },
148
154
  {
149
155
  children: [
@@ -151,36 +157,36 @@ export const useMenu = () => {
151
157
  icon: <Icon icon={Book} />,
152
158
  key: 'docs',
153
159
  label: (
154
- <Link href={DOCUMENTS_REFER_URL} target={'_blank'}>
160
+ <a href={DOCUMENTS_REFER_URL} rel="noopener noreferrer" target="_blank">
155
161
  {t('userPanel.docs')}
156
- </Link>
162
+ </a>
157
163
  ),
158
164
  },
159
165
  {
160
166
  icon: <Icon icon={Feather} />,
161
167
  key: 'feedback',
162
168
  label: (
163
- <Link href={GITHUB_ISSUES} target={'_blank'}>
169
+ <a href={GITHUB_ISSUES} rel="noopener noreferrer" target="_blank">
164
170
  {t('userPanel.feedback')}
165
- </Link>
171
+ </a>
166
172
  ),
167
173
  },
168
174
  {
169
175
  icon: <Icon icon={DiscordIcon} />,
170
176
  key: 'discord',
171
177
  label: (
172
- <Link href={SOCIAL_URL.discord} target={'_blank'}>
178
+ <a href={SOCIAL_URL.discord} rel="noopener noreferrer" target="_blank">
173
179
  {t('userPanel.discord')}
174
- </Link>
180
+ </a>
175
181
  ),
176
182
  },
177
183
  {
178
184
  icon: <Icon icon={Mail} />,
179
185
  key: 'email',
180
186
  label: (
181
- <Link href={mailTo(BRANDING_EMAIL.support)} target={'_blank'}>
187
+ <a href={mailTo(BRANDING_EMAIL.support)} rel="noopener noreferrer" target="_blank">
182
188
  {t('userPanel.email')}
183
- </Link>
189
+ </a>
184
190
  ),
185
191
  },
186
192
  ],
@@ -1,4 +1,5 @@
1
1
  import { act, render, screen } from '@testing-library/react';
2
+ import { MemoryRouter } from 'react-router-dom';
2
3
  import { afterEach, describe, expect, it, vi } from 'vitest';
3
4
 
4
5
  import { useUserStore } from '@/store/user';
@@ -79,13 +80,18 @@ vi.mock('@/const/auth', () => ({
79
80
  describe('PanelContent', () => {
80
81
  const closePopover = vi.fn();
81
82
 
83
+ // Helper function to render component with Router provider
84
+ const renderWithRouter = (ui: React.ReactElement) => {
85
+ return render(<MemoryRouter>{ui}</MemoryRouter>);
86
+ };
87
+
82
88
  describe('enable auth', () => {
83
89
  it('should render UserInfo when user is signed in', () => {
84
90
  act(() => {
85
91
  useUserStore.setState({ isSignedIn: true });
86
92
  });
87
93
 
88
- render(<PanelContent closePopover={closePopover} />);
94
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
89
95
 
90
96
  expect(screen.getByText('Mocked UserInfo')).toBeInTheDocument();
91
97
  expect(screen.getByText('Mocked DataStatistics')).toBeInTheDocument();
@@ -97,7 +103,7 @@ describe('PanelContent', () => {
97
103
  useUserStore.setState({ isSignedIn: false });
98
104
  });
99
105
 
100
- render(<PanelContent closePopover={closePopover} />);
106
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
101
107
 
102
108
  expect(screen.getByText('Mocked SignInBlock')).toBeInTheDocument();
103
109
  expect(screen.queryByText('Mocked DataStatistics')).not.toBeInTheDocument();
@@ -109,7 +115,7 @@ describe('PanelContent', () => {
109
115
  useUserStore.setState({ isSignedIn: true });
110
116
  });
111
117
 
112
- render(<PanelContent closePopover={closePopover} />);
118
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
113
119
 
114
120
  expect(screen.getAllByText('Mocked Menu').length).toBe(2);
115
121
  });
@@ -119,7 +125,7 @@ describe('PanelContent', () => {
119
125
  useUserStore.setState({ isSignedIn: false });
120
126
  });
121
127
 
122
- render(<PanelContent closePopover={closePopover} />);
128
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
123
129
 
124
130
  expect(screen.getByText('Mocked BrandWatermark')).toBeInTheDocument();
125
131
  });
@@ -131,7 +137,7 @@ describe('PanelContent', () => {
131
137
  useUserStore.setState({ isSignedIn: true });
132
138
  });
133
139
 
134
- render(<PanelContent closePopover={closePopover} />);
140
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
135
141
 
136
142
  expect(screen.getByText('Mocked UserInfo')).toBeInTheDocument();
137
143
  expect(screen.getByText('Mocked DataStatistics')).toBeInTheDocument();
@@ -145,14 +151,14 @@ describe('PanelContent', () => {
145
151
  useUserStore.setState({ isSignedIn: false });
146
152
  });
147
153
 
148
- render(<PanelContent closePopover={closePopover} />);
154
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
149
155
 
150
156
  expect(screen.getByText('Mocked BrandWatermark')).toBeInTheDocument();
151
157
  });
152
158
  });
153
159
 
154
160
  it('should render Menu with main items', () => {
155
- render(<PanelContent closePopover={closePopover} />);
161
+ renderWithRouter(<PanelContent closePopover={closePopover} />);
156
162
 
157
163
  expect(screen.getByText('Mocked Menu')).toBeInTheDocument();
158
164
  });
@@ -1,7 +1,7 @@
1
- import { useQueryState } from 'nuqs';
2
-
3
1
  import { DiscoverTab } from '@/types/discover';
4
2
 
3
+ import { useQueryState } from './useQueryParam';
4
+
5
5
  export const useDiscoverTab = () => {
6
6
  const [type] = useQueryState('type', {
7
7
  clearOnDefault: true,
@@ -1,35 +1,20 @@
1
1
  import { act, renderHook } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { INBOX_SESSION_ID } from '@/const/session';
5
5
  import { useIsMobile } from '@/hooks/useIsMobile';
6
6
  import { useAgentStore } from '@/store/agent';
7
- import { ChatSettingsTabs } from '@/store/global/initialState';
7
+ import { ChatSettingsTabs, SettingsTabs } from '@/store/global/initialState';
8
8
  import { useSessionStore } from '@/store/session';
9
9
 
10
10
  import { useOpenChatSettings } from './useInterceptingRoutes';
11
11
 
12
- // Mocks
13
- const mockPush = vi.fn((href) => href);
14
- vi.mock('next/navigation', () => ({
15
- useRouter: vi.fn(() => ({
16
- push: mockPush,
17
- replace: vi.fn((href) => href),
18
- })),
19
- }));
20
- vi.mock('nextjs-toploader/app', () => ({
21
- useRouter: vi.fn(() => ({
22
- push: mockPush,
23
- replace: vi.fn((href) => href),
24
- })),
25
- }));
26
- vi.mock('@/hooks/useQuery', () => ({
27
- useQuery: vi.fn(() => ({})),
28
- }));
29
- vi.mock('@/hooks/useQueryRoute', () => ({
30
- useQueryRoute: vi.fn(() => ({
31
- push: mockPush,
32
- })),
12
+ const mockNavigate = vi.fn();
13
+ const mockUseNavigate = vi.fn(() => mockNavigate);
14
+ const mockUseLocation = vi.fn(() => ({ pathname: '/' }));
15
+ vi.mock('react-router-dom', () => ({
16
+ useNavigate: () => mockUseNavigate(),
17
+ useLocation: () => mockUseLocation(),
33
18
  }));
34
19
  vi.mock('@/hooks/useIsMobile', () => ({
35
20
  useIsMobile: vi.fn(),
@@ -43,15 +28,26 @@ vi.mock('@/store/global', () => ({
43
28
  },
44
29
  }));
45
30
  describe('useOpenChatSettings', () => {
31
+ beforeEach(() => {
32
+ vi.clearAllMocks();
33
+ useAgentStore.setState({ showAgentSetting: false });
34
+ });
46
35
 
47
- it('should handle mobile route for chat settings', () => {
36
+ it('navigates to mobile chat settings with session info', () => {
48
37
  vi.mocked(useSessionStore).mockReturnValue('123');
49
38
  vi.mocked(useIsMobile).mockReturnValue(true);
50
39
  const { result } = renderHook(() => useOpenChatSettings(ChatSettingsTabs.Meta));
51
- expect(result.current()).toBe('/chat/settings');
40
+
41
+ act(() => {
42
+ result.current();
43
+ });
44
+
45
+ expect(mockNavigate).toHaveBeenCalledWith(
46
+ `/chat/settings?session=123&showMobileWorkspace=true`,
47
+ );
52
48
  });
53
49
 
54
- it('should handle desktop route for chat settings with session and tab', () => {
50
+ it('opens desktop agent settings overlay when not on mobile', () => {
55
51
  vi.mocked(useSessionStore).mockReturnValue('456');
56
52
  vi.mocked(useIsMobile).mockReturnValue(false);
57
53
 
@@ -62,5 +58,6 @@ describe('useOpenChatSettings', () => {
62
58
  });
63
59
 
64
60
  expect(useAgentStore.getState().showAgentSetting).toBeTruthy();
61
+ expect(mockNavigate).not.toHaveBeenCalled();
65
62
  });
66
63
  });
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
+ import { useLocation, useNavigate } from 'react-router-dom';
2
3
 
3
4
  import { useIsMobile } from '@/hooks/useIsMobile';
4
- import { useQueryRoute } from '@/hooks/useQueryRoute';
5
5
  import { useAgentStore } from '@/store/agent';
6
6
  import { ChatSettingsTabs } from '@/store/global/initialState';
7
7
  import { useSessionStore } from '@/store/session';
@@ -10,13 +10,14 @@ export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Met
10
10
  const activeId = useSessionStore((s) => s.activeId);
11
11
 
12
12
  const isMobile = useIsMobile();
13
- const router = useQueryRoute();
13
+ const navigate = useNavigate();
14
+ const location = useLocation();
14
15
 
15
16
  return useMemo(() => {
16
- if (isMobile) return () => router.push('/chat/settings', { query: { session: activeId } });
17
+ if (isMobile) return () => navigate(`/chat/settings?session=${activeId}&showMobileWorkspace=true`);
17
18
 
18
19
  return () => {
19
20
  useAgentStore.setState({ showAgentSetting: true });
20
21
  };
21
- }, [activeId, router, tab, isMobile]);
22
+ }, [activeId, navigate, location.pathname, tab, isMobile]);
22
23
  };
@@ -1,11 +1,11 @@
1
- import { parseAsBoolean, useQueryState } from 'nuqs';
2
1
  import { useMemo } from 'react';
3
2
 
3
+ import { parseAsBoolean, useQueryParam } from './useQueryParam';
4
+
4
5
  export const usePinnedAgentState = () => {
5
- const [isPinned, setIsPinned] = useQueryState(
6
- 'pinned',
7
- parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true }),
8
- );
6
+ const [isPinned, setIsPinned] = useQueryParam('pinned', parseAsBoolean.withDefault(false), {
7
+ clearOnDefault: true,
8
+ });
9
9
 
10
10
  const actions = useMemo(
11
11
  () => ({
@@ -14,7 +14,7 @@ export const usePinnedAgentState = () => {
14
14
  togglePinAgent: () => setIsPinned((prev) => !prev),
15
15
  unpinAgent: () => setIsPinned(false),
16
16
  }),
17
- [],
17
+ [setIsPinned],
18
18
  );
19
19
 
20
20
  return [isPinned, actions] as const;
@@ -2,6 +2,11 @@ import { useSearchParams } from 'next/navigation';
2
2
  import qs from 'query-string';
3
3
  import { useMemo } from 'react';
4
4
 
5
+ /**
6
+ * Hook to get query parameters
7
+ * This is the Next.js version
8
+ * For React Router version, use useQuery from @/app/[variants]/(main)/hooks/useQuery
9
+ */
5
10
  export const useQuery = () => {
6
11
  const rawQuery = useSearchParams();
7
12
  return useMemo(() => qs.parse(rawQuery.toString()), [rawQuery]);
@@ -0,0 +1,322 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { useSearchParams } from 'react-router-dom';
3
+
4
+ type HistoryMode = 'push' | 'replace';
5
+
6
+ // options 接口,用于 useQueryParam hook
7
+ interface QueryParamOptions<T> {
8
+ clearOnDefault?: boolean;
9
+ defaultValue?: T;
10
+ history?: HistoryMode;
11
+ throttleMs?: number;
12
+ }
13
+
14
+ // 解析器接口
15
+ interface Parser<T> {
16
+ parse: (value: string | null) => T;
17
+ serialize: (value: T) => string | null;
18
+ }
19
+
20
+ // 带默认值的解析器接口
21
+ interface ParserWithDefault<T> extends Parser<T> {
22
+ // nuqs has an optional default value on parsers
23
+ defaultValue?: T;
24
+ parse: (value: string | null) => T;
25
+ }
26
+
27
+ /**
28
+ * 核心钩子,用于管理单个查询参数
29
+ * 为 react-router-dom 替换 nuqs 的 useQueryState 功能
30
+ */
31
+ export function useQueryParam<T>(
32
+ key: string,
33
+ parser: Parser<T> | ParserWithDefault<T>,
34
+ options: QueryParamOptions<T> = {},
35
+ ): [T, (value: T | ((prev: T) => T)) => void] {
36
+ const [searchParams, setSearchParams] = useSearchParams();
37
+
38
+ // 从 options 或 parser 本身提取 defaultValue
39
+ const {
40
+ clearOnDefault = false,
41
+ defaultValue = (parser as ParserWithDefault<T>)?.defaultValue,
42
+ history = 'push',
43
+ throttleMs = 0,
44
+ } = options;
45
+
46
+ // eslint-disable-next-line no-undef
47
+ const throttleTimer = useRef<NodeJS.Timeout | null>(null);
48
+ const lastExecuteTime = useRef<number>(0);
49
+
50
+ // 使用 ref 存储最新的值,让 setValue 保持稳定
51
+ const searchParamsRef = useRef(searchParams);
52
+ const parserRef = useRef(parser);
53
+ const defaultValueRef = useRef(defaultValue);
54
+ const clearOnDefaultRef = useRef(clearOnDefault);
55
+ const historyRef = useRef(history);
56
+
57
+ // 每次渲染时更新 ref
58
+ useEffect(() => {
59
+ searchParamsRef.current = searchParams;
60
+ parserRef.current = parser;
61
+ defaultValueRef.current = defaultValue;
62
+ clearOnDefaultRef.current = clearOnDefault;
63
+ historyRef.current = history;
64
+ });
65
+
66
+ // 从 URL 解析当前值
67
+ const currentValue = parser.parse(searchParams.get(key));
68
+ const value = currentValue ?? (defaultValue as T);
69
+
70
+ // setValue 现在是稳定的,不会因为 searchParams 变化而重新创建
71
+ const setValue = useCallback(
72
+ (newValue: T | ((prev: T) => T)) => {
73
+ // 从 ref 读取最新值,避免闭包陈旧值问题
74
+ const currentSearchParams = searchParamsRef.current;
75
+ const currentParser = parserRef.current;
76
+ const currentDefaultValue = defaultValueRef.current;
77
+ const currentClearOnDefault = clearOnDefaultRef.current;
78
+ const currentHistory = historyRef.current;
79
+
80
+ // 通过函数形式获取最新值
81
+ const currentVal =
82
+ currentParser.parse(currentSearchParams.get(key)) ?? (currentDefaultValue as T);
83
+ const actualValue =
84
+ typeof newValue === 'function' ? (newValue as (prev: T) => T)(currentVal) : newValue;
85
+
86
+ const updateParams = () => {
87
+ // 使用函数式更新,确保基于最新的 searchParams
88
+ setSearchParams((prevParams) => {
89
+ const newSearchParams = new URLSearchParams(prevParams);
90
+ console.log('updateParams', newSearchParams.toString());
91
+ const serialized = currentParser.serialize(actualValue);
92
+
93
+ // 处理 clearOnDefault 选项
94
+ if (
95
+ currentClearOnDefault &&
96
+ currentDefaultValue !== undefined &&
97
+ serialized === currentParser.serialize(currentDefaultValue as T)
98
+ ) {
99
+ newSearchParams.delete(key);
100
+ } else if (serialized === null || serialized === undefined) {
101
+ newSearchParams.delete(key);
102
+ } else {
103
+ newSearchParams.set(key, serialized);
104
+ }
105
+
106
+ console.log('updateParams', newSearchParams.toString());
107
+
108
+ return newSearchParams;
109
+ }, { replace: currentHistory === 'replace' });
110
+ };
111
+
112
+ // 处理节流
113
+ if (throttleMs > 0) {
114
+ const now = Date.now();
115
+ const timeSinceLastExecute = now - lastExecuteTime.current;
116
+
117
+ if (timeSinceLastExecute >= throttleMs) {
118
+ // 距离上次执行已超过节流时间,立即执行
119
+ lastExecuteTime.current = now;
120
+ updateParams();
121
+ // 清理可能存在的定时器
122
+ if (throttleTimer.current) {
123
+ clearTimeout(throttleTimer.current);
124
+ throttleTimer.current = null;
125
+ }
126
+ } else {
127
+ // 还在节流期内,设置定时器在剩余时间后执行最后一次
128
+ if (throttleTimer.current) {
129
+ clearTimeout(throttleTimer.current);
130
+ }
131
+ const remainingTime = throttleMs - timeSinceLastExecute;
132
+ throttleTimer.current = setTimeout(() => {
133
+ lastExecuteTime.current = Date.now();
134
+ updateParams();
135
+ throttleTimer.current = null;
136
+ }, remainingTime);
137
+ }
138
+ } else {
139
+ updateParams();
140
+ }
141
+ },
142
+ [key, setSearchParams, throttleMs], // 只依赖不会频繁变化的值
143
+ );
144
+
145
+ // 组件卸载时清理节流计时器
146
+ useEffect(() => {
147
+ return () => {
148
+ if (throttleTimer.current) {
149
+ clearTimeout(throttleTimer.current);
150
+ }
151
+ };
152
+ }, []);
153
+
154
+ return [value, setValue];
155
+ }
156
+
157
+ // ===== 解析器 (Parsers) =====
158
+
159
+ /**
160
+ * 字符串解析器 - 默认行为
161
+ */
162
+ export const parseAsString: Parser<string | null> & {
163
+ withDefault: (defaultValue: string) => ParserWithDefault<string>;
164
+ } = {
165
+ parse: (value: string | null) => value,
166
+ serialize: (value: string | null) => value,
167
+ withDefault: (defaultValue: string) => ({
168
+ defaultValue,
169
+ parse: (value: string | null) => value ?? defaultValue,
170
+ serialize: (value: string) => value,
171
+ }),
172
+ };
173
+
174
+ /**
175
+ * 布尔值解析器
176
+ */
177
+ export const parseAsBoolean: Parser<boolean | null> & {
178
+ withDefault: (defaultValue: boolean) => ParserWithDefault<boolean>;
179
+ } = {
180
+ parse: (value: string | null) => {
181
+ if (value === null) return null;
182
+ return value === 'true' || value === '1';
183
+ },
184
+ serialize: (value: boolean | null) => {
185
+ if (value === null) return null;
186
+ return value ? 'true' : 'false';
187
+ },
188
+ withDefault: (defaultValue: boolean) => ({
189
+ defaultValue,
190
+ parse: (value: string | null) => {
191
+ if (value === null) return defaultValue;
192
+ return value === 'true' || value === '1';
193
+ },
194
+ serialize: (value: boolean) => (value ? 'true' : 'false'),
195
+ }),
196
+ };
197
+
198
+ /**
199
+ * 整数解析器
200
+ */
201
+ export const parseAsInteger: Parser<number | null> & {
202
+ withDefault: (defaultValue: number) => ParserWithDefault<number>;
203
+ } = {
204
+ parse: (value: string | null) => {
205
+ if (value === null) return null;
206
+ const parsed = Number.parseInt(value, 10);
207
+ return Number.isNaN(parsed) ? null : parsed;
208
+ },
209
+ serialize: (value: number | null) => {
210
+ if (value === null) return null;
211
+ return value.toString();
212
+ },
213
+ withDefault: (defaultValue: number) => ({
214
+ defaultValue,
215
+ parse: (value: string | null) => {
216
+ if (value === null) return defaultValue;
217
+ const parsed = Number.parseInt(value, 10);
218
+ return Number.isNaN(parsed) ? defaultValue : parsed;
219
+ },
220
+ serialize: (value: number) => value.toString(),
221
+ }),
222
+ };
223
+
224
+ /**
225
+ * 字符串枚举解析器
226
+ */
227
+ export function parseAsStringEnum<T extends string>(validValues: readonly T[]) {
228
+ const parser: Parser<T | null> = {
229
+ parse: (value: string | null): T | null => {
230
+ if (value === null) return null;
231
+ return validValues.includes(value as T) ? (value as T) : null;
232
+ },
233
+ serialize: (value: T | null) => value,
234
+ };
235
+
236
+ const withDefault = (defaultValue: T): ParserWithDefault<T> => ({
237
+ defaultValue,
238
+ parse: (value: string | null): T => {
239
+ if (value === null) return defaultValue;
240
+ return validValues.includes(value as T) ? (value as T) : defaultValue;
241
+ },
242
+ serialize: (value: T) => value,
243
+ });
244
+
245
+ return Object.assign(parser, { withDefault });
246
+ }
247
+
248
+ // ===== 简化的 API (Simplified API) =====
249
+
250
+ // --- 重载签名 ---
251
+
252
+ // 字符串 (无解析器或仅有 options)
253
+ export function useQueryState(
254
+ key: string,
255
+ options?: QueryParamOptions<string | null>,
256
+ ): [string | null, (value: string | null | ((prev: string | null) => string | null)) => void];
257
+
258
+ // 带默认值的字符串解析器
259
+ // eslint-disable-next-line no-redeclare
260
+ export function useQueryState(
261
+ key: string,
262
+ parserWithDefault: ParserWithDefault<string>,
263
+ ): [string, (value: string | ((prev: string) => string)) => void];
264
+
265
+ // 布尔值解析器
266
+ // eslint-disable-next-line no-redeclare
267
+ export function useQueryState(
268
+ key: string,
269
+ parser: typeof parseAsBoolean,
270
+ ): [boolean | null, (value: boolean | null | ((prev: boolean | null) => boolean | null)) => void];
271
+
272
+ // 带默认值的布尔值解析器
273
+ // eslint-disable-next-line no-redeclare
274
+ export function useQueryState(
275
+ key: string,
276
+ parserWithDefault: ParserWithDefault<boolean>,
277
+ ): [boolean, (value: boolean | ((prev: boolean) => boolean)) => void];
278
+
279
+ // 整数解析器
280
+ // eslint-disable-next-line no-redeclare
281
+ export function useQueryState(
282
+ key: string,
283
+ parser: typeof parseAsInteger,
284
+ ): [number | null, (value: number | null | ((prev: number | null) => number | null)) => void];
285
+
286
+ // 带默认值的整数解析器
287
+ // eslint-disable-next-line no-redeclare
288
+ export function useQueryState(
289
+ key: string,
290
+ parserWithDefault: ParserWithDefault<number>,
291
+ ): [number, (value: number | ((prev: number) => number)) => void];
292
+
293
+ // 带默认值的字符串枚举解析器
294
+ // eslint-disable-next-line no-redeclare
295
+ export function useQueryState<T extends string>(
296
+ key: string,
297
+ parserWithDefault: ParserWithDefault<T>,
298
+ ): [T, (value: T | ((prev: T) => T)) => void];
299
+
300
+ // --- 单一实现 ---
301
+ // eslint-disable-next-line no-redeclare
302
+ export function useQueryState(key: string, parserOrOptions?: any): any {
303
+ // 修复了逻辑分派问题
304
+ let parser: Parser<any>;
305
+ let options: QueryParamOptions<any> = {};
306
+
307
+ if (!parserOrOptions) {
308
+ // 场景 1: useQueryState('key')
309
+ parser = parseAsString;
310
+ } else if (typeof parserOrOptions.parse === 'function') {
311
+ // 场景 2: useQueryState('key', parseAsInteger) 或 useQueryState('key', parseAsInteger.withDefault(10))
312
+ parser = parserOrOptions;
313
+ // 从解析器自身提取 options (例如 defaultValue)
314
+ options = parserOrOptions;
315
+ } else {
316
+ // 场景 3: useQueryState('key', { defaultValue: 'foo', throttleMs: 500 })
317
+ parser = parseAsString;
318
+ options = parserOrOptions;
319
+ }
320
+
321
+ return useQueryParam(key, parser, options);
322
+ }
@@ -4,18 +4,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import { useQueryRoute } from './useQueryRoute';
5
5
 
6
6
  // Mocks
7
- vi.mock('next/navigation', () => ({
8
- useRouter: vi.fn(() => ({
9
- push: vi.fn((href) => href),
10
- replace: vi.fn((href) => href),
11
- })),
12
- }));
13
-
14
- vi.mock('nextjs-toploader/app', () => ({
15
- useRouter: vi.fn(() => ({
16
- push: vi.fn((href) => href),
17
- replace: vi.fn((href) => href),
18
- })),
7
+ vi.mock('react-router-dom', () => ({
8
+ useNavigate: vi.fn(() => vi.fn((href) => href)),
19
9
  }));
20
10
 
21
11
  vi.mock('@/utils/env', () => ({