Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
<svg class="card__type-icon" viewBox="0 0 24 24"><path [attr.d]="cardTypeIcon(card.type)" /></svg>
</div>
<div class="card__head-text">
<span class="card__badge" [ngClass]="'card__badge--' + card.type">{{ card.type }}</span>
<span class="card__badge" [ngClass]="'card__badge--' + card.type">{{
'copilot.cardType.' + card.type | translate
}}</span>
<span class="card__title">{{ card.title }}</span>
</div>
</div>
Expand All @@ -23,7 +25,7 @@
</div>
<div *ngIf="card.type === 'confirmation'" class="card__warn-stripe">
<svg viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" /></svg>
<span>This action involves real money. Please confirm.</span>
<span>{{ 'copilot.confirmWarning' | translate }}</span>
</div>
<div *ngIf="card.actions?.length" class="card__actions">
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { ActionCard, ActionCardType } from '../../core/models/action-card.model';

/** Renders a single structured ActionCard (client / loan / savings / insight / confirmation). */
@Component({
selector: 'mifosx-action-card',
imports: [CommonModule],
imports: [
CommonModule,
TranslateModule
],
templateUrl: './action-card.component.html',
styleUrls: ['./action-card.component.scss']
})
Expand Down
4 changes: 2 additions & 2 deletions src/app/copilot/components/chat-area/chat-area.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<div class="chat-body" #messageContainer>
<!-- Empty / welcome state -->
<div *ngIf="messages.length === 0" class="welcome">
<span class="welcome__badge">{{ greetingTime }}, {{ displayName }}</span>
<h1 class="welcome__heading">How can I help you support<br />your clients today?</h1>
<span class="welcome__badge">{{ greetingTime | translate }}</span>
<h1 class="welcome__heading">{{ 'copilot.welcomeHeading' | translate }}</h1>
<mifosx-quick-chips
class="welcome__prompts"
[prompts]="emptySuggestions"
Expand Down
3 changes: 2 additions & 1 deletion src/app/copilot/components/chat-area/chat-area.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import moment from 'moment';
import { ChatMessage } from '../../core/models/chat-message.model';
import { MarkdownPipe } from '../../pipes/markdown.pipe';
Expand All @@ -19,6 +20,7 @@ import { QuickChipsComponent } from '../quick-chips/quick-chips.component';
selector: 'mifosx-chat-area',
imports: [
CommonModule,
TranslateModule,
MarkdownPipe,
ActionCardComponent,
QuickChipsComponent
Expand All @@ -30,7 +32,6 @@ export class ChatAreaComponent {
@Input() messages: ChatMessage[] = [];
@Input() isStreaming = false;
@Input() greetingTime = '';
@Input() displayName = '';
@Input() emptySuggestions: string[] = [];

@Output() promptSelected = new EventEmitter<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,25 @@
<header class="topbar">
<div class="topbar__left">
<div class="topbar__logo"><img src="assets/images/MifosX_logo.png" alt="Mifos Logo" /></div>
<span class="topbar__brand">Mifos Intelligence AI</span>
<span class="topbar__brand">{{ 'copilot.brand' | translate }}</span>
</div>
<div class="topbar__right">
<div class="topbar__session" *ngIf="contextLabel">
<span class="topbar__session-label">CURRENT SESSION</span>
<span class="topbar__session-label">{{ 'copilot.currentSession' | translate }}</span>
<span class="topbar__session-value">{{ contextLabel }}</span>
</div>
<button type="button" class="topbar__new-chat" (click)="newChat.emit()" title="New conversation">
<button
type="button"
class="topbar__new-chat"
(click)="newChat.emit()"
[title]="'copilot.newChatTitle' | translate"
>
<svg viewBox="0 0 24 24">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" />
</svg>
<span>New Chat</span>
<span>{{ 'copilot.newChat' | translate }}</span>
</button>
<button type="button" class="topbar__btn" (click)="closePanel.emit()" title="Close">
<button type="button" class="topbar__btn" (click)="closePanel.emit()" [title]="'copilot.close' | translate">
<svg viewBox="0 0 24 24">
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';

/** Topbar: logo + brand, current-session label, New Chat and Close buttons. */
@Component({
selector: 'mifosx-copilot-header',
imports: [CommonModule],
imports: [
CommonModule,
TranslateModule
],
templateUrl: './copilot-header.component.html',
styleUrls: ['./copilot-header.component.scss']
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*ngIf="isEnabled && !isOpen"
class="ai-fab"
(click)="togglePanel()"
aria-label="Open AI Assistant"
[attr.aria-label]="'copilot.openAssistant' | translate"
>
<img src="assets/images/MifosX_logo.png" alt="Mifos AI" class="ai-fab__icon" />
</button>
Expand All @@ -34,7 +34,6 @@
[messages]="messages"
[isStreaming]="isStreaming"
[greetingTime]="greetingTime"
[displayName]="displayName"
[emptySuggestions]="emptySuggestions"
(promptSelected)="sendSuggestedPrompt($event)"
(cardAction)="onActionClick($event)"
Expand All @@ -54,7 +53,7 @@
<!-- Preferences tab (placeholder) -->
<div class="chat-body" *ngIf="activeTab === 'preferences'">
<div class="welcome">
<h1 class="welcome__heading">Preferences</h1>
<h1 class="welcome__heading">{{ 'copilot.preferencesHeading' | translate }}</h1>
</div>
</div>

Expand All @@ -69,28 +68,28 @@ <h1 class="welcome__heading">Preferences</h1>
/>
</svg>
</div>
<span class="help-center__section-title">What can Mifos Intelligence AI do?</span>
<span class="help-center__section-title">{{ 'copilot.help.capabilitiesTitle' | translate }}</span>
</div>
<div class="help-center__list">
<div class="help-center__item">
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg
><span>Look up client information and profiles</span>
><span>{{ 'copilot.help.capabilities.lookup' | translate }}</span>
</div>
<div class="help-center__item">
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg
><span>View loan details and repayment schedules</span>
><span>{{ 'copilot.help.capabilities.loans' | translate }}</span>
</div>
<div class="help-center__item">
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg
><span>Check savings account balances</span>
><span>{{ 'copilot.help.capabilities.savings' | translate }}</span>
</div>
<div class="help-center__item">
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg
><span>Get insights on client portfolios</span>
><span>{{ 'copilot.help.capabilities.portfolio' | translate }}</span>
</div>
<div class="help-center__item">
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg
><span>Navigate to specific pages in Mifos X</span>
><span>{{ 'copilot.help.capabilities.navigate' | translate }}</span>
</div>
</div>
</div>
Expand All @@ -101,39 +100,39 @@ <h1 class="welcome__heading">Preferences</h1>
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" />
</svg>
</div>
<span class="help-center__section-title">Example prompts</span>
<span class="help-center__section-title">{{ 'copilot.help.examplesTitle' | translate }}</span>
</div>
<div class="help-center__list">
<button
type="button"
class="help-center__prompt"
(click)="sendSuggestedPrompt('Show me details for client John Doe')"
(click)="sendSuggestedPrompt('copilot.suggestions.clientDetails')"
>
<span>"Show me details for client John Doe"</span
<span>{{ 'copilot.suggestions.clientDetails' | translate }}</span
><svg viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" /></svg>
</button>
<button
type="button"
class="help-center__prompt"
(click)="sendSuggestedPrompt('What is the repayment schedule for loan #107?')"
(click)="sendSuggestedPrompt('copilot.suggestions.repaymentSchedule')"
>
<span>"What is the repayment schedule for loan #107?"</span
<span>{{ 'copilot.suggestions.repaymentSchedule' | translate }}</span
><svg viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" /></svg>
</button>
<button
type="button"
class="help-center__prompt"
(click)="sendSuggestedPrompt('Show savings account balance for client #52')"
(click)="sendSuggestedPrompt('copilot.suggestions.savingsBalance')"
>
<span>"Show savings account balance for client #52"</span
<span>{{ 'copilot.suggestions.savingsBalance' | translate }}</span
><svg viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" /></svg>
</button>
<button
type="button"
class="help-center__prompt"
(click)="sendSuggestedPrompt('How many loans are overdue this week?')"
(click)="sendSuggestedPrompt('copilot.suggestions.overdueLoans')"
>
<span>"How many loans are overdue this week?"</span
<span>{{ 'copilot.suggestions.overdueLoans' | translate }}</span
><svg viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" /></svg>
</button>
</div>
Expand All @@ -143,17 +142,17 @@ <h1 class="welcome__heading">Preferences</h1>
<div class="help-center__section-icon">
<svg viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" /></svg>
</div>
<span class="help-center__section-title">Important</span>
<span class="help-center__section-title">{{ 'copilot.help.importantTitle' | translate }}</span>
</div>
<div class="help-center__list">
<div class="help-center__item help-center__item--muted">
<span>AI responses are for assistance only, not final decisions</span>
<span>{{ 'copilot.help.important.assist' | translate }}</span>
</div>
<div class="help-center__item help-center__item--muted">
<span>Always verify critical financial data before taking action</span>
<span>{{ 'copilot.help.important.verify' | translate }}</span>
</div>
<div class="help-center__item help-center__item--muted">
<span>The AI does not execute transactions on its own</span>
<span>{{ 'copilot.help.important.noExecute' | translate }}</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -181,7 +180,7 @@ <h1 class="welcome__heading">Preferences</h1>
d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"
/>
</svg>
<span>RECENT CHATS</span>
<span>{{ 'copilot.nav.recentChats' | translate }}</span>
</button>
<button
type="button"
Expand All @@ -192,7 +191,7 @@ <h1 class="welcome__heading">Preferences</h1>
<svg viewBox="0 0 24 24">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z" />
</svg>
<span>CHAT</span>
<span>{{ 'copilot.nav.chat' | translate }}</span>
</button>
<button
type="button"
Expand All @@ -205,7 +204,7 @@ <h1 class="welcome__heading">Preferences</h1>
d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"
/>
</svg>
<span>PREFERENCES</span>
<span>{{ 'copilot.nav.preferences' | translate }}</span>
</button>
<button
type="button"
Expand All @@ -218,7 +217,7 @@ <h1 class="welcome__heading">Preferences</h1>
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"
/>
</svg>
<span>HELP CENTER</span>
<span>{{ 'copilot.nav.helpCenter' | translate }}</span>
</button>
</nav>
</div>
Expand Down
32 changes: 19 additions & 13 deletions src/app/copilot/components/copilot-panel/copilot-panel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/** Angular Imports */
import { Component, Input, ViewEncapsulation, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule, TranslateService } from '@ngx-translate/core';

/** Models */
import { ChatMessage, Conversation } from '../../core/models/chat-message.model';
Expand Down Expand Up @@ -40,6 +41,7 @@ export type CopilotTab = 'chat' | 'recent' | 'preferences' | 'help';
selector: 'mifosx-copilot-panel',
imports: [
CommonModule,
TranslateModule,
CopilotHeaderComponent,
ChatAreaComponent,
RecentChatsComponent,
Expand All @@ -51,6 +53,7 @@ export type CopilotTab = 'chat' | 'recent' | 'preferences' | 'help';
})
export class CopilotPanelComponent {
private readonly featureService = inject(CopilotFeatureService);
private readonly translate = inject(TranslateService);

/** Master enable check (deployment + role + user preference). */
isEnabled = this.featureService.shouldShowPanel();
Expand All @@ -71,28 +74,26 @@ export class CopilotPanelComponent {
/** Header context label, e.g. "Client: Rajesh Kumar". */
contextLabel: string | null = null;

/** Welcome-state greeting. */
displayName = 'there';

/** Suggestions shown on the empty state. */
/** Suggestions shown on the empty state (translation keys). */
emptySuggestions: string[] = [
'Show me details for client John Doe',
'What is the repayment schedule for loan #107?',
'Show savings account balance for client #52',
'How many loans are overdue this week?'
'copilot.suggestions.clientDetails',
'copilot.suggestions.repaymentSchedule',
'copilot.suggestions.savingsBalance',
'copilot.suggestions.overdueLoans'
];

private seq = 0;

/** Returns the translation key for the time-of-day greeting. */
get greetingTime(): string {
const hour = new Date().getHours();
if (hour < 12) {
return 'Good morning';
return 'copilot.greeting.morning';
}
if (hour < 17) {
return 'Good afternoon';
return 'copilot.greeting.afternoon';
}
return 'Good evening';
return 'copilot.greeting.evening';
}

togglePanel(): void {
Expand Down Expand Up @@ -122,8 +123,13 @@ export class CopilotPanelComponent {
this.respondMock(content);
}

sendSuggestedPrompt(prompt: string): void {
this.sendMessage(prompt);
/**
* Suggestion chips and help prompts pass a translation key (or, for assistant
* follow-ups, plain text). Translate it so the actual prompt text is sent;
* TranslateService returns the input unchanged when it is not a known key.
*/
sendSuggestedPrompt(promptKey: string): void {
this.sendMessage(this.translate.instant(promptKey));
}

stopStreaming(): void {
Expand Down
Loading
Loading