the chat panel gets the header and the history it should have had
ChatPanelWrapper, not EmbeddableChat — one level up, and the session persistence, the agent identity bar, the spawned-by bar and resume-the-newest all come with it. mounting the composer directly meant reimplementing those or going without. and a header: the conversation picker, scoped to this plugin's own directory, with new chat, inline rename and delete. none of it is new — it is what a dashboard's chat panel has always had, pointed at a context instead of a path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
+29
-14
@@ -1,4 +1,4 @@
|
|||||||
import { EmbeddableChat } from 'officerdev';
|
import { ChatPanelWrapper, ChatHeader } from 'officerdev';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A chat, sitting under the two lists.
|
* A chat, sitting under the two lists.
|
||||||
@@ -40,22 +40,37 @@ const PREFIX = [
|
|||||||
* the same reason.
|
* the same reason.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* `context`/`contextId` name the plugin; the platform turns that into a working directory —
|
* The context this plugin's chats belong to.
|
||||||
* `$DATA_PATH/<email>/plugins/proxy-manager/chats` — and the path is never sent from here. See
|
|
||||||
* `resolveChatCwd`, and `docs/commander-directory.md` for why a client does not choose a cwd.
|
|
||||||
*
|
*
|
||||||
* What that buys beyond tidiness: Claude Code reads a `CLAUDE.md` from its cwd at session start, so
|
* The platform turns it into `$DATA_PATH/<email>/plugins/proxy-manager/chats` — for the turn, and for the
|
||||||
* anything these chats should know can live in that directory instead of in every conversation on the
|
* header's session list — and the path is never sent from here. `docs/commander-directory.md` has why a
|
||||||
* machine.
|
* client does not choose a cwd; `cwdOf` in `api/chat/chat.ts` has the listing half.
|
||||||
|
*
|
||||||
|
* What the directory buys beyond tidiness: Claude Code reads a `CLAUDE.md` from its cwd at session start,
|
||||||
|
* so anything these chats should know can live there instead of in every conversation on the machine.
|
||||||
|
*/
|
||||||
|
const CHAT_CONTEXT = { context: 'plugin', contextId: 'proxy-manager' } as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `ChatPanelWrapper`, not `EmbeddableChat` — one level up, and everything below it comes free.
|
||||||
|
*
|
||||||
|
* The wrapper already carries the session persistence (`usePanelSession`, so this panel stays on its own
|
||||||
|
* conversation across a reload), the agent identity bar, the spawned-by bar, and the resume-the-newest
|
||||||
|
* behaviour. Mounting the composer directly meant reimplementing all of that or going without it, which is
|
||||||
|
* what this panel was doing.
|
||||||
*/
|
*/
|
||||||
export const ChatPanel = ({ panelId }: { panelId: string }) => (
|
export const ChatPanel = ({ panelId }: { panelId: string }) => (
|
||||||
<div className="flex h-full min-h-0 flex-col">
|
<div className="flex h-full min-h-0 flex-col">
|
||||||
<EmbeddableChat
|
<ChatPanelWrapper panelId={panelId} promptPrefix={PREFIX} chatContext={CHAT_CONTEXT} />
|
||||||
className="min-h-0 flex-1"
|
|
||||||
panelId={panelId}
|
|
||||||
promptPrefix={PREFIX}
|
|
||||||
context="plugin"
|
|
||||||
contextId="proxy-manager"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The panel's header: the conversation picker, scoped to this plugin'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 ChatPanelHeader = ({ panelId }: { panelId: string }) => (
|
||||||
|
<ChatHeader panelId={panelId} chatContext={CHAT_CONTEXT} />
|
||||||
|
);
|
||||||
|
|||||||
+10
-2
@@ -2,7 +2,7 @@ import { MessageSquare, Route, ShieldCheck } from 'lucide-react';
|
|||||||
import type { AppRegistryMeta } from 'officerdev';
|
import type { AppRegistryMeta } from 'officerdev';
|
||||||
import { HostsPanel } from './HostsPanel';
|
import { HostsPanel } from './HostsPanel';
|
||||||
import { CertificatesPanel } from './CertificatesPanel';
|
import { CertificatesPanel } from './CertificatesPanel';
|
||||||
import { ChatPanel } from './ChatPanel';
|
import { ChatPanel, ChatPanelHeader } from './ChatPanel';
|
||||||
|
|
||||||
// Three panels. Hosts is what the plugin is for; certificates is the thing that quietly breaks and is
|
// Three panels. Hosts is what the plugin is for; certificates is the thing that quietly breaks and is
|
||||||
// worth seeing beside it rather than a click away; chat is where you ask about either of them.
|
// worth seeing beside it rather than a click away; chat is where you ask about either of them.
|
||||||
@@ -21,5 +21,13 @@ export const appRegistryMetas: AppRegistryMeta[] = [
|
|||||||
component: CertificatesPanel,
|
component: CertificatesPanel,
|
||||||
availableOnPanel: false,
|
availableOnPanel: false,
|
||||||
},
|
},
|
||||||
{ key: 'proxy-manager-chat', name: 'Chat', icon: MessageSquare, component: ChatPanel, availableOnPanel: false },
|
{
|
||||||
|
key: 'proxy-manager-chat',
|
||||||
|
name: 'Chat',
|
||||||
|
icon: MessageSquare,
|
||||||
|
component: ChatPanel,
|
||||||
|
// The conversation picker, scoped to this plugin's own chat directory.
|
||||||
|
header: ChatPanelHeader,
|
||||||
|
availableOnPanel: false,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user