/** * The blank transcript, on every Piggy surface. * * There used to be two of these. The workspace had this one; the dock, the * sheet and the phone drawer had a second, narrower one that offered three * read openers, showed a Sparkles glyph and never once mentioned that Piggy * can write — the product's headline capability, missing from the surface most * people keep open all day. One agent gets one front door, so this is now it, * and `narrow` is what the 22rem column asks for instead of a second file. * * It has one job that the old blank transcript did not have: Piggy can write * now, and nobody will discover that by typing into a box. So the openers are * in two columns — what it can find out, and what it can get done — and the * second column says plainly that a change is proposed and waits for a person. * * Every sentence here comes from `lib/piggy-copy`. It is written once because * it was written three times and had already drifted. * * The read openers come from `piggySuggestions`, which picks them by the one * read tool this context resolves to, so every line is one Piggy can ground. * The write openers are held here because there is no equivalent table for them * yet, and they are written against the same constraint: each one is answerable * with the tools a `/piggy` turn is actually given — the workspace summary, the * record lookups, the renewals list — and none of them names a record that only * exists in the demo book. * * Pressing a write opener while Piggy is in Read only moves it to Ask first. * That is a change to a permission, so it is never silent: the card says so * before it is pressed, and the mode control in the header changes with it. Ask * first cannot write unattended — it proposes, and the Apply button is the * person — so the escalation this performs is from "no tools" to "a proposal * you must approve", which is the thing the user just asked for by pressing it. */ import { ArrowRight, PenLine, Search } from 'lucide-react'; import type { PiggyChatContext, PiggyMode } from '@pig/core'; import { piggyCopy, piggyLine } from '@/lib/piggy-copy'; import { piggySuggestions } from '@/lib/piggy-suggestions'; import { PiggyMark } from '@/components/PiggyMark'; import { Label, cn } from '@/components/ui'; /** * Openers that end in a change to the book. * * Every write tool takes a record id, and none of the tools a `/piggy` turn is * given returns one from the page context alone — so each of these is a lookup * followed by a write, and none of them names a record. Naming one would make * them land beautifully on the seeded demo book and fail on the first real * deployment, which is the opposite of the trade this file should make. * * The consequence is stated to the user rather than hidden: where the sentence * does not identify the record, Piggy asks which one instead of choosing. That * is the behaviour a CRM should have, and it is measurably what the default * model does — see the note under the column. */ const WRITE_STARTERS = [ 'Find the block furthest from break-even and log a note on its account.', 'Look up the contract renewing soonest and log a call about extending it.', 'Add a task to chase the account we have not spoken to in a month.', ]; const READ_STARTERS_SHOWN = 3; export function PiggyWorkspaceStarters({ context, mode, canWrite, onAsk, onAskWithChange, narrow = false, }: { context?: PiggyChatContext; /** Only to word the note. The escalation itself belongs to the thread. */ mode: PiggyMode; canWrite: boolean; onAsk: (text: string) => void; /** * An opener that ends in a write. The thread raises the mode first and sends * once the conversation is holding the new one — `send` reads the mode out of * the conversation, so sending in the same tick would ask for a change with * the write tools still withheld. */ onAskWithChange: (text: string) => void; /** * The surface is under ~26rem — the dock, the phone drawer, the workspace's * middle column on a phone. Stacks the two groups, halves the openers and * takes the short form of every sentence. */ narrow?: boolean; }) { /* * Two openers each on a phone, three on a desktop. * * Not a taste decision: the transcript sticks to the bottom of its * scrollport, so anything taller than the viewport opens with its own * heading scrolled off the top. Measured at 393x852 the six-opener version * overran by about 180px, which put the pig, the headline and the first * column header above the fold on the screen that is supposed to introduce * the product. */ const perGroup = narrow ? 2 : READ_STARTERS_SHOWN; const reads = piggySuggestions(context).slice(0, perGroup); const writes = WRITE_STARTERS.slice(0, perGroup); return ( // `flex-1` rather than `h-full`: the conversation viewport's content element // is sized by its children, so a percentage height resolves to nothing. // Centred where there is room to spare, and airless where there is not: on // a narrow surface this front door has to land whole above the composer at // 393x852 and in the dock's 22rem column, and it is measured to.
{narrow ? piggyLine(piggyCopy.capability, true) : `${piggyCopy.capability.long} ${piggyCopy.safety.long}`}
{note}
: null}