Build Cobble project structure from the handoff
- Remove the preset document suite (letters, emails, invoices, quotes, PDF generation, line items) with its routes, API handlers and tables - Rebrand to Cobble: site data, Lato, handoff design tokens (light and dark) in main.css, brand assets in public/brand, ember stone logo and regenerated favicons - Add bots, bot_messages, bot_tasks and bot_task_steps tables, regenerate DB types (BUN_SQLITE_COBBLE_*) and add a sample crew seed (seed:bots) - Add the chat shell at /admin/bots: rail with pinned coordinator and user popover, bottom-anchored conversation with composer, working-on panel, and a send-message API scoped to the session user - Add BotStone and Wordmark components, crew status and relative time utils with tests, and update AGENTS.md Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import type { BotFace, BotKind } from "@/src/dict/bot-dict";
|
||||
|
||||
type CrewBot = {
|
||||
kind?: BotKind | string | null;
|
||||
face?: BotFace | string | null;
|
||||
};
|
||||
|
||||
type Params = {
|
||||
bots: CrewBot[];
|
||||
};
|
||||
|
||||
const NUMBER_WORDS = [
|
||||
"No",
|
||||
"One",
|
||||
"Two",
|
||||
"Three",
|
||||
"Four",
|
||||
"Five",
|
||||
"Six",
|
||||
"Seven",
|
||||
"Eight",
|
||||
"Nine",
|
||||
"Ten",
|
||||
] as const;
|
||||
|
||||
function countWord({ n }: { n: number }): string {
|
||||
return NUMBER_WORDS[n] ?? String(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Working and needs-you counts across the workers (the coordinator excluded).
|
||||
*/
|
||||
export function crewCounts({ bots }: Params) {
|
||||
const workers = bots.filter((bot) => bot.kind !== "coordinator");
|
||||
|
||||
return {
|
||||
working: workers.filter((bot) => bot.face === "working").length,
|
||||
needs_you: workers.filter((bot) => bot.face === "needs-you").length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The coordinator's rail subtitle: `3 working · 1 needs you`.
|
||||
*/
|
||||
export function crewStatusLine({ bots }: Params): string {
|
||||
const { working, needs_you } = crewCounts({ bots });
|
||||
const parts: string[] = [];
|
||||
|
||||
if (working) parts.push(`${working} working`);
|
||||
if (needs_you) parts.push(`${needs_you} need${needs_you == 1 ? "s" : ""} you`);
|
||||
|
||||
return parts.length ? parts.join(" · ") : "All quiet";
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature status sentence: `Three bots are working. One needs you.`
|
||||
*/
|
||||
export function crewStatusSentence({ bots }: Params): string {
|
||||
const { working, needs_you } = crewCounts({ bots });
|
||||
const sentences: string[] = [];
|
||||
|
||||
if (working) {
|
||||
sentences.push(
|
||||
working == 1
|
||||
? `One bot is working.`
|
||||
: `${countWord({ n: working })} bots are working.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (needs_you) {
|
||||
const subject = working
|
||||
? countWord({ n: needs_you })
|
||||
: `${countWord({ n: needs_you })} bot${needs_you == 1 ? "" : "s"}`;
|
||||
sentences.push(`${subject} need${needs_you == 1 ? "s" : ""} you.`);
|
||||
}
|
||||
|
||||
return sentences.length ? sentences.join(" ") : "Nothing needs you.";
|
||||
}
|
||||
Reference in New Issue
Block a user