Skip to content

Commit 65ce10d

Browse files
committed
fix: refine AI routing, NER keywords, timezone shift, and UI title summary
1 parent c74cd9d commit 65ce10d

File tree

5 files changed

+66
-55
lines changed

5 files changed

+66
-55
lines changed

Frontend/src/hooks/useRealtimeNotifications.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,28 @@ const useRealtimeNotifications = () => {
9292
// For now, we rely on recipientRole filter in the UI components.
9393

9494
if (isFromAdmin) {
95-
// Sent by Admin -> Notify User
96-
addNotification({
97-
title: 'New Response from Support',
98-
message: newMessage.message || "An agent replied to your ticket.",
99-
ticketId: newMessage.ticket_id,
100-
type: 'message',
101-
recipientRole: 'user'
102-
});
95+
// Sent by Admin -> Notify User (only if it's their ticket)
96+
// Note: ideally we'd check isOwner here, but for now we filter by role
97+
if (profile.role === 'user') {
98+
addNotification({
99+
title: 'New Response from Support',
100+
message: newMessage.message?.length > 120 ? newMessage.message.substring(0, 120) + "..." : (newMessage.message || "An agent replied to your ticket."),
101+
ticketId: newMessage.ticket_id,
102+
type: 'message',
103+
recipientRole: 'user'
104+
});
105+
}
103106
} else {
104107
// Sent by User -> Notify Admin
105-
addNotification({
106-
title: 'New Message from User',
107-
message: newMessage.message || "A user replied to their ticket.",
108-
ticketId: newMessage.ticket_id,
109-
type: 'message',
110-
recipientRole: 'admin'
111-
});
108+
if (isAdmin) {
109+
addNotification({
110+
title: 'New Message from User',
111+
message: newMessage.message || "A user replied to their ticket.",
112+
ticketId: newMessage.ticket_id,
113+
type: 'message',
114+
recipientRole: 'admin'
115+
});
116+
}
112117
}
113118
};
114119

Frontend/src/user/pages/TicketDetail.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ const TicketDetail = () => {
180180
</span>
181181
</div>
182182
<h1 className="text-3xl sm:text-4xl font-black text-gray-900 tracking-tight leading-[1.15] max-w-3xl">
183-
{ticket.text || ticket.summary || "No description provided"}
183+
{ticket.text?.length > 120 ? ticket.text.substring(0, 120) + "..." : (ticket.text || ticket.summary || "No description provided")}
184184
</h1>
185185
</div>
186186
</div>

Frontend/src/utils/dateUtils.js

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,47 @@
11
/**
22
* Unified Date Utility for HELPDESK.AI
3-
* Follows IST by default but adapts to user's local timezone.
3+
* Fixes timezone shift issues by explicitly forcing local display.
44
*/
55

6-
/**
7-
* Formats a date string into a readable format.
8-
* Defaults to the user's local timezone but ensures consistency.
9-
*/
106
export const formatTimelineDate = (dateStr) => {
117
if (!dateStr) return null;
12-
const date = new Date(dateStr);
138

14-
// User's locale and timezone
15-
const userLocale = navigator.language || 'en-IN';
16-
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
9+
// Ensure the date string is interpreted as UTC if it's an ISO string from DB
10+
let date;
11+
if (typeof dateStr === 'string' && !dateStr.includes('Z') && !dateStr.includes('+')) {
12+
// If it's a raw string without TZ, assume it was intended as UTC from our backend
13+
date = new Date(dateStr + 'Z');
14+
} else {
15+
date = new Date(dateStr);
16+
}
1717

18-
return new Intl.DateTimeFormat(userLocale, {
19-
timeZone: userTimeZone,
18+
if (isNaN(date.getTime())) return 'Invalid Date';
19+
20+
// Using the browser's default locale and timeZone (which is the user's local)
21+
return date.toLocaleString(undefined, {
2022
day: '2-digit',
2123
month: 'short',
2224
year: 'numeric',
2325
hour: '2-digit',
2426
minute: '2-digit',
2527
hour12: true
26-
}).format(date);
28+
});
2729
};
2830

29-
/**
30-
* Returns the timezone abbreviation (e.g., "IST", "EST")
31-
*/
3231
export const getTimeZoneAbbr = () => {
33-
return new Intl.DateTimeFormat('en-US', {
34-
timeZoneName: 'short',
35-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
36-
})
37-
.formatToParts(new Date())
38-
.find(part => part.type === 'timeZoneName')?.value || 'UTC';
32+
try {
33+
return new Intl.DateTimeFormat('en-US', {
34+
timeZoneName: 'short'
35+
})
36+
.formatToParts(new Date())
37+
.find(part => part.type === 'timeZoneName')?.value || 'IST';
38+
} catch (e) {
39+
return 'IST';
40+
}
3941
};
4042

41-
/**
42-
* Combines date and timezone for a clear UI display.
43-
* Example: "Mar 25, 2026, 01:30 PM (IST)"
44-
*/
4543
export const formatFullTimestamp = (dateStr) => {
4644
const formatted = formatTimelineDate(dateStr);
47-
if (!formatted) return 'Pending...';
45+
if (!formatted) return 'Processing...';
4846
return `${formatted} (${getTimeZoneAbbr()})`;
4947
};

backend/services/classifier_service.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,23 @@ def predict(self, text: str) -> dict:
122122
# Derive auto_resolve
123123
auto_resolve = subcategory in AUTO_RESOLVE_SUBS
124124

125-
# Apply confidence threshold
126-
CONFIDENCE_THRESHOLD = 0.20
127-
if confidence < CONFIDENCE_THRESHOLD:
128-
return {
129-
"category": "General",
130-
"subcategory": "Incomplete Information",
131-
"priority": "Low",
132-
"auto_resolve": False,
133-
"assigned_team": "General Support",
134-
"confidence": confidence,
135-
}
125+
# --- Regex Override Layer (Boost for Technical Keywords) ---
126+
tech_keywords = {
127+
"Network": ["IP address", "hostname", "connection", "network", "bandwidth", "DNS", "firewall", "VPN"],
128+
"Software": ["crash", "load", "website", "application", "error", "bug", "failing", "software"],
129+
"Access": ["login", "password", "access", "authentication", "account", "permission"]
130+
}
131+
132+
lower_text = text.lower()
133+
for cat, keywords in tech_keywords.items():
134+
if any(k.lower() in lower_text for k in keywords):
135+
# If current prediction is generic, override it
136+
if category == "General" or confidence < 0.6:
137+
category = cat
138+
assigned_team = TEAM_MAP.get(cat, "General Support")
139+
# If we found a keyword, we're more confident
140+
confidence = max(confidence, 0.85)
141+
break
136142

137143
return {
138144
"category": category,

backend/services/ner_service.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
# Regex patterns for high-fidelity extraction
1919
REGEX_PATTERNS = {
20-
"IP_ADDRESS": r"\b(?:\d{1,3}\.){3}\d{1,3}\b",
21-
"HOSTNAME": r"\b(?:srv|db|app|web|dev|prod)-[\w\d-]+\b", # Common conventions
20+
"IP_ADDRESS": r"\b(?:\d{1,3}\.){3}\d{1,3}\b|IP\s?Address",
21+
"HOSTNAME": r"\b(?:srv|db|app|web|dev|prod)-[\w\d-]+\b|Hostname",
22+
"NETWORK_ERROR": r"Network issues|Timeout|Connection failed|Cannot load",
23+
"LOGIN_ISSUE": r"logging in|login error|authentication failed",
2224
"VLAN": r"\bVLAN\s?\d+\b"
2325
}
2426

0 commit comments

Comments
 (0)