Wegent Frontend Architecture Analysis
Project: Wegent AI - AI Agent Management Platform
Tech Stack: Next.js 15 + React 19 + TypeScript + Tailwind CSS + shadcn/ui
Analysis Date: 2026-02-01
Table of Contents
- Overview
- Next.js 15 App Router Structure
- Component Organization
- State Management Architecture
- Responsive Design System
- API Client Layer
- Internationalization (i18n)
- Chat Message Data Flow
- Key Design Patterns
Overview
The Wegent frontend is a Next.js 15 application built with modern React patterns. It uses a mobile-first responsive architecture with separate mobile/desktop page implementations for optimal UX. The architecture emphasizes:
- High cohesion, low coupling: Each module has single responsibility
- Context-based state management: React Context for global state
- WebSocket-driven real-time updates: Socket.IO for streaming chat
- Component-level responsive separation: Mobile vs Desktop components
- Namespace-based i18n: Feature-organized translations
Next.js 15 App Router Structure
Directory Layout
src/app/
├── layout.tsx # Root layout with providers
├── page.tsx # Entry point (redirects to chat/code)
├── globals.css # Global styles
├── (tasks)/ # Route group for task pages
│ ├── layout.tsx # Shared layout for chat/code
│ ├── chat/
│ │ ├── page.tsx # Router component (mobile/desktop)
│ │ ├── ChatPageDesktop.tsx # Desktop implementation
│ │ └── ChatPageMobile.tsx # Mobile implementation
│ ├── code/
│ │ ├── page.tsx
│ │ ├── CodePageDesktop.tsx
│ │ └── CodePageMobile.tsx
│ └── knowledge/ # Knowledge base pages
├── api/ # Next.js API routes
│ ├── chat/
│ │ ├── stream/route.ts
│ │ ├── cancel/route.ts
│ │ └── ...
│ └── [...path]/route.ts # Proxy to backend
├── login/ # Authentication pages
├── admin/ # Admin dashboard
└── shared/task/ # Public task sharing page
Page Hierarchy Diagram
Route Groups
The (tasks) route group wraps chat and code pages with shared providers:
// src/app/(tasks)/layout.tsx
export default function TasksLayout({ children }: { children: React.ReactNode }) {
return (
<UserProvider>
<SocketProvider>
<DeviceProvider>
<PetProvider>
<TaskContextProvider>
<ChatStreamProvider>
{children}
<PetStreamingBridge />
<PetWidget />
<GlobalAdminSetupWizard />
</ChatStreamProvider>
</TaskContextProvider>
</PetProvider>
</DeviceProvider>
</SocketProvider>
</UserProvider>
)
}
This prevents task list reloading when switching between chat and code pages.
Component Organization
Directory Structure
src/
├── components/
│ ├── ui/ # shadcn/ui base components
│ │ ├── button.tsx
│ │ ├── dialog.tsx
│ │ ├── input.tsx
│ │ └── ...
│ ├── common/ # Shared common components
│ │ ├── ErrorBoundary.tsx
│ │ └── LoadingSpinner.tsx
│ └── I18nProvider.tsx # i18n setup
│
├── features/ # Feature-based modules
│ ├── tasks/
│ │ ├── components/
│ │ │ ├── sidebar/ # Task sidebar components
│ │ │ ├── message/ # Message display components
│ │ │ ├── input/ # Chat input components
│ │ │ ├── chat/ # Chat area components
│ │ │ ├── selector/ # Team/Repo selectors
│ │ │ └── group-chat/ # Group chat components
│ │ ├── contexts/ # Feature contexts
│ │ ├── hooks/ # Feature hooks
│ │ ├── service/ # Business logic services
│ │ └── state/ # State machines
│ ├── layout/ # Layout components
│ ├── theme/ # Theme management
│ └── common/ # Common feature utilities
│
└── hooks/ # Global custom hooks
├── useTranslation.ts
├── useMediaQuery.ts
└── ...
Component Hierarchy (Chat Page)
State Management Architecture
Context Provider Stack
TaskContext Structure
interface TaskContextType {
// Task Lists
tasks: Task[]
groupTasks: Task[]
personalTasks: Task[]
// Selection State
selectedTask: Task | null
selectedTaskDetail: TaskDetail | null
setSelectedTask: (task: Task | null) => void
// Data Operations
refreshTasks: () => void
refreshSelectedTaskDetail: (isAutoRefresh?: boolean) => void
// Pagination
loadMore: () => void
hasMore: boolean
loadingMore: boolean
// Search
searchTerm: string
setSearchTerm: (term: string) => void
searchTasks: (term: string) => Promise<void>
// View Status
markTaskAsViewed: (taskId: number, status: TaskStatus) => void
getUnreadCount: (tasks: Task[]) => number
viewStatusVersion: number
}
ChatStreamContext Flow
Responsive Design System
Breakpoint System
| Breakpoint | Screen Size | Usage |
|---|---|---|
| Mobile | ≤767px | Touch-optimized UI with drawer sidebar |
| Tablet | 768px-1023px | Uses desktop layout |
| Desktop | ≥1024px | Full-featured UI with resizable sidebar |
Media Query Hooks
// src/features/layout/hooks/useMediaQuery.ts
export function useIsMobile(): boolean {
return useMediaQuery('(max-width: 767px)')
}
export function useIsTablet(): boolean {
return useMediaQuery('(min-width: 768px) and (max-width: 1023px)')
}
export function useIsDesktop(): boolean {
return useMediaQuery('(min-width: 1024px)')
}
Responsive Component Organization
Dynamic Import Pattern
// src/app/(tasks)/chat/page.tsx
const ChatPageDesktop = dynamic(
() => import('./ChatPageDesktop').then(mod => ({ default: mod.ChatPageDesktop })),
{ ssr: false }
)
const ChatPageMobile = dynamic(
() => import('./ChatPageMobile').then(mod => ({ default: mod.ChatPageMobile })),
{ ssr: false }
)
export default function ChatPage() {
const isMobile = useIsMobile()
return isMobile ? <ChatPageMobile /> : <ChatPageDesktop />
}
Touch-Friendly Requirements
All interactive elements must be at least 44px × 44px:
// Mobile button styling
<Button className="h-11 min-w-[44px] px-4">
{icon}
</Button>
When to Separate Components
| Scenario | Solution |
|---|---|
| Layout differences >30% | Create separate Mobile/Desktop components |
| Different interaction patterns | Separate for better UX |
| Performance optimization | Use dynamic imports with code splitting |
| Simple styling adjustments | Use Tailwind responsive classes |
API Client Layer
Directory Structure
src/apis/
├── client.ts # Base HTTP client with interceptors
├── user.ts # User authentication APIs
├── tasks.ts # Task management APIs
├── subtasks.ts # Subtask/chat message APIs
├── team.ts # Team/Bot management APIs
├── chat.ts # Chat streaming APIs
├── skills.ts # Skill management APIs
├── knowledge.ts # Knowledge base APIs
├── github.ts # GitHub integration APIs
├── group-chat.ts # Group chat APIs
└── mocks/ # MSW mock handlers (dev only)
Base Client Architecture
APIClient Implementation
// src/apis/client.ts
class APIClient {
async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.getBaseURL()}${endpoint}`
const token = getToken()
const config: RequestInit = {
...options,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options.headers,
},
}
const response = await fetch(url, config)
// Handle 401 - Redirect to login
if (response.status === 401) {
removeToken()
redirectToLogin()
}
if (!response.ok) {
throw new ApiError(errorMsg, response.status)
}
return response.json()
}
}
export const apiClient = new APIClient()
API Module Pattern
// src/apis/tasks.ts
export const taskApis = {
getTasksLite: (params: PaginationParams): Promise<TaskListResponse> =>
apiClient.get(`/tasks/lite?page=${params.page}&limit=${params.limit}`),
getGroupTasksLite: (params: PaginationParams): Promise<TaskListResponse> =>
apiClient.get(`/tasks/group/lite?page=${params.page}&limit=${params.limit}`),
getPersonalTasksLite: (params: PaginationParams): Promise<TaskListResponse> =>
apiClient.get(`/tasks/personal/lite?page=${params.page}&limit=${params.limit}`),
createTask: (data: CreateTaskRequest): Promise<Task> =>
apiClient.post('/tasks', data),
updateTask: (id: number, data: UpdateTaskRequest): Promise<Task> =>
apiClient.patch(`/tasks/${id}`, data),
deleteTask: (id: number): Promise<void> =>
apiClient.delete(`/tasks/${id}`),
}
WebSocket Integration
Internationalization (i18n)
Setup
// src/i18n/setup.ts
export const supportedLanguages = ['en', 'zh-CN']
export async function initI18n() {
const resources = await loadTranslations()
await i18next.use(initReactI18next).init({
lng: process.env.I18N_LNG || 'en',
fallbackLng: 'en',
resources,
defaultNS: 'common',
ns: [
'common',
'chat',
'settings',
'tasks',
'admin',
'wizard',
'groups',
'knowledge',
// ... 13 namespaces
],
})
}
Namespace Structure
src/i18n/locales/
├── en/
│ ├── common.json # Shared translations
│ ├── chat.json # Chat feature
│ ├── tasks.json # Task management
│ ├── settings.json # User settings
│ ├── admin.json # Admin dashboard
│ ├── wizard.json # Setup wizard
│ ├── groups.json # Group chat
│ └── ...
└── zh-CN/
└── [same structure]
Usage Patterns
// ✅ CORRECT - Single namespace import
const { t } = useTranslation('groups')
t('title') // Current namespace
t('common:actions.save') // Access other namespace
t('chat:export.no_messages') // Cross-namespace access
// ❌ WRONG - Array with common first
const { t } = useTranslation(['common', 'groups']) // Breaks feature keys
Key Naming Convention
| Namespace | Usage |
|---|---|
common | Shared UI elements (buttons, labels, errors) |
chat | Chat interface, messages, streaming |
tasks | Task list, creation, management |
settings | User preferences, configuration |
admin | Admin dashboard, system settings |
groups | Group chat, member management |
knowledge | Knowledge base, documents |
Chat Message Data Flow
CRITICAL: Single Source of Truth
┌─────────────────────────────────────────────────────────────────┐
│ ⚠️ ALWAYS use messages from useUnifiedMessages() │
│ │
│ selectedTaskDetail.subtasks = Backend cached data (stale) │
│ messages (from hook) = Real-time data via WebSocket │
│ │
│ For display/export: Use messages │
│ For refresh/sync: Use selectedTaskDetail.subtasks │
└─────────────────────────────────────────────────────────────────┘
Data Flow Architecture
Message Flow Sequence
useUnifiedMessages Hook
// src/features/tasks/hooks/useUnifiedMessages.ts
export function useUnifiedMessages({
team,
isGroupChat
}: UseUnifiedMessagesOptions): {
messages: DisplayMessage[]
isLoading: boolean
error: Error | null
} {
const { selectedTaskDetail } = useTaskContext()
const { getTaskState } = useChatStreamContext()
// Get real-time state from TaskStateManager
const taskState = getTaskState(selectedTaskDetail?.id)
// Merge backend data with real-time updates
const messages = useMemo(() => {
// 1. Start with backend subtasks
const backendMessages = convertSubtasksToMessages(
selectedTaskDetail?.subtasks
)
// 2. Merge with streamState.messages (WebSocket updates)
const merged = mergeMessages(backendMessages, taskState?.messages)
// 3. Sort and format for display
return formatForDisplay(merged)
}, [selectedTaskDetail?.subtasks, taskState?.messages])
return { messages, isLoading, error }
}
Key Design Patterns
1. State Machine Pattern
TaskStateManager manages state for each task:
class TaskStateMachine {
private state: TaskStateData
private subscribers: Set<Listener>
// Actions
addUserMessage(content: string): void
addAIMessage(): string // Returns messageId
appendContent(messageId: string, chunk: string): void
markComplete(messageId: string): void
markError(messageId: string, error: string): void
// Recovery
recover(): Promise<void> // Resume interrupted streams
}
2. Service Pattern
Business logic extracted into services:
// src/features/tasks/service/teamService.ts
export const teamService = {
useTeams: () => {
// React Query or custom hook logic
const [teams, setTeams] = useState<Team[]>([])
const refreshTeams = async () => { /* ... */ }
return { teams, refreshTeams }
},
useTeam: (id: number) => {
// Single team operations
}
}
3. Context Composition Pattern
Providers are composed hierarchically:
4. Component Separation Pattern
// Router component with dynamic imports
export default function ChatPage() {
const isMobile = useIsMobile()
// Dynamic imports for code splitting
const ChatPageDesktop = dynamic(() => import('./ChatPageDesktop'))
const ChatPageMobile = dynamic(() => import('./ChatPageMobile'))
return isMobile ? <ChatPageMobile /> : <ChatPageDesktop />
}
// Desktop implementation
export function ChatPageDesktop() {
// Desktop-specific logic and layout
return (
<ResizableSidebar>
<TaskList />
</ResizableSidebar>
<ChatArea />
)
}
// Mobile implementation
export function ChatPageMobile() {
// Mobile-specific logic and layout
return (
<DrawerSidebar>
<TaskList />
</DrawerSidebar>
<MobileChatArea />
)
}
5. Hook Pattern
// Feature-specific hook
export function useTaskOperations() {
const { selectedTask, refreshTasks } = useTaskContext()
const { sendMessage } = useChatStreamContext()
const handleSend = async (content: string) => {
await sendMessage({
message: content,
team_id: selectedTask?.team_id,
task_id: selectedTask?.id,
})
}
return { handleSend }
}
Complex UI/UX Logic Requiring Deep Analysis
1. Streaming Message Recovery
When WebSocket reconnects, the system must:
- Detect interrupted streams
- Resume from last received chunk
- Handle temp-to-real task ID migration
- Sync missed messages
File: src/features/tasks/state/TaskStateMachine.ts (lines 200-300)
2. Message Thinking/Tool Display
Complex parsing of thinking blocks:
- Tool call extraction and rendering
- Mixed content (thinking + code + results)
- Step-by-step progress display
- Error state visualization
Files:
src/features/tasks/components/message/thinking/ThinkingDisplay.tsxsrc/features/tasks/components/message/thinking/utils/toolExtractor.ts
3. Responsive Sidebar Behavior
Desktop vs Mobile sidebar states:
- Desktop: Resizable width, collapse state persisted to localStorage
- Mobile: Drawer overlay, swipe to close, touch gestures
Files:
src/features/tasks/components/sidebar/ResizableSidebar.tsxsrc/features/tasks/components/sidebar/TaskSidebar.tsx
4. Attachment & Context Management
Multi-file upload with context preservation:
- File upload progress tracking
- Attachment preview generation
- Context items (knowledge bases, repos)
- Cross-message context persistence
Files:
src/hooks/useMultiAttachment.tssrc/features/tasks/components/input/AttachmentPreview.tsx
5. Export Functionality
Message export with format selection:
- Selectable message list
- Markdown/HTML/JSON export formats
- Attachment inclusion logic
- Knowledge base reference handling
File: src/features/tasks/components/share/ExportSelectModal.tsx
Summary
Architecture Patterns
- Mobile-First Responsive: Separate Mobile/Desktop page implementations with dynamic imports
- Context-Based State: React Context for global state, no Redux/Zustand
- WebSocket-Driven: Real-time chat via Socket.IO with automatic recovery
- State Machine: TaskStateManager/TaskStateMachine for complex chat state
- Feature-Based Organization: Co-located components, hooks, and services
- Namespace i18n: Feature-organized translations for maintainability
Key Strengths
- Clean separation of concerns: Features are self-contained
- Optimized mobile UX: Touch-friendly, drawer-based navigation
- Resilient streaming: Automatic recovery from connection issues
- Type-safe: Full TypeScript coverage
- Maintainable: Clear patterns, consistent naming
Areas Requiring Attention
- State synchronization: Complex flow between backend data and WebSocket updates
- Component splitting: Large components (>1000 lines) need further decomposition
- Test coverage: Ensure all state machine transitions are tested
- Performance: Virtualize long message lists for better performance
Document generated by AI analysis of Wegent frontend codebase