Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions talent-management/public/data/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
}
}
]
},
{
"route": "ai-chat",
"name": "aiChat",
"type": "link",
"icon": "smart_toy"
}
]
}
1 change: 1 addition & 0 deletions talent-management/public/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"salaryRanges": "Salary Ranges",
"salaryRanges.salaryRangeList": "List",
"salaryRanges.addSalaryRange": "Create",
"aiChat": "AI Assistant",
"design": "Design",
"design.colors": "Color System",
"design.icons": "Material Icons",
Expand Down
2 changes: 2 additions & 0 deletions talent-management/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SalaryRangeDetailComponent } from './routes/salary-ranges/salary-range-
import { SalaryRangeFormComponent } from './routes/salary-ranges/salary-range-form.component';
import { ProfileOverviewComponent } from './routes/profile/profile-overview.component';
import { ProfileSettingsComponent } from './routes/profile/profile-settings.component';
import { AiChatComponent } from './routes/ai-chat/ai-chat.component';

export const routes: Routes = [
{
Expand Down Expand Up @@ -57,6 +58,7 @@ export const routes: Routes = [
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
],
},
{ path: 'ai-chat', component: AiChatComponent },
{ path: '403', component: Error403 },
{ path: '404', component: Error404 },
{ path: '500', component: Error500 },
Expand Down
161 changes: 161 additions & 0 deletions talent-management/src/app/routes/ai-chat/ai-chat.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<page-header />

<!-- AI Disabled Banner -->
<div *ngIf="!aiEnabled" class="ai-disabled-banner">
<mat-card class="disabled-card">
<mat-card-content>
<div class="disabled-content">
<mat-icon>info</mat-icon>
<div>
<strong>AI features are disabled.</strong>
<p>
To enable AI, set <code>aiEnabled: true</code> in
<code>src/environments/environment.ts</code> and
<code>"AiEnabled": true</code> in the API's <code>appsettings.json</code>.
</p>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

<!-- AI Chat Tabs -->
<div *ngIf="aiEnabled" class="chat-container">
<mat-tab-group animationDuration="200ms">

<!-- Tab 1: General Chat -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">chat</mat-icon>
General Chat
</ng-template>

<div class="tab-content">
<mat-card class="chat-card">
<mat-card-header>
<mat-card-title>AI Assistant</mat-card-title>
<mat-card-subtitle>Ask anything — general knowledge, writing help, code questions</mat-card-subtitle>
<div class="header-actions">
<button mat-icon-button (click)="clearChat()" matTooltip="Clear conversation">
<mat-icon>delete_sweep</mat-icon>
</button>
</div>
</mat-card-header>

<mat-card-content>
<!-- Message History -->
<div class="message-list" *ngIf="chatMessages.length > 0">
<div *ngFor="let msg of chatMessages" class="message" [class.user-message]="msg.role === 'user'" [class.assistant-message]="msg.role === 'assistant'">
<mat-icon class="avatar-icon">{{ msg.role === 'user' ? 'person' : 'smart_toy' }}</mat-icon>
<div class="bubble">{{ msg.content }}</div>
</div>
</div>

<!-- Empty state -->
<div class="empty-state" *ngIf="chatMessages.length === 0 && !chatLoading">
<mat-icon>chat_bubble_outline</mat-icon>
<p>Start a conversation</p>
</div>

<!-- Loading -->
<div class="loading-row" *ngIf="chatLoading">
<mat-spinner diameter="24"></mat-spinner>
<span>Thinking…</span>
</div>

<!-- Error -->
<div class="error-row" *ngIf="chatError">
<mat-icon>error_outline</mat-icon>
<span>{{ chatError }}</span>
</div>
</mat-card-content>

<mat-divider></mat-divider>

<mat-card-actions class="input-area">
<mat-form-field appearance="outline" class="message-input">
<mat-label>Message</mat-label>
<input matInput [(ngModel)]="chatInput" (keydown)="onChatKeydown($event)" placeholder="Ask the AI assistant anything…" [disabled]="chatLoading" />
</mat-form-field>
<button mat-fab extended color="primary" (click)="sendChat()" [disabled]="!chatInput.trim() || chatLoading">
<mat-icon>send</mat-icon>
Send
</button>
</mat-card-actions>
</mat-card>
</div>
</mat-tab>

<!-- Tab 2: HR Insights -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">analytics</mat-icon>
HR Insights
</ng-template>

<div class="tab-content">
<mat-card class="chat-card">
<mat-card-header>
<mat-card-title>HR AI Assistant</mat-card-title>
<mat-card-subtitle>Ask about your live workforce data — headcount, departments, recent hires</mat-card-subtitle>
<div class="header-actions">
<button mat-icon-button (click)="clearHr()" matTooltip="Clear conversation">
<mat-icon>delete_sweep</mat-icon>
</button>
</div>
</mat-card-header>

<mat-card-content>
<!-- Suggestion chips -->
<div class="suggestions" *ngIf="hrMessages.length === 0 && !hrLoading">
<p class="suggestions-label">Try asking:</p>
<div class="suggestion-list">
<button mat-stroked-button (click)="hrInput = 'Which department has the most employees?'">Which department has the most employees?</button>
<button mat-stroked-button (click)="hrInput = 'How many new hires joined this month?'">How many new hires joined this month?</button>
<button mat-stroked-button (click)="hrInput = 'What is the gender distribution?'">What is the gender distribution?</button>
<button mat-stroked-button (click)="hrInput = 'Who are the most recent hires?'">Who are the most recent hires?</button>
</div>
</div>

<!-- Message History -->
<div class="message-list" *ngIf="hrMessages.length > 0">
<div *ngFor="let msg of hrMessages" class="message" [class.user-message]="msg.role === 'user'" [class.assistant-message]="msg.role === 'assistant'">
<mat-icon class="avatar-icon">{{ msg.role === 'user' ? 'person' : 'analytics' }}</mat-icon>
<div class="bubble-wrapper">
<div class="bubble">{{ msg.content }}</div>
<span class="exec-time" *ngIf="msg.executionTimeMs">{{ msg.executionTimeMs }}ms</span>
</div>
</div>
</div>

<!-- Loading -->
<div class="loading-row" *ngIf="hrLoading">
<mat-spinner diameter="24"></mat-spinner>
<span>Fetching live data and reasoning…</span>
</div>

<!-- Error -->
<div class="error-row" *ngIf="hrError">
<mat-icon>error_outline</mat-icon>
<span>{{ hrError }}</span>
</div>
</mat-card-content>

<mat-divider></mat-divider>

<mat-card-actions class="input-area">
<mat-form-field appearance="outline" class="message-input">
<mat-label>Question</mat-label>
<input matInput [(ngModel)]="hrInput" (keydown)="onHrKeydown($event)" placeholder="Ask about your workforce data…" [disabled]="hrLoading" />
</mat-form-field>
<button mat-fab extended color="primary" (click)="sendHrInsight()" [disabled]="!hrInput.trim() || hrLoading">
<mat-icon>send</mat-icon>
Ask
</button>
</mat-card-actions>
</mat-card>
</div>
</mat-tab>

</mat-tab-group>
</div>
Loading