-
Notifications
You must be signed in to change notification settings - Fork 115
Feat: Support Triage [agentkit-challenge] #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
940d0c4
5e70655
69d4027
cbb842d
04f3aad
afe8151
51d3b9f
d696140
3f9c961
5c592b7
17a3de1
2fc331d
00fe5b3
8c6539c
689aa8a
b1e4705
053de0e
07516c3
2ae78f9
688183a
b93288a
0b00150
e597773
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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= |
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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` | ||
|
amanintech marked this conversation as resolved.
|
||
|
|
||
| ## Lamatic Flow | ||
| Flow ID: `your-flow-id` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| 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) { | ||
| try { | ||
| const { ticket_text } = await request.json(); | ||
| const flowId = process.env.LAMATIC_FLOW_ID; | ||
|
|
||
| if (!flowId) { | ||
| return NextResponse.json({ error: "Missing LAMATIC_FLOW_ID configuration." }, { status: 500 }); | ||
| } | ||
|
|
||
| const response = await lamaticClient.executeFlow(flowId, { ticket_text }); | ||
| return NextResponse.json(response); | ||
| } catch (error: any) { | ||
| return NextResponse.json({ error: error.message || "API execution failed" }, { status: 500 }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| @import "tailwindcss"; | ||
|
|
||
| :root { | ||
| --background: #ffffff; | ||
| --foreground: #171717; | ||
| } | ||
|
|
||
| @theme inline { | ||
|
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: var(--font-sans), Arial, Helvetica, sans-serif; | ||
| } | ||
| 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 = { | ||
| 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`} | ||
| > | ||
| <body className="min-h-full flex flex-col">{children}</body> | ||
| </html> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| '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 () => { | ||
| 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 }) | ||
| }); | ||
|
amanintech marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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) { | ||
|
amanintech marked this conversation as resolved.
Outdated
|
||
| 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-[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 htmlFor="ticketText" className="block text-sm font-semibold text-gray-300 mb-3 uppercase tracking-wider"> | ||
| Inbound Customer Ticket | ||
| </label> | ||
| <textarea | ||
| id="ticketText" | ||
| 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" | ||
| 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-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> | ||
| ); | ||
| } | ||
| 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": "" | ||
| } |
| 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; |
| 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; |

Uh oh!
There was an error while loading. Please reload this page.