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.
- package/.env +2 -0
- package/app/actions/vercel.ts +417 -0
- package/app/api/chat/route.ts +192 -0
- package/app/api/chat/status/route.ts +55 -0
- package/app/api/chat/suggestions/route.ts +115 -0
- package/app/api/config/route.ts +73 -0
- package/app/api/config/test/route.ts +67 -0
- package/app/api/config/test-embedding/route.ts +40 -0
- package/app/api/config/test-llm/route.ts +47 -0
- package/app/api/config/test-provider/route.ts +36 -0
- package/app/api/demo/route.ts +176 -0
- package/app/api/deploy/ssh/route.ts +340 -0
- package/app/api/documents/route.ts +90 -0
- package/app/api/firecrawl/local/route.ts +46 -0
- package/app/api/index/route.ts +113 -0
- package/app/api/jobs/[id]/route.ts +44 -0
- package/app/api/jobs/route.ts +116 -0
- package/app/api/parse-file/route.ts +39 -0
- package/app/api/search/google/route.ts +162 -0
- package/app/api/search/route.ts +29 -0
- package/app/api/server/download/route.ts +112 -0
- package/app/api/server/generate/route.ts +46 -0
- package/app/api/server/local/route.ts +42 -0
- package/app/api/servers/route.ts +137 -0
- package/app/api/vector-stores/install/route.ts +65 -0
- package/app/api/vector-stores/status/route.ts +26 -0
- package/app/chat/page.tsx +15 -0
- package/app/configure/page.tsx +53 -0
- package/app/data/page.tsx +15 -0
- package/app/demo/page.tsx +15 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +190 -0
- package/app/index-data/page.tsx +15 -0
- package/app/layout.tsx +51 -0
- package/app/page.tsx +34 -0
- package/app/server/page.tsx +22 -0
- package/app/simple/chat/page.tsx +17 -0
- package/app/simple/docs/page.tsx +21 -0
- package/app/simple/layout.tsx +9 -0
- package/app/simple/settings/page.tsx +627 -0
- package/app/template.tsx +16 -0
- package/app/themes.css +733 -0
- package/components/alerts/generic-alert.tsx +74 -0
- package/components/app-sidebar.tsx +187 -0
- package/components/app-topnav.tsx +195 -0
- package/components/chat/chat-settings-modal.tsx +209 -0
- package/components/chat/chat-workspace.tsx +348 -0
- package/components/chat/knowledge-base-result.tsx +97 -0
- package/components/chat/message-item.tsx +103 -0
- package/components/client-layout-wrapper.tsx +182 -0
- package/components/configure/configure-form.tsx +1683 -0
- package/components/configure/store-fields.tsx +152 -0
- package/components/data/corpus-panel.tsx +499 -0
- package/components/data/data-workspace.tsx +272 -0
- package/components/data/firecrawl-notice.tsx +236 -0
- package/components/data/jobs-panel.tsx +485 -0
- package/components/data/paste-panel.tsx +79 -0
- package/components/data/scrape-panel.tsx +964 -0
- package/components/data/upload-panel.tsx +714 -0
- package/components/demo/demo-workspace.tsx +419 -0
- package/components/global-settings.tsx +286 -0
- package/components/index/index-workspace.tsx +482 -0
- package/components/onboarding/simple-setup.tsx +702 -0
- package/components/onboarding/tech-setup.tsx +121 -0
- package/components/onboarding/welcome-screen.tsx +183 -0
- package/components/page-header.tsx +44 -0
- package/components/server/code-viewer.tsx +87 -0
- package/components/server/deploy-button.tsx +1390 -0
- package/components/server/server-workspace.tsx +392 -0
- package/components/simple/sdk-connect-dialog.tsx +201 -0
- package/components/simple/simple-index-button.tsx +83 -0
- package/components/simple/simple-sidebar.tsx +161 -0
- package/components/theme-customizer-provider.tsx +171 -0
- package/components/theme-switcher.tsx +445 -0
- package/components/ui/alert-dialog.tsx +187 -0
- package/components/ui/alert.tsx +76 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +59 -0
- package/components/ui/card.tsx +103 -0
- package/components/ui/checkbox.tsx +28 -0
- package/components/ui/collapsible.tsx +21 -0
- package/components/ui/dialog.tsx +159 -0
- package/components/ui/dropdown-menu.tsx +281 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +20 -0
- package/components/ui/popover.tsx +90 -0
- package/components/ui/progress.tsx +83 -0
- package/components/ui/provider-icon.tsx +94 -0
- package/components/ui/scroll-area.tsx +55 -0
- package/components/ui/select.tsx +248 -0
- package/components/ui/separator.tsx +25 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/sonner.tsx +63 -0
- package/components/ui/switch.tsx +32 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +84 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components/workspace/delete-server-dialog.tsx +71 -0
- package/components/workspace/server-form-dialog.tsx +103 -0
- package/components/workspace/server-switcher.tsx +163 -0
- package/components/workspace/workspace-provider.tsx +177 -0
- package/components/workspace/workspace-top-bar.tsx +23 -0
- package/components.json +21 -0
- package/lib/error-formatter.ts +15 -0
- package/lib/utils.ts +6 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +18 -0
- package/package.json +65 -0
- package/postcss.config.mjs +8 -0
- package/public/docker.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/github.svg +3 -0
- package/public/hetzner.svg +1 -0
- package/public/icons/aws.svg +38 -0
- package/public/icons/azure.svg +1 -0
- package/public/icons/chroma.png +0 -0
- package/public/icons/cohere.svg +30 -0
- package/public/icons/digital-ocean.webp +0 -0
- package/public/icons/firecrawl2.png +0 -0
- package/public/icons/gcp.svg +1 -0
- package/public/icons/gemini.svg +1 -0
- package/public/icons/jina.svg +1 -0
- package/public/icons/lancedb2.png +0 -0
- package/public/icons/mistral.svg +469 -0
- package/public/icons/nomic.png +0 -0
- package/public/icons/openai.svg +1 -0
- package/public/icons/pgvector2.png +0 -0
- package/public/icons/pinecone.png +0 -0
- package/public/icons/qdrant.svg +35 -0
- package/public/icons/supabase.png +0 -0
- package/public/icons/vercel copy.svg +1 -0
- package/public/icons/vercel.svg +1 -0
- package/public/icons/voyage-light.png +0 -0
- package/public/icons/weaviate.webp +0 -0
- package/public/logo-light.png +0 -0
- package/public/logo.png +0 -0
- package/public/logo9.png +0 -0
- package/public/vercel.svg +1 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { ArrowLeft, ArrowRight, Loader2, Boxes } 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 { useWorkspace } from "@/components/workspace/workspace-provider";
|
|
10
|
+
|
|
11
|
+
interface TechSetupProps {
|
|
12
|
+
onBack: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function TechSetup({ onBack }: TechSetupProps) {
|
|
16
|
+
const { setUsername, createServer, setMode } = useWorkspace();
|
|
17
|
+
const [username, setName] = useState("");
|
|
18
|
+
const [serverName, setServerName] = useState("my-first-rag");
|
|
19
|
+
const [busy, setBusy] = useState(false);
|
|
20
|
+
|
|
21
|
+
async function start() {
|
|
22
|
+
const who = username.trim();
|
|
23
|
+
const srv = serverName.trim();
|
|
24
|
+
if (!who) {
|
|
25
|
+
toast.error("Tell us your name first.");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!srv) {
|
|
29
|
+
toast.error("Name your first server.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
setBusy(true);
|
|
33
|
+
try {
|
|
34
|
+
await setUsername(who);
|
|
35
|
+
await createServer(srv);
|
|
36
|
+
await setMode("tech");
|
|
37
|
+
toast.success(`Welcome, ${who}! "${srv}" is ready.`);
|
|
38
|
+
} catch {
|
|
39
|
+
toast.error("Setup failed. Please try again.");
|
|
40
|
+
setBusy(false);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center" style={{ backgroundColor: '#FCFCFB' }}>
|
|
46
|
+
{/* Background blobs */}
|
|
47
|
+
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
|
48
|
+
<div className="absolute -left-20 -top-20 h-[500px] w-[500px] rounded-full bg-stone-200/40 blur-3xl animate-pulse" />
|
|
49
|
+
<div className="absolute -bottom-32 -right-32 h-[600px] w-[600px] rounded-full bg-amber-100/30 blur-3xl animate-pulse [animation-delay:1s]" />
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="relative z-10 flex w-full max-w-md flex-col items-center gap-8 px-6">
|
|
53
|
+
{/* Back button */}
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
onClick={onBack}
|
|
57
|
+
className="absolute left-6 top-8 flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors sm:left-0 sm:top-0 sm:relative sm:self-start sm:-mt-4"
|
|
58
|
+
>
|
|
59
|
+
<ArrowLeft className="size-4" />
|
|
60
|
+
Back
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
{/* Header */}
|
|
64
|
+
<div className="flex flex-col items-center gap-3 text-center">
|
|
65
|
+
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
|
|
66
|
+
<img src="/logo9.png" className="size-7" alt="Larkup" />
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
<h1 className="text-2xl font-bold tracking-tight text-foreground">
|
|
70
|
+
Set up your workspace
|
|
71
|
+
</h1>
|
|
72
|
+
<p className="mt-1.5 text-sm text-muted-foreground text-pretty">
|
|
73
|
+
Build, index, and serve your own RAG pipelines. Let's create
|
|
74
|
+
your first server.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Form */}
|
|
80
|
+
<div className="w-full rounded-2xl border bg-transparent p-6 space-y-5">
|
|
81
|
+
<div className="grid gap-2">
|
|
82
|
+
<Label htmlFor="tech-name">What should we call you?</Label>
|
|
83
|
+
<Input
|
|
84
|
+
id="tech-name"
|
|
85
|
+
autoFocus
|
|
86
|
+
value={username}
|
|
87
|
+
placeholder="Your name"
|
|
88
|
+
onChange={(e) => setName(e.target.value)}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="grid gap-2">
|
|
92
|
+
<Label htmlFor="tech-server">Name your first server</Label>
|
|
93
|
+
<Input
|
|
94
|
+
id="tech-server"
|
|
95
|
+
value={serverName}
|
|
96
|
+
placeholder="my-first-rag"
|
|
97
|
+
onChange={(e) => setServerName(e.target.value)}
|
|
98
|
+
onKeyDown={(e) => {
|
|
99
|
+
if (e.key === "Enter") void start();
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<Button
|
|
105
|
+
className="w-full"
|
|
106
|
+
size="lg"
|
|
107
|
+
onClick={() => void start()}
|
|
108
|
+
disabled={busy}
|
|
109
|
+
>
|
|
110
|
+
{busy ? (
|
|
111
|
+
<Loader2 className="size-4 animate-spin" />
|
|
112
|
+
) : (
|
|
113
|
+
<ArrowRight className="size-4" />
|
|
114
|
+
)}
|
|
115
|
+
Create my first server
|
|
116
|
+
</Button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Code2, Sparkles, ArrowRight, Boxes } from "lucide-react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
interface WelcomeScreenProps {
|
|
8
|
+
onSelectTech: () => void;
|
|
9
|
+
onSelectSimple: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function WelcomeScreen({
|
|
13
|
+
onSelectTech,
|
|
14
|
+
onSelectSimple,
|
|
15
|
+
}: WelcomeScreenProps) {
|
|
16
|
+
const [hovered, setHovered] = useState<"tech" | "simple" | null>(null);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center" style={{ backgroundColor: '#FCFCFB' }}>
|
|
20
|
+
{/* Subtle animated background pattern */}
|
|
21
|
+
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
|
22
|
+
<div className="absolute -left-20 -top-20 h-[500px] w-[500px] rounded-full bg-stone-200/40 blur-3xl animate-pulse" />
|
|
23
|
+
<div className="absolute -bottom-32 -right-32 h-[600px] w-[600px] rounded-full bg-amber-100/30 blur-3xl animate-pulse [animation-delay:1s]" />
|
|
24
|
+
<div className="absolute left-1/2 top-1/3 h-[300px] w-[300px] -translate-x-1/2 rounded-full bg-stone-100/50 blur-3xl animate-pulse [animation-delay:2s]" />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="relative z-10 flex w-full max-w-4xl flex-col items-center gap-10 px-6">
|
|
28
|
+
{/* Header */}
|
|
29
|
+
<div className="flex flex-col items-center gap-4 text-center">
|
|
30
|
+
<div className="flex size-16 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
|
|
31
|
+
<img src="/logo9.png" className="size-8" alt="Larkup" />
|
|
32
|
+
</div>
|
|
33
|
+
<div>
|
|
34
|
+
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
|
35
|
+
Welcome to Larkup
|
|
36
|
+
</h1>
|
|
37
|
+
<p className="mt-2 text-base text-muted-foreground max-w-md mx-auto text-pretty">
|
|
38
|
+
Build, index, and query your knowledge base with AI. How would you
|
|
39
|
+
like to get started?
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
{/* Cards */}
|
|
45
|
+
<div className="grid w-full gap-5 sm:grid-cols-2">
|
|
46
|
+
{/* Tech Card */}
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={onSelectTech}
|
|
50
|
+
onMouseEnter={() => setHovered("tech")}
|
|
51
|
+
onMouseLeave={() => setHovered(null)}
|
|
52
|
+
className={cn(
|
|
53
|
+
"group relative flex flex-col items-start gap-5 rounded-2xl border-2 bg-transparent p-7 text-left transition-all duration-300 cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
54
|
+
hovered === "tech"
|
|
55
|
+
? "border-primary scale-[1.02]"
|
|
56
|
+
: hovered === "simple"
|
|
57
|
+
? "border-border/60 opacity-70"
|
|
58
|
+
: "border-border hover:border-primary/40",
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
<div className="flex w-full items-start justify-between">
|
|
62
|
+
<div
|
|
63
|
+
className={cn(
|
|
64
|
+
"flex size-12 items-center justify-center rounded-xl transition-colors duration-300",
|
|
65
|
+
hovered === "tech"
|
|
66
|
+
? "bg-primary text-primary-foreground"
|
|
67
|
+
: "bg-slate-100 text-slate-600",
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
<Code2 className="size-6" />
|
|
71
|
+
</div>
|
|
72
|
+
<ArrowRight
|
|
73
|
+
className={cn(
|
|
74
|
+
"size-5 text-muted-foreground transition-all duration-300",
|
|
75
|
+
hovered === "tech" ? "translate-x-1 text-primary" : "",
|
|
76
|
+
)}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="space-y-2">
|
|
81
|
+
<h2 className="text-xl font-semibold text-foreground">
|
|
82
|
+
Developer Mode
|
|
83
|
+
</h2>
|
|
84
|
+
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
85
|
+
Full control over your RAG pipeline, configure embeddings,
|
|
86
|
+
vector stores, indexing strategies, and deploy a custom server.
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="flex flex-wrap gap-2">
|
|
91
|
+
{[
|
|
92
|
+
"Pipeline Control",
|
|
93
|
+
"Custom Vector Stores",
|
|
94
|
+
"Server Deploy",
|
|
95
|
+
].map((tag) => (
|
|
96
|
+
<span
|
|
97
|
+
key={tag}
|
|
98
|
+
className="rounded-full border border-border bg-slate-50 px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground"
|
|
99
|
+
>
|
|
100
|
+
{tag}
|
|
101
|
+
</span>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<span
|
|
106
|
+
className={cn(
|
|
107
|
+
"text-xs font-medium transition-colors",
|
|
108
|
+
hovered === "tech"
|
|
109
|
+
? "text-primary"
|
|
110
|
+
: "text-muted-foreground/60",
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
I know what I'm doing →
|
|
114
|
+
</span>
|
|
115
|
+
</button>
|
|
116
|
+
|
|
117
|
+
{/* Simple Card */}
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
onClick={onSelectSimple}
|
|
121
|
+
onMouseEnter={() => setHovered("simple")}
|
|
122
|
+
onMouseLeave={() => setHovered(null)}
|
|
123
|
+
className={cn(
|
|
124
|
+
"group relative flex flex-col items-start gap-5 rounded-2xl border-2 bg-transparent p-7 text-left transition-all duration-300 cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
125
|
+
hovered === "simple"
|
|
126
|
+
? "border-primary scale-[1.02]"
|
|
127
|
+
: hovered === "tech"
|
|
128
|
+
? "border-border/60 opacity-70"
|
|
129
|
+
: "border-border hover:border-primary/30",
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
<div className="flex w-full items-start justify-between">
|
|
133
|
+
<div
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex size-12 items-center justify-center rounded-xl transition-colors duration-300",
|
|
136
|
+
hovered === "simple"
|
|
137
|
+
? "bg-primary text-primary-foreground"
|
|
138
|
+
: "bg-primary/10 text-primary",
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
<Sparkles className="size-6" />
|
|
142
|
+
</div>
|
|
143
|
+
<ArrowRight
|
|
144
|
+
className={cn(
|
|
145
|
+
"size-5 text-muted-foreground transition-all duration-300",
|
|
146
|
+
hovered === "simple" ? "translate-x-1 text-primary" : "",
|
|
147
|
+
)}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div className="space-y-2">
|
|
152
|
+
<h2 className="text-xl font-semibold text-foreground">
|
|
153
|
+
Simple Mode
|
|
154
|
+
</h2>
|
|
155
|
+
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
156
|
+
Just upload your documents and start chatting. we'll handle
|
|
157
|
+
the rest. Perfect for getting quick answers from your knowledge
|
|
158
|
+
base.
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div className="flex flex-wrap gap-2">
|
|
163
|
+
{["Upload & Chat", "Auto Setup", "Instant RAG"].map((tag) => (
|
|
164
|
+
<span key={tag} className="rounded-full border border-primary/20 bg-primary/5 px-2.5 py-0.5 text-[11px] font-medium text-primary">
|
|
165
|
+
{tag}
|
|
166
|
+
</span>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<span
|
|
171
|
+
className={cn(
|
|
172
|
+
"text-xs font-medium transition-colors",
|
|
173
|
+
hovered === "simple" ? "text-primary" : "text-muted-foreground/60",
|
|
174
|
+
)}
|
|
175
|
+
>
|
|
176
|
+
Just let my LLM know my docs →
|
|
177
|
+
</span>
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
interface PageHeaderProps {
|
|
5
|
+
eyebrow?: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function PageHeader({
|
|
13
|
+
eyebrow,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
actions,
|
|
17
|
+
className,
|
|
18
|
+
}: PageHeaderProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
className={cn(
|
|
22
|
+
"flex flex-col gap-4 px-6 py-4 sm:flex-row sm:items-center sm:justify-between md:px-8",
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
>
|
|
26
|
+
<div className="space-y-1">
|
|
27
|
+
{eyebrow && (
|
|
28
|
+
<p className="text-[11px] font-medium uppercase tracking-wider text-primary font-mono">
|
|
29
|
+
{eyebrow}
|
|
30
|
+
</p>
|
|
31
|
+
)}
|
|
32
|
+
<h1 className="text-2xl font-semibold tracking-tight text-balance">
|
|
33
|
+
{title}
|
|
34
|
+
</h1>
|
|
35
|
+
{description && (
|
|
36
|
+
<p className="max-w-2xl text-sm leading-relaxed text-muted-foreground text-pretty">
|
|
37
|
+
{description}
|
|
38
|
+
</p>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react"
|
|
4
|
+
import CodeMirror, { EditorView, type Extension } from "@uiw/react-codemirror"
|
|
5
|
+
import { javascript } from "@codemirror/lang-javascript"
|
|
6
|
+
import { json } from "@codemirror/lang-json"
|
|
7
|
+
import { markdown } from "@codemirror/lang-markdown"
|
|
8
|
+
import { yaml } from "@codemirror/lang-yaml"
|
|
9
|
+
import { python } from "@codemirror/lang-python"
|
|
10
|
+
import { githubLight, githubDark } from "@uiw/codemirror-theme-github"
|
|
11
|
+
|
|
12
|
+
/** Map a generated file's language hint to a CodeMirror language extension. */
|
|
13
|
+
function languageExtension(language: string): Extension[] {
|
|
14
|
+
switch (language) {
|
|
15
|
+
case "javascript":
|
|
16
|
+
return [javascript({ jsx: true })]
|
|
17
|
+
case "json":
|
|
18
|
+
return [json()]
|
|
19
|
+
case "markdown":
|
|
20
|
+
return [markdown()]
|
|
21
|
+
case "yaml":
|
|
22
|
+
return [yaml()]
|
|
23
|
+
case "python":
|
|
24
|
+
return [python()]
|
|
25
|
+
default:
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Blend CodeMirror into the card surface instead of its own white sheet. */
|
|
31
|
+
const surfaceTheme = EditorView.theme({
|
|
32
|
+
"&": {
|
|
33
|
+
backgroundColor: "transparent",
|
|
34
|
+
fontSize: "12px",
|
|
35
|
+
},
|
|
36
|
+
".cm-gutters": {
|
|
37
|
+
backgroundColor: "transparent",
|
|
38
|
+
border: "none",
|
|
39
|
+
color: "var(--muted-foreground)",
|
|
40
|
+
},
|
|
41
|
+
".cm-activeLine": { backgroundColor: "transparent" },
|
|
42
|
+
".cm-activeLineGutter": { backgroundColor: "transparent" },
|
|
43
|
+
".cm-scroller": {
|
|
44
|
+
fontFamily: "var(--font-mono), ui-monospace, monospace",
|
|
45
|
+
lineHeight: "1.6",
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read-only, syntax-highlighted file viewer used by the Server stage. Replaces
|
|
51
|
+
* the plain <pre> preview with proper tokenized highlighting.
|
|
52
|
+
*/
|
|
53
|
+
export function CodeViewer({
|
|
54
|
+
value,
|
|
55
|
+
language,
|
|
56
|
+
height = "26rem",
|
|
57
|
+
theme = "light",
|
|
58
|
+
}: {
|
|
59
|
+
value: string
|
|
60
|
+
language: string
|
|
61
|
+
height?: string
|
|
62
|
+
theme?: "light" | "dark"
|
|
63
|
+
}) {
|
|
64
|
+
const extensions = useMemo(
|
|
65
|
+
() => [...languageExtension(language), surfaceTheme, EditorView.lineWrapping],
|
|
66
|
+
[language],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<CodeMirror
|
|
71
|
+
value={value}
|
|
72
|
+
height={height}
|
|
73
|
+
readOnly
|
|
74
|
+
editable={false}
|
|
75
|
+
theme={theme === "dark" ? githubDark : githubLight}
|
|
76
|
+
extensions={extensions}
|
|
77
|
+
basicSetup={{
|
|
78
|
+
lineNumbers: true,
|
|
79
|
+
foldGutter: false,
|
|
80
|
+
highlightActiveLine: false,
|
|
81
|
+
highlightActiveLineGutter: false,
|
|
82
|
+
highlightSelectionMatches: false,
|
|
83
|
+
searchKeymap: false,
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|