larkup 0.1.14

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 (141) hide show
  1. package/.env +2 -0
  2. package/app/actions/vercel.ts +417 -0
  3. package/app/api/chat/route.ts +192 -0
  4. package/app/api/chat/status/route.ts +55 -0
  5. package/app/api/chat/suggestions/route.ts +115 -0
  6. package/app/api/config/route.ts +73 -0
  7. package/app/api/config/test/route.ts +67 -0
  8. package/app/api/config/test-embedding/route.ts +40 -0
  9. package/app/api/config/test-llm/route.ts +47 -0
  10. package/app/api/config/test-provider/route.ts +36 -0
  11. package/app/api/demo/route.ts +176 -0
  12. package/app/api/deploy/ssh/route.ts +340 -0
  13. package/app/api/documents/route.ts +90 -0
  14. package/app/api/firecrawl/local/route.ts +46 -0
  15. package/app/api/index/route.ts +113 -0
  16. package/app/api/jobs/[id]/route.ts +44 -0
  17. package/app/api/jobs/route.ts +116 -0
  18. package/app/api/parse-file/route.ts +39 -0
  19. package/app/api/search/google/route.ts +162 -0
  20. package/app/api/search/route.ts +29 -0
  21. package/app/api/server/download/route.ts +112 -0
  22. package/app/api/server/generate/route.ts +46 -0
  23. package/app/api/server/local/route.ts +42 -0
  24. package/app/api/servers/route.ts +137 -0
  25. package/app/api/vector-stores/install/route.ts +65 -0
  26. package/app/api/vector-stores/status/route.ts +26 -0
  27. package/app/chat/page.tsx +15 -0
  28. package/app/configure/page.tsx +53 -0
  29. package/app/data/page.tsx +15 -0
  30. package/app/demo/page.tsx +15 -0
  31. package/app/favicon.ico +0 -0
  32. package/app/globals.css +190 -0
  33. package/app/index-data/page.tsx +15 -0
  34. package/app/layout.tsx +51 -0
  35. package/app/page.tsx +34 -0
  36. package/app/server/page.tsx +22 -0
  37. package/app/simple/chat/page.tsx +17 -0
  38. package/app/simple/docs/page.tsx +21 -0
  39. package/app/simple/layout.tsx +9 -0
  40. package/app/simple/settings/page.tsx +627 -0
  41. package/app/template.tsx +16 -0
  42. package/app/themes.css +733 -0
  43. package/components/alerts/generic-alert.tsx +74 -0
  44. package/components/app-sidebar.tsx +187 -0
  45. package/components/app-topnav.tsx +195 -0
  46. package/components/chat/chat-settings-modal.tsx +209 -0
  47. package/components/chat/chat-workspace.tsx +348 -0
  48. package/components/chat/knowledge-base-result.tsx +97 -0
  49. package/components/chat/message-item.tsx +103 -0
  50. package/components/client-layout-wrapper.tsx +182 -0
  51. package/components/configure/configure-form.tsx +1683 -0
  52. package/components/configure/store-fields.tsx +152 -0
  53. package/components/data/corpus-panel.tsx +499 -0
  54. package/components/data/data-workspace.tsx +272 -0
  55. package/components/data/firecrawl-notice.tsx +236 -0
  56. package/components/data/jobs-panel.tsx +485 -0
  57. package/components/data/paste-panel.tsx +79 -0
  58. package/components/data/scrape-panel.tsx +964 -0
  59. package/components/data/upload-panel.tsx +714 -0
  60. package/components/demo/demo-workspace.tsx +419 -0
  61. package/components/global-settings.tsx +286 -0
  62. package/components/index/index-workspace.tsx +482 -0
  63. package/components/onboarding/simple-setup.tsx +702 -0
  64. package/components/onboarding/tech-setup.tsx +121 -0
  65. package/components/onboarding/welcome-screen.tsx +183 -0
  66. package/components/page-header.tsx +44 -0
  67. package/components/server/code-viewer.tsx +87 -0
  68. package/components/server/deploy-button.tsx +1390 -0
  69. package/components/server/server-workspace.tsx +392 -0
  70. package/components/simple/sdk-connect-dialog.tsx +201 -0
  71. package/components/simple/simple-index-button.tsx +83 -0
  72. package/components/simple/simple-sidebar.tsx +161 -0
  73. package/components/theme-customizer-provider.tsx +171 -0
  74. package/components/theme-switcher.tsx +445 -0
  75. package/components/ui/alert-dialog.tsx +187 -0
  76. package/components/ui/alert.tsx +76 -0
  77. package/components/ui/badge.tsx +52 -0
  78. package/components/ui/button.tsx +59 -0
  79. package/components/ui/card.tsx +103 -0
  80. package/components/ui/checkbox.tsx +28 -0
  81. package/components/ui/collapsible.tsx +21 -0
  82. package/components/ui/dialog.tsx +159 -0
  83. package/components/ui/dropdown-menu.tsx +281 -0
  84. package/components/ui/input.tsx +22 -0
  85. package/components/ui/label.tsx +20 -0
  86. package/components/ui/popover.tsx +90 -0
  87. package/components/ui/progress.tsx +83 -0
  88. package/components/ui/provider-icon.tsx +94 -0
  89. package/components/ui/scroll-area.tsx +55 -0
  90. package/components/ui/select.tsx +248 -0
  91. package/components/ui/separator.tsx +25 -0
  92. package/components/ui/sheet.tsx +138 -0
  93. package/components/ui/skeleton.tsx +13 -0
  94. package/components/ui/sonner.tsx +63 -0
  95. package/components/ui/switch.tsx +32 -0
  96. package/components/ui/table.tsx +116 -0
  97. package/components/ui/tabs.tsx +84 -0
  98. package/components/ui/textarea.tsx +18 -0
  99. package/components/ui/tooltip.tsx +66 -0
  100. package/components/workspace/delete-server-dialog.tsx +71 -0
  101. package/components/workspace/server-form-dialog.tsx +103 -0
  102. package/components/workspace/server-switcher.tsx +163 -0
  103. package/components/workspace/workspace-provider.tsx +177 -0
  104. package/components/workspace/workspace-top-bar.tsx +23 -0
  105. package/components.json +21 -0
  106. package/lib/error-formatter.ts +15 -0
  107. package/lib/utils.ts +6 -0
  108. package/next-env.d.ts +6 -0
  109. package/next.config.mjs +18 -0
  110. package/package.json +65 -0
  111. package/postcss.config.mjs +8 -0
  112. package/public/docker.png +0 -0
  113. package/public/favicon.ico +0 -0
  114. package/public/github.svg +3 -0
  115. package/public/hetzner.svg +1 -0
  116. package/public/icons/aws.svg +38 -0
  117. package/public/icons/azure.svg +1 -0
  118. package/public/icons/chroma.png +0 -0
  119. package/public/icons/cohere.svg +30 -0
  120. package/public/icons/digital-ocean.webp +0 -0
  121. package/public/icons/firecrawl2.png +0 -0
  122. package/public/icons/gcp.svg +1 -0
  123. package/public/icons/gemini.svg +1 -0
  124. package/public/icons/jina.svg +1 -0
  125. package/public/icons/lancedb2.png +0 -0
  126. package/public/icons/mistral.svg +469 -0
  127. package/public/icons/nomic.png +0 -0
  128. package/public/icons/openai.svg +1 -0
  129. package/public/icons/pgvector2.png +0 -0
  130. package/public/icons/pinecone.png +0 -0
  131. package/public/icons/qdrant.svg +35 -0
  132. package/public/icons/supabase.png +0 -0
  133. package/public/icons/vercel copy.svg +1 -0
  134. package/public/icons/vercel.svg +1 -0
  135. package/public/icons/voyage-light.png +0 -0
  136. package/public/icons/weaviate.webp +0 -0
  137. package/public/logo-light.png +0 -0
  138. package/public/logo.png +0 -0
  139. package/public/logo9.png +0 -0
  140. package/public/vercel.svg +1 -0
  141. package/tsconfig.json +33 -0
@@ -0,0 +1,65 @@
1
+ import { NextResponse } from "next/server";
2
+ import { exec } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ /**
8
+ * POST /api/vector-stores/install
9
+ *
10
+ * Install an optional vector store package on demand.
11
+ * Only works in local development — not on serverless (Vercel) production.
12
+ *
13
+ * Body: { "storeId": "chroma" }
14
+ */
15
+ export async function POST(request: Request) {
16
+ // Block in production — pnpm add can't run on Vercel
17
+ if (process.env.NODE_ENV === "production") {
18
+ return NextResponse.json(
19
+ {
20
+ error:
21
+ 'Package installation is only available in local development. For production, add "chromadb" to your dependencies before deploying.',
22
+ },
23
+ { status: 403 },
24
+ );
25
+ }
26
+
27
+ const body = await request.json();
28
+ const { storeId } = body;
29
+
30
+ // Only Chroma is installable for now
31
+ const INSTALLABLE: Record<string, { pkg: string; version: string }> = {
32
+ chroma: { pkg: "chromadb", version: "^1.9.0" },
33
+ };
34
+
35
+ const entry = INSTALLABLE[storeId];
36
+ if (!entry) {
37
+ return NextResponse.json(
38
+ { error: `Store "${storeId}" is not installable or not recognized.` },
39
+ { status: 400 },
40
+ );
41
+ }
42
+
43
+ try {
44
+ const cmd = `pnpm add ${entry.pkg}@${entry.version} --filter @larkup/vector-stores`;
45
+ const { stdout, stderr } = await execAsync(cmd, {
46
+ cwd: process.cwd(),
47
+ timeout: 120_000, // 2 minute timeout
48
+ });
49
+
50
+ return NextResponse.json({
51
+ success: true,
52
+ package: entry.pkg,
53
+ version: entry.version,
54
+ output: stdout || stderr,
55
+ });
56
+ } catch (err: any) {
57
+ return NextResponse.json(
58
+ {
59
+ error: `Failed to install ${entry.pkg}: ${err.message}`,
60
+ output: err.stderr || err.stdout,
61
+ },
62
+ { status: 500 },
63
+ );
64
+ }
65
+ }
@@ -0,0 +1,26 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ /**
4
+ * GET /api/vector-stores/status
5
+ *
6
+ * Checks whether optional vector store packages are importable.
7
+ * Returns a map of storeId → { installed: boolean }.
8
+ */
9
+ export async function GET() {
10
+ const status: Record<string, { installed: boolean }> = {};
11
+
12
+ // Check chromadb by reading the vector-stores package.json
13
+ // Dynamic import caching in Next.js might fail even after installation.
14
+ try {
15
+ const fs = await import("fs");
16
+ const path = await import("path");
17
+ const pkgPath = path.join(process.cwd(), "../../packages/vector-stores/package.json");
18
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
19
+ const hasChroma = pkg.dependencies?.chromadb || pkg.devDependencies?.chromadb || pkg.optionalDependencies?.chromadb;
20
+ status.chroma = { installed: !!hasChroma };
21
+ } catch {
22
+ status.chroma = { installed: false };
23
+ }
24
+
25
+ return NextResponse.json({ stores: status });
26
+ }
@@ -0,0 +1,15 @@
1
+ import { PageHeader } from "@/components/page-header";
2
+ import { ChatWorkspace } from "@/components/chat/chat-workspace";
3
+
4
+ export default function ChatPage() {
5
+ return (
6
+ <div className="flex flex-col overflow-hidden" style={{ height: "100%" }}>
7
+ <PageHeader
8
+ eyebrow="Step 6 · Chat"
9
+ title="Chat with your knowledge base"
10
+ className="gap-0 pb-0"
11
+ />
12
+ <ChatWorkspace />
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import { PageHeader } from "@/components/page-header";
5
+ import {
6
+ ConfigureForm,
7
+ type ConfigureFormHandle,
8
+ } from "@/components/configure/configure-form";
9
+ import { Button } from "@/components/ui/button";
10
+ import { Loader2, Save, Check } from "lucide-react";
11
+
12
+ export default function ConfigurePage() {
13
+ const [handle, setHandle] = useState<ConfigureFormHandle | null>(null);
14
+
15
+ const onHandleReady = useCallback((h: ConfigureFormHandle) => {
16
+ setHandle({ ...h });
17
+ }, []);
18
+
19
+ const dirty = handle?.dirty ?? false;
20
+ const saving = handle?.saving ?? false;
21
+
22
+ return (
23
+ <div className="flex min-h-full flex-col">
24
+ <PageHeader
25
+ eyebrow="Step 1 · Configure"
26
+ title="Pipeline configuration"
27
+ description="Choose your embedding model, indexing strategy, and vector store. "
28
+ actions={
29
+ <Button
30
+ size="lg"
31
+ variant={dirty ? "default" : "default"}
32
+ disabled={saving || !dirty}
33
+ onClick={() => handle?.requestSave()}
34
+ className="min-w-[110px] px-3.5 h-9.5 rounded-md transition-all text-white/90 text-[13px]"
35
+ >
36
+ {saving ? (
37
+ <>
38
+ <Loader2 className="size-4 animate-spin" />
39
+ Saving…
40
+ </>
41
+ ) : (
42
+ <>
43
+ <Save className="size-4" />
44
+ Save Configuration
45
+ </>
46
+ )}
47
+ </Button>
48
+ }
49
+ />
50
+ <ConfigureForm onHandleReady={onHandleReady} />
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,15 @@
1
+ import { PageHeader } from "@/components/page-header";
2
+ import { DataWorkspace } from "@/components/data/data-workspace";
3
+
4
+ export default function DataPage() {
5
+ return (
6
+ <div className="flex min-h-full flex-col">
7
+ <PageHeader
8
+ eyebrow="Step 2 · Data"
9
+ title="Load &amp; scrape data"
10
+ description="Manage your corpus and ETL jobs. Load new data via web scraping, pasting, or uploading files."
11
+ />
12
+ <DataWorkspace />
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ import { PageHeader } from "@/components/page-header"
2
+ import { DemoWorkspace } from "@/components/demo/demo-workspace"
3
+
4
+ export default function DemoPage() {
5
+ return (
6
+ <div className="flex min-h-full flex-col">
7
+ <PageHeader
8
+ eyebrow="Step 5 · Demo"
9
+ title="Query your RAG pipeline"
10
+ description="Send a test question and inspect the top-k documents your pipeline returns. If a generated server is running, the query hits it directly; otherwise it runs in-process against your store."
11
+ />
12
+ <DemoWorkspace />
13
+ </div>
14
+ )
15
+ }
Binary file
@@ -0,0 +1,190 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+ @import "./themes.css";
5
+
6
+ @theme inline {
7
+ --font-heading: var(--font-sans);
8
+ --font-sans: var(--font-geist-sans), "Geist Fallback";
9
+ --font-mono: var(--font-geist-mono), "Geist Mono Fallback";
10
+ --color-sidebar-ring: var(--sidebar-ring);
11
+ --color-sidebar-border: var(--sidebar-border);
12
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
13
+ --color-sidebar-accent: var(--sidebar-accent);
14
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
15
+ --color-sidebar-primary: var(--sidebar-primary);
16
+ --color-sidebar-foreground: var(--sidebar-foreground);
17
+ --color-sidebar: var(--sidebar);
18
+ --color-chart-5: var(--chart-5);
19
+ --color-chart-4: var(--chart-4);
20
+ --color-chart-3: var(--chart-3);
21
+ --color-chart-2: var(--chart-2);
22
+ --color-chart-1: var(--chart-1);
23
+ --color-ring: var(--ring);
24
+ --color-input: var(--input);
25
+ --color-border: var(--border);
26
+ --color-destructive: var(--destructive);
27
+ --color-accent-foreground: var(--accent-foreground);
28
+ --color-accent: var(--accent);
29
+ --color-muted-foreground: var(--muted-foreground);
30
+ --color-muted: var(--muted);
31
+ --color-secondary-foreground: var(--secondary-foreground);
32
+ --color-secondary: var(--secondary);
33
+ --color-primary-foreground: var(--primary-foreground);
34
+ --color-primary: var(--primary);
35
+ --color-popover-foreground: var(--popover-foreground);
36
+ --color-popover: var(--popover);
37
+ --color-card-foreground: var(--card-foreground);
38
+ --color-card: var(--card);
39
+ --color-panel: var(--panel);
40
+ --color-panel-foreground: var(--panel-foreground);
41
+ --color-foreground: var(--foreground);
42
+ --color-background: var(--background);
43
+ --radius-sm: calc(var(--radius) * 0.6);
44
+ --radius-md: calc(var(--radius) * 0.8);
45
+ --radius-lg: var(--radius);
46
+ --radius-xl: calc(var(--radius) * 1.4);
47
+ --radius-2xl: calc(var(--radius) * 1.8);
48
+ --radius-3xl: calc(var(--radius) * 2.2);
49
+ --radius-4xl: calc(var(--radius) * 2.6);
50
+ }
51
+
52
+ :root {
53
+ --background: #FAFAFA;
54
+ --foreground: #171717;
55
+ --card: #FFFFFF;
56
+ --card-foreground: #171717;
57
+ --panel: #F4F4F4;
58
+ --panel-foreground: #171717;
59
+ --popover: #FFFFFF;
60
+ --popover-foreground: #171717;
61
+ --primary: #000000;
62
+ --primary-foreground: #ffffff;
63
+ --secondary: #F5F5F5;
64
+ --secondary-foreground: #000000;
65
+ --muted: #F5F5F5;
66
+ --muted-foreground: #737373;
67
+ --accent: #F5F5F5;
68
+ --accent-foreground: #000000;
69
+ --destructive: oklch(0.577 0.245 27.325);
70
+ --destructive-foreground: oklch(0.577 0.245 27.325);
71
+ --border: #E5E5E5;
72
+ --input: #E5E5E5;
73
+ --ring: #000000;
74
+ --sidebar: #F4F4F4;
75
+ --sidebar-foreground: #525252;
76
+ --sidebar-primary: #000000;
77
+ --sidebar-primary-foreground: #ffffff;
78
+ --sidebar-border: #E5E5E5;
79
+ --sidebar-accent: #F5F5F5;
80
+ --sidebar-accent-foreground: #000000;
81
+ --sidebar-ring: #000000;
82
+ --radius: 0.625rem;
83
+ --chart-1: #6e56cf;
84
+ --chart-2: oklch(0.6 0.13 220);
85
+ --chart-3: oklch(0.68 0.15 145);
86
+ --chart-4: oklch(0.7 0.16 60);
87
+ --chart-5: oklch(0.62 0.2 25);
88
+ }
89
+
90
+ .dark {
91
+ --background: #0A0A0A;
92
+ --foreground: #FAFAFA;
93
+ --card: #1C1C1C;
94
+ --card-foreground: #FAFAFA;
95
+ --panel: #141414;
96
+ --panel-foreground: #FAFAFA;
97
+ --popover: #1C1C1C;
98
+ --popover-foreground: #FAFAFA;
99
+ --primary: #FFFFFF;
100
+ --primary-foreground: #000000;
101
+ --secondary: #1A1A1A;
102
+ --secondary-foreground: #FAFAFA;
103
+ --muted: #1A1A1A;
104
+ --muted-foreground: #A1A1A1;
105
+ --accent: #1A1A1A;
106
+ --accent-foreground: #FAFAFA;
107
+ --border: #2E2E2E;
108
+ --input: #2E2E2E;
109
+ --ring: #FFFFFF;
110
+ --sidebar: #141414;
111
+ --sidebar-foreground: #A1A1A1;
112
+ --sidebar-primary: #FFFFFF;
113
+ --sidebar-primary-foreground: #000000;
114
+ --sidebar-border: #2E2E2E;
115
+ }
116
+
117
+ @layer base {
118
+ * {
119
+ @apply border-border outline-ring/50;
120
+ }
121
+ body {
122
+ @apply bg-background text-foreground;
123
+ }
124
+
125
+ html {
126
+ @apply font-sans;
127
+ }
128
+ }
129
+
130
+ /* Darker, thinner scrollbar for dropdown/select popups. */
131
+ @layer base {
132
+ [data-slot="dropdown-menu-content"],
133
+ [data-slot="select-content"] {
134
+ scrollbar-width: thin;
135
+ scrollbar-color: var(--muted-foreground) transparent;
136
+ }
137
+ [data-slot="dropdown-menu-content"]::-webkit-scrollbar,
138
+ [data-slot="select-content"]::-webkit-scrollbar {
139
+ width: 6px;
140
+ height: 6px;
141
+ }
142
+ [data-slot="dropdown-menu-content"]::-webkit-scrollbar-track,
143
+ [data-slot="select-content"]::-webkit-scrollbar-track {
144
+ background: transparent;
145
+ }
146
+ [data-slot="dropdown-menu-content"]::-webkit-scrollbar-thumb,
147
+ [data-slot="select-content"]::-webkit-scrollbar-thumb {
148
+ border-radius: 9999px;
149
+ background-color: color-mix(in oklch, var(--foreground) 45%, transparent);
150
+ }
151
+ [data-slot="dropdown-menu-content"]::-webkit-scrollbar-thumb:hover,
152
+ [data-slot="select-content"]::-webkit-scrollbar-thumb:hover {
153
+ background-color: color-mix(in oklch, var(--foreground) 65%, transparent);
154
+ }
155
+ }
156
+
157
+ /* ── Mesh gradient + noise utility (same as landing) ── */
158
+
159
+ /* Apply on the layout root div to get the mesh gradient backdrop */
160
+ .bg-mesh-light {
161
+ position: relative;
162
+ background-image:
163
+ radial-gradient(
164
+ ellipse 55% 45% at 8% 4%,
165
+ color-mix(in oklch, var(--primary) 6%, transparent),
166
+ transparent 70%
167
+ ),
168
+ radial-gradient(
169
+ ellipse 45% 40% at 94% 92%,
170
+ color-mix(in oklch, oklch(0.62 0.08 30) 5%, transparent),
171
+ transparent 65%
172
+ ),
173
+ radial-gradient(
174
+ ellipse 70% 55% at 50% 50%,
175
+ color-mix(in oklch, var(--primary) 2%, transparent),
176
+ transparent 80%
177
+ );
178
+ }
179
+
180
+ .dark .bg-mesh-light {
181
+ background-image: radial-gradient(
182
+ circle at 20% 0%,
183
+ color-mix(in oklch, var(--primary) 8%, transparent),
184
+ transparent 40%
185
+ );
186
+ }
187
+
188
+ [data-sonner-toaster] {
189
+ z-index: 999999 !important;
190
+ }
@@ -0,0 +1,15 @@
1
+ import { PageHeader } from "@/components/page-header"
2
+ import { IndexWorkspace } from "@/components/index/index-workspace"
3
+
4
+ export default function IndexPage() {
5
+ return (
6
+ <div className="flex min-h-full flex-col">
7
+ <PageHeader
8
+ eyebrow="Step 3 · Index"
9
+ title="Chunk, embed &amp; store"
10
+ description="Split your corpus into chunks, embed them with your selected model, and write the vectors into your vector store. Progress streams live as the run works through the pipeline."
11
+ />
12
+ <IndexWorkspace />
13
+ </div>
14
+ )
15
+ }
package/app/layout.tsx ADDED
@@ -0,0 +1,51 @@
1
+ import { Analytics } from "@vercel/analytics/next";
2
+ import type { Metadata } from "next";
3
+ import { Geist, Geist_Mono } from "next/font/google";
4
+ import "./globals.css";
5
+ import { AppSidebar } from "@/components/app-sidebar";
6
+ import { Toaster } from "@/components/ui/sonner";
7
+ import { WorkspaceProvider } from "@/components/workspace/workspace-provider";
8
+ import { WorkspaceTopBar } from "@/components/workspace/workspace-top-bar";
9
+ import { ThemeCustomizerProvider } from "@/components/theme-customizer-provider";
10
+ import { ClientLayoutWrapper } from "@/components/client-layout-wrapper";
11
+
12
+ const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
13
+ const geistMono = Geist_Mono({
14
+ variable: "--font-geist-mono",
15
+ subsets: ["latin"],
16
+ });
17
+
18
+ export const metadata: Metadata = {
19
+ title: "larkup",
20
+ description:
21
+ "larkup — a dual-mode (Web + CLI) toolkit to build, index, and serve a lightweight, deployable RAG pipeline.",
22
+ };
23
+
24
+ export default function RootLayout({
25
+ children,
26
+ }: Readonly<{
27
+ children: React.ReactNode;
28
+ }>) {
29
+ return (
30
+ <html
31
+ lang="en"
32
+ suppressHydrationWarning
33
+ className={`${geistSans.variable} ${geistMono.variable} bg-transparent`}
34
+ >
35
+ <body suppressHydrationWarning className="font-sans antialiased ">
36
+ <script
37
+ dangerouslySetInnerHTML={{
38
+ __html: `(function(){try{var t=localStorage.getItem('app-theme');var b=localStorage.getItem('app-background');var r=localStorage.getItem('app-radius');var d=document.body;if(t&&t!=='default')d.classList.add(t);if(b)d.classList.add(b);if(r&&r!=='radius-default')d.classList.add(r);}catch(e){}})();`,
39
+ }}
40
+ />
41
+ <ThemeCustomizerProvider>
42
+ <WorkspaceProvider>
43
+ <ClientLayoutWrapper>{children}</ClientLayoutWrapper>
44
+ <Toaster position="bottom-right" />
45
+ </WorkspaceProvider>
46
+ </ThemeCustomizerProvider>
47
+ {process.env.NODE_ENV === "production" && <Analytics />}
48
+ </body>
49
+ </html>
50
+ );
51
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,34 @@
1
+ "use client"
2
+
3
+ import { useEffect } from "react"
4
+ import { useRouter } from "next/navigation"
5
+ import { useWorkspace } from "@/components/workspace/workspace-provider"
6
+ import { Loader2 } from "lucide-react"
7
+
8
+ export default function Page() {
9
+ const router = useRouter()
10
+ const { mode, isLoading, isFirstRun } = useWorkspace()
11
+
12
+ useEffect(() => {
13
+ if (isLoading) return
14
+ // If it's first run, the ClientLayoutWrapper will show the welcome screen
15
+ if (isFirstRun || !mode) return
16
+
17
+ if (mode === "simple") {
18
+ router.replace("/simple/chat")
19
+ } else {
20
+ router.replace("/configure")
21
+ }
22
+ }, [mode, isLoading, isFirstRun, router])
23
+
24
+ // Show a minimal loading state while determining the redirect
25
+ if (isLoading) {
26
+ return (
27
+ <div className="flex h-[50vh] items-center justify-center">
28
+ <Loader2 className="size-6 animate-spin text-muted-foreground" />
29
+ </div>
30
+ )
31
+ }
32
+
33
+ return null
34
+ }
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { PageHeader } from "@/components/page-header";
5
+ import { ServerWorkspace } from "@/components/server/server-workspace";
6
+ import { DeployButton } from "@/components/server/deploy-button";
7
+
8
+ export default function ServerPage() {
9
+ const [serverId, setServerId] = useState<string>("default");
10
+
11
+ return (
12
+ <div className="flex min-h-full flex-col">
13
+ <PageHeader
14
+ eyebrow="Step 4 · Server"
15
+ title="Generate &amp; launch your RAG server"
16
+ description="Emit a standalone, deployable RAG server tailored to your selected vector store"
17
+ actions={<DeployButton serverId={serverId} />}
18
+ />
19
+ <ServerWorkspace onServerId={setServerId} />
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,17 @@
1
+ import { ChatWorkspace } from "@/components/chat/chat-workspace"
2
+
3
+ export default function SimpleChatPage() {
4
+ return (
5
+ <div className="flex flex-col overflow-hidden" style={{ height: "100%" }}>
6
+ <div className="px-6 pt-5 pb-1 md:px-8">
7
+ <h1 className="text-lg font-semibold tracking-tight text-foreground">
8
+ Chat
9
+ </h1>
10
+ <p className="text-sm text-muted-foreground">
11
+ Ask questions and get AI-powered answers from your documents.
12
+ </p>
13
+ </div>
14
+ <ChatWorkspace />
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,21 @@
1
+ import { DataWorkspace } from "@/components/data/data-workspace"
2
+ import { SimpleIndexButton } from "@/components/simple/simple-index-button"
3
+
4
+ export default function SimpleDocsPage() {
5
+ return (
6
+ <div className="flex min-h-full flex-col">
7
+ <div className="flex items-center justify-between px-6 pt-5 pb-4 md:px-8">
8
+ <div>
9
+ <h1 className="text-lg font-semibold tracking-tight text-foreground">
10
+ Documents
11
+ </h1>
12
+ <p className="text-sm text-muted-foreground">
13
+ Upload files, paste text, or scrape the web to build your knowledge base.
14
+ </p>
15
+ </div>
16
+ <SimpleIndexButton />
17
+ </div>
18
+ <DataWorkspace />
19
+ </div>
20
+ )
21
+ }
@@ -0,0 +1,9 @@
1
+ export default function SimpleLayout({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode
5
+ }) {
6
+ // The SimpleSidebar is rendered by ClientLayoutWrapper when mode === "simple"
7
+ // This layout just passes children through
8
+ return <>{children}</>
9
+ }