Skip to content

Commit 5f353e8

Browse files
committed
AI grouping functionality added
1 parent 0d7b608 commit 5f353e8

File tree

3 files changed

+253
-1
lines changed

3 files changed

+253
-1
lines changed

src/app/retrospective/components/sticky-note/sticky-note.component.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ import { JiraControlModule } from '../../../jira-control/jira-control.module';
114114
<div class="note-content">
115115
{{ note.content }}
116116
</div>
117+
118+
<!-- Tags - Show if present -->
119+
<div *ngIf="note.tags && note.tags.length > 0" class="note-tags">
120+
<nz-tag
121+
*ngFor="let tag of note.tags"
122+
[nzColor]="getTagColor(tag)"
123+
class="note-tag"
124+
>
125+
<span nz-icon nzType="tag" nzTheme="outline" class="tag-icon"></span>
126+
{{ tag }}
127+
</nz-tag>
128+
</div>
117129
</div>
118130
119131
<!-- Note Footer - User info and actions -->
@@ -263,6 +275,32 @@ import { JiraControlModule } from '../../../jira-control/jira-control.module';
263275
padding: 4px 0;
264276
}
265277
278+
/* Tags */
279+
.note-tags {
280+
display: flex;
281+
flex-wrap: wrap;
282+
gap: 6px;
283+
margin-top: 12px;
284+
padding-top: 8px;
285+
border-top: 1px dashed rgba(0, 0, 0, 0.08);
286+
}
287+
288+
.note-tag {
289+
margin: 0;
290+
font-size: 11px;
291+
padding: 2px 8px;
292+
border-radius: 4px;
293+
display: inline-flex;
294+
align-items: center;
295+
gap: 4px;
296+
font-weight: 500;
297+
letter-spacing: 0.3px;
298+
}
299+
300+
.tag-icon {
301+
font-size: 10px;
302+
}
303+
266304
/* Footer */
267305
.note-footer {
268306
display: flex;
@@ -503,4 +541,22 @@ export class StickyNoteComponent implements OnInit, OnDestroy {
503541
this.noteDelete.emit(this.note.id);
504542
}
505543
}
544+
545+
getTagColor(tag: string): string {
546+
const tagColors: { [key: string]: string } = {
547+
'Communication': 'blue',
548+
'Process': 'cyan',
549+
'Technical': 'purple',
550+
'Team': 'green',
551+
'Documentation': 'geekblue',
552+
'Time': 'orange',
553+
'Quality': 'lime',
554+
'Planning': 'magenta',
555+
'Tools': 'volcano',
556+
'Blocker': 'red',
557+
'General': 'default'
558+
};
559+
560+
return tagColors[tag] || 'default';
561+
}
506562
}

src/app/retrospective/interfaces/retrospective.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export interface StickyNote {
1212
};
1313
votes: number;
1414
voterIds: string[];
15+
tags?: string[];
16+
groupId?: string;
1517
createdAt: string;
1618
updatedAt: string;
1719
}

src/app/retrospective/pages/retrospective-board/retrospective-board-page.component.ts

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,44 @@ export class RetrospectiveBoardPageComponent implements OnInit, OnDestroy {
946946
return;
947947
}
948948

949-
// Show confirmation modal
949+
// Special handling for GROUPING phase - offer AI assistance
950+
if (phase === RetroPhase.GROUPING && this.currentBoard?.currentPhase === RetroPhase.BRAINSTORMING) {
951+
this.modal.confirm({
952+
nzTitle: `Switch to ${this.getPhaseTitle(phase)} Phase?`,
953+
nzContent: `
954+
<div style="margin-bottom: 12px;">
955+
<strong>Switching to Grouping phase will:</strong>
956+
<ul style="margin-top: 8px; padding-left: 20px;">
957+
<li>Disable adding new notes</li>
958+
<li>Allow moving notes between columns</li>
959+
<li>Keep author information hidden</li>
960+
</ul>
961+
</div>
962+
<div style="margin-top: 16px; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; color: white;">
963+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
964+
<span style="font-size: 18px;">💡</span>
965+
<strong>AI-Powered Grouping Available!</strong>
966+
</div>
967+
<p style="margin: 0; font-size: 13px; opacity: 0.95;">
968+
Would you like AI to automatically analyze and group similar notes with tags?
969+
</p>
970+
</div>
971+
`,
972+
nzOkText: '✨ Use AI Grouping',
973+
nzOkType: 'primary',
974+
nzCancelText: 'Switch Without AI',
975+
nzOnOk: () => {
976+
this.retrospectiveService.updatePhase(phase);
977+
setTimeout(() => this.performAIGrouping(), 500);
978+
},
979+
nzOnCancel: () => {
980+
this.retrospectiveService.updatePhase(phase);
981+
}
982+
});
983+
return;
984+
}
985+
986+
// Show confirmation modal for other phases
950987
this.modal.confirm({
951988
nzTitle: `Switch to ${this.getPhaseTitle(phase)} Phase?`,
952989
nzContent: this.getPhaseChangeWarning(phase),
@@ -1170,4 +1207,161 @@ export class RetrospectiveBoardPageComponent implements OnInit, OnDestroy {
11701207
this.isPhaseModalVisible = false;
11711208
this.selectedPhase = this.currentBoard?.currentPhase || RetroPhase.BRAINSTORMING;
11721209
}
1210+
1211+
// AI Grouping functionality
1212+
performAIGrouping() {
1213+
if (!this.currentBoard) return;
1214+
1215+
const notificationKey = `ai-grouping-${Date.now()}`;
1216+
1217+
// Show loading notification
1218+
this.modal.info({
1219+
nzTitle: '🤖 AI Grouping in Progress',
1220+
nzContent: `
1221+
<div style="text-align: center; padding: 20px;">
1222+
<div style="font-size: 48px; margin-bottom: 16px;">
1223+
<span style="display: inline-block; animation: spin 2s linear infinite;">🔄</span>
1224+
</div>
1225+
<p style="font-size: 14px; color: #6b7280;">
1226+
Analyzing ${this.currentBoard.stickyNotes.length} notes across ${this.currentBoard.columns.length} columns...
1227+
</p>
1228+
<p style="font-size: 13px; color: #9ca3af; margin-top: 8px;">
1229+
This may take a few moments
1230+
</p>
1231+
<style>
1232+
@keyframes spin {
1233+
from { transform: rotate(0deg); }
1234+
to { transform: rotate(360deg); }
1235+
}
1236+
</style>
1237+
</div>
1238+
`,
1239+
nzOkText: 'Working...',
1240+
nzOkDisabled: true,
1241+
nzClosable: false,
1242+
nzMaskClosable: false
1243+
});
1244+
1245+
// Simulate AI processing (in production, this would call an AI service)
1246+
setTimeout(() => {
1247+
const groupedNotes = this.analyzeAndGroupNotes();
1248+
1249+
// Apply the grouping
1250+
groupedNotes.forEach(noteUpdate => {
1251+
this.retrospectiveService.updateStickyNote(noteUpdate.noteId, {
1252+
tags: noteUpdate.tags,
1253+
groupId: noteUpdate.groupId
1254+
});
1255+
});
1256+
1257+
// Close loading modal and show success
1258+
this.modal.closeAll();
1259+
1260+
setTimeout(() => {
1261+
this.modal.success({
1262+
nzTitle: '✨ AI Grouping Complete!',
1263+
nzContent: `
1264+
<div style="padding: 12px;">
1265+
<p style="margin-bottom: 12px;">Successfully analyzed and grouped ${this.currentBoard?.stickyNotes.length} notes.</p>
1266+
<div style="background: #f3f4f6; padding: 12px; border-radius: 8px; margin-top: 12px;">
1267+
<strong style="display: block; margin-bottom: 8px;">Groups Identified:</strong>
1268+
<ul style="margin: 0; padding-left: 20px; font-size: 13px;">
1269+
${this.getUniqueGroups().map(group => `<li>${group}</li>`).join('')}
1270+
</ul>
1271+
</div>
1272+
<p style="margin-top: 12px; font-size: 13px; color: #6b7280;">
1273+
Notes have been tagged and can now be easily grouped by dragging them together.
1274+
</p>
1275+
</div>
1276+
`,
1277+
nzOkText: 'Great!',
1278+
nzWidth: 500
1279+
});
1280+
}, 100);
1281+
}, 2500); // Simulate AI processing time
1282+
}
1283+
1284+
private analyzeAndGroupNotes(): Array<{ noteId: string; tags: string[]; groupId: string }> {
1285+
if (!this.currentBoard) return [];
1286+
1287+
const noteUpdates: Array<{ noteId: string; tags: string[]; groupId: string }> = [];
1288+
1289+
// Group notes by column first
1290+
const notesByColumn: { [columnId: string]: StickyNote[] } = {};
1291+
this.currentBoard.stickyNotes.forEach(note => {
1292+
if (!notesByColumn[note.columnId]) {
1293+
notesByColumn[note.columnId] = [];
1294+
}
1295+
notesByColumn[note.columnId].push(note);
1296+
});
1297+
1298+
// Analyze each column separately
1299+
Object.entries(notesByColumn).forEach(([columnId, notes]) => {
1300+
const columnGroups = this.identifyGroupsInColumn(notes, columnId);
1301+
noteUpdates.push(...columnGroups);
1302+
});
1303+
1304+
return noteUpdates;
1305+
}
1306+
1307+
private identifyGroupsInColumn(notes: StickyNote[], columnId: string): Array<{ noteId: string; tags: string[]; groupId: string }> {
1308+
// Simple keyword-based grouping (in production, use NLP/AI service)
1309+
const keywords = {
1310+
'Communication': ['communication', 'talk', 'discuss', 'meeting', 'sync', 'share', 'update', 'inform'],
1311+
'Process': ['process', 'workflow', 'procedure', 'system', 'method', 'approach', 'way'],
1312+
'Technical': ['code', 'bug', 'technical', 'deploy', 'build', 'test', 'review', 'refactor', 'architecture'],
1313+
'Team': ['team', 'collaboration', 'together', 'help', 'support', 'pair', 'cooperation'],
1314+
'Documentation': ['document', 'docs', 'documentation', 'wiki', 'readme', 'guide', 'manual'],
1315+
'Time': ['time', 'deadline', 'schedule', 'late', 'early', 'duration', 'speed', 'fast', 'slow'],
1316+
'Quality': ['quality', 'improvement', 'better', 'improve', 'enhance', 'optimize', 'excellent'],
1317+
'Planning': ['plan', 'planning', 'estimate', 'forecast', 'strategy', 'goal', 'objective'],
1318+
'Tools': ['tool', 'platform', 'software', 'application', 'service', 'framework', 'library'],
1319+
'Blocker': ['blocker', 'blocked', 'issue', 'problem', 'obstacle', 'challenge', 'difficulty']
1320+
};
1321+
1322+
const noteUpdates: Array<{ noteId: string; tags: string[]; groupId: string }> = [];
1323+
1324+
notes.forEach(note => {
1325+
const content = note.content.toLowerCase();
1326+
const matchedTags: string[] = [];
1327+
1328+
// Find matching keywords
1329+
Object.entries(keywords).forEach(([category, words]) => {
1330+
const hasMatch = words.some(word => content.includes(word.toLowerCase()));
1331+
if (hasMatch) {
1332+
matchedTags.push(category);
1333+
}
1334+
});
1335+
1336+
// If no tags matched, assign a default tag
1337+
if (matchedTags.length === 0) {
1338+
matchedTags.push('General');
1339+
}
1340+
1341+
// Use the primary tag as the group ID
1342+
const primaryTag = matchedTags[0];
1343+
const groupId = `${columnId}-${primaryTag.toLowerCase().replace(/\s+/g, '-')}`;
1344+
1345+
noteUpdates.push({
1346+
noteId: note.id,
1347+
tags: matchedTags.slice(0, 3), // Limit to 3 tags per note
1348+
groupId: groupId
1349+
});
1350+
});
1351+
1352+
return noteUpdates;
1353+
}
1354+
1355+
private getUniqueGroups(): string[] {
1356+
if (!this.currentBoard) return [];
1357+
1358+
const groups = new Set<string>();
1359+
this.currentBoard.stickyNotes.forEach(note => {
1360+
if (note.tags && note.tags.length > 0) {
1361+
note.tags.forEach(tag => groups.add(tag));
1362+
}
1363+
});
1364+
1365+
return Array.from(groups).sort();
1366+
}
11731367
}

0 commit comments

Comments
 (0)