Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion kits/agentic/generation/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ yarn-error.log*

# env files
.env

.env.local
.env.*
# vercel
.vercel

Expand Down
4 changes: 4 additions & 0 deletions kits/agentic/interview-automation/.env.example
Original file line number Diff line number Diff line change
@@ -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"
33 changes: 33 additions & 0 deletions kits/agentic/interview-automation/.gitignore
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions kits/agentic/interview-automation/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
force=true
89 changes: 89 additions & 0 deletions kits/agentic/interview-automation/README.md
Original file line number Diff line number Diff line change
@@ -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.
83 changes: 83 additions & 0 deletions kits/agentic/interview-automation/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
153 changes: 153 additions & 0 deletions kits/agentic/interview-automation/app/globals.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading