Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
940d0c4
feat: add support triage kit UI and configuration
VITianYash42 Mar 21, 2026
5e70655
trigger preview build
VITianYash42 Mar 21, 2026
69d4027
style: deploy enterprise glassmorphic UI
VITianYash42 Mar 21, 2026
cbb842d
Fixed some minor bugs
VITianYash42 Mar 21, 2026
04f3aad
refactor: address CodeRabbit review and secure API route
VITianYash42 Mar 21, 2026
afe8151
fix: remove dead utils import
VITianYash42 Mar 21, 2026
51d3b9f
fix: rename route.js to route.ts to resolve build error
VITianYash42 Mar 21, 2026
d696140
Merge branch 'main' into feat/support-triage
amanintech Mar 27, 2026
3f9c961
Merge branch 'main' into feat/support-triage
VITianYash42 Mar 28, 2026
5c592b7
fix: implement defensive initialization and strict error typing
VITianYash42 Mar 28, 2026
17a3de1
chore: add missing flows directory to satisfy CI validation
VITianYash42 Mar 28, 2026
2fc331d
chore: add dummy flow.json to satisfy subdirectory validation
VITianYash42 Mar 28, 2026
00fe5b3
chore: add valid flow schema files
VITianYash42 Mar 28, 2026
8c6539c
Merge branch 'main' into feat/support-triage
VITianYash42 Mar 30, 2026
689aa8a
Merge branch 'main' into feat/support-triage
vrijraj Mar 30, 2026
b1e4705
Merge branch 'Lamatic:main' into feat/support-triage
VITianYash42 Mar 31, 2026
053de0e
fix: resolve light mode text visibility and remove global body bg
VITianYash42 Mar 31, 2026
07516c3
fix: wipe legacy globals.css to fix tailwind gradient
VITianYash42 Mar 31, 2026
2ae78f9
Merge branch 'main' into feat/support-triage
VITianYash42 Apr 1, 2026
688183a
Fix: Light Mode UI
VITianYash42 Apr 1, 2026
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
4 changes: 4 additions & 0 deletions kits/automation/support-triage/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LAMATIC_PROJECT_ENDPOINT=
LAMATIC_PROJECT_ID=
LAMATIC_PROJECT_API_KEY=
LAMATIC_FLOW_ID=
42 changes: 42 additions & 0 deletions kits/automation/support-triage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
!.env.example
17 changes: 17 additions & 0 deletions kits/automation/support-triage/README.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a live demo link to this file? would allow us to view the functionality better, and allow users to see the kit before forking it. otherwise, LGTM. Thanks! Will check once that is fixed and github action will be handled

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@d-pamneja I already have added the live demo link to the README! You can also test the deployment here: https://agent-kit-ten.vercel.app/

Thanks for the review. Let me know if you need anything else to unblock the merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-03-31 at 11 37 51 AM If you could resolve the UI Issues here as well. thanks!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@d-pamneja Sorry for that! I had hardcoded the text colors for a dark theme and missed the light mode toggle. I've updated the Tailwind classes to properly support both light and dark system preferences. The Vercel deployment has been automatically updated with the fix.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# AI Support Triage Engine

## What This Kit Does
Automates customer support by taking inbound tickets and instantly generating a category, sentiment analysis, urgency level, and a draft email response. This saves time, reduces manual routing work, and improves response clarity.

## Providers & Prerequisites
- Lamatic Studio Flow
- Gemini Free Tier (Note: Requires a 60-second cooldown between requests to prevent 429 Rate Limit errors).

## How to Run Locally
1. `cd kits/automation/support-triage`
2. `npm install`
3. `cp .env.example .env.local` and fill in your Lamatic API values.
4. `npm run dev`

## Lamatic Flow
Flow ID: `your-flow-id`
30 changes: 30 additions & 0 deletions kits/automation/support-triage/app/api/triage/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Lamatic } from 'lamatic';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
try {
const endpoint = process.env.LAMATIC_PROJECT_ENDPOINT;
const projectId = process.env.LAMATIC_PROJECT_ID;
const apiKey = process.env.LAMATIC_PROJECT_API_KEY;
const flowId = process.env.LAMATIC_FLOW_ID;

// Fails fast and safely if env vars are missing
if (!endpoint || !projectId || !apiKey || !flowId) {
return NextResponse.json(
{ error: "Missing required Lamatic environment variables on the server." },
{ status: 500 }
);
}

const lamaticClient = new Lamatic({ endpoint, projectId, apiKey });
const { ticket_text } = await request.json();

const response = await lamaticClient.executeFlow(flowId, { ticket_text });
return NextResponse.json(response);
Comment on lines +4 to +23
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Verify /api/token route exists =="
fd -i 'route\.ts$' kits/automation/support-triage/app/api/token

echo
echo "== Verify JWT issuance/verification usage in support-triage API routes =="
rg -nP --type=ts -C2 'SignJWT|jwtVerify|HS256|JWT_SECRET|authorization' kits/automation/support-triage/app/api

Repository: Lamatic/AgentKit

Length of output: 245


🏁 Script executed:

# Search entire support-triage codebase for JWT/token references
find kits/automation/support-triage -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name ".env*" -o -name "*.json" \) | head -20

Repository: Lamatic/AgentKit

Length of output: 475


🏁 Script executed:

# Look for jose library usage or JWT references across entire triage kit
rg -i 'jose|jwtverify|signjwt|jwt_secret|authorization.*bearer' kits/automation/support-triage --type=ts --type=tsx --type=json -l

Repository: Lamatic/AgentKit

Length of output: 87


🏁 Script executed:

# Check the actual triage route file to see complete implementation including error handling
cat kits/automation/support-triage/app/api/triage/route.ts

Repository: Lamatic/AgentKit

Length of output: 1186


🏁 Script executed:

# Check config.json and .env files for environment variable naming
find kits/automation/support-triage -name "config.json" -o -name ".env*" | xargs cat 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 1237


This mission cannot be accepted in its current state: /api/triage is executing privileged Lamatic flows without authentication.

Your route processes API requests on line 4 without verifying bearer tokens. This exposes the Lamatic flow to unauthorized abuse and quota exhaustion. Additionally, your config.json declares NEXT_PUBLIC_LAMATIC_FLOW_ID, but per architectural requirements, all Lamatic credentials must remain server-only non-NEXT_PUBLIC_ environment variables—your code correctly reads LAMATIC_FLOW_ID, creating a configuration inconsistency that will confuse developers.

Critical blockers:

  1. Missing JWT authentication. Gate the route with HS256 JWT verification before line 20 body processing. Use the jose library with tokens issued by an /api/token endpoint (currently missing). The JWT_SECRET environment variable must also be server-only.

  2. config.json environment key violation. Correct "envKey": "NEXT_PUBLIC_LAMATIC_FLOW_ID" to "envKey": "LAMATIC_FLOW_ID" to align with .env.example and maintain server-side credential isolation.

🔐 JWT verification patch (before line 20)
 import { Lamatic } from 'lamatic';
+import { jwtVerify } from 'jose';
 import { NextRequest, NextResponse } from 'next/server';

 export async function POST(request: NextRequest) {
   try {
+    const authHeader = request.headers.get("authorization");
+    if (!authHeader?.startsWith("Bearer ")) {
+      return NextResponse.json({ error: "Missing bearer token." }, { status: 401 });
+    }
+
+    const jwtSecret = process.env.JWT_SECRET;
+    if (!jwtSecret) {
+      return NextResponse.json({ error: "Missing JWT_SECRET on server." }, { status: 500 });
+    }
+
+    try {
+      const token = authHeader.slice("Bearer ".length);
+      await jwtVerify(token, new TextEncoder().encode(jwtSecret), {
+        algorithms: ["HS256"],
+      });
+    } catch {
+      return NextResponse.json({ error: "Invalid or expired token." }, { status: 401 });
+    }
+
     const endpoint = process.env.LAMATIC_PROJECT_ENDPOINT;

Outstanding issues from prior review:

  • Validate ticket_text input before passing to executeFlow
  • Map Lamatic response status errors (e.g., response.status >= 400) before returning raw response
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kits/automation/support-triage/app/api/triage/route.ts` around lines 4 - 23,
The POST route is unprotected and uses a public env key; add HS256 JWT
verification at the start of the POST handler (before parsing the body and
calling lamaticClient.executeFlow) using the `jose` library and the server-only
`JWT_SECRET` env var to reject requests with invalid/missing bearer tokens, then
proceed only on successful verification; update your config.json entry from
"envKey": "NEXT_PUBLIC_LAMATIC_FLOW_ID" to "envKey": "LAMATIC_FLOW_ID" to keep
the flow ID server-only; additionally, validate `ticket_text` (non-empty string)
before calling `Lamatic.executeFlow` and inspect the Lamatic response (treat
response.status >= 400 as an error and map it to a proper NextResponse with an
error status/message) so you never return raw error payloads directly.


} catch (error: unknown) {
// Safely narrows the 'unknown' error type
const errorMessage = error instanceof Error ? error.message : "API execution failed";
return NextResponse.json({ error: errorMessage }, { status: 500 });
}
}
Binary file added kits/automation/support-triage/app/favicon.ico
Binary file not shown.
26 changes: 26 additions & 0 deletions kits/automation/support-triage/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@import "tailwindcss";

:root {
--background: #ffffff;
--foreground: #171717;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans), Arial, Helvetica, sans-serif;
}
34 changes: 34 additions & 0 deletions kits/automation/support-triage/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata = {
title: "AI Support Triage Engine",
description: "Automated customer support ticket categorization, sentiment analysis, and draft response generation",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
suppressHydrationWarning
>
<body className="min-h-full flex flex-col">{children}</body>
</html>
);
}
169 changes: 169 additions & 0 deletions kits/automation/support-triage/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
'use client';
import React, { useState } from 'react';

export default function Page() {
const [ticketText, setTicketText] = useState('');
const [loading, setLoading] = useState(false);
const [triageData, setTriageData] = useState<any>(null);

const executeFlow = async () => {
const normalizedTicket = ticketText.trim();
if (!normalizedTicket) {
setTriageData({ error: "Please enter a ticket before processing." });
return;
}

setLoading(true);
setTriageData(null);

try {
const res = await fetch('/api/triage', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ticket_text: normalizedTicket })
});

const response = await res.json();
const data = response.result || response;

// Improved Rate Limit Detection
const message = typeof data?.message === 'string' ? data.message.toLowerCase() : '';
const isRateLimited = message.includes('quota') || message.includes('rate limit') || data?.status === 429;

if (isRateLimited) {
setTriageData({ error: "API Rate Limit: Free tier restricted. Please wait 60 seconds before testing the next ticket." });
} else if (data.error) {
setTriageData({ error: data.error });
} else {
setTriageData(data);
}

} catch (error) {
console.error("Execution error:", error);
setTriageData({ error: "Failed to connect to AI triage." });
} finally {
setLoading(false);
}
};

return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-white to-cyan-100 dark:from-gray-950 dark:via-gray-900 dark:to-black text-gray-900 dark:text-gray-100 p-4 md:p-8 font-sans selection:bg-blue-500/30">
<div className="max-w-3xl mx-auto space-y-8 mt-10 md:mt-20">

{/* Header */}
<div className="text-center space-y-4">
<div className="inline-flex items-center justify-center p-2 bg-blue-100 dark:bg-blue-500/10 rounded-full mb-4 border border-blue-200 dark:border-blue-500/20">
<span className="text-blue-600 dark:text-blue-400 text-xs font-bold tracking-widest uppercase px-3">AgentKit Challenge</span>
</div>
<h1 className="text-4xl md:text-5xl font-extrabold tracking-tight text-gray-900 dark:text-transparent dark:bg-clip-text dark:bg-gradient-to-r dark:from-blue-400 dark:via-indigo-400 dark:to-purple-400 pb-2">
AI Support Triage Engine
</h1>
<p className="text-gray-600 dark:text-gray-400 text-lg max-w-xl mx-auto">
Instantly categorize issues, analyze customer sentiment, and draft responses using Lamatic flows.
</p>
</div>

{/* Input Card */}
<div className="bg-white dark:bg-white/[0.02] backdrop-blur-xl border border-gray-200 dark:border-white/10 rounded-2xl p-6 md:p-8 shadow-xl dark:shadow-2xl">
<label htmlFor="ticketText" className="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3 uppercase tracking-wider">
Inbound Customer Ticket
</label>
<textarea
id="ticketText"
className="w-full p-5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl focus:ring-2 focus:ring-blue-500/50 outline-none resize-none text-gray-900 dark:text-gray-100 placeholder-gray-400 shadow-sm transition-all duration-300"
value={ticketText}
onChange={(e) => setTicketText(e.target.value)}
placeholder="I was overcharged by $50 on my invoice today and I am furious!"
spellCheck="false"
/>

<button
onClick={executeFlow}
disabled={loading}
className="mt-6 w-full group relative overflow-hidden bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 disabled:from-gray-300 disabled:to-gray-400 dark:disabled:from-gray-800 dark:disabled:to-gray-900 disabled:text-gray-500 text-white font-bold py-4 px-6 rounded-xl transition-all duration-300 shadow-md dark:shadow-[0_0_20px_rgba(79,70,229,0.2)] hover:shadow-lg dark:hover:shadow-[0_0_30px_rgba(79,70,229,0.4)] disabled:shadow-none flex justify-center items-center"
>
{loading ? (
<span className="flex items-center gap-2">
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing Ticket...
</span>
) : 'Process Ticket'}
</button>
</div>

{/* Error State */}
{triageData?.error && (
<div className="bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20 rounded-xl p-5 flex items-start gap-4">
<div className="bg-red-100 dark:bg-red-500/20 p-2 rounded-full mt-0.5">
<svg className="w-5 h-5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h3 className="text-red-800 dark:text-red-400 font-bold">Execution Error</h3>
<p className="text-red-600 dark:text-red-300/80 text-sm mt-1">{triageData.error}</p>
</div>
</div>
)}

{/* Results Area */}
{triageData && !triageData.error && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-700">
{/* Metrics Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white dark:bg-white/[0.02] border border-gray-200 dark:border-white/10 rounded-xl p-6 shadow-md dark:shadow-lg backdrop-blur-md">
<div className="flex items-center gap-3 mb-2">
<div className="w-2 h-2 rounded-full bg-blue-500 dark:bg-blue-400 shadow-[0_0_10px_rgba(59,130,246,0.5)] dark:shadow-[0_0_10px_rgba(96,165,250,0.8)]"></div>
<p className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-widest font-semibold">Category</p>
</div>
<p className="text-2xl font-bold text-gray-900 dark:text-gray-100">{triageData.category || 'N/A'}</p>
</div>

<div className="bg-white dark:bg-white/[0.02] border border-gray-200 dark:border-white/10 rounded-xl p-6 shadow-md dark:shadow-lg backdrop-blur-md">
<div className="flex items-center gap-3 mb-2">
<div className="w-2 h-2 rounded-full bg-emerald-500 dark:bg-emerald-400 shadow-[0_0_10px_rgba(16,185,129,0.5)] dark:shadow-[0_0_10px_rgba(52,211,153,0.8)]"></div>
<p className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-widest font-semibold">Sentiment</p>
</div>
<p className="text-2xl font-bold text-gray-900 dark:text-gray-100">{triageData.sentiment || 'N/A'}</p>
</div>

<div className="bg-white dark:bg-white/[0.02] border border-gray-200 dark:border-white/10 rounded-xl p-6 shadow-md dark:shadow-lg backdrop-blur-md">
<div className="flex items-center gap-3 mb-2">
<div className="w-2 h-2 rounded-full bg-rose-500 dark:bg-rose-400 shadow-[0_0_10px_rgba(244,63,94,0.5)] dark:shadow-[0_0_10px_rgba(251,113,133,0.8)]"></div>
<p className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-widest font-semibold">Urgency</p>
</div>
<p className="text-2xl font-bold text-gray-900 dark:text-gray-100">{triageData.urgency || 'N/A'}</p>
</div>
</div>

{/* AI Draft Response */}
<div className="bg-white dark:bg-white/[0.02] border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden shadow-md dark:shadow-lg backdrop-blur-md">
<div className="bg-gray-50 dark:bg-white/[0.02] border-b border-gray-200 dark:border-white/10 px-6 py-4 flex items-center justify-between">
<p className="text-sm font-semibold text-indigo-600 dark:text-indigo-400 flex items-center gap-2">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
Generated AI Draft
</p>
</div>
<div className="p-6 bg-white dark:bg-black/20">
<p className="text-sm md:text-base leading-relaxed whitespace-pre-wrap text-gray-800 dark:text-gray-300 font-medium">
{triageData.draft || JSON.stringify(triageData, null, 2)}
</p>
</div>
</div>
</div>
)}
{/* Subtle Credit Footer */}
<div className="text-center pt-8 pb-4">
<p className="text-gray-400 dark:text-gray-500 text-sm">
Built for the AgentKit Challenge by Yash Singhal
</p>
</div>
</div>
</div>
);
}
32 changes: 32 additions & 0 deletions kits/automation/support-triage/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "AI Support Triage Engine",
"description": "Automated triage system that categorizes, analyzes sentiment, and drafts responses for customer support tickets.",
"tags": ["🤖 Automation", "💬 Assistant"],
"author": {
"name": "Yash Singhal",
"email": "yashjee979@gmail.com",
"url": "https://github.com/VITianYash42"
},
"steps": [
{
"id": "your-flow-id",
"type": "mandatory",
"envKey": "NEXT_PUBLIC_LAMATIC_FLOW_ID"
}
],
"integrations": [
{
"name": "Lamatic Studio Flow",
"type": "AI Framework"
},
{
"name": "Gemini Free Tier",
"type": "LLM"
}
],
"features": ["Sentiment Analysis", "Auto-Drafting", "Urgency Detection"],
"demoUrl": "https://agent-kit-git-feat-suppo-3f22fd-yash-singhals-projects-d43367ba.vercel.app/",
"githubUrl": "https://github.com/VITianYash42/AgentKit/tree/feat/support-triage/kits/automation/support-triage",
"deployUrl": "",
"documentationUrl": ""
}
18 changes: 18 additions & 0 deletions kits/automation/support-triage/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);

export default eslintConfig;
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Support Triage Flow
This flow takes an inbound customer ticket and automatically routes it based on category and sentiment.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "Support Triage Flow",
"type": "flow"
}
Loading
Loading