larkup 0.1.36 → 0.1.38

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 (282) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +12 -12
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/required-server-files.js +1 -1
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +18 -18
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -18
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +12 -12
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/analytics.html +1 -1
  25. package/.next/server/app/analytics.rsc +19 -19
  26. package/.next/server/app/analytics.segments/_full.segment.rsc +19 -19
  27. package/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
  28. package/.next/server/app/analytics.segments/_index.segment.rsc +12 -12
  29. package/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
  30. package/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
  31. package/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
  32. package/.next/server/app/api/analytics/route.js.nft.json +1 -1
  33. package/.next/server/app/api/chat/route.js.nft.json +1 -1
  34. package/.next/server/app/api/chat/status/route.js.nft.json +1 -1
  35. package/.next/server/app/api/chat/suggestions/route.js.nft.json +1 -1
  36. package/.next/server/app/api/config/route.js.nft.json +1 -1
  37. package/.next/server/app/api/config/test/route.js.nft.json +1 -1
  38. package/.next/server/app/api/connections/route.js.nft.json +1 -1
  39. package/.next/server/app/api/connections/webhook/route.js.nft.json +1 -1
  40. package/.next/server/app/api/demo/route.js.nft.json +1 -1
  41. package/.next/server/app/api/deploy/cloud-project/route.js.nft.json +1 -1
  42. package/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
  43. package/.next/server/app/api/deployment/blob-setup/route.js.nft.json +1 -1
  44. package/.next/server/app/api/deployment/config/route.js.nft.json +1 -1
  45. package/.next/server/app/api/describe-image/route.js.nft.json +1 -1
  46. package/.next/server/app/api/documents/route.js.nft.json +1 -1
  47. package/.next/server/app/api/firecrawl/local/route.js.nft.json +1 -1
  48. package/.next/server/app/api/index/route.js.nft.json +1 -1
  49. package/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -1
  50. package/.next/server/app/api/jobs/route.js.nft.json +1 -1
  51. package/.next/server/app/api/media/[id]/route.js.nft.json +1 -1
  52. package/.next/server/app/api/media/process/route.js.nft.json +1 -1
  53. package/.next/server/app/api/media/route.js.nft.json +1 -1
  54. package/.next/server/app/api/media/stream/route.js.nft.json +1 -1
  55. package/.next/server/app/api/search/bing/route.js.nft.json +1 -1
  56. package/.next/server/app/api/search/brave/route.js.nft.json +1 -1
  57. package/.next/server/app/api/search/exa/route.js.nft.json +1 -1
  58. package/.next/server/app/api/search/google/route.js.nft.json +1 -1
  59. package/.next/server/app/api/search/route.js.nft.json +1 -1
  60. package/.next/server/app/api/search/tavily/route.js.nft.json +1 -1
  61. package/.next/server/app/api/server/agent/route.js.nft.json +1 -1
  62. package/.next/server/app/api/server/download/route.js.nft.json +1 -1
  63. package/.next/server/app/api/server/generate/route.js.nft.json +1 -1
  64. package/.next/server/app/api/server/local/route.js.nft.json +1 -1
  65. package/.next/server/app/api/servers/route.js.nft.json +1 -1
  66. package/.next/server/app/api/tabular/query/route.js.nft.json +1 -1
  67. package/.next/server/app/api/tabular/route.js.nft.json +1 -1
  68. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  69. package/.next/server/app/chat.html +1 -1
  70. package/.next/server/app/chat.rsc +19 -19
  71. package/.next/server/app/chat.segments/_full.segment.rsc +19 -19
  72. package/.next/server/app/chat.segments/_head.segment.rsc +4 -4
  73. package/.next/server/app/chat.segments/_index.segment.rsc +12 -12
  74. package/.next/server/app/chat.segments/_tree.segment.rsc +2 -2
  75. package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
  76. package/.next/server/app/chat.segments/chat.segment.rsc +3 -3
  77. package/.next/server/app/data/page_client-reference-manifest.js +1 -1
  78. package/.next/server/app/data.html +1 -1
  79. package/.next/server/app/data.rsc +19 -19
  80. package/.next/server/app/data.segments/_full.segment.rsc +19 -19
  81. package/.next/server/app/data.segments/_head.segment.rsc +4 -4
  82. package/.next/server/app/data.segments/_index.segment.rsc +12 -12
  83. package/.next/server/app/data.segments/_tree.segment.rsc +2 -2
  84. package/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
  85. package/.next/server/app/data.segments/data.segment.rsc +3 -3
  86. package/.next/server/app/index.html +1 -1
  87. package/.next/server/app/index.rsc +17 -17
  88. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  89. package/.next/server/app/index.segments/_full.segment.rsc +17 -17
  90. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  91. package/.next/server/app/index.segments/_index.segment.rsc +12 -12
  92. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  93. package/.next/server/app/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/settings/page.js.nft.json +1 -1
  95. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  96. package/.next/server/app/settings.html +1 -1
  97. package/.next/server/app/settings.rsc +19 -19
  98. package/.next/server/app/settings.segments/_full.segment.rsc +19 -19
  99. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  100. package/.next/server/app/settings.segments/_index.segment.rsc +12 -12
  101. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  102. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  103. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  104. package/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_0kbcuk-.js +23 -24
  105. package/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_0sbf4ny.js +1 -1
  106. package/.next/server/chunks/[root-of-the-server]__1jc54o2._.js +1 -1
  107. package/.next/server/chunks/_1twj5f0._.js +3 -3
  108. package/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
  109. package/.next/server/chunks/packages_scraper_src_0kkcv_v._.js +1 -1
  110. package/.next/server/chunks/ssr/_169pdlf._.js +1 -1
  111. package/.next/server/chunks/ssr/_18jagqc._.js +2 -2
  112. package/.next/server/chunks/ssr/_1hetwwu._.js +1 -1
  113. package/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
  114. package/.next/server/middleware-build-manifest.js +3 -3
  115. package/.next/server/pages/404.html +1 -1
  116. package/.next/server/pages/500.html +1 -1
  117. package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  118. package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
  119. package/.next/standalone/apps/web/.next/required-server-files.json +1 -1
  120. package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
  121. package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
  122. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  123. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  124. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  125. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  126. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  127. package/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  128. package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  129. package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +18 -18
  130. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +18 -18
  131. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  132. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +12 -12
  133. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  134. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  135. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  136. package/.next/standalone/apps/web/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
  137. package/.next/standalone/apps/web/.next/server/app/analytics.html +1 -1
  138. package/.next/standalone/apps/web/.next/server/app/analytics.rsc +19 -19
  139. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_full.segment.rsc +19 -19
  140. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
  141. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_index.segment.rsc +12 -12
  142. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
  143. package/.next/standalone/apps/web/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
  144. package/.next/standalone/apps/web/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
  145. package/.next/standalone/apps/web/.next/server/app/api/analytics/route.js.nft.json +1 -1
  146. package/.next/standalone/apps/web/.next/server/app/api/chat/route.js.nft.json +1 -1
  147. package/.next/standalone/apps/web/.next/server/app/api/chat/status/route.js.nft.json +1 -1
  148. package/.next/standalone/apps/web/.next/server/app/api/chat/suggestions/route.js.nft.json +1 -1
  149. package/.next/standalone/apps/web/.next/server/app/api/config/route.js.nft.json +1 -1
  150. package/.next/standalone/apps/web/.next/server/app/api/config/test/route.js.nft.json +1 -1
  151. package/.next/standalone/apps/web/.next/server/app/api/connections/route.js.nft.json +1 -1
  152. package/.next/standalone/apps/web/.next/server/app/api/connections/webhook/route.js.nft.json +1 -1
  153. package/.next/standalone/apps/web/.next/server/app/api/demo/route.js.nft.json +1 -1
  154. package/.next/standalone/apps/web/.next/server/app/api/deploy/cloud-project/route.js.nft.json +1 -1
  155. package/.next/standalone/apps/web/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
  156. package/.next/standalone/apps/web/.next/server/app/api/deployment/blob-setup/route.js.nft.json +1 -1
  157. package/.next/standalone/apps/web/.next/server/app/api/deployment/config/route.js.nft.json +1 -1
  158. package/.next/standalone/apps/web/.next/server/app/api/describe-image/route.js.nft.json +1 -1
  159. package/.next/standalone/apps/web/.next/server/app/api/documents/route.js.nft.json +1 -1
  160. package/.next/standalone/apps/web/.next/server/app/api/firecrawl/local/route.js.nft.json +1 -1
  161. package/.next/standalone/apps/web/.next/server/app/api/index/route.js.nft.json +1 -1
  162. package/.next/standalone/apps/web/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -1
  163. package/.next/standalone/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
  164. package/.next/standalone/apps/web/.next/server/app/api/media/[id]/route.js.nft.json +1 -1
  165. package/.next/standalone/apps/web/.next/server/app/api/media/process/route.js.nft.json +1 -1
  166. package/.next/standalone/apps/web/.next/server/app/api/media/route.js.nft.json +1 -1
  167. package/.next/standalone/apps/web/.next/server/app/api/media/stream/route.js.nft.json +1 -1
  168. package/.next/standalone/apps/web/.next/server/app/api/search/bing/route.js.nft.json +1 -1
  169. package/.next/standalone/apps/web/.next/server/app/api/search/brave/route.js.nft.json +1 -1
  170. package/.next/standalone/apps/web/.next/server/app/api/search/exa/route.js.nft.json +1 -1
  171. package/.next/standalone/apps/web/.next/server/app/api/search/google/route.js.nft.json +1 -1
  172. package/.next/standalone/apps/web/.next/server/app/api/search/route.js.nft.json +1 -1
  173. package/.next/standalone/apps/web/.next/server/app/api/search/tavily/route.js.nft.json +1 -1
  174. package/.next/standalone/apps/web/.next/server/app/api/server/agent/route.js.nft.json +1 -1
  175. package/.next/standalone/apps/web/.next/server/app/api/server/download/route.js.nft.json +1 -1
  176. package/.next/standalone/apps/web/.next/server/app/api/server/generate/route.js.nft.json +1 -1
  177. package/.next/standalone/apps/web/.next/server/app/api/server/local/route.js.nft.json +1 -1
  178. package/.next/standalone/apps/web/.next/server/app/api/servers/route.js.nft.json +1 -1
  179. package/.next/standalone/apps/web/.next/server/app/api/tabular/query/route.js.nft.json +1 -1
  180. package/.next/standalone/apps/web/.next/server/app/api/tabular/route.js.nft.json +1 -1
  181. package/.next/standalone/apps/web/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  182. package/.next/standalone/apps/web/.next/server/app/chat.html +1 -1
  183. package/.next/standalone/apps/web/.next/server/app/chat.rsc +19 -19
  184. package/.next/standalone/apps/web/.next/server/app/chat.segments/_full.segment.rsc +19 -19
  185. package/.next/standalone/apps/web/.next/server/app/chat.segments/_head.segment.rsc +4 -4
  186. package/.next/standalone/apps/web/.next/server/app/chat.segments/_index.segment.rsc +12 -12
  187. package/.next/standalone/apps/web/.next/server/app/chat.segments/_tree.segment.rsc +2 -2
  188. package/.next/standalone/apps/web/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
  189. package/.next/standalone/apps/web/.next/server/app/chat.segments/chat.segment.rsc +3 -3
  190. package/.next/standalone/apps/web/.next/server/app/data/page_client-reference-manifest.js +1 -1
  191. package/.next/standalone/apps/web/.next/server/app/data.html +1 -1
  192. package/.next/standalone/apps/web/.next/server/app/data.rsc +19 -19
  193. package/.next/standalone/apps/web/.next/server/app/data.segments/_full.segment.rsc +19 -19
  194. package/.next/standalone/apps/web/.next/server/app/data.segments/_head.segment.rsc +4 -4
  195. package/.next/standalone/apps/web/.next/server/app/data.segments/_index.segment.rsc +12 -12
  196. package/.next/standalone/apps/web/.next/server/app/data.segments/_tree.segment.rsc +2 -2
  197. package/.next/standalone/apps/web/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
  198. package/.next/standalone/apps/web/.next/server/app/data.segments/data.segment.rsc +3 -3
  199. package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
  200. package/.next/standalone/apps/web/.next/server/app/index.rsc +17 -17
  201. package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  202. package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +17 -17
  203. package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +4 -4
  204. package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +12 -12
  205. package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  206. package/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  207. package/.next/standalone/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  208. package/.next/standalone/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  209. package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
  210. package/.next/standalone/apps/web/.next/server/app/settings.rsc +19 -19
  211. package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +19 -19
  212. package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  213. package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +12 -12
  214. package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  215. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  216. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  217. package/.next/standalone/apps/web/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_0kbcuk-.js +23 -24
  218. package/.next/standalone/apps/web/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_0sbf4ny.js +1 -1
  219. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__1jc54o2._.js +1 -1
  220. package/.next/standalone/apps/web/.next/server/chunks/_1twj5f0._.js +3 -3
  221. package/.next/standalone/apps/web/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
  222. package/.next/standalone/apps/web/.next/server/chunks/packages_scraper_src_0kkcv_v._.js +1 -1
  223. package/.next/standalone/apps/web/.next/server/chunks/ssr/_169pdlf._.js +1 -1
  224. package/.next/standalone/apps/web/.next/server/chunks/ssr/_18jagqc._.js +2 -2
  225. package/.next/standalone/apps/web/.next/server/chunks/ssr/_1hetwwu._.js +1 -1
  226. package/.next/standalone/apps/web/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
  227. package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
  228. package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  229. package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  230. package/.next/standalone/apps/web/.next/static/chunks/{2-st4vd5971ry.js → 06u04tzgkz4_f.js} +1 -1
  231. package/.next/standalone/apps/web/.next/static/chunks/{3q4k1i384p4oi.js → 19k_g3af0cf7b.js} +1 -1
  232. package/.next/standalone/apps/web/.next/static/chunks/{3mp3jya53i-g-.js → 19krxv-ou9c8c.js} +6 -6
  233. package/.next/standalone/apps/web/.next/static/chunks/1_nk3imchwn8a.css +1 -0
  234. package/.next/standalone/apps/web/.next/static/chunks/{0qyuw19snetjl.js → 2ljwki2xxnblc.js} +2 -2
  235. package/.next/standalone/apps/web/CHANGELOG.md +17 -0
  236. package/.next/standalone/apps/web/app/api/chat/route.ts +62 -33
  237. package/.next/standalone/apps/web/app/api/media/process/route.ts +6 -0
  238. package/.next/standalone/apps/web/app/api/media/route.ts +3 -2
  239. package/.next/standalone/apps/web/app/api/media/stream/route.ts +19 -3
  240. package/.next/standalone/apps/web/app/settings/page.tsx +8 -17
  241. package/.next/standalone/apps/web/components/chat/chat-workspace.tsx +11 -4
  242. package/.next/standalone/apps/web/components/chat/message-item.tsx +18 -6
  243. package/.next/standalone/apps/web/components/chat/tools/knowledge-base-result.tsx +3 -4
  244. package/.next/standalone/apps/web/components/data/data-workspace.tsx +6 -2
  245. package/.next/standalone/apps/web/components/data/jobs-panel.tsx +22 -2
  246. package/.next/standalone/apps/web/components/data/media-panel.tsx +117 -91
  247. package/.next/standalone/apps/web/components/settings/marketplace-section.tsx +5 -0
  248. package/.next/standalone/apps/web/components/settings/prompts-section.tsx +13 -3
  249. package/.next/standalone/apps/web/package.json +1 -1
  250. package/.next/standalone/apps/web/server.js +1 -1
  251. package/.next/standalone/packages/tools/video-audio/CHANGELOG.md +6 -0
  252. package/.next/standalone/packages/tools/video-audio/dist/url-importer.js +34 -8
  253. package/.next/standalone/packages/tools/video-audio/package.json +1 -1
  254. package/.next/standalone/packages/tools/video-audio/src/url-importer.ts +56 -21
  255. package/.next/static/chunks/{2-st4vd5971ry.js → 06u04tzgkz4_f.js} +1 -1
  256. package/.next/static/chunks/{3q4k1i384p4oi.js → 19k_g3af0cf7b.js} +1 -1
  257. package/.next/static/chunks/{3mp3jya53i-g-.js → 19krxv-ou9c8c.js} +6 -6
  258. package/.next/static/chunks/1_nk3imchwn8a.css +1 -0
  259. package/.next/static/chunks/{0qyuw19snetjl.js → 2ljwki2xxnblc.js} +2 -2
  260. package/CHANGELOG.md +17 -0
  261. package/app/api/chat/route.ts +62 -33
  262. package/app/api/media/process/route.ts +6 -0
  263. package/app/api/media/route.ts +3 -2
  264. package/app/api/media/stream/route.ts +19 -3
  265. package/app/settings/page.tsx +8 -17
  266. package/components/chat/chat-workspace.tsx +11 -4
  267. package/components/chat/message-item.tsx +18 -6
  268. package/components/chat/tools/knowledge-base-result.tsx +3 -4
  269. package/components/data/data-workspace.tsx +6 -2
  270. package/components/data/jobs-panel.tsx +22 -2
  271. package/components/data/media-panel.tsx +117 -91
  272. package/components/settings/marketplace-section.tsx +5 -0
  273. package/components/settings/prompts-section.tsx +13 -3
  274. package/package.json +6 -6
  275. package/.next/standalone/apps/web/.next/static/chunks/0fa79dhcoeytz.css +0 -1
  276. package/.next/static/chunks/0fa79dhcoeytz.css +0 -1
  277. /package/.next/standalone/apps/web/.next/static/{ZV6fJc5ZoZ_a-UPNhgKZS → IOngxM7BWde96UQMAebFB}/_buildManifest.js +0 -0
  278. /package/.next/standalone/apps/web/.next/static/{ZV6fJc5ZoZ_a-UPNhgKZS → IOngxM7BWde96UQMAebFB}/_clientMiddlewareManifest.js +0 -0
  279. /package/.next/standalone/apps/web/.next/static/{ZV6fJc5ZoZ_a-UPNhgKZS → IOngxM7BWde96UQMAebFB}/_ssgManifest.js +0 -0
  280. /package/.next/static/{ZV6fJc5ZoZ_a-UPNhgKZS → IOngxM7BWde96UQMAebFB}/_buildManifest.js +0 -0
  281. /package/.next/static/{ZV6fJc5ZoZ_a-UPNhgKZS → IOngxM7BWde96UQMAebFB}/_clientMiddlewareManifest.js +0 -0
  282. /package/.next/static/{ZV6fJc5ZoZ_a-UPNhgKZS → IOngxM7BWde96UQMAebFB}/_ssgManifest.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # larkup
2
2
 
3
+ ## 0.1.38
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix 10 production issues: SSE controller-closed error, cleaner system prompt, smarter KB search, one-shot web search, corpus refresh after scrape, minimal KB loading UI, settings sidebar navigation, force-delete processing media, playlist confirmation, image preview in chat, marketplace install progress toast.
8
+
9
+ ## 0.1.37
10
+
11
+ ### Patch Changes
12
+
13
+ - c1b12cb: Restore reliable no-Docker local crawler search with a resilient public-search fallback, make current-event chat questions use enabled web search, speed up first-party tool installation, and improve media URL progress handling. Simplify settings and prompt UX, and make failed-crawl URL copying reliable.
14
+ - Updated dependencies [c1b12cb]
15
+ - @larkup/scraper@0.1.27
16
+ - @larkup/marketplace@0.1.10
17
+ - @larkup/tool-video-audio@0.2.3
18
+ - @larkup/tool-doc-editor@0.2.5
19
+
3
20
  ## 0.1.36
4
21
 
5
22
  ### Patch Changes
@@ -64,28 +64,24 @@ function createChatModel(
64
64
 
65
65
  const SMART_RETRIEVAL_POLICY = `
66
66
 
67
- CONVERSATION, RETRIEVAL, AND MEDIA POLICY:
68
-
69
- TOOL PRIORITY (strict order):
70
- 1. searchKnowledgeBase ALWAYS try this FIRST when the question may relate to the user's indexed data. This includes any question mentioning "I", "my", "our", "the document", "the file", "the diagram", "the image", "show me", or referencing specific topics the user has likely indexed.
71
- 2. Web search (if enabled) Use ONLY if searchKnowledgeBase returned zero relevant hits AND the question is factual and benefits from web results.
72
- 3. Other tools (tabular queries, analysis, etc.) — Use when the question specifically requires them.
73
- 4. General knowledge — Answer from your training data ONLY for general knowledge questions clearly unrelated to the user's data.
74
- 5. If no tool yields relevant results AND you cannot answer reliably from general knowledge, respond honestly: "I could not find relevant information in your knowledge base or available sources."
75
-
76
- RETRIEVAL RULES:
77
- - Default to a short, direct answer. One to three sentences is usually enough; use structure only when the task is genuinely complex.
78
- - Do not search for greetings, casual conversation, writing help, brainstorming, explanations you can answer reliably from general knowledge, or questions unrelated to the user's indexed data.
79
- - Use searchKnowledgeBase only when the answer depends on the user's indexed/private content, an exact source-specific fact, or a topic that plausibly refers to something the user indexed.
80
- - Treat questions about the user or their materials as knowledge-base questions: phrases such as "I", "my", "our", "you have", "the database", "the document", "the file", or "the diagram" require searchKnowledgeBase before answering whenever indexed content may contain the answer.
81
- - Search at most once per user request. Never repeat a search when a prior tool result in this conversation already contains enough evidence. If the results do not answer the question, say so plainly instead of making more searches or guessing.
82
-
83
- IMAGE AND MEDIA RULES:
84
- - NEVER output raw markdown image syntax like ![alt](url). The chat UI blocks these and shows "[Image unavailable]". Instead, always use the presentMedia tool to display images, videos, or audio from indexed content.
85
- - When search results contain images[].imageUrl (an image extracted from a PDF), use presentMedia with that exact imageUrl. When they contain a mediaAssetId, use presentMedia with the exact assetId. This is the ONLY way indexed images can appear in the conversation.
86
- - For a follow-up such as "show me that part", "play the clip", "show the image", or "let me hear it", reuse the mediaAssetId and timestamps already present in the earlier search result and call presentMedia directly. Do not search again just to rediscover the same media.
87
- - Use presentMedia to embed exactly one best video, image, or audio citation when the user asks to see/hear it or when one compact preview materially supports the answer. Do not present every search result.
88
- - Keep the written answer minimal around a media citation. Do not print internal media reference markers.
67
+ TOOL RULES:
68
+ 1. searchKnowledgeBase → Use FIRST when the question is about the user's indexed data (documents, files, images, videos, diagrams, anything they uploaded).
69
+ 2. webSearch Use ONLY after searchKnowledgeBase returned nothing relevant AND the question needs current/public facts.
70
+ 3. Other tools Use when the question specifically requires them.
71
+ 4. General knowledge Answer directly for common knowledge unrelated to the user's data.
72
+ 5. If nothing helps, say so honestly.
73
+
74
+ DO:
75
+ - Keep answers short and direct. 1–3 sentences unless the task is complex.
76
+ - Search at most ONCE per tool per turn. Never repeat the same search.
77
+ - Use presentMedia to show images/videos/audio from indexed content.
78
+ - For follow-ups like "show me that", reuse the mediaAssetId from earlier results do not search again.
79
+
80
+ DO NOT:
81
+ - Search for greetings, casual chat, writing help, or general knowledge you already know.
82
+ - Output markdown images like ![alt](url) — the UI blocks them. Always use presentMedia instead.
83
+ - Present every search result. Pick the single best one.
84
+ - Guess or fabricate when results are insufficient.
89
85
  `;
90
86
 
91
87
  function latestUserText(messages: UIMessage[]): string {
@@ -101,8 +97,29 @@ function latestUserText(messages: UIMessage[]): string {
101
97
  return '';
102
98
  }
103
99
 
100
+ /** Only force KB search when the user explicitly refers to their own data.
101
+ * Matches "my document", "our files", "the uploaded image" etc. but NOT
102
+ * generic uses like "I want to know", "can you tell me", "I think". */
104
103
  function requiresKnowledgeBaseSearch(text: string): boolean {
105
- return /\b(my|our|we|i|database|db|document|file|diagram|image|picture|upload(?:ed)?|knowledge base|corpus)\b|\bwhat is my\b/i.test(
104
+ // Skip generic conversational patterns
105
+ if (/^(hi|hello|hey|thanks|thank you|ok|sure|yes|no|please|help)\b/i.test(text.trim())) {
106
+ return false;
107
+ }
108
+ // Match possessive references to user data
109
+ return (
110
+ /\b(my|our)\s+(document|file|data|image|picture|diagram|video|audio|upload|corpus|knowledge|database|db|pdf|report|spreadsheet|presentation)/i.test(
111
+ text,
112
+ ) ||
113
+ /\b(the|this|that)\s+(document|file|diagram|image|picture|upload|pdf|report)/i.test(text) ||
114
+ /\b(uploaded?|indexed|scraped|knowledge base|corpus)\b/i.test(text) ||
115
+ /\bshow\s+me\b/i.test(text)
116
+ );
117
+ }
118
+
119
+ /** Questions whose answer is inherently time-sensitive should not rely on the
120
+ * model's training snapshot when Web Search is available. */
121
+ function requiresCurrentWebSearch(text: string): boolean {
122
+ return /\b(who won|winner|final score|match result|latest|current|today|yesterday|breaking news|weather|stock price|exchange rate|election result|schedule)\b/i.test(
106
123
  text,
107
124
  );
108
125
  }
@@ -364,10 +381,13 @@ ${fieldLines}`;
364
381
  }
365
382
 
366
383
  const systemPrompt =
367
- (config.systemPrompt || DEFAULT_SYSTEM_PROMPT) +
384
+ DEFAULT_SYSTEM_PROMPT +
385
+ (config.systemPrompt && config.systemPrompt !== DEFAULT_SYSTEM_PROMPT
386
+ ? `\n\nUSER INSTRUCTIONS:\n${config.systemPrompt}`
387
+ : '') +
368
388
  SMART_RETRIEVAL_POLICY +
369
389
  (config.webSearchEnabled
370
- ? '\nWEB SEARCH IS ENABLED: For a public, current, or external factual question, you MUST call webSearch after any required knowledge-base search. Do not tell the user to check the web yourself when this tool is available.\n'
390
+ ? '\nWeb search is available. Use it for current events or public facts not in the knowledge base.\n'
371
391
  : '') +
372
392
  tabularContext +
373
393
  docContext;
@@ -375,6 +395,10 @@ ${fieldLines}`;
375
395
  const forceKnowledgeBaseSearch =
376
396
  requiresKnowledgeBaseSearch(latestUserText(messagesToProcess)) &&
377
397
  Boolean(tools.searchKnowledgeBase);
398
+ const forceWebSearch =
399
+ config.webSearchEnabled === true &&
400
+ requiresCurrentWebSearch(latestUserText(messagesToProcess)) &&
401
+ Boolean(tools.webSearch);
378
402
 
379
403
  // Debug: log payload sizes to console in development
380
404
  if (process.env.NODE_ENV === 'development') {
@@ -386,19 +410,24 @@ ${fieldLines}`;
386
410
  system: systemPrompt,
387
411
  messages: await convertToModelMessages(safeMessages, { tools }),
388
412
  maxOutputTokens: 4096,
389
- // Five steps covers the only useful multi-step case (retrieve, optionally
390
- // present one media item, then answer) without allowing a tool loop to
391
- // burn the context window and end with no response.
392
- stopWhen: stepCountIs(5),
413
+ // Three steps: retrieve optionally present media answer.
414
+ stopWhen: stepCountIs(3),
393
415
  toolChoice: 'auto',
394
416
  prepareStep: ({ stepNumber }) => {
417
+ // Step 0: force KB search if needed
395
418
  if (forceKnowledgeBaseSearch && stepNumber === 0) {
396
419
  return { toolChoice: { type: 'tool', toolName: 'searchKnowledgeBase' } };
397
420
  }
398
- // A retrieval is deliberately one-shot per turn. This is applied at the
399
- // runtime level rather than relying only on the model prompt.
400
- if (forceKnowledgeBaseSearch) {
401
- return { activeTools: Object.keys(tools).filter((name) => name !== 'searchKnowledgeBase') };
421
+ // Step 1 (or 0 if no KB): force web search if needed
422
+ if (forceWebSearch && (!forceKnowledgeBaseSearch || stepNumber === 1)) {
423
+ return { toolChoice: { type: 'tool', toolName: 'webSearch' } };
424
+ }
425
+ // After forced tools, remove them so the model cannot loop on them.
426
+ const usedTools: string[] = [];
427
+ if (forceKnowledgeBaseSearch) usedTools.push('searchKnowledgeBase');
428
+ if (forceWebSearch) usedTools.push('webSearch');
429
+ if (usedTools.length > 0) {
430
+ return { activeTools: Object.keys(tools).filter((name) => !usedTools.includes(name)) };
402
431
  }
403
432
  return undefined;
404
433
  },
@@ -165,6 +165,12 @@ async function enqueueMediaProcessing(
165
165
  const entries = await tool.importMediaUrl(currentAsset.originalUrl, {
166
166
  outputDir: tmpDir,
167
167
  playlistMax: 10, // max items
168
+ onProgress: ({ percent, message }: { percent?: number; message: string }) =>
169
+ reportStage('download', {
170
+ status: 'running',
171
+ percent,
172
+ message,
173
+ }),
168
174
  });
169
175
 
170
176
  if (!entries || entries.length === 0) {
@@ -177,6 +177,7 @@ async function removeMedia(req: Request) {
177
177
  const url = new URL(req.url);
178
178
  const id = url.searchParams.get('id');
179
179
  const ids = url.searchParams.get('ids');
180
+ const force = url.searchParams.get('force') === 'true';
180
181
 
181
182
  const storage = createStorageProvider();
182
183
 
@@ -184,7 +185,7 @@ async function removeMedia(req: Request) {
184
185
  const { getMediaAsset } = await import('@larkup/core/media-store');
185
186
  const asset = await getMediaAsset(id);
186
187
  if (asset) {
187
- if (isMediaProcessing(asset)) {
188
+ if (!force && isMediaProcessing(asset)) {
188
189
  return NextResponse.json(
189
190
  { error: 'Wait for the media indexing job to finish before deleting it.' },
190
191
  { status: 409 },
@@ -201,7 +202,7 @@ async function removeMedia(req: Request) {
201
202
  const idList = ids.split(',');
202
203
  const assets = await readMediaAssets();
203
204
  const selectedAssets = assets.filter((asset) => idList.includes(asset.id));
204
- if (selectedAssets.some(isMediaProcessing)) {
205
+ if (!force && selectedAssets.some(isMediaProcessing)) {
205
206
  return NextResponse.json(
206
207
  { error: 'Wait for all selected media indexing jobs to finish before deleting them.' },
207
208
  { status: 409 },
@@ -50,14 +50,29 @@ export async function GET(req: Request) {
50
50
 
51
51
  let interval: ReturnType<typeof setInterval> | undefined;
52
52
  let keepAlive: ReturnType<typeof setInterval> | undefined;
53
+ let closed = false;
54
+
53
55
  const stream = new ReadableStream<Uint8Array>({
54
56
  async start(controller) {
55
57
  let lastPayload = '';
56
58
  let checking = false;
57
59
  let storageSnapshot = { usedBytes: 0, fileCount: 0 };
58
60
  let storageCheckedAt = 0;
61
+
62
+ const safeEnqueue = (data: Uint8Array) => {
63
+ if (closed) return;
64
+ try {
65
+ controller.enqueue(data);
66
+ } catch {
67
+ // Controller already closed — client disconnected.
68
+ closed = true;
69
+ if (interval) clearInterval(interval);
70
+ if (keepAlive) clearInterval(keepAlive);
71
+ }
72
+ };
73
+
59
74
  const publish = async () => {
60
- if (checking) return;
75
+ if (checking || closed) return;
61
76
  checking = true;
62
77
  try {
63
78
  if (Date.now() - storageCheckedAt > 30_000) {
@@ -81,7 +96,7 @@ export async function GET(req: Request) {
81
96
  0,
82
97
  ...nextSnapshot.assets.map((asset) => asset.processingRevision ?? 0),
83
98
  );
84
- controller.enqueue(
99
+ safeEnqueue(
85
100
  encoder.encode(`id: ${revision}\nevent: media-update\ndata: ${payload}\n\n`),
86
101
  );
87
102
  }
@@ -94,9 +109,10 @@ export async function GET(req: Request) {
94
109
 
95
110
  await publish();
96
111
  interval = setInterval(() => void publish(), 1_000);
97
- keepAlive = setInterval(() => controller.enqueue(encoder.encode(': keep-alive\n\n')), 15_000);
112
+ keepAlive = setInterval(() => safeEnqueue(encoder.encode(': keep-alive\n\n')), 15_000);
98
113
  },
99
114
  cancel() {
115
+ closed = true;
100
116
  if (interval) clearInterval(interval);
101
117
  if (keepAlive) clearInterval(keepAlive);
102
118
  },
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, Suspense } from 'react';
3
+ import { useEffect, Suspense } from 'react';
4
4
  import { useSearchParams, useRouter, usePathname } from 'next/navigation';
5
5
  import { SettingsLayout, type SettingsSection } from '@/components/settings/settings-layout';
6
6
  import { GeneralSection } from '@/components/settings/general-section';
@@ -20,38 +20,29 @@ function SettingsContent() {
20
20
  const router = useRouter();
21
21
  const pathname = usePathname();
22
22
 
23
- const getInitialSection = (): SettingsSection => {
23
+ const resolveSection = (): SettingsSection => {
24
24
  if (searchParams.has('ai-models')) return 'models';
25
25
  const section = searchParams.get('section') as SettingsSection;
26
26
  if (section) return section;
27
27
  return 'general';
28
28
  };
29
29
 
30
- const [activeSection, setActiveSection] = useState<SettingsSection>(getInitialSection());
30
+ // Always derive the active section from the URL — this ensures navigating
31
+ // away and back never leaves the sidebar stuck on a stale section.
32
+ const activeSection = resolveSection();
31
33
 
32
34
  useEffect(() => {
33
- const currentSection = searchParams.get('section');
34
- const hasAiModels = searchParams.has('ai-models');
35
-
36
- if (hasAiModels) {
35
+ // Normalize legacy `?ai-models` param to `?section=models`
36
+ if (searchParams.has('ai-models')) {
37
37
  const params = new URLSearchParams(searchParams.toString());
38
38
  params.delete('ai-models');
39
39
  params.set('section', 'models');
40
40
  router.replace(`${pathname}?${params.toString()}`);
41
- if (activeSection !== 'models') {
42
- setActiveSection('models');
43
- }
44
- } else if (currentSection && currentSection !== activeSection) {
45
- setActiveSection(currentSection as SettingsSection);
46
41
  }
47
42
  }, [searchParams, pathname, router]);
48
43
 
49
44
  const handleSectionChange = (newSection: SettingsSection) => {
50
- setActiveSection(newSection);
51
- const params = new URLSearchParams(searchParams.toString());
52
- params.delete('ai-models');
53
- params.set('section', newSection);
54
- router.replace(`${pathname}?${params.toString()}`);
45
+ router.replace(`${pathname}?section=${newSection}`, { scroll: false });
55
46
  };
56
47
 
57
48
  return (
@@ -530,12 +530,19 @@ function ChatWorkspaceInner() {
530
530
  const config = configData?.config;
531
531
  if (!config) return;
532
532
 
533
- const isTavily = config.webSearchProvider === 'tavily' || !config.webSearchProvider;
534
- const hasKey = isTavily ? !!config.tavilyApiKey : !!config.serperApiKey; // Server uses its own or serper, fallback to check serper or just assume server doesn't need one if it's configured. Let's just check tavily if tavily.
533
+ const provider = config.webSearchProvider || 'tavily';
534
+ const apiKeys: Record<string, string | undefined> = {
535
+ tavily: config.tavilyApiKey,
536
+ serper: config.serperApiKey,
537
+ google: config.serperApiKey,
538
+ brave: config.braveApiKey,
539
+ bing: config.bingApiKey,
540
+ exa: config.exaApiKey,
541
+ };
535
542
 
536
- if (isTavily && !config.tavilyApiKey && !config.serperApiKey) {
543
+ if (provider !== 'local' && !apiKeys[provider]) {
537
544
  toast.error('Web Search API key is missing. Please configure it in Settings.');
538
- router.push('/settings');
545
+ router.push('/settings?section=general');
539
546
  return;
540
547
  }
541
548
 
@@ -382,7 +382,7 @@ function renderToolPart(
382
382
  <div key={index} className="mb-2 w-full">
383
383
  <div className="inline-flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground bg-muted/30 rounded-md border border-border/50">
384
384
  <Globe className="h-3.5 w-3.5 shrink-0" />
385
- <span className="truncate max-w-[250px] sm:max-w-[400px]">
385
+ <span className="truncate max-w-62.5 sm:max-w-100">
386
386
  Searched web for "{input?.query}"
387
387
  </span>
388
388
  <span className="shrink-0 text-[10px] bg-secondary text-foreground px-1.5 py-0.5 rounded-full font-medium ml-1">
@@ -395,12 +395,24 @@ function renderToolPart(
395
395
 
396
396
  case 'analyzeImageDeeply': {
397
397
  if (output.error) return null;
398
+ const imageUrl = input?.imageUrl as string | undefined;
398
399
  return (
399
400
  <div key={index} className="mb-2 w-full">
400
401
  <div className="inline-flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground bg-muted/30 rounded-md border border-border/50">
401
402
  <Sparkles className="h-3.5 w-3.5 shrink-0" />
402
- <span className="truncate max-w-[250px] sm:max-w-[400px]">Analyzed image</span>
403
+ <span className="truncate max-w-62.5 sm:max-w-100">Analyzed image</span>
403
404
  </div>
405
+ {imageUrl ? (
406
+ <div className="mt-1.5 max-w-xs">
407
+ {/* eslint-disable-next-line @next/next/no-img-element */}
408
+ <img
409
+ src={imageUrl}
410
+ alt="Analyzed image"
411
+ className="rounded-lg border border-border/50 object-cover max-h-48 w-auto"
412
+ loading="lazy"
413
+ />
414
+ </div>
415
+ ) : null}
404
416
  </div>
405
417
  );
406
418
  }
@@ -433,7 +445,7 @@ function renderToolPart(
433
445
  <div key={index} className="mb-2 w-full">
434
446
  <div className="inline-flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground bg-muted/30 rounded-md border border-border/50">
435
447
  <CheckCircle2 className="h-3.5 w-3.5 shrink-0" />
436
- <span className="truncate max-w-[250px] sm:max-w-[400px]">Used {toolName}</span>
448
+ <span className="truncate max-w-62.5 sm:max-w-100">Used {toolName}</span>
437
449
  </div>
438
450
  </div>
439
451
  );
@@ -714,7 +726,7 @@ export function MessageItem({
714
726
  <div className="size-7 shrink-0 rounded-md overflow-hidden bg-background border border-border/90 flex items-center justify-center p-1">
715
727
  <img src={iconPath} alt={nameStr} className="w-full h-full object-contain" />
716
728
  </div>
717
- <div className="flex flex-col justify-center max-w-[150px]">
729
+ <div className="flex flex-col justify-center max-w-37.5">
718
730
  <span className="text-xs font-medium truncate text-foreground">
719
731
  {nameStr}
720
732
  </span>
@@ -932,10 +944,10 @@ function renderMarkdown(text: string): string {
932
944
  html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, url) => {
933
945
  const lowerUrl = url.toLowerCase();
934
946
  if (lowerUrl.match(/\.(mp4|webm|ogg)$/) || lowerUrl.includes('video')) {
935
- return `<video src="${url}" controls preload="metadata" class="max-w-[360px] w-full rounded-lg my-2 border border-border bg-card"></video>`;
947
+ return `<video src="${url}" controls preload="metadata" class="max-w-90 w-full rounded-lg my-2 border border-border bg-card"></video>`;
936
948
  }
937
949
  if (lowerUrl.match(/\.(mp3|wav|m4a|oga)$/) || lowerUrl.includes('audio')) {
938
- return `<audio src="${url}" controls preload="metadata" class="max-w-[320px] w-full my-2"></audio>`;
950
+ return `<audio src="${url}" controls preload="metadata" class="max-w-80 w-full my-2"></audio>`;
939
951
  }
940
952
  return `<a href="${url}" target="_blank" rel="noreferrer" class="msg-link">${label}</a>`;
941
953
  });
@@ -73,7 +73,7 @@ export function KnowledgeBaseResult({
73
73
  ) : (
74
74
  <Database className="h-3.5 w-3.5 shrink-0" />
75
75
  )}
76
- <span className="truncate max-w-[250px] sm:max-w-[400px]">
76
+ <span className="truncate max-w-62.5 sm:max-w-100">
77
77
  {isRunning ? 'Checking knowledge base…' : 'Checked knowledge base'}
78
78
  </span>
79
79
  {!isRunning && hits.length > 0 ? (
@@ -92,9 +92,8 @@ export function KnowledgeBaseResult({
92
92
  {open && (isRunning || hits.length > 0) ? (
93
93
  <div className="mt-2 flex flex-col gap-px border border-border rounded-xl bg-transparent overflow-hidden">
94
94
  {isRunning && hits.length === 0 ? (
95
- <div className="flex items-center gap-2 px-3.5 py-3 text-xs text-muted-foreground">
96
- <Loader2 className="size-3.5 animate-spin" />
97
- Looking through your Knowledge Base…
95
+ <div className="flex items-center gap-2 px-3.5 py-2.5">
96
+ <Loader2 className="size-3 animate-spin text-muted-foreground" />
98
97
  </div>
99
98
  ) : null}
100
99
  {hits.slice(0, 3).map((h, i) => (
@@ -172,11 +172,15 @@ export function DataWorkspace() {
172
172
  );
173
173
 
174
174
  if (justCompleted.length > 0) {
175
- toast.success('Indexing completed.');
175
+ toast.success('Scraping completed.');
176
+ // Refresh the corpus immediately so new documents appear.
177
+ mutateDocuments();
178
+ // Documents may still be flushing — re-fetch after a short delay.
179
+ setTimeout(() => mutateDocuments(), 2500);
176
180
  }
177
181
  }
178
182
  prevJobsRef.current = jobs;
179
- }, [jobs]);
183
+ }, [jobs, mutateDocuments]);
180
184
 
181
185
  const docsQuery = useSWR<DocsResponse>('/api/documents', fetcher, {
182
186
  refreshInterval: hasActive ? 5000 : 0,
@@ -119,6 +119,27 @@ export function JobsPanel({ jobs, onChanged }: { jobs: CrawlJob[]; onChanged: ()
119
119
  setSelected(next);
120
120
  }
121
121
 
122
+ async function copyFailedUrls(urls: string) {
123
+ try {
124
+ if (navigator.clipboard?.writeText) {
125
+ await navigator.clipboard.writeText(urls);
126
+ } else {
127
+ const textarea = document.createElement('textarea');
128
+ textarea.value = urls;
129
+ textarea.style.position = 'fixed';
130
+ textarea.style.opacity = '0';
131
+ document.body.appendChild(textarea);
132
+ textarea.select();
133
+ const copied = document.execCommand('copy');
134
+ textarea.remove();
135
+ if (!copied) throw new Error('The browser denied clipboard access.');
136
+ }
137
+ toast.success('Copied failed URLs to clipboard');
138
+ } catch {
139
+ toast.error('Could not copy URLs. Select them manually and try again.');
140
+ }
141
+ }
142
+
122
143
  async function executeCancel(id: string) {
123
144
  setCancelling(true);
124
145
  try {
@@ -316,8 +337,7 @@ export function JobsPanel({ jobs, onChanged }: { jobs: CrawlJob[]; onChanged: ()
316
337
  .filter((t) => t.status === 'failed')
317
338
  .map((t) => t.url)
318
339
  .join('\n');
319
- navigator.clipboard.writeText(failedUrls);
320
- toast.success('Copied failed URLs to clipboard');
340
+ void copyFailedUrls(failedUrls);
321
341
  }}
322
342
  className="rounded p-1 text-muted-foreground hover:text-foreground transition-colors"
323
343
  title="Copy failed URLs"