= ({
{/* Drawer */}
void;
- /** A control seated between the back arrow and the title. */
- leading: ReactNode;
- setLeading: (node: ReactNode) => void;
}
const TopBarSlotContext = createContext(null);
@@ -23,17 +20,13 @@ const TopBarSlotContext = createContext(null);
* Lets a page inject a custom element into the shared TopBar's left area (where
* the breadcrumb normally renders). Used by the notebook to surface its
* "Notebook" notes dropdown in the standard top bar, and by the workspace to
- * put the open document's or conversation's title there. Pages that don't set
+ * put the open document's title there. Pages that don't set
* anything fall back to the default breadcrumb.
*/
export function TopBarSlotProvider({ children }: { children: ReactNode }) {
const [leftSlot, setLeftSlot] = useState(null);
const [title, setTitle] = useState(null);
- const [leading, setLeading] = useState(null);
- const value = useMemo(
- () => ({ leftSlot, setLeftSlot, title, setTitle, leading, setLeading }),
- [leftSlot, title, leading]
- );
+ const value = useMemo(() => ({ leftSlot, setLeftSlot, title, setTitle }), [leftSlot, title]);
return {children};
}
diff --git a/hooks/useChatSession.ts b/hooks/useChatSession.ts
index 7d793d4ed..bfe35c52e 100644
--- a/hooks/useChatSession.ts
+++ b/hooks/useChatSession.ts
@@ -73,6 +73,11 @@ export interface ChatSession {
readonly clearNotice: () => void;
/** A brand-new chat is being created for the first message. */
readonly creatingChat: boolean;
+ /**
+ * A message is on its way and its turn has not shown up yet: its chat is
+ * being created, it waits for that chat to open, or it is being posted.
+ */
+ readonly messagePending: boolean;
/**
* Sending would be refused: the allowance is unknown or spent, or a tier
* that picks its model has no catalog yet to pick from.
@@ -385,6 +390,7 @@ export function useChatSession({
notice,
clearNotice,
creatingChat,
+ messagePending: creatingChat || queuedMessage != null || chat.pendingSend !== null,
sendBlocked,
composerBusy,
canStop,
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 8dcc81237..4daf69176 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -70,6 +70,23 @@ export default {
'0%, 18%': { backgroundPosition: '100% 0' },
'82%, 100%': { backgroundPosition: '0% 0' },
},
+ // A paper plane leaving up and to the right, then coming back in from
+ // the lower left and settling with a small overshoot. Each leg has its
+ // own easing: it speeds up on the way out and slows on the way in.
+ 'take-off': {
+ '0%': {
+ transform: 'translate(0, 0) scale(1)',
+ opacity: '1',
+ animationTimingFunction: 'cubic-bezier(0.55, 0, 1, 0.45)',
+ },
+ '38%': { transform: 'translate(160%, -160%) scale(0.6)', opacity: '0' },
+ '39%': {
+ transform: 'translate(-160%, 160%) scale(0.6)',
+ opacity: '0',
+ animationTimingFunction: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
+ },
+ '100%': { transform: 'translate(0, 0) scale(1)', opacity: '1' },
+ },
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
@@ -80,6 +97,9 @@ export default {
// Single sweep. `both` keeps the streak parked off-screen during the
// delay instead of sitting mid-element until it starts.
shimmer: 'shimmer 1.6s ease-in-out 0.45s both',
+ // The same streak, answering a hover: no wait before it starts.
+ 'shimmer-quick': 'shimmer 0.9s ease-out 0.05s both',
+ 'take-off': 'take-off 0.8s both',
'text-shine': 'text-shine 2.25s cubic-bezier(0.25, 0.1, 0.25, 1) infinite',
},
backgroundImage: {