Template
Merge pull request 'Schulungs-Vorlage auf neues App-Template umgestellt' (#1) from feat/Template-Anpassung into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
commit
cec573f682
17 files changed
+162
-414
No files matched your search
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"permissions": {
|
||||
"defaultMode": "plan",
|
||||
"deny": [
|
||||
@@ -13,4 +14,5 @@
|
||||
"Bash(git stash:*)",
|
||||
"Read(~/.claude/projects/**)"
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"features": {
|
||||
"ghcr.io/anthropics/devcontainer-features/claude-code:1": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a",
|
||||
"integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,18 @@
|
||||
"mounts": [
|
||||
"source=claude-config-${devcontainerId},target=/home/node/.claude,type=volume",
|
||||
"source=${localEnv:HOME}/.ssh/config,target=/home/node/.ssh/config,type=bind,readonly",
|
||||
"source=${localEnv:HOME}/.ssh/known_hosts,target=/home/node/.ssh/known_hosts,type=bind,readonly"
|
||||
"source=${localEnv:HOME}/.ssh/known_hosts,target=/home/node/.ssh/known_hosts,type=bind,readonly",
|
||||
"source=npm-cache,target=/home/node/.npm,type=volume"
|
||||
],
|
||||
"forwardPorts": [3000],
|
||||
"containerEnv": {
|
||||
"NEXT_TELEMETRY_DISABLED": "1",
|
||||
"NPM_CONFIG_UPDATE_NOTIFIER": "false"
|
||||
},
|
||||
"portsAttributes": {
|
||||
"3000": { "label": "App", "onAutoForward": "openBrowser" }
|
||||
},
|
||||
"postCreateCommand": "sudo chown -R node:node /home/node/.claude && cd frontend-starter && npm install",
|
||||
"postCreateCommand": "sudo chown -R node:node /home/node/.claude /home/node/.npm && cd frontend-starter && npm install",
|
||||
"postStartCommand": "cd frontend-starter && npm install --no-audit --no-fund",
|
||||
"postAttachCommand": "cd frontend-starter && npx -y kill-port 3000 && npm run dev",
|
||||
"customizations": {
|
||||
@@ -33,4 +38,5 @@
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -4,4 +4,5 @@ node_modules/
|
||||
.env.*
|
||||
!.env.local.example
|
||||
data/
|
||||
.claude/settings.local.json
|
||||
.claude/settings.local.json
|
||||
next-env.d.ts
|
||||
@@ -1,52 +0,0 @@
|
||||
import { streamText, convertToModelMessages, type UIMessage } from "ai";
|
||||
import { getModel } from "@/lib/ai/model";
|
||||
|
||||
// Streaming-Chat-Endpoint (Muster C). Wird von useChat (Default /api/chat) aufgerufen.
|
||||
//
|
||||
// ACHTUNG: Dieser Endpoint hat KEINEN Login. Wer die Adresse der App kennt, kann hier
|
||||
// auf Kosten des hinterlegten ANTHROPIC_API_KEY chatten (offener KI-Proxy).
|
||||
// Deshalb ist er standardmäßig AUS und läuft nur mit CHAT_ENABLED=true.
|
||||
// Nie mit echtem Key öffentlich veröffentlichen, solange die App keinen Schutz hat
|
||||
// (Login, Zugangscode). Die Grenzen unten dämpfen Missbrauch nur, sie verhindern ihn nicht.
|
||||
|
||||
const MAX_MESSAGES = 40;
|
||||
const MAX_REQUEST_CHARS = 60000;
|
||||
const MAX_OUTPUT_TOKENS = 1024;
|
||||
|
||||
function parseJson(text: string): unknown {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
if (process.env.CHAT_ENABLED !== "true") {
|
||||
return new Response(
|
||||
"KI-Chat ist deaktiviert. Zum Einschalten CHAT_ENABLED=true setzen (nur mit Schutz öffentlich betreiben).",
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
const raw = await req.text();
|
||||
if (raw.length > MAX_REQUEST_CHARS) {
|
||||
return new Response("Anfrage zu groß.", { status: 413 });
|
||||
}
|
||||
|
||||
const body = parseJson(raw) as { messages?: unknown } | null;
|
||||
const messages = body?.messages;
|
||||
if (!Array.isArray(messages) || messages.length === 0) {
|
||||
return new Response("Ungültige Anfrage.", { status: 400 });
|
||||
}
|
||||
if (messages.length > MAX_MESSAGES) {
|
||||
return new Response("Der Chat ist zu lang. Bitte die Seite neu laden.", { status: 413 });
|
||||
}
|
||||
|
||||
const result = streamText({
|
||||
model: getModel(),
|
||||
messages: await convertToModelMessages(messages as UIMessage[]),
|
||||
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
||||
});
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
|
||||
// Muster C: KI-Chat mit Token-Streaming. Ruft POST /api/chat auf.
|
||||
// Achtung: /api/chat hat keinen Login und ist nur mit CHAT_ENABLED=true aktiv (siehe route.ts).
|
||||
// In der Schulung bleibt der Chat aus. KI läuft über einen n8n-Workflow.
|
||||
export default function ChatPage() {
|
||||
const { messages, sendMessage, status, error } = useChat();
|
||||
const [input, setInput] = useState("");
|
||||
const busy = status === "submitted" || status === "streaming";
|
||||
|
||||
function onSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!input.trim() || busy) return;
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<h1 className="ep-ueberschrift text-3xl">KI-Chat (Muster C)</h1>
|
||||
<p className="text-sm text-ink-muted">
|
||||
Streamt Antworten von Claude. Braucht <code>ANTHROPIC_API_KEY</code> und <code>CHAT_ENABLED=true</code>. Ohne
|
||||
Login nie mit echtem Key öffentlich veröffentlichen.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={
|
||||
message.role === "user"
|
||||
? "self-end bg-accent px-3 py-2 text-on-accent"
|
||||
: "self-start border border-hairline bg-surface px-3 py-2"
|
||||
}
|
||||
>
|
||||
{message.parts.map((part, i) => (part.type === "text" ? <span key={i}>{part.text}</span> : null))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{error && <div className="ep-fehler">{error.message}</div>}
|
||||
|
||||
<form onSubmit={onSubmit} className="flex gap-2">
|
||||
<input
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Frag mich etwas …"
|
||||
className="ep-feld flex-1"
|
||||
/>
|
||||
<button type="submit" disabled={busy} className="ep-knopf">
|
||||
Senden
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,12 +4,8 @@ import "./globals.css";
|
||||
import { EpKopf } from "@/components/EpKopf";
|
||||
import { EpFuss } from "@/components/EpFuss";
|
||||
import { APP_BESCHREIBUNG, APP_TITEL } from "@/lib/app";
|
||||
import { Inter } from "next/font/google";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const inter = Inter({subsets:['latin'],variable:'--font-sans'});
|
||||
|
||||
|
||||
// Hausschrift von ep-reisen.de (Lato, freie Lizenz OFL). Die Dateien liegen im Projekt,
|
||||
// damit der Build ohne Internet klappt. Lato hat keine Stärke 600, font-semibold erscheint fett.
|
||||
const lato = localFont({
|
||||
@@ -37,7 +33,7 @@ export const viewport: Viewport = {
|
||||
// E&P-Layout für jede Seite: Kopf mit Logo, Inhalt, Fuß. Seiten liefern nur ihren Inhalt, kein eigenes <main>.
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="de" className={cn("font-sans", inter.variable)}>
|
||||
<html lang="de" className={cn("font-sans", lato.variable)}>
|
||||
<body className="flex min-h-screen flex-col bg-ground text-ink">
|
||||
<a href="#inhalt" className="skip-link">
|
||||
Zum Inhalt springen
|
||||
|
||||
@@ -1,76 +1,17 @@
|
||||
"use client";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { APP_BESCHREIBUNG, APP_TITEL } from "@/lib/app";
|
||||
|
||||
import { useState } from "react";
|
||||
import { postWebhook } from "@/lib/n8n";
|
||||
|
||||
type HelloResponse = { message?: string };
|
||||
|
||||
// Pfad des eigenen Webhooks mit Kürzel, zum Beispiel "anna-hello" (ohne /webhook/).
|
||||
// In der E&P-n8n gehört jeder Pfad genau einem Workflow. Ohne Angabe ruft die Seite nichts auf.
|
||||
const HELLO_PATH = (process.env.NEXT_PUBLIC_HELLO_PATH || "").trim();
|
||||
|
||||
// Beispielseite im E&P-Layout. Kopf und Fuß kommen aus app/layout.tsx,
|
||||
// Farben und Bausteine (ep-karte, ep-feld, ep-knopf, ep-cta, ep-hinweis, ep-fehler) aus app/globals.css.
|
||||
// Startseite im E&P-Layout. Kopf und Fuß kommen aus app/layout.tsx,
|
||||
// Farben und Bausteine (ep-karte, ep-ueberschrift, …) aus app/globals.css.
|
||||
export default function Home() {
|
||||
const [name, setName] = useState("");
|
||||
const [answer, setAnswer] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function onSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setAnswer(null);
|
||||
if (!HELLO_PATH) {
|
||||
setError(
|
||||
"NEXT_PUBLIC_HELLO_PATH fehlt. Beim ersten Veröffentlichen den eigenen Pfad mitgeben, zum Beispiel anna-hello.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await postWebhook<HelloResponse>(HELLO_PATH, { name });
|
||||
setAnswer(res.message ?? JSON.stringify(res));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<h1 className="ep-ueberschrift text-3xl">Hallo aus n8n</h1>
|
||||
<p className="mt-4 text-ink-muted">
|
||||
Das Formular ruft einen n8n-Webhook deines eigenen Workflows auf (Vorlage <code>hello-webhook.json</code>,
|
||||
Pfad <code>/webhook/{HELLO_PATH || "anna-hello"}</code>).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit} className="ep-karte flex flex-col gap-3">
|
||||
<label htmlFor="name" className="ep-label">
|
||||
Dein Name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Max"
|
||||
className="ep-feld"
|
||||
required
|
||||
/>
|
||||
<button type="submit" disabled={loading} className="ep-knopf ep-cta self-start">
|
||||
{loading ? "Sende …" : "An n8n senden"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{answer && <div className="ep-hinweis">{answer}</div>}
|
||||
{error && (
|
||||
<div role="alert" className="ep-fehler">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Card className="ep-karte rounded-none shadow-none ring-0">
|
||||
<CardHeader className="px-0">
|
||||
<h1 className="ep-ueberschrift text-3xl">{APP_TITEL}</h1>
|
||||
</CardHeader>
|
||||
<CardContent className="px-0">
|
||||
<p className="text-ink-muted">{APP_BESCHREIBUNG}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ export function EpFuss() {
|
||||
return (
|
||||
<footer className="mt-12 border-t border-hairline bg-surface">
|
||||
<div className="mx-auto max-w-5xl px-4 py-6 text-sm text-ink-muted sm:px-6">
|
||||
E&P Reisen & Events · entstanden in der KI-Schulung
|
||||
E&P Reisen & Events · interne Anwendung
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "cn"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Card({
|
||||
className,
|
||||
size = "default",
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-4xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-md ring-1 ring-foreground/5 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] dark:ring-foreground/10 *:[img:first-child]:rounded-t-4xl *:[img:last-child]:rounded-b-4xl",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-1.5 rounded-t-4xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn("font-heading text-base font-medium", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-(--card-spacing)", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn(
|
||||
"flex items-center rounded-b-4xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Muster C: Provider und Modell an einer Stelle.
|
||||
// Der Standard-Provider liest ANTHROPIC_API_KEY aus der Umgebung.
|
||||
import { anthropic } from "@ai-sdk/anthropic";
|
||||
|
||||
// Ein günstiges Modell, weil jeder Aufruf Geld kostet (Claude Sonnet 5: 2 $ Input / 10 $ Output pro 1 Mio. Tokens).
|
||||
export const getModel = () => anthropic("claude-sonnet-5");
|
||||
@@ -1,30 +0,0 @@
|
||||
// Muster A: n8n-Webhook aus dem Frontend aufrufen.
|
||||
// Die Basis-Adresse wird beim Bauen eingesetzt (NEXT_PUBLIC_*). Ohne Angabe gilt die E&P-n8n.
|
||||
|
||||
const BASE = (process.env.NEXT_PUBLIC_N8N_BASE || "https://n8n.ep-reisen.app").replace(/\/+$/, "");
|
||||
|
||||
export function webhookUrl(path: string): string {
|
||||
const clean = path.replace(/^\/+/, "");
|
||||
return `${BASE}/webhook/${clean}`;
|
||||
}
|
||||
|
||||
export async function postWebhook<T = unknown>(
|
||||
path: string,
|
||||
body: unknown,
|
||||
): Promise<T> {
|
||||
const res = await fetch(webhookUrl(path), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Webhook-Fehler ${res.status}: ${await res.text()}`);
|
||||
}
|
||||
// n8n kann JSON oder Text zurückgeben:
|
||||
const text = await res.text();
|
||||
try {
|
||||
return JSON.parse(text) as T;
|
||||
} catch {
|
||||
return text as T;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,6 @@
|
||||
export { cn } from "cn"
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
Generated
+15
-177
@@ -8,17 +8,15 @@
|
||||
"name": "frontend-starter",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.0",
|
||||
"@ai-sdk/react": "^3.0.0",
|
||||
"@base-ui/react": "^1.9.0",
|
||||
"ai": "^6.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cn": "^0.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.63.0",
|
||||
"next": "^16.2.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"shadcn": "^4.23.0",
|
||||
"tailwind-merge": "^3.7.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -30,86 +28,6 @@
|
||||
"typescript": "^5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/anthropic": {
|
||||
"version": "3.0.81",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.81.tgz",
|
||||
"integrity": "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.10",
|
||||
"@ai-sdk/provider-utils": "4.0.27"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/gateway": {
|
||||
"version": "3.0.124",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.124.tgz",
|
||||
"integrity": "sha512-h8CrmbSG+8X0C+M/E1M4oiDHYevqwbzAPN+uLRHS0eJaatF2MZ+juNtOHXNOjk7Bsk9mD2RjYMjJO9dFkb9I7Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.10",
|
||||
"@ai-sdk/provider-utils": "4.0.27",
|
||||
"@vercel/oidc": "3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz",
|
||||
"integrity": "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"json-schema": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "4.0.27",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz",
|
||||
"integrity": "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.10",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"eventsource-parser": "^3.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/react": {
|
||||
"version": "3.0.198",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.198.tgz",
|
||||
"integrity": "sha512-ozlxMidzvKXAefvnq95Y34rJ5MipXABIv1bg2RLEnWUBxGrKxNHrYl0fTfi6grTN88wSXMZYaMY2oHMCHDFuJw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "4.0.27",
|
||||
"ai": "6.0.196",
|
||||
"swr": "^2.2.5",
|
||||
"throttleit": "2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
@@ -1503,6 +1421,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
|
||||
"integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -1567,12 +1487,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -1898,33 +1812,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz",
|
||||
"integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="
|
||||
},
|
||||
"node_modules/@vercel/oidc": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz",
|
||||
"integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/ai": {
|
||||
"version": "6.0.196",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.196.tgz",
|
||||
"integrity": "sha512-2T45UeqKL4a11KQ14I5i1YYHOvCFrMF478E1k6PVjlQSGUvXSv4xrxIaQbUL4qgv91DADSbddwv3oR49pPAK3g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/gateway": "3.0.124",
|
||||
"@ai-sdk/provider": "3.0.10",
|
||||
"@ai-sdk/provider-utils": "4.0.27",
|
||||
"@opentelemetry/api": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
@@ -2154,21 +2041,11 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cn": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cn/-/cn-0.5.0.tgz",
|
||||
"integrity": "sha512-JGEixZEpfFXm1P5uXwvEobOFLlHwIWi5v4tjd1ZwfiYmcEilkuD2l+054rvJcYCR1B+uVThHeUbgmNz1FwuxuA==",
|
||||
"bin": {
|
||||
"cn": "bin/cn.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
},
|
||||
"node_modules/code-block-writer": {
|
||||
"version": "13.0.3",
|
||||
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz",
|
||||
@@ -2348,15 +2225,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@@ -2464,15 +2332,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource-parser": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
|
||||
"integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
|
||||
@@ -2877,12 +2736,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@@ -4204,19 +4057,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz",
|
||||
"integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/systeminformation": {
|
||||
"version": "5.33.18",
|
||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.18.tgz",
|
||||
@@ -4242,6 +4082,16 @@
|
||||
"url": "https://www.buymeacoffee.com/systeminfo"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz",
|
||||
"integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
|
||||
@@ -4263,18 +4113,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/throttleit": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
|
||||
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
|
||||
@@ -8,17 +8,15 @@
|
||||
"start": "next start -p 3000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.0",
|
||||
"@ai-sdk/react": "^3.0.0",
|
||||
"@base-ui/react": "^1.9.0",
|
||||
"ai": "^6.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cn": "^0.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.63.0",
|
||||
"next": "^16.2.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"shadcn": "^4.23.0",
|
||||
"tailwind-merge": "^3.7.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -29,4 +27,4 @@
|
||||
"tailwindcss": "^4.3.0",
|
||||
"typescript": "^5.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user