ChatHeader already supports commanderLabel; this panel just never passed one. Version bumped to match this release's tag.
76 lines
3.8 KiB
TypeScript
76 lines
3.8 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useSearchParams } from 'react-router';
|
|
import { ChatPanelWrapper, ChatHeader } from 'officerdev';
|
|
|
|
/**
|
|
* A chat, sitting under the message you are reading.
|
|
*
|
|
* ── Why the panel is ours ──
|
|
*
|
|
* `PluginScreen` renders the workspace with `appTypes={{ allowed, fallback: allowed[0] }}` where `allowed`
|
|
* is this plugin's own panel keys, so a layout naming `officerdev/chat` silently renders the fallback
|
|
* instead. That is deliberate — a persisted layout must not be able to mount another plugin's panel here.
|
|
* So the panel is ours and the chat inside it is the platform's; `ChatPanelWrapper` is exported from
|
|
* `officerdev` for exactly this, and carries the composer, transcript, queue, model picker, session
|
|
* persistence and the agent identity bar with no second implementation to fall behind the first.
|
|
*
|
|
* ── What these chats are told, and where it lives ──
|
|
*
|
|
* `chat/CLAUDE.md`, in this plugin. The platform writes it into the directory these chats run in, and
|
|
* Claude Code reads it from its own cwd at session start.
|
|
*
|
|
* There WAS a `promptPrefix` here and it is gone. A prefix rides every message: paid per turn forever,
|
|
* invisible as a design artefact because it lives in a TypeScript constant, and visible in the one place
|
|
* it should not be — three words sent, and a paragraph of preamble going out attached to them. The file
|
|
* costs nothing per turn and can be opened by the person it is about.
|
|
*/
|
|
|
|
/**
|
|
* Which mailbox this chat is about.
|
|
*
|
|
* `context: 'email'` resolves server-side to the ACCOUNT's own directory — the one holding `emails.db` —
|
|
* rather than to a chats folder. That is the whole point: the agent starts beside the database it is meant
|
|
* to answer from, and its brief can say so.
|
|
*
|
|
* This panel declared no context at all until now, and it did not need to while email was part of the
|
|
* platform: `ChatPanelWrapper` recognised the screen by `workspace.id === 'email'` and supplied it. A
|
|
* plugin's workspace key is `screens/plugin/email`, so that id became `plugin/email` and the branch
|
|
* stopped matching the day email was extracted — every email chat since has run somewhere with no mailbox
|
|
* near it. Declaring it here is what that branch's own comment asked for, and it carries the mailbox too,
|
|
* which a hardcoded screen id never could.
|
|
*
|
|
* `all` is the list's sentinel for the unified view and is NOT an address. Sending it would name a mailbox
|
|
* that does not exist, so it is dropped here and the server picks the most recently synced account.
|
|
*
|
|
* Memoised because it is passed to hooks that key on it. An object rebuilt every render is the trap that
|
|
* silently broke playback reporting in the Jellyfin player for days; it costs one line to not repeat.
|
|
*/
|
|
const useEmailChatContext = () => {
|
|
const [searchParams] = useSearchParams();
|
|
const account = searchParams.get('account');
|
|
const mailbox = account && account !== 'all' ? account : undefined;
|
|
return useMemo(() => ({ context: 'email', ...(mailbox ? { contextId: mailbox } : {}) }), [mailbox]);
|
|
};
|
|
|
|
export const EmailChatPanel = ({ panelId }: { panelId: string }) => {
|
|
const chatContext = useEmailChatContext();
|
|
|
|
return (
|
|
<div className="flex h-full min-h-0 flex-col">
|
|
<ChatPanelWrapper panelId={panelId} chatContext={chatContext} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* The panel's header: the conversation picker, scoped to this mailbox's directory.
|
|
*
|
|
* A registry `header` is mounted with `{ panelId }` only, so the context is bound here rather than passed —
|
|
* which is the whole reason this wrapper exists.
|
|
*/
|
|
export const EmailChatPanelHeader = ({ panelId }: { panelId: string }) => {
|
|
const chatContext = useEmailChatContext();
|
|
|
|
return <ChatHeader panelId={panelId} chatContext={chatContext} commanderLabel="Email" />;
|
|
};
|