ui changes
This commit is contained in:
@@ -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…'],
|
||||
|
||||
Reference in New Issue
Block a user