AI-powered task management interface built with Next.js 16, React 19, and TypeScript.
- 🤖 AI Chat Interface - Natural language task management
- 🎯 Custom AG-UI Integration - SSE streaming with
/api/agent/chatendpoint - 💡 Smart Suggestions - Clickable contextual suggestions from AI agent
- 📂 Chat Management - List, load, and delete chats
- 🗂️ Sidebar Navigation - Collapsible sidebar with chat history
- 🏷️ Auto-generated Titles - Titles extracted from first user message
- ⏳ Enhanced Loading States - Contextual loading messages with animations
- 📱 Responsive Design - Works on desktop, tablet, and mobile
- 🎨 Modern UI - ChatGPT-inspired clean design with Tailwind CSS
- 📐 Adaptive Layout - Centered welcome state, fixed input when chatting
- 🔄 Smart Scrolling - Independent message scroll with fixed header and input
- 💾 localStorage Persistence - Remembers current chat across sessions
- ✅ STEP_STARTED/STEP_FINISHED Events - AG-UI standard lifecycle events for function calls
- ✅ Dynamic Status Messages - Backend generates status from
[Description]attributes - ✅ Multi-Agent Ready - Scalable architecture without hardcoded status mappings
- ✅ Enhanced Event Flow -
STEP_STARTED→STATUS_UPDATE→STEP_FINISHED
- ✅ Server-Driven Status Messages - Backend sends operation-specific status during processing
- ✅ STATUS_UPDATE SSE Event - Real-time progress feedback during function execution
- ✅ ChatGPT-like Loading UX - Status message + cursor shown in assistant message bubble
- ✅ Dynamic Status by Operation - Status messages auto-generated from function descriptions
- ✅ Seamless Transition - Status disappears when actual content starts streaming
- ✅ System Theme Detection - Automatic dark/light mode based on OS preference
- ✅ Theme Toggle - Manual toggle in header with sun/moon icons
- ✅ next-themes Integration - Flicker-free theme switching with localStorage persistence
- ✅ Tailwind CSS 4 Dark Mode - Class-based dark mode with
selectorstrategy - ✅ Component Updates - All components support dark theme variants
- ✅ Copy Button Positioning - Fixed code block copy button positioning
- ✅ Blocked Messages in Chat - Content Safety violations appear as assistant messages (not toasts)
- ✅ Thread Continuity - Blocked chats create threads for seamless continuation
- ✅ Smart Title Updates - Titles regenerate when first valid message sent after block
- ✅ Optimized Sidebar Refresh - Only reloads when title changes (efficient flag-based approach)
- ✅ ChatGPT-like Behavior - Natural chat flow even with blocked messages
- ✅ Vitest Configuration - Unit testing with React Testing Library
- ✅ Playwright E2E - End-to-end tests for critical flows
- ✅ 18 Unit Tests - Components and utilities coverage
- ✅ 13 E2E Tests - Chat, conversations, navigation, theme switching
- ✅ ConversationSidebar Component - Full chat history with search
- ✅ ConversationList Component - Paginated list with auto-generated titles
- ✅ ConversationItem Component - Individual chat cards with metadata
- ✅ DeleteConfirmModal Component - Confirmation dialog with smooth animations
- ✅ useConversations Hook - Chat state management
- ✅ localStorage Integration - Persists current thread ID
- ✅ API Integration - List, load, and delete endpoints
- ✅ ChatGPT-Inspired Layout - Full-height chat with adaptive behavior
- ✅ SuggestionsBar Component - Click suggestions to send messages
- ✅ LoadingIndicator Component - Rotating contextual messages
- ✅ Improved UX - Smooth animations and visual feedback
- ✅ Minimalist Header - Compact header only when messages exist
- ✅ Optimized Input - Icon-based send button with hover states
- ✅ Type-safe - Full TypeScript with strict mode
- Node.js 18+ or 20+
- pnpm (recommended) or npm
- .NET backend running at
https://localhost:5001
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm startOpen http://localhost:3000 to see the application.
For Local Development:
Create a .env.local file:
# Backend API URL (Next.js public env var)
NEXT_PUBLIC_API_URL=https://localhost:5001For Production (Azure Static Web Apps):
Environment variables must be configured at build time as GitHub repository secrets because Next.js static export (output: "export") doesn't support runtime environment variables.
-
Add GitHub Secret:
- Go to Settings → Secrets and variables → Actions
- Click "New repository secret"
- Name:
NEXT_PUBLIC_API_URL - Value: Your production backend URL (e.g.,
https://app-taskagent-prod.azurewebsites.net)
-
Verify GitHub Actions Workflow: The
frontend.ymlworkflow should include:- name: Build Next.js env: NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} run: | cd src/frontend/task-agent-web pnpm build
Why this approach?
- ✅ Next.js replaces
process.env.NEXT_PUBLIC_API_URLwith actual value during build - ✅ Static files include the correct backend URL
- ❌ Azure Static Web Apps cannot inject runtime variables into pre-built static files
- ❌ Environment variables in Azure portal don't work with static exports
src/frontend/task-agent-web/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page (chat interface)
│ └── globals.css # Global styles
├── components/ # React components
│ ├── chat/ # Chat-specific components
│ │ ├── ChatInterface.tsx # Main chat component (adaptive layout)
│ │ ├── ChatInterfaceClient.tsx # Client wrapper (dynamic loading)
│ │ ├── ChatMessagesList.tsx # Messages container (conditional layout)
│ │ ├── ChatMessage.tsx # Individual message bubble
│ │ ├── ChatInput.tsx # Input field (icon-based send)
│ │ ├── ChatHeader.tsx # Minimalist header
│ │ ├── EmptyChatState.tsx # Welcome state
│ │ ├── SuggestionsBar.tsx # Clickable suggestion buttons
│ │ ├── ErrorToast.tsx # Error display
│ │ └── LoadingIndicator.tsx # Contextual loading states
│ ├── conversations/ # Chat management
│ │ ├── ConversationSidebar.tsx # Sidebar layout
│ │ ├── ConversationList.tsx # List of chats
│ │ ├── ConversationItem.tsx # Individual chat card
│ │ └── DeleteConfirmModal.tsx # Delete confirmation
│ └── shared/ # Shared components
│ └── LoadingIndicator.tsx # Reusable loading component
├── hooks/ # Custom React hooks
│ ├── use-chat.ts # Chat state management
│ └── use-conversations.ts # Chat management
├── lib/ # Utilities
│ ├── utils.ts # Helper functions (cn utility)
│ ├── constants.ts # App constants
│ └── api/ # API client functions
│ └── chat-service.ts # Chat & API client
├── types/ # TypeScript definitions
│ ├── chat.ts # Chat types
│ └── conversation.ts # Thread/conversation types (technical)
├── public/ # Static assets
└── types/ # TypeScript definitions
└── chat.ts # Chat types
Custom Implementation with AG-UI Foundation:
Frontend (Next.js)
├── Custom UI Components
│ ├── ChatInterface.tsx
│ ├── ConversationSidebar.tsx
│ ├── ChatMessagesList.tsx
│ └── use-chat.ts hook
│
↕️ SSE Streaming (Server-Sent Events)
│ POST /api/agent/chat
│ • serializedState → Backend
│ • SSE events ← Backend
│ • STEP_STARTED/STEP_FINISHED events (AG-UI lifecycle)
│ • STATUS_UPDATE event (dynamic from [Description])
│ • THREAD_STATE event (new serializedState)
│
Backend (.NET)
├── AgentController (Custom SSE endpoint)
│ └── Wraps Microsoft Agent Framework
│ • Deserializes thread from serializedState
│ • Streams responses via RunStreamingAsync
│ • Sends STEP_STARTED → STATUS_UPDATE → STEP_FINISHED
│ • Returns updated serializedState
│
├── FunctionDescriptionProvider
│ └── Generates status from [Description] attributes
│ • Auto-discovers function descriptions at startup
│ • Converts to gerund form: "Creates..." → "Creating..."
│ • Cached for performance
│
└── PostgresChatMessageStore
└── Automatic persistence in PostgreSQL
Why Custom AG-UI Endpoint (not standard /agui)?
- ✅ Full SSE control: Custom event types (
STEP_STARTED,STATUS_UPDATE,STEP_FINISHED,THREAD_STATE) - ✅ Dynamic status messages: Generated from
[Description]attributes (multi-agent ready) - ✅ serializedState pattern: Frontend receives updated state after each response
- ✅ Chat continuity: Backend deserializes full thread from PostgreSQL
- ✅ No protocol limitations: Can add custom events as needed
- ✅ Integrated chat sidebar: 291 lines with auto-generated titles
- ❌ Standard
/aguidoesn't returnserializedStatein streaming mode
Why Custom UI (not CopilotKit)?
- ✅ Chat-first application: Not auxiliary chat over another app
- ✅ Full UX control: ChatGPT-inspired adaptive layout
- ✅ Minimal dependencies: No heavy UI framework
- ❌ CopilotKit designed for auxiliary chat, not main application
Microsoft Agent Framework Benefits:
- 🔄 Automatic message persistence via
ChatMessageStore - 📡 SSE streaming with
RunStreamingAsync - 🧵 Thread serialization/deserialization built-in
- 📦 Function calling with
AIFunctionFactory
- Next.js 16 - React framework with App Router
- React 19 - UI library with Server Components
- TypeScript - Type safety
- Tailwind CSS 4 - Utility-first CSS with class-based dark mode
- next-themes - Flicker-free theme switching
- pnpm - Fast, efficient package manager
- ESLint - Code quality
All styles use Tailwind CSS. Customize in:
tailwind.config.ts- Theme configurationapp/globals.css- Global styles- Component files - Component-specific styles
Main color palette:
- Primary: Blue (blue-500 to blue-700)
- Background Light: Gray (gray-50 to gray-100)
- Background Dark: Zinc/Gray (zinc-800 to zinc-950)
- Suggestions: Blue gradient (blue-50 to blue-200)
Dark mode is implemented using next-themes with class-based switching:
// app/layout.tsx
import { ThemeProvider } from 'next-themes'
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>Components use Tailwind's dark: prefix:
<div className="bg-white dark:bg-zinc-900 text-gray-900 dark:text-white">
{/* Content */}
</div>- Font: Geist (optimized by Next.js)
- Sizes: Tailwind's default scale
POST /api/Chat/send- Send message (non-streaming)POST /api/Chat/stream- Streaming support (paused for future releases)
GET /api/Chat/threads- List chats with paginationGET /api/Chat/threads/{threadId}/messages- Get chat historyDELETE /api/Chat/threads/{threadId}- Delete chat
// Request
POST /api/Chat/send
{
"message": "Create a high priority task",
"threadId": "abc-123-def" // Optional, creates new if null
}
// Response
{
"message": "✅ Task created successfully",
"threadId": "abc-123-def",
"messageId": "msg-456",
"createdAt": "2025-11-17T10:30:00Z",
"suggestions": ["View all tasks", "Create another task"],
"metadata": {
"functionCalls": [
{
"functionName": "CreateTask",
"arguments": { "title": "...", "priority": "High" },
"result": "✅ Task created"
}
]
}
}// Request
GET /api/Chat/threads?page=1&pageSize=20&sortBy=UpdatedAt&sortOrder=desc&isActive=true
// Response
{
"threads": [
{
"id": "abc-123-def",
"title": "Create a high priority task to review quarterl...",
"preview": "✅ Task created successfully. I've added a high...",
"messageCount": 5,
"createdAt": "2025-11-17T10:00:00Z",
"updatedAt": "2025-11-17T10:35:00Z",
"isActive": true
}
],
"totalCount": 42,
"page": 1,
"pageSize": 20,
"totalPages": 3
}// Request
GET /api/Chat/threads/abc-123-def/messages?page=1&pageSize=50
// Response
{
"messages": [
{
"id": "msg-123",
"role": "user",
"content": "Create a high priority task",
"timestamp": "2025-11-17T10:30:00Z"
},
{
"id": "msg-124",
"role": "assistant",
"content": "✅ Task created successfully...",
"timestamp": "2025-11-17T10:30:05Z"
}
],
"threadId": "abc-123-def",
"totalCount": 5,
"page": 1,
"pageSize": 50
}// types/chat.ts
export interface ChatMessage {
id: string;
role: "user" | "assistant";
content: string;
timestamp: string;
}
export interface ChatResponse {
message: string;
threadId: string;
messageId: string;
createdAt: string;
metadata?: MessageMetadata;
suggestions?: string[];
}
// types/conversation.ts
export interface ConversationThread {
id: string;
title: string;
preview: string;
messageCount: number;
createdAt: string;
updatedAt: string;
isActive: boolean;
}
export interface ListThreadsResponse {
threads: ConversationThread[];
totalCount: number;
page: number;
pageSize: number;
totalPages: number;
}The project uses Vitest for unit tests and Playwright for E2E tests.
| Type | Framework | Tests | Coverage |
|---|---|---|---|
| Unit Tests | Vitest + Testing Library | 57 | Utilities, Components |
| E2E Tests | Playwright | 37 | Navigation, Chat, Conversations, Theme |
| Total | 94 |
# Unit Tests
pnpm test # Watch mode (development)
pnpm test:run # Single run (CI/CD)
pnpm test:coverage # With coverage report
# E2E Tests
pnpm playwright:install chromium # First time setup
pnpm test:e2e # Headless
pnpm test:e2e:headed # With visible browser
pnpm test:e2e:ui # Interactive UI__tests__/ # Unit tests (57)
├── lib/
│ ├── constants.test.ts # 6 tests
│ └── utils/date-utils.test.ts # 10 tests
└── components/chat/
├── ChatInput.test.tsx # 19 tests
└── ChatMessage.test.tsx # 22 tests
e2e/ # E2E tests (37)
├── navigation.spec.ts # 6 tests
├── chat.spec.ts # 7 tests
├── conversations.spec.ts # 10 tests
├── theme.spec.ts # 14 tests
└── fixtures/ # Mock data & API mocks
For detailed test documentation: See TESTING_STRATEGY.md
For manual testing scenarios: See docs/FRONTEND_E2E_TESTING.md
- ✅ TypeScript strict mode
- ✅ ESLint with Next.js rules
- ✅ Prettier formatting
- ✅ SOLID principles
- ✅ Clean Architecture
- Server Components by default
- Client Components (
"use client") only when needed - Custom hooks for logic reuse
- Composition over inheritance
# Deploy to Vercel
vercel
# Or connect GitHub repo for auto-deployments# Dockerfile included in project
docker build -t task-agent-web .
docker run -p 3000:3000 task-agent-webNEXT_PUBLIC_API_URL=https://your-backend-api.com
PORT=3000 # Optional, defaults to 3000- Follow TypeScript strict mode
- Use functional components
- Write self-documenting code
- Add JSDoc comments for public APIs
- Test manually before committing
See LICENSE file in root directory.
- Backend: TaskAgent.WebApp
- Next.js Docs: https://nextjs.org/docs
- React Docs: https://react.dev
- Tailwind CSS: https://tailwindcss.com
Built with ❤️ using modern web technologies