diff --git a/kits/agentic/generation/.gitignore b/kits/agentic/generation/.gitignore
index b28ea810..63904427 100644
--- a/kits/agentic/generation/.gitignore
+++ b/kits/agentic/generation/.gitignore
@@ -18,7 +18,8 @@ yarn-error.log*
# env files
.env
-
+.env.local
+.env.*
# vercel
.vercel
diff --git a/kits/agentic/interview-automation/.env.example b/kits/agentic/interview-automation/.env.example
new file mode 100644
index 00000000..65d0b75c
--- /dev/null
+++ b/kits/agentic/interview-automation/.env.example
@@ -0,0 +1,4 @@
+AUTOMATION_INTERVIEW_AUTOMATION="AUTOMATION_INTERVIEW_AUTOMATION Flow ID"
+LAMATIC_API_URL="LAMATIC_API_URL"
+LAMATIC_PROJECT_ID="LAMATIC_PROJECT_ID"
+LAMATIC_API_KEY="LAMATIC_API_KEY"
\ No newline at end of file
diff --git a/kits/agentic/interview-automation/.gitignore b/kits/agentic/interview-automation/.gitignore
new file mode 100644
index 00000000..4fa2e808
--- /dev/null
+++ b/kits/agentic/interview-automation/.gitignore
@@ -0,0 +1,33 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files
+.env
+.env.local
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# package manager
+package-lock.json
+yarn.lock
+pnpm-lock.yaml
+bun.lockb
\ No newline at end of file
diff --git a/kits/agentic/interview-automation/.npmrc b/kits/agentic/interview-automation/.npmrc
new file mode 100644
index 00000000..5e3cde50
--- /dev/null
+++ b/kits/agentic/interview-automation/.npmrc
@@ -0,0 +1 @@
+force=true
diff --git a/kits/agentic/interview-automation/README.md b/kits/agentic/interview-automation/README.md
new file mode 100644
index 00000000..54bde103
--- /dev/null
+++ b/kits/agentic/interview-automation/README.md
@@ -0,0 +1,89 @@
+# Interview Automation Kit (Next.js)
+
+Run a complete interview assistant in one app: capture live speech, build a transcript in real time, and send it to Lamatic for structured interview insights.
+
+## The Story
+
+Interviews move fast. Notes get messy. Signals get missed.
+
+This kit helps you keep up by turning spoken conversation into an always-visible transcript, then transforming that transcript into useful guidance:
+
+- What happened (summary)
+- What mattered (key signals)
+- What to ask next (follow-ups)
+- What to decide (recommendation)
+
+You can start with this as a local prototype and evolve it into a production interview copilot.
+
+## How It Works
+
+1. The browser captures speech with Web Speech API.
+2. The UI shows interim and final transcript text as the interview progresses.
+3. On Analyze, the full transcript is sent to a Next.js server action.
+4. The server action invokes your Lamatic workflow using your configured workflow ID.
+5. Lamatic returns analysis output that is rendered in the app.
+
+## Why This Kit Is Next.js Only
+
+Everything lives in one project by design:
+
+- Frontend: camera + transcript UI
+- Backend: server action for Lamatic workflow execution
+- Configuration: environment variables + kit metadata
+
+No separate Express service is required for this starter.
+
+## What You Get Out Of The Box
+
+- Live transcript experience with start, stop, and reset controls
+- Optional camera preview for interview context
+- Lamatic workflow integration through a single server action
+- Clear environment-variable driven setup
+
+## Environment Variables
+
+Create a `.env.local` file in this kit folder:
+
+```bash
+AUTOMATION_INTERVIEW_AUTOMATION="YOUR_FLOW_ID"
+LAMATIC_API_URL="YOUR_LAMATIC_API_URL"
+LAMATIC_PROJECT_ID="YOUR_PROJECT_ID"
+LAMATIC_API_KEY="YOUR_API_KEY"
+```
+
+## Local Development
+
+```bash
+npm install
+npm run dev
+```
+
+Then open `http://localhost:3000`.
+
+## Lamatic Workflow Requirements
+
+Configure a Lamatic flow that:
+
+1. Accepts transcript text as input.
+2. Performs interview analysis.
+3. Returns these fields (recommended):
+ - `summary`
+ - `keySignals`
+ - `followUps`
+ - `recommendation`
+
+Set the deployed workflow ID in `AUTOMATION_INTERVIEW_AUTOMATION`.
+
+## Suggested User Journey
+
+1. Start camera (optional).
+2. Start live transcription.
+3. Conduct interview normally.
+4. Stop transcription.
+5. Click Analyze Transcript.
+6. Review summary and recommendations.
+
+## Production Notes
+
+- Browser speech recognition support is strongest in Chrome and Edge.
+- For multi-speaker or higher-accuracy transcription, replace browser STT with a streaming provider, while keeping the same UI and Lamatic analysis flow.
diff --git a/kits/agentic/interview-automation/actions/orchestrate.ts b/kits/agentic/interview-automation/actions/orchestrate.ts
new file mode 100644
index 00000000..481b9ff2
--- /dev/null
+++ b/kits/agentic/interview-automation/actions/orchestrate.ts
@@ -0,0 +1,83 @@
+"use server"
+
+import { lamaticClient } from "@/lib/lamatic-client"
+import { config } from "../orchestrate.js"
+
+type AnalysisInput = {
+ transcript: string
+}
+
+type AnalysisResult = {
+ summary: string
+}
+
+const analysisFlow = config.flows.interviewAnalysis
+
+export async function analyzeInterviewTranscript(input: AnalysisInput): Promise<{
+ success: boolean
+ result?: AnalysisResult
+ error?: string
+}> {
+ try {
+ if (!analysisFlow?.workflowId) {
+ throw new Error("Workflow not found in config.")
+ }
+
+ if (!input.transcript.trim()) {
+ throw new Error("Transcript is empty. Start transcription before analysis.")
+ }
+
+ const inputType = "text"
+ const instructions = input.transcript
+
+ console.log("[v0] Generating content with:", { inputType, instructions })
+ console.log("[v0] Using workflow:", analysisFlow.name, analysisFlow.workflowId)
+
+ const workflowInput = {
+ mode: inputType,
+ instructions,
+ userPrompt: instructions,
+ prompt: instructions,
+ type: inputType,
+ }
+
+ console.log("[v0] Sending inputs:", workflowInput)
+ const response = await lamaticClient.executeFlow(analysisFlow.workflowId, workflowInput)
+ console.log("[v0] Raw response:", response)
+ if (response?.status === "error") {
+ throw new Error(response.message || "Lamatic workflow execution failed")
+ }
+
+ const result = response?.result || {}
+
+ // Support multiple output shapes from deployed Lamatic workflows.
+ const output = {
+ summary: result.summary || result.output?.summary || result.response || "",
+ }
+
+ if (!output.summary) {
+ throw new Error("No analysis summary returned from workflow")
+ }
+
+ return {
+ success: true,
+ result: output,
+ }
+ } catch (error) {
+ let errorMessage = "Unknown error occurred"
+
+ if (error instanceof Error) {
+ errorMessage = error.message
+ if (error.message.includes("fetch failed")) {
+ errorMessage = "Network error: Unable to connect to Lamatic service."
+ } else if (error.message.includes("API key")) {
+ errorMessage = "Authentication error: Please check your Lamatic API credentials."
+ }
+ }
+
+ return {
+ success: false,
+ error: errorMessage,
+ }
+ }
+}
diff --git a/kits/agentic/interview-automation/app/globals.css b/kits/agentic/interview-automation/app/globals.css
new file mode 100644
index 00000000..f4a49a29
--- /dev/null
+++ b/kits/agentic/interview-automation/app/globals.css
@@ -0,0 +1,153 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+
+ .prose-invert {
+ color: rgb(255, 255, 255);
+ }
+
+ .prose-invert p,
+ .prose-invert ul,
+ .prose-invert ol,
+ .prose-invert li,
+ .prose-invert strong,
+ .prose-invert em,
+ .prose-invert span,
+ .prose-invert div {
+ color: rgb(229, 231, 235) !important;
+ }
+
+ .prose-invert strong {
+ color: rgb(255, 255, 255) !important;
+ font-weight: 600;
+ }
+
+ .prose-invert ul,
+ .prose-invert ol {
+ padding-left: 1.5rem;
+ margin: 0.5rem 0;
+ }
+
+ .prose-invert li {
+ margin: 0.25rem 0;
+ }
+}
diff --git a/kits/agentic/interview-automation/app/layout.tsx b/kits/agentic/interview-automation/app/layout.tsx
new file mode 100644
index 00000000..5f0fbdc0
--- /dev/null
+++ b/kits/agentic/interview-automation/app/layout.tsx
@@ -0,0 +1,28 @@
+import type React from "react"
+import type { Metadata } from "next"
+import { GeistSans } from "geist/font/sans"
+import { GeistMono } from "geist/font/mono"
+import { Analytics } from "@vercel/analytics/next"
+import { Suspense } from "react"
+import "./globals.css"
+
+export const metadata: Metadata = {
+ title: "Interview Automation Kit - Live Transcription and Analysis",
+ description: "Capture interview speech in real time and run Lamatic-powered transcript analysis.",
+ generator: "v0.app",
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+ Loading...}>{children}
+
+
+
+ )
+}
diff --git a/kits/agentic/interview-automation/app/page.tsx b/kits/agentic/interview-automation/app/page.tsx
new file mode 100644
index 00000000..2c7bc246
--- /dev/null
+++ b/kits/agentic/interview-automation/app/page.tsx
@@ -0,0 +1,236 @@
+"use client"
+
+import { useMemo, useState } from "react"
+import { analyzeInterviewTranscript } from "@/actions/orchestrate"
+import { useCameraPreview } from "@/hooks/use-camera-preview"
+import { useLiveTranscription } from "@/hooks/use-live-transcription"
+
+type AnalysisResult = {
+ summary: string
+}
+
+export default function InterviewAutomationPage() {
+ const [isAnalyzing, setIsAnalyzing] = useState(false)
+ const [analysisError, setAnalysisError] = useState("")
+ const [analysisResult, setAnalysisResult] = useState(null)
+
+ const {
+ videoRef,
+ isSupported: isCameraSupported,
+ isCameraOn,
+ error: cameraError,
+ startCamera,
+ stopCamera,
+ } = useCameraPreview()
+
+ const {
+ isSupported,
+ isListening,
+ transcript,
+ interimTranscript,
+ error,
+ start,
+ stop,
+ reset,
+ } = useLiveTranscription()
+
+ const mergedTranscript = useMemo(
+ () => `${transcript}${interimTranscript ? ` ${interimTranscript}` : ""}`.trim(),
+ [transcript, interimTranscript],
+ )
+
+ const handleAnalyze = async () => {
+ setAnalysisError("")
+ setAnalysisResult(null)
+
+ if (!mergedTranscript.trim()) {
+ setAnalysisError("No transcript found. Start speaking first.")
+ return
+ }
+
+ setIsAnalyzing(true)
+
+ const response = await analyzeInterviewTranscript({
+ transcript: mergedTranscript,
+ })
+
+ setIsAnalyzing(false)
+
+ if (!response.success) {
+ setAnalysisError(response.error || "Analysis failed")
+ return
+ }
+
+ setAnalysisResult(response.result || null)
+ }
+
+ return (
+
+
+
+
+ Automation Kit
+
+ Interview Automation - Live Transcription
+
+ Capture speech in real time, review transcript as the interview happens, and run Lamatic analysis for
+ summary, interviewer signals, follow-ups, and recommendation.
+
+
+
+ {!isSupported && (
+
+ This browser does not support Web Speech API live recognition. Use Chrome or Edge for local real-time
+ transcription.
+
+ )}
+
+ {error && (
+
{error}
+ )}
+
+ {cameraError && (
+
+ {cameraError}
+
+ )}
+
+
+
+
+
Camera Preview
+
+ {isCameraOn ? "Camera On" : "Camera Off"}
+
+
+
+
+ {isCameraOn ? (
+
+ ) : (
+
+ Enable the camera for a more natural interview setup.
+
+ )}
+
+
+
+
+ Start Camera
+
+
+ Stop Camera
+
+
+
+
+
+
+
+
Live Transcript
+
+ {isListening ? "Listening" : "Idle"}
+
+
+
+
+ {mergedTranscript ? (
+ <>
+ {transcript}
+ {interimTranscript && {interimTranscript} }
+ >
+ ) : (
+ Start the mic to transcribe interview speech live.
+ )}
+
+
+
+
+ Start Live Transcription
+
+
+ Stop
+
+ {
+ reset()
+ setAnalysisResult(null)
+ setAnalysisError("")
+ }}
+ className="rounded-lg border border-white/20 px-4 py-2 text-sm font-medium transition hover:bg-white/10"
+ >
+ Reset
+
+
+
+
+
+ Analysis uses only the transcript content from the live stream.
+
+
+
+ {isAnalyzing ? "Analyzing..." : "Analyze Transcript"}
+
+
+ {analysisError && (
+
+ {analysisError}
+
+ )}
+
+
+
+
+ Lamatic Analysis
+
+ {analysisResult ? (
+
+
+
Summary
+
{analysisResult.summary}
+
+
+ ) : (
+ Run transcript analysis to see the output here.
+ )}
+
+
+
+
+
+ )
+}
diff --git a/kits/agentic/interview-automation/components.json b/kits/agentic/interview-automation/components.json
new file mode 100644
index 00000000..4ee62ee1
--- /dev/null
+++ b/kits/agentic/interview-automation/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/kits/agentic/interview-automation/components/theme-provider.tsx b/kits/agentic/interview-automation/components/theme-provider.tsx
new file mode 100644
index 00000000..55c2f6eb
--- /dev/null
+++ b/kits/agentic/interview-automation/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/kits/agentic/interview-automation/components/ui/accordion.tsx b/kits/agentic/interview-automation/components/ui/accordion.tsx
new file mode 100644
index 00000000..e538a33b
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/accordion.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import * as React from 'react'
+import * as AccordionPrimitive from '@radix-ui/react-accordion'
+import { ChevronDownIcon } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180',
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/kits/agentic/interview-automation/components/ui/alert-dialog.tsx b/kits/agentic/interview-automation/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..97044526
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+'use client'
+
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+import { cn } from '@/lib/utils'
+import { buttonVariants } from '@/components/ui/button'
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/kits/agentic/interview-automation/components/ui/alert.tsx b/kits/agentic/interview-automation/components/ui/alert.tsx
new file mode 100644
index 00000000..e6751abe
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const alertVariants = cva(
+ 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
+ {
+ variants: {
+ variant: {
+ default: 'bg-card text-card-foreground',
+ destructive:
+ 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/kits/agentic/interview-automation/components/ui/aspect-ratio.tsx b/kits/agentic/interview-automation/components/ui/aspect-ratio.tsx
new file mode 100644
index 00000000..40bb1208
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+export { AspectRatio }
diff --git a/kits/agentic/interview-automation/components/ui/avatar.tsx b/kits/agentic/interview-automation/components/ui/avatar.tsx
new file mode 100644
index 00000000..aa98465a
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+'use client'
+
+import * as React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+
+import { cn } from '@/lib/utils'
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/kits/agentic/interview-automation/components/ui/badge.tsx b/kits/agentic/interview-automation/components/ui/badge.tsx
new file mode 100644
index 00000000..fc4126b7
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const badgeVariants = cva(
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
+ secondary:
+ 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
+ destructive:
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'span'> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'span'
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/kits/agentic/interview-automation/components/ui/breadcrumb.tsx b/kits/agentic/interview-automation/components/ui/breadcrumb.tsx
new file mode 100644
index 00000000..1750ff26
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { ChevronRight, MoreHorizontal } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
+ return
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : 'a'
+
+ return (
+
+ )
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<'li'>) {
+ return (
+ svg]:size-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/kits/agentic/interview-automation/components/ui/button-group.tsx b/kits/agentic/interview-automation/components/ui/button-group.tsx
new file mode 100644
index 00000000..09d44309
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+import { Separator } from '@/components/ui/separator'
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ '[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
+ vertical:
+ 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
+ },
+ },
+ defaultVariants: {
+ orientation: 'horizontal',
+ },
+ },
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : 'div'
+
+ return (
+
+ )
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = 'vertical',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/kits/agentic/interview-automation/components/ui/button.tsx b/kits/agentic/interview-automation/components/ui/button.tsx
new file mode 100644
index 00000000..f64632d1
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/button.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary:
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ ghost:
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ 'icon-sm': 'size-8',
+ 'icon-lg': 'size-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : 'button'
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/kits/agentic/interview-automation/components/ui/calendar.tsx b/kits/agentic/interview-automation/components/ui/calendar.tsx
new file mode 100644
index 00000000..eaa373e2
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/calendar.tsx
@@ -0,0 +1,213 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from 'lucide-react'
+import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker'
+
+import { cn } from '@/lib/utils'
+import { Button, buttonVariants } from '@/components/ui/button'
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = 'label',
+ buttonVariant = 'ghost',
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps['variant']
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className,
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString('default', { month: 'short' }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn('w-fit', defaultClassNames.root),
+ months: cn(
+ 'flex gap-4 flex-col md:flex-row relative',
+ defaultClassNames.months,
+ ),
+ month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
+ nav: cn(
+ 'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',
+ defaultClassNames.nav,
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
+ defaultClassNames.button_previous,
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
+ defaultClassNames.button_next,
+ ),
+ month_caption: cn(
+ 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
+ defaultClassNames.month_caption,
+ ),
+ dropdowns: cn(
+ 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
+ defaultClassNames.dropdowns,
+ ),
+ dropdown_root: cn(
+ 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
+ defaultClassNames.dropdown_root,
+ ),
+ dropdown: cn(
+ 'absolute bg-popover inset-0 opacity-0',
+ defaultClassNames.dropdown,
+ ),
+ caption_label: cn(
+ 'select-none font-medium',
+ captionLayout === 'label'
+ ? 'text-sm'
+ : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
+ defaultClassNames.caption_label,
+ ),
+ table: 'w-full border-collapse',
+ weekdays: cn('flex', defaultClassNames.weekdays),
+ weekday: cn(
+ 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
+ defaultClassNames.weekday,
+ ),
+ week: cn('flex w-full mt-2', defaultClassNames.week),
+ week_number_header: cn(
+ 'select-none w-(--cell-size)',
+ defaultClassNames.week_number_header,
+ ),
+ week_number: cn(
+ 'text-[0.8rem] select-none text-muted-foreground',
+ defaultClassNames.week_number,
+ ),
+ day: cn(
+ 'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
+ defaultClassNames.day,
+ ),
+ range_start: cn(
+ 'rounded-l-md bg-accent',
+ defaultClassNames.range_start,
+ ),
+ range_middle: cn('rounded-none', defaultClassNames.range_middle),
+ range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
+ today: cn(
+ 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
+ defaultClassNames.today,
+ ),
+ outside: cn(
+ 'text-muted-foreground aria-selected:text-muted-foreground',
+ defaultClassNames.outside,
+ ),
+ disabled: cn(
+ 'text-muted-foreground opacity-50',
+ defaultClassNames.disabled,
+ ),
+ hidden: cn('invisible', defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === 'left') {
+ return (
+
+ )
+ }
+
+ if (orientation === 'right') {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+ span]:text-xs [&>span]:opacity-70',
+ defaultClassNames.day,
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+export { Calendar, CalendarDayButton }
diff --git a/kits/agentic/interview-automation/components/ui/card.tsx b/kits/agentic/interview-automation/components/ui/card.tsx
new file mode 100644
index 00000000..db7dd3cc
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/kits/agentic/interview-automation/components/ui/carousel.tsx b/kits/agentic/interview-automation/components/ui/carousel.tsx
new file mode 100644
index 00000000..d4a768e7
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/carousel.tsx
@@ -0,0 +1,241 @@
+'use client'
+
+import * as React from 'react'
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from 'embla-carousel-react'
+import { ArrowLeft, ArrowRight } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/button'
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: 'horizontal' | 'vertical'
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error('useCarousel must be used within a ')
+ }
+
+ return context
+}
+
+function Carousel({
+ orientation = 'horizontal',
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === 'horizontal' ? 'x' : 'y',
+ },
+ plugins,
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === 'ArrowLeft') {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === 'ArrowRight') {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext],
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) return
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) return
+ onSelect(api)
+ api.on('reInit', onSelect)
+ api.on('select', onSelect)
+
+ return () => {
+ api?.off('select', onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<'div'>) {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<'div'>) {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselPrevious({
+ className,
+ variant = 'outline',
+ size = 'icon',
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+
+ Previous slide
+
+ )
+}
+
+function CarouselNext({
+ className,
+ variant = 'outline',
+ size = 'icon',
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+
+ Next slide
+
+ )
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/kits/agentic/interview-automation/components/ui/chart.tsx b/kits/agentic/interview-automation/components/ui/chart.tsx
new file mode 100644
index 00000000..421fe589
--- /dev/null
+++ b/kits/agentic/interview-automation/components/ui/chart.tsx
@@ -0,0 +1,353 @@
+'use client'
+
+import * as React from 'react'
+import * as RechartsPrimitive from 'recharts'
+
+import { cn } from '@/lib/utils'
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: '', dark: '.dark' } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error('useChart must be used within a ')
+ }
+
+ return context
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<'div'> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >['children']
+}) {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+