larkup 0.1.29 → 0.1.31

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 (229) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +5 -5
  3. package/.next/diagnostics/route-bundle-stats.json +35 -35
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/next-minimal-server.js.nft.json +1 -1
  6. package/.next/next-server.js.nft.json +1 -1
  7. package/.next/prerender-manifest.json +3 -3
  8. package/.next/required-server-files.js +4 -4
  9. package/.next/required-server-files.json +4 -4
  10. package/.next/server/app/_global-error/page/build-manifest.json +2 -2
  11. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  12. package/.next/server/app/_global-error.html +1 -1
  13. package/.next/server/app/_global-error.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/server/app/_not-found/page/build-manifest.json +2 -2
  20. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/_not-found.html +1 -1
  23. package/.next/server/app/_not-found.rsc +16 -16
  24. package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  25. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  27. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  30. package/.next/server/app/analytics/page/build-manifest.json +2 -2
  31. package/.next/server/app/analytics/page.js.nft.json +1 -1
  32. package/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
  33. package/.next/server/app/analytics.html +1 -1
  34. package/.next/server/app/analytics.rsc +17 -17
  35. package/.next/server/app/analytics.segments/_full.segment.rsc +17 -17
  36. package/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
  37. package/.next/server/app/analytics.segments/_index.segment.rsc +11 -11
  38. package/.next/server/app/analytics.segments/_tree.segment.rsc +1 -1
  39. package/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
  40. package/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
  41. package/.next/server/app/api/analytics/route.js.nft.json +1 -1
  42. package/.next/server/app/api/chat/route.js.nft.json +1 -1
  43. package/.next/server/app/api/chat/status/route.js.nft.json +1 -1
  44. package/.next/server/app/api/chat/suggestions/route.js.nft.json +1 -1
  45. package/.next/server/app/api/config/route.js.nft.json +1 -1
  46. package/.next/server/app/api/config/test/route.js.nft.json +1 -1
  47. package/.next/server/app/api/config/test-embedding/route.js.nft.json +1 -1
  48. package/.next/server/app/api/config/test-llm/route.js.nft.json +1 -1
  49. package/.next/server/app/api/config/test-provider/route.js +4 -4
  50. package/.next/server/app/api/config/test-provider/route.js.nft.json +1 -1
  51. package/.next/server/app/api/config/verify/route.js +3 -3
  52. package/.next/server/app/api/config/verify/route.js.nft.json +1 -1
  53. package/.next/server/app/api/connections/route.js.nft.json +1 -1
  54. package/.next/server/app/api/connections/webhook/route.js.nft.json +1 -1
  55. package/.next/server/app/api/demo/route.js.nft.json +1 -1
  56. package/.next/server/app/api/deploy/cloud-project/route.js.nft.json +1 -1
  57. package/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
  58. package/.next/server/app/api/deployment/blob-setup/route.js.nft.json +1 -1
  59. package/.next/server/app/api/deployment/config/route.js.nft.json +1 -1
  60. package/.next/server/app/api/describe-image/route.js.nft.json +1 -1
  61. package/.next/server/app/api/doc-editor/edit/route.js.nft.json +1 -1
  62. package/.next/server/app/api/doc-editor/export/route.js.nft.json +1 -1
  63. package/.next/server/app/api/doc-editor/extract-text/route.js.nft.json +1 -1
  64. package/.next/server/app/api/doc-editor/parse/route.js.nft.json +1 -1
  65. package/.next/server/app/api/doc-editor/restore/route.js.nft.json +1 -1
  66. package/.next/server/app/api/document/sign/route.js.nft.json +1 -1
  67. package/.next/server/app/api/documents/route.js.nft.json +1 -1
  68. package/.next/server/app/api/firecrawl/local/route.js.nft.json +1 -1
  69. package/.next/server/app/api/index/route.js.nft.json +1 -1
  70. package/.next/server/app/api/integrations/notion/callback/route.js.nft.json +1 -1
  71. package/.next/server/app/api/integrations/notion/route.js.nft.json +1 -1
  72. package/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -1
  73. package/.next/server/app/api/jobs/route.js.nft.json +1 -1
  74. package/.next/server/app/api/marketplace/[toolId]/route.js.nft.json +1 -1
  75. package/.next/server/app/api/marketplace/route.js.nft.json +1 -1
  76. package/.next/server/app/api/media/[id]/route.js.nft.json +1 -1
  77. package/.next/server/app/api/media/process/route.js.nft.json +1 -1
  78. package/.next/server/app/api/media/route.js.nft.json +1 -1
  79. package/.next/server/app/api/media/stream/route.js.nft.json +1 -1
  80. package/.next/server/app/api/models/route.js.nft.json +1 -1
  81. package/.next/server/app/api/parse-file/route.js.nft.json +1 -1
  82. package/.next/server/app/api/proxy/verify/route.js.nft.json +1 -1
  83. package/.next/server/app/api/sandbox/route.js.nft.json +1 -1
  84. package/.next/server/app/api/search/bing/route.js.nft.json +1 -1
  85. package/.next/server/app/api/search/brave/route.js.nft.json +1 -1
  86. package/.next/server/app/api/search/exa/route.js.nft.json +1 -1
  87. package/.next/server/app/api/search/google/route.js.nft.json +1 -1
  88. package/.next/server/app/api/search/route.js.nft.json +1 -1
  89. package/.next/server/app/api/search/tavily/route.js.nft.json +1 -1
  90. package/.next/server/app/api/search/verify/route.js.nft.json +1 -1
  91. package/.next/server/app/api/server/agent/route.js.nft.json +1 -1
  92. package/.next/server/app/api/server/download/route.js.nft.json +1 -1
  93. package/.next/server/app/api/server/generate/route.js.nft.json +1 -1
  94. package/.next/server/app/api/server/local/route.js.nft.json +1 -1
  95. package/.next/server/app/api/servers/route.js.nft.json +1 -1
  96. package/.next/server/app/api/tabular/query/route.js.nft.json +1 -1
  97. package/.next/server/app/api/tabular/route.js.nft.json +1 -1
  98. package/.next/server/app/api/upload-file/route.js.nft.json +1 -1
  99. package/.next/server/app/api/uploads/[...slug]/route.js.nft.json +1 -1
  100. package/.next/server/app/api/vector-stores/install/route.js.nft.json +1 -1
  101. package/.next/server/app/api/vector-stores/status/route.js.nft.json +1 -1
  102. package/.next/server/app/chat/page/build-manifest.json +2 -2
  103. package/.next/server/app/chat/page.js.nft.json +1 -1
  104. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/chat.html +1 -1
  106. package/.next/server/app/chat.rsc +17 -17
  107. package/.next/server/app/chat.segments/_full.segment.rsc +17 -17
  108. package/.next/server/app/chat.segments/_head.segment.rsc +4 -4
  109. package/.next/server/app/chat.segments/_index.segment.rsc +11 -11
  110. package/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
  111. package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
  112. package/.next/server/app/chat.segments/chat.segment.rsc +3 -3
  113. package/.next/server/app/data/page/build-manifest.json +2 -2
  114. package/.next/server/app/data/page.js.nft.json +1 -1
  115. package/.next/server/app/data/page_client-reference-manifest.js +1 -1
  116. package/.next/server/app/data.html +1 -1
  117. package/.next/server/app/data.rsc +17 -17
  118. package/.next/server/app/data.segments/_full.segment.rsc +17 -17
  119. package/.next/server/app/data.segments/_head.segment.rsc +4 -4
  120. package/.next/server/app/data.segments/_index.segment.rsc +11 -11
  121. package/.next/server/app/data.segments/_tree.segment.rsc +1 -1
  122. package/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
  123. package/.next/server/app/data.segments/data.segment.rsc +3 -3
  124. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  125. package/.next/server/app/index.html +1 -1
  126. package/.next/server/app/index.rsc +16 -16
  127. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  128. package/.next/server/app/index.segments/_full.segment.rsc +16 -16
  129. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  130. package/.next/server/app/index.segments/_index.segment.rsc +11 -11
  131. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  132. package/.next/server/app/page/build-manifest.json +2 -2
  133. package/.next/server/app/page.js.nft.json +1 -1
  134. package/.next/server/app/page_client-reference-manifest.js +1 -1
  135. package/.next/server/app/settings/page/build-manifest.json +2 -2
  136. package/.next/server/app/settings/page/server-reference-manifest.json +3 -3
  137. package/.next/server/app/settings/page.js.nft.json +1 -1
  138. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/settings.html +1 -1
  140. package/.next/server/app/settings.rsc +18 -18
  141. package/.next/server/app/settings.segments/_full.segment.rsc +18 -18
  142. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  143. package/.next/server/app/settings.segments/_index.segment.rsc +11 -11
  144. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  145. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  146. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  147. package/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_0kbcuk-.js +17 -3
  148. package/.next/server/chunks/0598_next_dist_esm_build_templates_app-route_1f4h7rr.js +1 -1
  149. package/.next/server/chunks/[root-of-the-server]__01al2n0._.js +1 -1
  150. package/.next/server/chunks/[root-of-the-server]__067hiof._.js +18 -6
  151. package/.next/server/chunks/[root-of-the-server]__0a_0ywc._.js +18 -6
  152. package/.next/server/chunks/[root-of-the-server]__0u17zp8._.js +18 -6
  153. package/.next/server/chunks/{[root-of-the-server]__1xuhh_i._.js → [root-of-the-server]__16jk_se._.js} +2 -2
  154. package/.next/server/chunks/[root-of-the-server]__1ahdfq9._.js +18 -6
  155. package/.next/server/chunks/[root-of-the-server]__1o3rcbz._.js +2 -1
  156. package/.next/server/chunks/{[root-of-the-server]__10gcb7d._.js → [root-of-the-server]__1qh2h5v._.js} +2 -2
  157. package/.next/server/chunks/[root-of-the-server]__1s9is1o._.js +2 -1
  158. package/.next/server/chunks/[root-of-the-server]__1vsvck0._.js +18 -6
  159. package/.next/server/chunks/[root-of-the-server]__1wy6wes._.js +1 -1
  160. package/.next/server/chunks/[root-of-the-server]__1zptg4z._.js +2 -1
  161. package/.next/server/chunks/packages_core_src_generator_generate-server_ts_11-w3m3._.js +2 -2
  162. package/.next/server/chunks/packages_core_src_generator_server-runtime_ts_1_68l7t._.js +4 -4
  163. package/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
  164. package/.next/server/chunks/ssr/{[root-of-the-server]__0wdatpx._.js → [root-of-the-server]__0m21fli._.js} +1 -1
  165. package/.next/server/chunks/ssr/{[root-of-the-server]__1r9ap67._.js → [root-of-the-server]__0t8ud_u._.js} +1 -1
  166. package/.next/server/chunks/ssr/{[root-of-the-server]__046ldu_._.js → [root-of-the-server]__0xyo3ni._.js} +1 -1
  167. package/.next/server/chunks/ssr/{_0k81--x._.js → _0ckxy33._.js} +2 -2
  168. package/.next/server/chunks/ssr/_169pdlf._.js +6 -6
  169. package/.next/server/chunks/ssr/_1hetwwu._.js +4 -3
  170. package/.next/server/chunks/ssr/_1iff9ia._.js +1 -1
  171. package/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
  172. package/.next/server/chunks/ssr/apps_web_components_analytics_analytics-dashboard_tsx_1056-z3._.js +1 -1
  173. package/.next/server/chunks/ssr/packages_core_src_generator_generate-server_ts_15zzrn2._.js +2 -2
  174. package/.next/server/functions-config-manifest.json +3 -1
  175. package/.next/server/middleware-build-manifest.js +5 -5
  176. package/.next/server/pages/404.html +1 -1
  177. package/.next/server/pages/500.html +1 -1
  178. package/.next/server/server-reference-manifest.js +1 -1
  179. package/.next/server/server-reference-manifest.json +4 -4
  180. package/.next/static/chunks/{1sh7d5tt49x8b.js → 0ff5osu771ajz.js} +6 -6
  181. package/.next/static/chunks/{1z4nte07k3dt6.js → 1-afr-snks55m.js} +1 -1
  182. package/.next/static/chunks/{1dykihoo_1ae5.js → 1fz_0-qb5r5wd.js} +1 -1
  183. package/.next/static/chunks/{3g_wl1fri6jb-.js → 21_6-9hjj2i68.js} +1 -1
  184. package/.next/static/chunks/{13kyiy7q3of41.js → 2jqjx4-rh414_.js} +1 -1
  185. package/.next/static/chunks/2jrpgzxyrmuqp.js +121 -0
  186. package/.next/static/chunks/390ds7l_plpmd.js +1 -0
  187. package/.next/static/chunks/{3een1tiktxr6a.js → 3zw6tgf7mxo39.js} +1 -1
  188. package/.next/static/chunks/{turbopack-2qt7vn_ocj4ao.js → turbopack-2gu-fmc6o5wkn.js} +1 -1
  189. package/CHANGELOG.md +33 -0
  190. package/app/api/chat/route.ts +14 -1
  191. package/app/api/chat/tools.ts +7 -3
  192. package/app/api/config/test-provider/route.ts +16 -18
  193. package/app/api/config/verify/route.ts +5 -7
  194. package/app/api/firecrawl/local/route.ts +7 -4
  195. package/app/api/jobs/route.ts +1 -2
  196. package/app/api/marketplace/[toolId]/route.ts +1 -0
  197. package/archive/redesign-v2/app/configure/page.tsx +53 -0
  198. package/archive/redesign-v2/app/demo/page.tsx +15 -0
  199. package/archive/redesign-v2/app/server/page.tsx +22 -0
  200. package/archive/redesign-v2/app/simple/chat/page.tsx +17 -0
  201. package/archive/redesign-v2/app/simple/docs/page.tsx +21 -0
  202. package/archive/redesign-v2/app/simple/settings/page.tsx +1104 -0
  203. package/archive/redesign-v2/app-routes/configure/page.tsx +53 -0
  204. package/archive/redesign-v2/app-routes/demo/page.tsx +15 -0
  205. package/archive/redesign-v2/app-routes/server/page.tsx +22 -0
  206. package/archive/redesign-v2/app-routes/simple/chat/page.tsx +17 -0
  207. package/archive/redesign-v2/app-routes/simple/docs/page.tsx +21 -0
  208. package/archive/redesign-v2/app-routes/simple/layout.tsx +9 -0
  209. package/archive/redesign-v2/app-routes/simple/settings/page.tsx +1104 -0
  210. package/archive/redesign-v2/components/app-sidebar.tsx +188 -0
  211. package/archive/redesign-v2/components/app-topnav.tsx +195 -0
  212. package/archive/redesign-v2/components/global-settings.tsx +288 -0
  213. package/archive/redesign-v2/components/simple/simple-sidebar.tsx +171 -0
  214. package/archive/redesign-v2/components/simple/simple-topnav.tsx +175 -0
  215. package/components/analytics/analytics-dashboard.tsx +1 -1
  216. package/components/data/firecrawl-notice.tsx +41 -98
  217. package/components/data/media-panel.tsx +0 -3
  218. package/components/data/scrape-panel.tsx +17 -1
  219. package/components/onboarding/welcome-screen.tsx +45 -15
  220. package/components/settings/general-section.tsx +15 -10
  221. package/components/settings/server-section.tsx +3 -3
  222. package/lib/error-formatter.ts +19 -4
  223. package/next-env.d.ts +1 -1
  224. package/package.json +7 -7
  225. package/.next/static/chunks/01qmngac69n9j.js +0 -1
  226. package/.next/static/chunks/102b8vinedggz.js +0 -120
  227. /package/.next/static/{W6QdudzTPo_OVPpNt29f7 → kaW1RhJBmrNB5F_JAJ6QO}/_buildManifest.js +0 -0
  228. /package/.next/static/{W6QdudzTPo_OVPpNt29f7 → kaW1RhJBmrNB5F_JAJ6QO}/_clientMiddlewareManifest.js +0 -0
  229. /package/.next/static/{W6QdudzTPo_OVPpNt29f7 → kaW1RhJBmrNB5F_JAJ6QO}/_ssgManifest.js +0 -0
@@ -0,0 +1,188 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import {
6
+ FileStack,
7
+ FlaskConical,
8
+ Lock,
9
+ type LucideIcon,
10
+ MessageCircle,
11
+ Server,
12
+ SlidersHorizontal,
13
+ } from "lucide-react";
14
+ import { cn } from "@/lib/utils";
15
+ import { STAGES, CURRENT_PHASE } from "@larkup/core/stages";
16
+ import type { StageId } from "@larkup/core/types";
17
+ import {
18
+ Tooltip,
19
+ TooltipContent,
20
+ TooltipProvider,
21
+ TooltipTrigger,
22
+ } from "@/components/ui/tooltip";
23
+ import {
24
+ useThemeCustomizer,
25
+ type PanelBgVariant,
26
+ } from "./theme-customizer-provider";
27
+ import { ThemeSwitcher } from "./theme-switcher";
28
+
29
+ const NAV_BG_COLORS: Record<PanelBgVariant, string | undefined> = {
30
+ "panel-default": undefined,
31
+ "panel-white": "#FFFFFF",
32
+ "panel-fafafa": "#F4F4F4",
33
+ "panel-warm": "#F7F1EA",
34
+ "panel-soft": "#FBFAF8",
35
+ "panel-silver": "#F8F8F8",
36
+ "panel-stone": "#F5F5F2",
37
+ };
38
+
39
+ // [#CDA1FE]
40
+ const STAGE_ICONS: Record<StageId, LucideIcon> = {
41
+ configure: SlidersHorizontal,
42
+ data: FileStack,
43
+ server: Server,
44
+ demo: FlaskConical,
45
+ chat: MessageCircle,
46
+ };
47
+
48
+ export function AppSidebar() {
49
+ const pathname = usePathname();
50
+ const { pageStyle, navBg } = useThemeCustomizer();
51
+
52
+ const navColor = navBg ? NAV_BG_COLORS[navBg] : undefined;
53
+ const navStyle = navColor ? { backgroundColor: navColor } : undefined;
54
+
55
+ return (
56
+ <TooltipProvider delay={150}>
57
+ <aside
58
+ className={cn(
59
+ "sticky top-0 hidden h-screen w-[84px] shrink-0 flex-col items-center gap-1 self-start py-3 md:flex",
60
+ !navColor ? "bg-background" : "",
61
+ pageStyle === "fused" ? "border-r border-border" : "",
62
+ )}
63
+ style={navStyle}
64
+ >
65
+ {/* Brand */}
66
+ <Tooltip>
67
+ <TooltipTrigger
68
+ render={
69
+ <Link
70
+ href="/configure"
71
+ aria-label="larkup home"
72
+ className="mb-2 flex size-11 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm transition-transform hover:scale-105"
73
+ />
74
+ }
75
+ >
76
+ {/* <Boxes className="size-5" /> */}
77
+ <img src={"/logo9.png"} className="size-6.5" alt="logo" />
78
+ </TooltipTrigger>
79
+ <TooltipContent side="right" sideOffset={10}>
80
+ Larkup
81
+ </TooltipContent>
82
+ </Tooltip>
83
+
84
+ {/* Pipeline rail */}
85
+ <nav className="flex flex-1 flex-col items-center gap-4.5 ">
86
+ {STAGES.map((stage) => {
87
+ const Icon = STAGE_ICONS[stage.id];
88
+ const active =
89
+ pathname === stage.href ||
90
+ (stage.href === "/configure" && pathname === "/");
91
+ const locked = stage.phase > CURRENT_PHASE;
92
+
93
+ const body = (
94
+ <>
95
+ {/* active accent bar (Teams-style) */}
96
+ <span
97
+ className={cn(
98
+ "absolute left-0 top-1/2 h-6 w-1 -translate-y-1/2 rounded-r-full bg-primary transition-opacity",
99
+ active ? "opacity-100" : "opacity-0",
100
+ )}
101
+ />
102
+ <span
103
+ className={cn(
104
+ "relative flex size-9 items-center justify-center rounded-xl transition-colors",
105
+ active
106
+ ? "bg-white border text-primary"
107
+ : locked
108
+ ? "text-muted-foreground/50"
109
+ : "text-foreground/70 group-hover:bg-white/90 group-hover:text-foreground",
110
+ )}
111
+ >
112
+ <Icon
113
+ className="size-[19px]"
114
+ strokeWidth={active ? 2.25 : 2}
115
+ />
116
+ {locked && (
117
+ <span className="absolute -right-0.5 -top-0.5 flex size-3.5 items-center justify-center rounded-full bg-background">
118
+ <Lock className="size-2.5 text-muted-foreground" />
119
+ </span>
120
+ )}
121
+ </span>
122
+ <span
123
+ className={cn(
124
+ "text-[10px] leading-none tracking-tight",
125
+ active
126
+ ? "font-semibold text-primary"
127
+ : locked
128
+ ? "text-muted-foreground/50"
129
+ : "text-muted-foreground",
130
+ )}
131
+ >
132
+ {stage.label}
133
+ </span>
134
+ </>
135
+ );
136
+
137
+ return (
138
+ <Tooltip key={stage.id}>
139
+ <TooltipTrigger
140
+ render={
141
+ locked ? (
142
+ <div
143
+ aria-disabled
144
+ className="group relative flex w-16 cursor-not-allowed flex-col items-center gap-0.5 rounded-2xl py-1"
145
+ />
146
+ ) : (
147
+ <Link
148
+ href={stage.href}
149
+ aria-current={active ? "page" : undefined}
150
+ className="group relative flex w-16 flex-col items-center gap-0.5 rounded-2xl py-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
151
+ />
152
+ )
153
+ }
154
+ >
155
+ {body}
156
+ </TooltipTrigger>
157
+ <TooltipContent
158
+ side="right"
159
+ sideOffset={10}
160
+ className="max-w-56"
161
+ >
162
+ <div className="flex flex-col gap-0.5">
163
+ <span className="font-medium">
164
+ {stage.label}
165
+ {locked && (
166
+ <span className="ml-1.5 font-mono text-[10px] opacity-70">
167
+ Phase {stage.phase}
168
+ </span>
169
+ )}
170
+ </span>
171
+ <span className="text-[11px] leading-snug opacity-80">
172
+ {locked
173
+ ? `Unlocks in Phase ${stage.phase}.`
174
+ : stage.description}
175
+ </span>
176
+ </div>
177
+ </TooltipContent>
178
+ </Tooltip>
179
+ );
180
+ })}
181
+ </nav>
182
+
183
+ {/* Footer — dual-mode indicator */}
184
+ <ThemeSwitcher floating={false} />
185
+ </aside>
186
+ </TooltipProvider>
187
+ );
188
+ }
@@ -0,0 +1,195 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import {
6
+ FileStack,
7
+ FlaskConical,
8
+ Layers,
9
+ Lock,
10
+ type LucideIcon,
11
+ MessageCircle,
12
+ Server,
13
+ SlidersHorizontal,
14
+ } from "lucide-react";
15
+ import { cn } from "@/lib/utils";
16
+ import { STAGES, CURRENT_PHASE } from "@larkup/core/stages";
17
+ import type { StageId } from "@larkup/core/types";
18
+ import {
19
+ Tooltip,
20
+ TooltipContent,
21
+ TooltipProvider,
22
+ TooltipTrigger,
23
+ } from "@/components/ui/tooltip";
24
+ import { ServerSwitcher } from "@/components/workspace/server-switcher";
25
+ import { useWorkspace } from "@/components/workspace/workspace-provider";
26
+ import { ThemeSwitcher } from "./theme-switcher";
27
+ import { useThemeCustomizer, type PanelBgVariant } from "./theme-customizer-provider";
28
+
29
+ const NAV_BG_COLORS: Record<PanelBgVariant, string | undefined> = {
30
+ "panel-default": undefined,
31
+ "panel-white": "#FFFFFF",
32
+ "panel-fafafa": "#F4F4F4",
33
+ "panel-warm": "#F7F1EA",
34
+ "panel-soft": "#FBFAF8",
35
+ "panel-silver": "#F8F8F8",
36
+ "panel-stone": "#F5F5F2",
37
+ };
38
+
39
+ const STAGE_ICONS: Record<StageId, LucideIcon> = {
40
+ configure: SlidersHorizontal,
41
+ data: FileStack,
42
+ index: Layers,
43
+ server: Server,
44
+ demo: FlaskConical,
45
+ chat: MessageCircle,
46
+ };
47
+
48
+ export function AppTopNav() {
49
+ const pathname = usePathname();
50
+ const { username } = useWorkspace();
51
+ const { navBg } = useThemeCustomizer();
52
+
53
+ const navColor = navBg ? NAV_BG_COLORS[navBg] : undefined;
54
+ const navStyle = navColor ? { backgroundColor: navColor } : undefined;
55
+
56
+ return (
57
+ <TooltipProvider delay={150}>
58
+ <header
59
+ className={cn(
60
+ "sticky top-0 z-50 flex h-16 shrink-0 items-center gap-4 border-b border-border px-4 md:px-6",
61
+ !navColor ? "bg-white" : "",
62
+ )}
63
+ style={navStyle}
64
+ >
65
+ {/* Brand */}
66
+ <Link
67
+ href="/configure"
68
+ aria-label="larkup home"
69
+ className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm transition-transform hover:scale-105"
70
+ >
71
+ <img src={"/logo9.png"} className="size-5" alt="logo" />
72
+ </Link>
73
+ <span className="font-semibold tracking-tight hidden sm:inline-block">
74
+ Larkup
75
+ </span>
76
+
77
+ {/* Pipeline rail */}
78
+ <nav className="flex flex-1 h-full items-end pb-0 gap-2 md:gap-4 ml-4">
79
+ {STAGES.map((stage, index) => {
80
+ const Icon = STAGE_ICONS[stage.id];
81
+ const active =
82
+ pathname === stage.href ||
83
+ (stage.href === "/configure" && pathname === "/");
84
+ const locked = stage.phase > CURRENT_PHASE;
85
+
86
+ const body = (
87
+ <div className="flex items-center ">
88
+ <span
89
+ className={cn(
90
+ "flex size-9 items-center justify-center rounded-lg transition-colors",
91
+ active
92
+ ? "text-primary"
93
+ : locked
94
+ ? "text-muted-foreground/50"
95
+ : "text-muted-foreground group-hover:text-foreground",
96
+ )}
97
+ >
98
+ <Icon
99
+ className="size-[18px]"
100
+ strokeWidth={active ? 2.25 : 2}
101
+ />
102
+ {locked && (
103
+ <span className="absolute -right-0.5 -top-0.5 flex size-3 items-center justify-center rounded-full bg-background">
104
+ <Lock className="size-2 text-muted-foreground" />
105
+ </span>
106
+ )}
107
+ </span>
108
+ <span
109
+ className={cn(
110
+ "hidden md:block text-[13px] tracking-tight",
111
+ active
112
+ ? "font-semibold text-primary"
113
+ : locked
114
+ ? "text-muted-foreground/50"
115
+ : "text-muted-foreground group-hover:text-foreground",
116
+ )}
117
+ >
118
+ {index + 1}. {stage.label}
119
+ </span>
120
+ {/* active accent bar (bottom) */}
121
+ <span
122
+ className={cn(
123
+ "absolute bottom-0 left-0 h-0.5 w-full bg-primary transition-opacity",
124
+ active
125
+ ? "opacity-100"
126
+ : "opacity-0 group-hover:opacity-100",
127
+ )}
128
+ />
129
+ </div>
130
+ );
131
+
132
+ return (
133
+ <Tooltip key={stage.id}>
134
+ <TooltipTrigger
135
+ render={
136
+ locked ? (
137
+ <div
138
+ aria-disabled
139
+ className="group relative flex cursor-not-allowed items-center gap-2 h-10 px-3 rounded-md transition-colors"
140
+ />
141
+ ) : (
142
+ <Link
143
+ href={stage.href}
144
+ aria-current={active ? "page" : undefined}
145
+ className={cn(
146
+ "group relative flex items-center gap-2 h-10 px-3 rounded-md transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
147
+ active ? "bg-muted/0" : "hover:bg-muted/30",
148
+ )}
149
+ />
150
+ )
151
+ }
152
+ >
153
+ {body}
154
+ </TooltipTrigger>
155
+ <TooltipContent
156
+ side="bottom"
157
+ sideOffset={10}
158
+ className="max-w-56"
159
+ >
160
+ <div className="flex flex-col gap-0.5">
161
+ <span className="font-medium">
162
+ {index + 1}. {stage.label}
163
+ {locked && (
164
+ <span className="ml-1.5 font-mono text-[10px] opacity-70">
165
+ Phase {stage.phase}
166
+ </span>
167
+ )}
168
+ </span>
169
+ <span className="text-[11px] leading-snug opacity-80">
170
+ {locked
171
+ ? `Unlocks in Phase ${stage.phase}.`
172
+ : stage.description}
173
+ </span>
174
+ </div>
175
+ </TooltipContent>
176
+ </Tooltip>
177
+ );
178
+ })}
179
+ </nav>
180
+
181
+ {/* Right side: Workspace & Theme Customizer */}
182
+ <div className="flex items-center gap-3">
183
+ <ServerSwitcher />
184
+ {username && (
185
+ <span className="hidden text-sm text-muted-foreground md:block">
186
+ Hi,{" "}
187
+ <span className="font-medium text-foreground">{username}</span>
188
+ </span>
189
+ )}
190
+ <ThemeSwitcher floating={false} />{" "}
191
+ </div>
192
+ </header>
193
+ </TooltipProvider>
194
+ );
195
+ }
@@ -0,0 +1,288 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import useSWR from "swr";
5
+ import { Loader2, Save, ChevronLeft } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Input } from "@/components/ui/input";
8
+ import { Label } from "@/components/ui/label";
9
+ import { toast } from "sonner";
10
+ import { RagConfig } from "@larkup/core/types";
11
+ import {
12
+ Select,
13
+ SelectContent,
14
+ SelectItem,
15
+ SelectTrigger,
16
+ } from "@/components/ui/select";
17
+ import { useSearchParams, useRouter, usePathname } from "next/navigation";
18
+ import { useWorkspace } from "@/components/workspace/workspace-provider";
19
+
20
+ const fetcher = (url: string) =>
21
+ fetch(url).then((r) => r.json() as Promise<{ config: RagConfig }>);
22
+
23
+ export function GlobalSettings({ onBack }: { onBack: () => void }) {
24
+ const { data, isLoading, mutate } = useSWR("/api/config", fetcher);
25
+ const [form, setForm] = useState<Partial<RagConfig>>({});
26
+ const [saving, setSaving] = useState(false);
27
+
28
+ const searchParams = useSearchParams();
29
+ const highlightSerper = searchParams.get("settings") === "serper";
30
+ const router = useRouter();
31
+ const pathname = usePathname();
32
+ const { setMode } = useWorkspace();
33
+
34
+ useEffect(() => {
35
+ if (data?.config) {
36
+ setForm(data.config);
37
+ }
38
+ }, [data]);
39
+
40
+ const handleSave = async () => {
41
+ setSaving(true);
42
+ try {
43
+ const res = await fetch("/api/config", {
44
+ method: "PUT",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({ ...data?.config, ...form }),
47
+ });
48
+ const json = await res.json();
49
+ if (!res.ok)
50
+ throw new Error(json.error ?? "Failed to save configuration");
51
+
52
+ await mutate(json, { revalidate: false });
53
+ toast.success("Global Settings saved");
54
+
55
+ if (highlightSerper) {
56
+ // Remove the query param if it was set
57
+ const params = new URLSearchParams(searchParams);
58
+ params.delete("settings");
59
+ router.replace(`${pathname}?${params.toString()}`);
60
+ }
61
+ } catch (err) {
62
+ toast.error(err instanceof Error ? err.message : "Failed to save");
63
+ } finally {
64
+ setSaving(false);
65
+ }
66
+ };
67
+
68
+ if (isLoading) {
69
+ return (
70
+ <div className="flex items-center justify-center h-40">
71
+ <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
72
+ </div>
73
+ );
74
+ }
75
+
76
+ return (
77
+ <div className="flex flex-col min-h-0 h-full">
78
+ <div className="border-b px-4 py-3 font-semibold flex items-center gap-2 shrink-0 bg-muted/30">
79
+ <Button
80
+ variant="ghost"
81
+ size="icon"
82
+ className="h-6 w-6 shrink-0"
83
+ onClick={onBack}
84
+ >
85
+ <ChevronLeft className="h-4 w-4" />
86
+ </Button>
87
+ <span className="flex-1 truncate">Global Settings</span>
88
+ <Button
89
+ size="sm"
90
+ onClick={handleSave}
91
+ disabled={saving}
92
+ className="h-7 px-3 text-xs"
93
+ >
94
+ {saving ? (
95
+ <Loader2 className="h-3 w-3 mr-1 animate-spin" />
96
+ ) : (
97
+ <Save className="h-3 w-3 mr-1" />
98
+ )}
99
+ Save
100
+ </Button>
101
+ </div>
102
+
103
+ <div className="flex-1 min-h-0 p-4 space-y-6 overflow-y-auto ">
104
+ <div className="space-y-4">
105
+ <div className=" bg-card text-card-foreground p-3 space-y-3">
106
+ <h4 className="font-semibold leading-none tracking-tight text-sm">
107
+ Embedding
108
+ </h4>
109
+ <div className="space-y-1.5">
110
+ <Label className="text-xs">Provider</Label>
111
+ <Select
112
+ value={form.embeddingProvider || ""}
113
+ onValueChange={(v) =>
114
+ setForm({ ...form, embeddingProvider: v === null ? undefined : v })
115
+ }
116
+ >
117
+ <SelectTrigger className="h-8! text-sm w-full">
118
+ <span>{form.embeddingProvider || "Select provider"}</span>
119
+ </SelectTrigger>
120
+ <SelectContent>
121
+ <SelectItem value="openai">OpenAI</SelectItem>
122
+ <SelectItem value="google">Google</SelectItem>
123
+ <SelectItem value="cohere">Cohere</SelectItem>
124
+ <SelectItem value="mistral">Mistral</SelectItem>
125
+ <SelectItem value="deepseek">DeepSeek</SelectItem>
126
+ <SelectItem value="vercel_ai_gateway">
127
+ Vercel AI Gateway
128
+ </SelectItem>
129
+ </SelectContent>
130
+ </Select>
131
+ </div>
132
+ <div className="space-y-1.5">
133
+ <Label className="text-xs">API Key</Label>
134
+ <Input
135
+ type="password"
136
+ className="h-8 text-sm"
137
+ value={form.embeddingApiKey || ""}
138
+ onChange={(e) =>
139
+ setForm({ ...form, embeddingApiKey: e.target.value })
140
+ }
141
+ placeholder="sk-..."
142
+ />
143
+ </div>
144
+ </div>
145
+
146
+ <div className=" text-card-foreground p-3 space-y-3">
147
+ <h4 className="font-semibold leading-none tracking-tight text-sm">
148
+ Chat (LLM)
149
+ </h4>
150
+ <div className="space-y-1.5">
151
+ <Label className="text-xs">Provider</Label>
152
+ <Select
153
+ value={form.chatProvider || form.embeddingProvider || ""}
154
+ onValueChange={(v) =>
155
+ setForm({ ...form, chatProvider: v === null ? undefined : v })
156
+ }
157
+ >
158
+ <SelectTrigger className="h-8! w-full text-sm">
159
+ <span>
160
+ {form.chatProvider ||
161
+ form.embeddingProvider ||
162
+ "Select provider"}
163
+ </span>
164
+ </SelectTrigger>
165
+ <SelectContent>
166
+ <SelectItem value="openai">OpenAI</SelectItem>
167
+ <SelectItem value="google">Google</SelectItem>
168
+ <SelectItem value="cohere">Cohere</SelectItem>
169
+ <SelectItem value="mistral">Mistral</SelectItem>
170
+ <SelectItem value="deepseek">DeepSeek</SelectItem>
171
+ <SelectItem value="vercel_ai_gateway">
172
+ Vercel AI Gateway
173
+ </SelectItem>
174
+ </SelectContent>
175
+ </Select>
176
+ </div>
177
+ <div className="space-y-1.5">
178
+ <Label className="text-xs">API Key</Label>
179
+ <Input
180
+ type="password"
181
+ className="h-8 text-sm"
182
+ value={form.chatApiKey || ""}
183
+ onChange={(e) =>
184
+ setForm({ ...form, chatApiKey: e.target.value })
185
+ }
186
+ placeholder="Leave blank to use Embedding Key"
187
+ />
188
+ </div>
189
+ </div>
190
+
191
+ <div className=" bg-card text-card-foreground p-3 space-y-3">
192
+ <h4 className="font-semibold leading-none tracking-tight text-sm">
193
+ Web Search
194
+ </h4>
195
+ <div className="space-y-1.5">
196
+ <Label className="text-xs">Serper.dev API Key</Label>
197
+ <Input
198
+ type="password"
199
+ className={`h-8 text-sm ${highlightSerper && !form.serperApiKey ? "border-red-500 focus-visible:ring-red-500" : ""}`}
200
+ value={form.serperApiKey || ""}
201
+ onChange={(e) =>
202
+ setForm({ ...form, serperApiKey: e.target.value })
203
+ }
204
+ placeholder="Serper API Key"
205
+ />
206
+ {highlightSerper && !form.serperApiKey && (
207
+ <p className="text-[10px] text-red-500">
208
+ API Key is required for web search.
209
+ </p>
210
+ )}
211
+ </div>
212
+ </div>
213
+
214
+ <div className=" bg-card text-card-foreground p-3 space-y-3">
215
+ <h4 className="font-semibold leading-none tracking-tight text-sm">
216
+ Scraper
217
+ </h4>
218
+ <div className="space-y-1.5">
219
+ <Label className="text-xs">Proxy Server</Label>
220
+ <Input
221
+ className="h-8 text-sm"
222
+ value={form.scraperProxyServer || ""}
223
+ onChange={(e) =>
224
+ setForm({ ...form, scraperProxyServer: e.target.value })
225
+ }
226
+ placeholder="http://proxy.example.com:8080"
227
+ />
228
+ </div>
229
+ <div className="space-y-1.5">
230
+ <Label className="text-xs">Proxy Username</Label>
231
+ <Input
232
+ className="h-8 text-sm"
233
+ value={form.scraperProxyUsername || ""}
234
+ onChange={(e) =>
235
+ setForm({ ...form, scraperProxyUsername: e.target.value })
236
+ }
237
+ />
238
+ </div>
239
+ <div className="space-y-1.5">
240
+ <Label className="text-xs">Proxy Password</Label>
241
+ <Input
242
+ type="password"
243
+ className="h-8 text-sm"
244
+ value={form.scraperProxyPassword || ""}
245
+ onChange={(e) =>
246
+ setForm({ ...form, scraperProxyPassword: e.target.value })
247
+ }
248
+ />
249
+ </div>
250
+ <div className="space-y-1.5 pt-2">
251
+ <Label className="text-xs">Firecrawl API Key (Cloud)</Label>
252
+ <Input
253
+ type="password"
254
+ className="h-8 text-sm"
255
+ value={form.firecrawlApiKey || ""}
256
+ onChange={(e) =>
257
+ setForm({ ...form, firecrawlApiKey: e.target.value })
258
+ }
259
+ placeholder="fc-..."
260
+ />
261
+ </div>
262
+ </div>
263
+
264
+ <div className="bg-card text-card-foreground p-3 space-y-3 rounded-lg border">
265
+ <h4 className="font-semibold leading-none tracking-tight text-sm">
266
+ Workspace Mode
267
+ </h4>
268
+ <p className="text-xs text-muted-foreground">
269
+ Switch to simple mode for a streamlined experience without
270
+ pipeline configuration.
271
+ </p>
272
+ <Button
273
+ variant="outline"
274
+ size="sm"
275
+ className="w-full text-xs"
276
+ onClick={async () => {
277
+ await setMode("simple");
278
+ router.push("/simple/chat");
279
+ }}
280
+ >
281
+ Switch to Simple Mode
282
+ </Button>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ );
288
+ }