ui changes

This commit is contained in:
2026-08-10 19:36:45 +05:30
parent 409dffe938
commit 5cd49a1655
49 changed files with 7988 additions and 470 deletions

View File

@@ -84,30 +84,48 @@ const GREETINGS = {
},
'admin.candidates': (f) => {
if (f.missingCredentials.length) {
return `${plural(f.missingCredentials.length, 'scored candidate')} ${verb(f.missingCredentials.length, 'has', 'have')} no credentials on file.`;
if (!f.total) return 'Nothing in the pipeline yet, so there is no pool to analyse.';
if (f.needsDeeperReview.length) {
return `${plural(f.needsDeeperReview.length, 'candidate')} currently ${verb(f.needsDeeperReview.length, 'needs', 'need')} deeper review.`;
}
return `${plural(f.profiles.length, 'worker')} in the pool, ${f.elite.length} at Elite.`;
if (f.singleCandidateRoles.length) {
return `${plural(f.singleCandidateRoles.length, 'role')} ${verb(f.singleCandidateRoles.length, 'rests', 'rest')} on a single viable candidate.`;
}
return `All ${f.total} candidates are scored, credentialed and comparable.`;
},
'admin.activity': (f) => {
if (!f.activity.length) return 'No activity logged yet.';
if (f.activity24h.length === 0) {
return `Quiet day — ${plural(f.activity7d.length, 'event')} this week, none in the last 24 hours.`;
const patterns = f.activitySignals.flags.length;
if (patterns) {
return `${plural(patterns, 'unusual activity pattern')} ${verb(patterns, 'needs', 'need')} attention.`;
}
return `${plural(f.activity24h.length, 'event')} today, ${f.activity7d.length} this week.`;
if (f.activity24h.length === 0) {
return `Quiet week — ${plural(f.activity7d.length, 'event')} logged, nothing out of pattern.`;
}
return `${plural(f.activity24h.length, 'event')} today, ${f.activity7d.length} this week, nothing out of pattern.`;
},
};
/** Title lines. First names read better than a formal greeting in a small panel. */
/**
* Title lines.
*
* Every Admin context greets by the clock. That is not decoration: the greeting
* is the one line guaranteed to be read, so it has to prove the panel knows when
* and where it is before it says anything about the data. A fixed "Good morning"
* on an 11pm shift does the opposite.
*
* First names read better than a formal greeting in a small panel, so the name is
* used where one is known.
*/
const TITLES = {
'employer.overview': (f, name) => `Good ${timeOfDay(f.today)}${name ? `, ${name}` : ''}`,
'employer.candidates': () => 'Candidate intelligence',
'employer.analytics': () => 'Analytics assistant',
'admin.controlCenter': (f) => `Platform check, ${timeOfDay(f.today)}`,
'admin.candidates': () => 'Talent analysis',
'admin.activity': () => 'Activity review',
'admin.controlCenter': (f, name) => `Good ${timeOfDay(f.today)}${name ? `, ${name}` : ''}`,
'admin.candidates': (f, name) => `Good ${timeOfDay(f.today)}${name ? `, ${name}` : ''}`,
'admin.activity': (f, name) => `Good ${timeOfDay(f.today)}${name ? `, ${name}` : ''}`,
};
/** Descriptions state what this page's assistant can actually do with the data. */
@@ -116,12 +134,20 @@ const DESCRIPTIONS = {
`Reading ${f.total} applicants across ${plural(f.openPositions.length, 'open role')}.`,
'employer.candidates': (f) => `All ${f.total} candidates and their score breakdowns.`,
'employer.analytics': () => 'Every figure on this page, explained or forecast.',
'admin.controlCenter': () => 'Health checks, data integrity and what needs intervention.',
'admin.candidates': () => 'Talent supply and risk across the candidate pool.',
'admin.controlCenter': (f) =>
`Reading ${plural(f.total, 'application')} across ${plural(f.postings.length, 'position')} — health, operations and what needs intervention.`,
'admin.candidates': (f) =>
`${plural(f.total, 'candidate')} and ${plural(f.profiles.length, 'talent profile')} — supply, quality and risk.`,
'admin.activity': (f) => `${plural(f.activity.length, 'logged event')}, reconciled and profiled.`,
};
/** Placeholders phrased as things this page's user would actually type. */
/**
* Placeholders phrased as things this page's user would actually type.
*
* The Admin ones name Owliver and name the page's subject, because the panel sits
* permanently beside the dashboard: someone glancing at it needs the input itself
* to say what it is for and what this page can be asked about.
*/
const PLACEHOLDERS = {
'employer.overview': (f) => [
`Summarize ${timeOfDay(f.today)}'s hiring…`,
@@ -138,9 +164,21 @@ const PLACEHOLDERS = {
'Forecast hiring…',
'Generate an executive summary…',
],
'admin.controlCenter': () => ['Show unusual activity…', 'Summarize platform usage…'],
'admin.candidates': () => ['Where is the risk in the pool?', 'Summarize recruitment trends…'],
'admin.activity': () => ['Show unusual activity…', 'Summarize platform usage…'],
'admin.controlCenter': () => [
'Ask Owliver about platform health…',
'Ask Owliver what needs attention…',
'Ask Owliver about hiring bottlenecks…',
],
'admin.candidates': () => [
'Ask Owliver about candidates…',
'Ask Owliver about screening gaps…',
'Ask Owliver where the risk is…',
],
'admin.activity': () => [
'Ask Owliver about recent activity…',
'Ask Owliver about unusual activity…',
'Ask Owliver about security concerns…',
],
};
/* ── Suggested prompts ──────────────────────────────────────────────────── */
@@ -195,28 +233,47 @@ const PROMPTS = {
{ label: 'Break down by role', prompt: 'Break down performance by role category', capability: 'department-insights' },
].filter(Boolean),
/* Admin. The first chip is whatever the data has made urgent, so the set
reorders itself as the platform's state changes rather than offering the
same five questions regardless. */
'admin.controlCenter': (f) => [
f.starvedPositions.length
? { label: `${f.starvedPositions.length} roles with no applicants`, prompt: 'What needs attention right now?', capability: 'attention-required' }
: { label: 'What needs attention?', prompt: 'What needs attention right now?', capability: 'attention-required' },
{ label: 'Platform health', prompt: 'Run a platform health check', capability: 'platform-health' },
{ label: 'Summarize platform usage', prompt: 'Summarize platform usage', capability: 'platform-health' },
f.unscoredProfiles.length
? { label: `${f.unscoredProfiles.length} unscored profiles`, prompt: 'What should I fix first?', capability: 'recommendations' }
: { label: 'What should I fix?', prompt: 'What should I fix first?', capability: 'recommendations' },
f.bottleneck && f.bottleneck.lost > 0
? { label: `Bottleneck at ${f.bottleneck.to}`, prompt: `Show hiring bottlenecks — why do candidates drop between ${f.bottleneck.from} and ${f.bottleneck.to}?`, capability: 'pipeline-health' }
: { label: 'Show hiring bottlenecks', prompt: 'Show hiring bottlenecks in the pipeline', capability: 'pipeline-health' },
{ label: 'Summarize platform usage', prompt: 'Summarize platform usage and workforce scale', capability: 'workforce-summary' },
{ label: 'Hiring operations', prompt: 'How are hiring operations performing?', capability: 'hiring-operations' },
{ label: 'Identify operational risks', prompt: 'Identify operational risks and what I should fix first', capability: 'recommendations' },
],
'admin.candidates': (f) => [
{ label: 'Recruitment insights', prompt: 'Show recruitment insights across the platform', capability: 'recruitment-insights' },
f.ranked.length >= 2
? { label: `Compare ${f.ranked[0].applicant_name.split(' ')[0]} & ${f.ranked[1].applicant_name.split(' ')[0]}`, prompt: 'Compare the top candidates in the pool', capability: 'top-candidates' }
: { label: 'Compare top candidates', prompt: 'Compare the top candidates in the pool', capability: 'top-candidates' },
f.missingCredentials.length
? { label: `${f.missingCredentials.length} missing credentials`, prompt: 'Analyse risk across the candidate pool', capability: 'candidate-risk' }
: { label: 'Candidate risk', prompt: 'Analyse risk across the candidate pool', capability: 'candidate-risk' },
? { label: `${f.missingCredentials.length} missing credentials`, prompt: 'Show candidate risks across the pool', capability: 'candidate-risk' }
: { label: 'Show candidate risks', prompt: 'Show candidate risks across the pool', capability: 'candidate-risk' },
f.unscreened.length
? { label: `${f.unscreened.length} unscored applicants`, prompt: 'Find screening gaps in the candidate pool', capability: 'screening-gaps' }
: { label: 'Find screening gaps', prompt: 'Find screening gaps in the candidate pool', capability: 'screening-gaps' },
{ label: 'Recommend next actions', prompt: 'Recommend next actions for these candidates', capability: 'hiring-recommendations' },
{ label: 'Recruitment insights', prompt: 'Show recruitment insights across the platform', capability: 'recruitment-insights' },
],
'admin.activity': (f) => [
{ label: 'Audit summary', prompt: 'Summarize the audit trail', capability: 'audit-summary' },
{ label: 'Show unusual activity', prompt: 'Show me unusual account activity', capability: 'user-activity' },
f.activity7d.length
? { label: `${f.activity7d.length} events this week`, prompt: 'Summarize platform usage this week', capability: 'audit-summary' }
: null,
].filter(Boolean),
f.activitySignals.flags.length
? { label: `${f.activitySignals.flags.length} unusual patterns`, prompt: 'Show unusual activity on the platform', capability: 'unusual-activity' }
: { label: 'Show unusual activity', prompt: 'Show unusual activity on the platform', capability: 'unusual-activity' },
f.activity24h.length
? { label: `Summarize today's ${f.activity24h.length} events`, prompt: "Summarize today's activity", capability: 'audit-summary' }
: { label: "Summarize today's activity", prompt: "Summarize today's activity", capability: 'audit-summary' },
{ label: 'What changed recently?', prompt: 'What changed recently on the platform?', capability: 'audit-summary' },
{ label: 'Identify security concerns', prompt: 'Identify security concerns in the audit trail', capability: 'security-insights' },
{ label: 'Who is most active?', prompt: 'Which accounts are most active?', capability: 'user-activity' },
],
};
@@ -231,7 +288,7 @@ const firstName = (fullName) => String(fullName || '').trim().split(/\s+/)[0] ||
export function buildIntro(contextId, facts, userName) {
const name = firstName(userName);
return {
title: TITLES[contextId]?.(facts, name) ?? 'Krow AI',
title: TITLES[contextId]?.(facts, name) ?? 'Owliver',
greeting: GREETINGS[contextId]?.(facts) ?? '',
description: DESCRIPTIONS[contextId]?.(facts) ?? '',
placeholders: PLACEHOLDERS[contextId]?.(facts) ?? ['Ask anything…'],