updates on the ui changes and changed into doormile
This commit is contained in:
413
src/components/assistant/DoormileAI/pageContext.jsx
Normal file
413
src/components/assistant/DoormileAI/pageContext.jsx
Normal file
@@ -0,0 +1,413 @@
|
||||
import {
|
||||
Clock3 as LuClock3,
|
||||
Package as LuPackage,
|
||||
Bike as LuBike,
|
||||
Truck as LuTruck,
|
||||
Building2 as LuBuilding2,
|
||||
CircleDot as LuCircleDot,
|
||||
Banknote as LuBanknote,
|
||||
Users as LuUsers,
|
||||
Layers as LuLayers,
|
||||
TimerOff as LuTimerOff,
|
||||
UserPlus as LuUserPlus,
|
||||
PackagePlus as LuPackagePlus,
|
||||
ListPlus as LuListPlus,
|
||||
Repeat as LuRepeat
|
||||
} from 'lucide-react';
|
||||
|
||||
// ==============================|| Doormile AI — page context ||============================== //
|
||||
//
|
||||
// Maps the current route to (a) a short context label shown under the panel
|
||||
// header and (b) the suggested questions offered in the welcome state, so the
|
||||
// assistant opens on questions relevant to what the operator is looking at
|
||||
// rather than a fixed Orders-shaped list.
|
||||
//
|
||||
// Every suggestion here MUST be a phrasing the deterministic matcher in
|
||||
// intents.js actually resolves — a suggestion chip that returns "I can't
|
||||
// answer that yet" is worse than no chip. When adding one, check it against
|
||||
// the INTENTS catalog first.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Short chip labels.
|
||||
//
|
||||
// Every `text` below is the exact phrasing the deterministic matcher in
|
||||
// intents.js resolves, so it CANNOT be shortened in place — that is the string
|
||||
// the click actually asks. This map only changes what the chip DISPLAYS.
|
||||
//
|
||||
// It exists because the welcome state offers up to twenty suggestions, and as
|
||||
// full sentences they are 130-255px wide in a ~393px panel: barely two fit per
|
||||
// row, so the "chips" laid out as fourteen near-full-width rows and read as a
|
||||
// list again. Shortened, three or four fit per row and the block becomes
|
||||
// something you scan.
|
||||
//
|
||||
// The full question stays the accessible name and the tooltip, so nothing is
|
||||
// lost for screen readers or for anyone unsure what a chip will ask. A
|
||||
// suggestion with no entry here simply shows its full text.
|
||||
// ---------------------------------------------------------------------------
|
||||
export const CHIP_LABELS = {
|
||||
"Give me today's operations summary": "Today's summary",
|
||||
'How many orders today?': 'Orders today',
|
||||
'Which orders are delayed?': 'Delayed orders',
|
||||
'Create an order': 'Create order',
|
||||
'Create multiple orders': 'Bulk orders',
|
||||
'Total revenue today': 'Revenue today',
|
||||
'How many pending orders today?': 'Pending today',
|
||||
'How many cancelled orders today?': 'Cancelled today',
|
||||
'How many delivered orders today?': 'Delivered today',
|
||||
'Morning Batch orders today': 'Morning batch',
|
||||
'Orders today vs yesterday': 'Today vs yesterday',
|
||||
'Create a customer': 'Create customer',
|
||||
'How many riders are active?': 'Active riders',
|
||||
'How many vehicles are available?': 'Vehicles available',
|
||||
'Current hub status': 'Hub status',
|
||||
'Which hubs are experiencing delays?': 'Hub delays',
|
||||
'How many tenants do we have?': 'Tenants',
|
||||
'How many orders this week?': 'Orders this week',
|
||||
'Total revenue this week': 'Revenue this week',
|
||||
'How many customers do we have?': 'Customers',
|
||||
'Assign a rider': 'Assign order',
|
||||
"Repeat yesterday's orders": 'Repeat yesterday'
|
||||
};
|
||||
|
||||
// The one follow-up phrasing that is not already a page suggestion. Verified
|
||||
// against ASSIGN_TRIGGER in assignActions.js — it opens the assign-rider flow.
|
||||
const ASSIGN_TEXT = 'Assign a rider';
|
||||
|
||||
// Recreates a previous day's run rather than building an order from scratch.
|
||||
// This is the phrasing intents.js documents for it ("repeat yesterday's
|
||||
// orders") and it is matched by REPEAT_TRIGGER in repeatRuns.js, which is
|
||||
// ordered AHEAD of createOrder/createBulkOrders precisely so a sentence
|
||||
// containing both "repeat" and "orders" recalls the run instead of opening a
|
||||
// blank create form.
|
||||
const REPEAT_TEXT = "Repeat yesterday's orders";
|
||||
|
||||
// Not a question — an OUTCOME. AIPanel sets this as the follow-up key when a
|
||||
// create or repeat run actually succeeds, so the next steps offered are the
|
||||
// ones that follow from an order existing, rather than the ones that follow
|
||||
// from having asked to make one. Assigning a rider only makes sense on this
|
||||
// side of the write.
|
||||
export const ORDER_CREATED = '__orderCreated';
|
||||
|
||||
// The same write, but with every new order already given a rider — a repeat run
|
||||
// that handed each copy back to whoever ran it yesterday.
|
||||
//
|
||||
// It needs its own key purely so ASSIGN_TEXT can be left out. Offering "Assign
|
||||
// order" after the bot has just assigned them all presents finished work as a
|
||||
// pending step, and an operator who takes the suggestion is walked into
|
||||
// reassigning orders that are already out.
|
||||
export const ORDER_CREATED_ASSIGNED = '__orderCreatedAssigned';
|
||||
|
||||
const ORDERS = {
|
||||
label: 'Orders',
|
||||
suggestions: [
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" },
|
||||
{ icon: LuPackage, text: 'How many orders today?' },
|
||||
{ icon: LuTimerOff, text: 'Which orders are delayed?' },
|
||||
{ icon: LuPackagePlus, text: 'Create an order' },
|
||||
{ icon: LuRepeat, text: REPEAT_TEXT }
|
||||
],
|
||||
more: [
|
||||
{ icon: LuListPlus, text: 'Create multiple orders' },
|
||||
{ icon: LuBanknote, text: 'Total revenue today' },
|
||||
{ icon: LuCircleDot, text: 'How many pending orders today?' },
|
||||
{ icon: LuPackage, text: 'How many cancelled orders today?' },
|
||||
{ icon: LuPackage, text: 'How many delivered orders today?' },
|
||||
{ icon: LuLayers, text: 'Morning Batch orders today' },
|
||||
{ icon: LuClock3, text: 'Orders today vs yesterday' }
|
||||
]
|
||||
};
|
||||
|
||||
const RIDERS = {
|
||||
label: 'Riders',
|
||||
suggestions: [
|
||||
{ icon: LuBike, text: 'How many riders are active?' },
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" },
|
||||
{ icon: LuPackage, text: 'How many orders today?' },
|
||||
{ icon: LuTruck, text: 'How many vehicles are available?' }
|
||||
],
|
||||
more: [
|
||||
{ icon: LuBuilding2, text: 'Current hub status' },
|
||||
{ icon: LuBanknote, text: 'Total revenue today' }
|
||||
]
|
||||
};
|
||||
|
||||
const VEHICLES = {
|
||||
label: 'Vehicles',
|
||||
suggestions: [
|
||||
{ icon: LuTruck, text: 'How many vehicles are available?' },
|
||||
{ icon: LuBike, text: 'How many riders are active?' },
|
||||
{ icon: LuBuilding2, text: 'Current hub status' },
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" }
|
||||
],
|
||||
more: [{ icon: LuPackage, text: 'How many orders today?' }]
|
||||
};
|
||||
|
||||
const HUBS = {
|
||||
label: 'Hubs',
|
||||
suggestions: [
|
||||
{ icon: LuBuilding2, text: 'Current hub status' },
|
||||
{ icon: LuTimerOff, text: 'Which hubs are experiencing delays?' },
|
||||
{ icon: LuTruck, text: 'How many vehicles are available?' },
|
||||
{ icon: LuBike, text: 'How many riders are active?' }
|
||||
],
|
||||
more: [{ icon: LuPackage, text: 'How many orders today?' }]
|
||||
};
|
||||
|
||||
const DISPATCH = {
|
||||
label: 'Dispatch',
|
||||
suggestions: [
|
||||
{ icon: LuLayers, text: 'Morning Batch orders today' },
|
||||
{ icon: LuTimerOff, text: 'Which orders are delayed?' },
|
||||
{ icon: LuBike, text: 'How many riders are active?' },
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" }
|
||||
],
|
||||
more: [
|
||||
{ icon: LuPackage, text: 'How many orders today?' },
|
||||
{ icon: LuCircleDot, text: 'How many pending orders today?' }
|
||||
]
|
||||
};
|
||||
|
||||
const TENANTS = {
|
||||
label: 'Tenants',
|
||||
suggestions: [
|
||||
{ icon: LuUsers, text: 'How many tenants do we have?' },
|
||||
{ icon: LuPackage, text: 'How many orders today?' },
|
||||
{ icon: LuBanknote, text: 'Total revenue today' },
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" }
|
||||
],
|
||||
more: []
|
||||
};
|
||||
|
||||
const REPORTS = {
|
||||
label: 'Reports',
|
||||
suggestions: [
|
||||
{ icon: LuBanknote, text: 'Total revenue today' },
|
||||
{ icon: LuClock3, text: 'Orders today vs yesterday' },
|
||||
{ icon: LuPackage, text: 'How many orders this week?' },
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" }
|
||||
],
|
||||
more: [{ icon: LuBanknote, text: 'Total revenue this week' }]
|
||||
};
|
||||
|
||||
const DEFAULT_CONTEXT = {
|
||||
label: 'Operations',
|
||||
suggestions: [
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" },
|
||||
{ icon: LuPackage, text: 'How many orders today?' },
|
||||
{ icon: LuBike, text: 'How many riders are active?' },
|
||||
{ icon: LuBuilding2, text: 'Current hub status' }
|
||||
],
|
||||
more: [
|
||||
{ icon: LuBanknote, text: 'Total revenue today' },
|
||||
{ icon: LuTruck, text: 'How many vehicles are available?' }
|
||||
]
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Follow-ups.
|
||||
//
|
||||
// What the suggestion strip offers AFTER a question has been asked. Showing
|
||||
// all twenty again is noise — the operator has already told you what they are
|
||||
// doing, so the next set should be the natural next steps for it. Creating an
|
||||
// order leads to assigning a rider and to what is still pending; asking about
|
||||
// revenue leads to the week and to the day-over-day comparison.
|
||||
//
|
||||
// Every string here is either one of the CHIP_LABELS questions above (all of
|
||||
// which the matcher already resolves) or ASSIGN_TEXT. That constraint is the
|
||||
// whole point: pageContext's rule is that a suggestion which returns "I can't
|
||||
// answer that yet" is worse than no suggestion, and a follow-up is MORE likely
|
||||
// to be clicked than a cold one, because it arrives exactly when it is
|
||||
// relevant. Do not add a phrasing here without checking it against INTENTS.
|
||||
// ---------------------------------------------------------------------------
|
||||
const FOLLOW_UPS = {
|
||||
// --- write actions ---
|
||||
// Note what is NOT here: assigning a rider.
|
||||
//
|
||||
// "Create an order" only STARTS the create conversation — no order exists at
|
||||
// that moment, so offering to assign one is offering to act on nothing. And
|
||||
// once the order really is created, AIPanel already drops the operator
|
||||
// straight into the assign flow itself (see the confirmOrder branch), because
|
||||
// that hand-off is mandatory rather than optional. A chip there would either
|
||||
// duplicate a step the product performs on its own or fire it too early.
|
||||
//
|
||||
// ASSIGN_TEXT belongs on the questions below that surface orders which
|
||||
// already EXIST and are waiting for a rider — and on ORDER_CREATED, which is
|
||||
// keyed off the write succeeding rather than off anything that was asked.
|
||||
'Create an order': [REPEAT_TEXT, 'How many pending orders today?', 'Which orders are delayed?', 'Create multiple orders'],
|
||||
'Create multiple orders': ['How many pending orders today?', 'How many orders today?'],
|
||||
[REPEAT_TEXT]: ['How many pending orders today?', 'How many orders today?', 'Orders today vs yesterday'],
|
||||
'Create a customer': ['Create an order', 'How many customers do we have?'],
|
||||
[ASSIGN_TEXT]: ['How many pending orders today?', 'Which orders are delayed?', 'How many riders are active?'],
|
||||
[ORDER_CREATED]: [ASSIGN_TEXT, 'How many pending orders today?', 'Which orders are delayed?', 'Create an order'],
|
||||
// No ASSIGN_TEXT: there is nothing left to assign.
|
||||
[ORDER_CREATED_ASSIGNED]: ['How many riders are active?', 'How many pending orders today?', 'Which orders are delayed?', 'Create an order'],
|
||||
|
||||
// --- order volume ---
|
||||
"Give me today's operations summary": [
|
||||
'How many pending orders today?',
|
||||
'Which orders are delayed?',
|
||||
'Total revenue today',
|
||||
'How many riders are active?'
|
||||
],
|
||||
'How many orders today?': [
|
||||
'How many pending orders today?',
|
||||
'How many delivered orders today?',
|
||||
'Orders today vs yesterday',
|
||||
'Total revenue today'
|
||||
],
|
||||
'How many pending orders today?': [ASSIGN_TEXT, 'Which orders are delayed?', 'How many delivered orders today?'],
|
||||
'How many delivered orders today?': ['How many pending orders today?', 'Total revenue today', 'Orders today vs yesterday'],
|
||||
'How many cancelled orders today?': ['How many delivered orders today?', 'How many orders today?', 'Which orders are delayed?'],
|
||||
'Which orders are delayed?': [ASSIGN_TEXT, 'Which hubs are experiencing delays?', 'How many riders are active?'],
|
||||
'Morning Batch orders today': ['Which orders are delayed?', 'How many riders are active?', 'How many orders today?'],
|
||||
|
||||
// --- money and trend ---
|
||||
'Total revenue today': ['Total revenue this week', 'Orders today vs yesterday', 'How many orders today?'],
|
||||
'Total revenue this week': ['How many orders this week?', 'Total revenue today'],
|
||||
'Orders today vs yesterday': [REPEAT_TEXT, 'Total revenue today', 'How many orders this week?'],
|
||||
'How many orders this week?': ['Total revenue this week', 'How many orders today?'],
|
||||
|
||||
// --- fleet and network ---
|
||||
'How many riders are active?': ['How many vehicles are available?', 'Current hub status', ASSIGN_TEXT],
|
||||
'How many vehicles are available?': ['How many riders are active?', 'Current hub status'],
|
||||
'Current hub status': ['Which hubs are experiencing delays?', 'How many vehicles are available?', 'How many riders are active?'],
|
||||
'Which hubs are experiencing delays?': ['Current hub status', 'Which orders are delayed?', 'How many riders are active?'],
|
||||
|
||||
// --- accounts ---
|
||||
'How many tenants do we have?': ['How many customers do we have?', 'How many orders today?'],
|
||||
'How many customers do we have?': ['Create a customer', 'How many tenants do we have?']
|
||||
};
|
||||
|
||||
// Longest-prefix first so 'orders/create' doesn't fall through to 'orders'
|
||||
// with the wrong label.
|
||||
const ROUTES = [
|
||||
['/doormile/dispatch', DISPATCH],
|
||||
['/doormile/deliveries', { ...ORDERS, label: 'Deliveries' }],
|
||||
['/doormile/orders', ORDERS],
|
||||
['/doormile/riders', RIDERS],
|
||||
['/doormile/vehicles', VEHICLES],
|
||||
['/doormile/hubs', HUBS],
|
||||
['/doormile/tenants', TENANTS],
|
||||
['/doormile/reports', REPORTS],
|
||||
[
|
||||
'/doormile/customers',
|
||||
{
|
||||
label: 'Customers',
|
||||
suggestions: [
|
||||
{ icon: LuUserPlus, text: 'Create a customer' },
|
||||
{ icon: LuUsers, text: 'How many customers do we have?' },
|
||||
{ icon: LuClock3, text: "Give me today's operations summary" },
|
||||
{ icon: LuPackage, text: 'How many orders today?' }
|
||||
],
|
||||
more: [{ icon: LuUsers, text: 'How many tenants do we have?' }]
|
||||
}
|
||||
],
|
||||
['/doormile/tripsheets', { ...DEFAULT_CONTEXT, label: 'Tripsheets' }],
|
||||
['/doormile/exceptions', { ...DEFAULT_CONTEXT, label: 'Exceptions' }],
|
||||
['/doormile/pricing', { ...DEFAULT_CONTEXT, label: 'Pricing' }]
|
||||
];
|
||||
|
||||
// ---- every question, from every page ---------------------------------------
|
||||
//
|
||||
// The catalog is the same wherever you are: the assistant can answer about
|
||||
// orders, riders, hubs and the rest regardless of which screen is open, so
|
||||
// hiding a question because you happen to be on Dispatch made it look narrower
|
||||
// than it is. What the page still decides is ORDER — its own questions lead,
|
||||
// then everything else follows, deduplicated by text.
|
||||
//
|
||||
// `more` is retired: AIWelcome renders one flat list now, so a second array
|
||||
// would only be a second place for a question to hide.
|
||||
const ALL_CONTEXTS = [ORDERS, RIDERS, VEHICLES, HUBS, DISPATCH, TENANTS, REPORTS, DEFAULT_CONTEXT, ...ROUTES.map(([, c]) => c)];
|
||||
|
||||
// Every question declared anywhere. Two jobs: it is the icon index behind
|
||||
// QUESTION_BY_TEXT (so a follow-up naming a question that is no longer global
|
||||
// still renders with the right icon), and it is the pool GLOBAL_SUGGESTIONS
|
||||
// filters. Do not narrow it — narrow GLOBAL_TEXTS instead.
|
||||
const EVERY_QUESTION = (() => {
|
||||
const seen = new Set();
|
||||
const out = [];
|
||||
ALL_CONTEXTS.forEach((c) => {
|
||||
[...(c.suggestions || []), ...(c.more || [])].forEach((q) => {
|
||||
if (seen.has(q.text)) return;
|
||||
seen.add(q.text);
|
||||
out.push(q);
|
||||
});
|
||||
});
|
||||
return out;
|
||||
})();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The questions that ride along on EVERY page.
|
||||
//
|
||||
// This used to be EVERY_QUESTION — all twenty-one, everywhere — which meant the
|
||||
// Orders page offered "How many tenants do we have?" and "Current hub status"
|
||||
// above the composer, questions an operator working orders is not asking. The
|
||||
// strip is a shortcut, and a shortcut that lists everything is a menu.
|
||||
//
|
||||
// What is left is the daily operating picture: what came in, what is stuck,
|
||||
// what got out, what it earned, and the three ways to put work into the system.
|
||||
//
|
||||
// Dropping a question from THIS list does not remove it from the assistant —
|
||||
// it stays typeable, it stays a follow-up, and it still leads on its own page,
|
||||
// because withGlobalQuestions puts each page's own suggestions first. Hubs
|
||||
// still opens on hub questions; they just no longer follow you to Orders.
|
||||
// ---------------------------------------------------------------------------
|
||||
const GLOBAL_TEXTS = new Set([
|
||||
"Give me today's operations summary",
|
||||
'How many orders today?',
|
||||
'Which orders are delayed?',
|
||||
'How many pending orders today?',
|
||||
'How many delivered orders today?',
|
||||
'How many cancelled orders today?',
|
||||
'Morning Batch orders today',
|
||||
'Orders today vs yesterday',
|
||||
'Total revenue today',
|
||||
'How many riders are active?',
|
||||
'Create an order',
|
||||
REPEAT_TEXT,
|
||||
'Create multiple orders'
|
||||
]);
|
||||
|
||||
const GLOBAL_SUGGESTIONS = EVERY_QUESTION.filter((q) => GLOBAL_TEXTS.has(q.text));
|
||||
|
||||
// The page's own suggestions lead; the global set follows, deduplicated.
|
||||
const withGlobalQuestions = (context) => {
|
||||
const seen = new Set();
|
||||
const suggestions = [];
|
||||
[...(context.suggestions || []), ...(context.more || []), ...GLOBAL_SUGGESTIONS].forEach((q) => {
|
||||
if (seen.has(q.text)) return;
|
||||
seen.add(q.text);
|
||||
suggestions.push(q);
|
||||
});
|
||||
return { ...context, suggestions, more: [] };
|
||||
};
|
||||
|
||||
export const getPageContext = (pathname = '') => {
|
||||
const match = ROUTES.find(([prefix]) => pathname.startsWith(prefix));
|
||||
return withGlobalQuestions(match ? match[1] : DEFAULT_CONTEXT);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Follow-ups are stored as bare strings (they have to be — they are the exact
|
||||
// text that gets asked), so this turns one back into a renderable chip by
|
||||
// looking its icon up among the questions already declared above. Built from
|
||||
// EVERY_QUESTION rather than a second hand-written list, so a suggestion can
|
||||
// never end up with an icon here that disagrees with its icon on a page.
|
||||
// ---------------------------------------------------------------------------
|
||||
const QUESTION_BY_TEXT = new Map(EVERY_QUESTION.map((q) => [q.text, q]));
|
||||
QUESTION_BY_TEXT.set(ASSIGN_TEXT, { icon: LuBike, text: ASSIGN_TEXT });
|
||||
|
||||
// Returns [] for anything unrecognised — a typed question, or one with no
|
||||
// natural next step. The caller falls back to the page's own suggestions, so
|
||||
// the strip is never empty.
|
||||
export const getFollowUps = (askedText) => {
|
||||
const next = FOLLOW_UPS[askedText];
|
||||
if (!next) return [];
|
||||
return next.map((text) => QUESTION_BY_TEXT.get(text)).filter(Boolean);
|
||||
};
|
||||
|
||||
// Turns arbitrary follow-up strings into chips. Used for the intent-keyed
|
||||
// FOLLOW_UP_SUGGESTIONS in intents.js, whose phrasings ("What about
|
||||
// yesterday?", "This week") are conversational rather than one of the declared
|
||||
// questions, so they have no icon of their own.
|
||||
export const toChips = (texts = []) => texts.map((text) => QUESTION_BY_TEXT.get(text) || { icon: LuCircleDot, text });
|
||||
Reference in New Issue
Block a user