Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 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
b93288a
Merge branch 'main' into feat/support-triage
VITianYash42 Apr 3, 2026
0b00150
fix ui
VITianYash42 Apr 3, 2026
e597773
kill dark mode in root
VITianYash42 Apr 3, 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
41 changes: 41 additions & 0 deletions kits/automation/support-triage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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*
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
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`
Comment thread
amanintech marked this conversation as resolved.

## Lamatic Flow
Flow ID: `your-flow-id`
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 {
Comment thread
amanintech marked this conversation as resolved.
--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: Arial, Helvetica, sans-serif;
}
33 changes: 33 additions & 0 deletions kits/automation/support-triage/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

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

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

const executeFlow = async () => {
setLoading(true);
setTriageData(null);

try {
const response = await lamaticClient.executeFlow(
process.env.NEXT_PUBLIC_LAMATIC_FLOW_ID as string,
{ ticket_text: ticketText }
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const data = response.result || response;

// Intercept the quota error before it breaks the UI
if (data.message && data.message.includes('quota')) {
setTriageData({ error: "API Rate Limit: Free tier restricted. Please wait 60 seconds before testing the next ticket." });
} else {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
setTriageData(data);
}

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

setLoading(false);
};

return (
<div className="min-h-screen bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-gray-900 via-gray-950 to-black 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-500/10 rounded-full mb-4 border border-blue-500/20">
<span className="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-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-indigo-400 to-purple-400 pb-2">
AI Support Triage Engine
</h1>
<p className="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/[0.02] backdrop-blur-xl border border-white/10 rounded-2xl p-6 md:p-8 shadow-2xl">
<label className="block text-sm font-semibold text-gray-300 mb-3 uppercase tracking-wider">
Inbound Customer Ticket
</label>
<textarea
className="w-full p-5 bg-black/40 border border-gray-800 rounded-xl focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 outline-none resize-none text-gray-200 placeholder-gray-600 transition-all duration-300"
rows={5}
value={ticketText}
onChange={(e) => setTicketText(e.target.value)}
placeholder="I was overcharged by $50 on my invoice today and I am furious!"
spellCheck="false"
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

<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-800 disabled:to-gray-900 disabled:text-gray-500 text-white font-bold py-4 px-6 rounded-xl transition-all duration-300 shadow-[0_0_20px_rgba(79,70,229,0.2)] 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-500/10 border border-red-500/20 rounded-xl p-5 flex items-start gap-4">
<div className="bg-red-500/20 p-2 rounded-full mt-0.5">
<svg className="w-5 h-5 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-400 font-bold">Execution Error</h3>
<p className="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/[0.02] border border-white/10 rounded-xl p-6 shadow-lg backdrop-blur-md">
<div className="flex items-center gap-3 mb-2">
<div className="w-2 h-2 rounded-full bg-blue-400 shadow-[0_0_10px_rgba(96,165,250,0.8)]"></div>
<p className="text-xs text-gray-400 uppercase tracking-widest font-semibold">Category</p>
</div>
<p className="text-2xl font-bold text-gray-100">{triageData.category || 'N/A'}</p>
</div>

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

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

{/* AI Draft Response */}
<div className="bg-white/[0.02] border border-white/10 rounded-xl overflow-hidden shadow-lg backdrop-blur-md">
<div className="bg-white/[0.02] border-b border-white/10 px-6 py-4 flex items-center justify-between">
<p className="text-sm font-semibold 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-black/20">
<p className="text-sm md:text-base leading-relaxed whitespace-pre-wrap 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-500 text-sm">
Built for the AgentKit Challenge by Yash Singhal
</p>
</div>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions kits/automation/support-triage/app/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Lamatic } from 'lamatic'

export const lamaticClient = new Lamatic({
endpoint:process.env.NEXT_PUBLIC_LAMATIC_PROJECT_ENDPOINT as string,
projectId: process.env.NEXT_PUBLIC_LAMATIC_PROJECT_ID as string,
apiKey: process.env.NEXT_PUBLIC_LAMATIC_PROJECT_API_KEY as string,
Comment thread
amanintech marked this conversation as resolved.
Outdated
})

@coderabbitai coderabbitai Bot Mar 21, 2026

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 | 🟠 Major

Security: API key exposed to the browser via NEXT_PUBLIC_ prefix.

Environment variables prefixed with NEXT_PUBLIC_ are bundled into the client-side JavaScript and visible to anyone inspecting the page. The NEXT_PUBLIC_LAMATIC_PROJECT_API_KEY will be exposed in the browser, allowing unauthorized use of your API credentials.

Move API calls to a server-side API route (app/api/triage/route.ts) and use non-prefixed environment variables.

🔒 Recommended approach: Create a server-side API route

Create app/api/triage/route.ts:

import { Lamatic } from 'lamatic';
import { NextRequest, NextResponse } from 'next/server';

const lamaticClient = new Lamatic({
  endpoint: process.env.LAMATIC_PROJECT_ENDPOINT!,
  projectId: process.env.LAMATIC_PROJECT_ID!,
  apiKey: process.env.LAMATIC_PROJECT_API_KEY!,
});

export async function POST(request: NextRequest) {
  const { ticket_text } = await request.json();
  
  const response = await lamaticClient.executeFlow(
    process.env.LAMATIC_FLOW_ID!,
    { ticket_text }
  );
  
  return NextResponse.json(response);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please use server side JWT encryptions

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.

@amanintech, understood! Here's the mission plan to implement server-side JWT authentication to lock down the /api/triage route while keeping all credentials out of the browser.

Mission: Server-side JWT Encryption for /api/triage

  • Add jose for Edge-compatible JWT signing/verification
  • Add JWT_SECRET to .env.example
  • Create /api/token route to issue short-lived JWTs server-side
  • Protect /api/triage route with JWT verification
  • Remove utils.ts (the leaked client)
  • Update page.tsx to fetch a token before calling /api/triage

Here's the overall security flow after these changes:

sequenceDiagram
    participant Browser
    participant TokenServer as /api/token (Server)
    participant TriageServer as /api/triage (Server)
    participant Lamatic

    Browser->>+TokenServer: POST (no credentials)
    TokenServer-->>-Browser: { token: "<signed JWT, 2min TTL>" }
    Browser->>+TriageServer: POST Authorization: Bearer <token>
    TriageServer->>+TriageServer: jwtVerify(token, JWT_SECRET)
    TriageServer->>+Lamatic: executeFlow(flowId, { ticket_text })
    Lamatic-->>-TriageServer: triage result
    TriageServer-->>-Browser: { category, sentiment, urgency, draft }
Loading

Key security properties of this approach:

  • 🔒 LAMATIC_PROJECT_API_KEY, LAMATIC_PROJECT_ID, and JWT_SECRET never leave the server
  • ⏱️ JWTs are short-lived (2 min), limiting replay attack windows
  • 🚫 /api/triage rejects any request without a valid, unexpired JWT
  • ✅ No NEXT_PUBLIC_ prefixes needed anywhere

✏️ Learnings added
Learnt from: amanintech
Repo: Lamatic/AgentKit PR: 80
File: kits/automation/support-triage/app/utils.ts:0-0
Timestamp: 2026-03-27T19:13:29.605Z
Learning: In `kits/automation/support-triage`, the maintainer (amanintech) requires server-side JWT authentication for API routes. Use the `jose` library with an `HS256` short-lived JWT (2-minute TTL) issued by `/api/token` and verified in `/api/triage`. The JWT_SECRET must be a non-NEXT_PUBLIC_ env var. All Lamatic credentials (LAMATIC_PROJECT_ENDPOINT, LAMATIC_PROJECT_ID, LAMATIC_PROJECT_API_KEY, LAMATIC_FLOW_ID) must also be non-NEXT_PUBLIC_ and server-only.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: Lamatic/AgentKit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T17:08:51.738Z
Learning: Applies to kits/**/lib/lamatic-client.ts : Use the `lib/lamatic-client.ts` pattern to initialize the Lamatic SDK with `LAMATIC_API_URL`, `LAMATIC_PROJECT_ID`, and `LAMATIC_API_KEY`

Learnt from: CR
Repo: Lamatic/AgentKit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T17:08:51.738Z
Learning: Applies to kits/**/actions/**/*.ts : Kit server action files (e.g., `actions/orchestrate.ts`) must call Lamatic flows via the SDK and read flow IDs from environment variables

Failed to handle agent chat message. Please try again.

23 changes: 23 additions & 0 deletions kits/automation/support-triage/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"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": [],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
"features": ["Sentiment Analysis", "Auto-Drafting", "Urgency Detection"],
"demoUrl": "",
"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;
7 changes: 7 additions & 0 deletions kits/automation/support-triage/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
};

export default nextConfig;
Loading