stop couriering a system prompt through the framework
`promptPrefix` was a workspace-context field: the email and browser screens set it, `WorkspaceView` put it on the context, and `ChatPanelWrapper` read it back off. Only the chat app has ever understood what the string is, so the framework was carrying an app's vocabulary between two places that both know each other. `components` already exists for this — a screen supplies its own component for a panel id, and `PanelSlot` prefers it over the registry while still taking header and provider from the registry entry, so a screen-mounted chat panel keeps its normal chrome. Both screens now do that, and pass the prefix as a prop. `ChatPanelWrapper` is exported from the barrel for it. Also removes the same prop from `WorkspaceLayout`, where it had no callers at all: every preview and settings pane rendering through it was already handing its chat panels an undefined prefix.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { WorkspaceView, ChatPanelWrapper } from 'officerdev';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
@@ -10,6 +10,9 @@ import { TabPreview } from './TabPreview';
|
||||
|
||||
const PROMPT_PREFIX = `You are a browser assistant. The user has connected Chrome tabs via the Officer Browser Relay. You have a 'browser' tool — use it to list tabs, take screenshots, navigate, evaluate JavaScript, and get page info. When asked about a page, take a screenshot first.`;
|
||||
|
||||
// This screen's prompt, so this screen's chat panel. See the note in `EmailScreen`.
|
||||
const BrowserChatPanel = () => <ChatPanelWrapper panelId="browser-chat" promptPrefix={PROMPT_PREFIX} />;
|
||||
|
||||
export const BrowserScreen = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const [selectedId, setSelectedId] = useGlobal<string | null>('BROWSER_SELECTED_TAB', null);
|
||||
@@ -19,6 +22,7 @@ export const BrowserScreen = () => {
|
||||
() => ({
|
||||
'browser-tabs': TabList,
|
||||
'browser-preview': TabPreview,
|
||||
'browser-chat': BrowserChatPanel,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -32,7 +36,6 @@ export const BrowserScreen = () => {
|
||||
workspace={workspace}
|
||||
locked
|
||||
components={components}
|
||||
promptPrefix={PROMPT_PREFIX}
|
||||
mobilePanelId={mobilePanelId}
|
||||
onMobilePanelChange={mobilePanelId ? () => onMobileBack() : undefined}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMemo, useCallback, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { WorkspaceView, ChatPanelWrapper } from 'officerdev';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
@@ -12,6 +12,11 @@ import { ComposeModal } from './Compose';
|
||||
|
||||
const PROMPT_PREFIX = `You are an email assistant. The user has a local SQLite email database available via the "email_db" tool — use it for all email queries (search, count, stats, aggregations, deletions) unless the user explicitly asks you to use Gmail. Do not use the Gmail integration for questions about existing emails.`;
|
||||
|
||||
// The prefix is this screen's, so this screen mounts the chat panel that gets it — rather than handing
|
||||
// the string to the workspace to courier down to whichever app happens to read it. The panel id is the
|
||||
// one in `defaultLayout`, and the layout is locked, so it is the same panel either way.
|
||||
const EmailChatPanel = () => <ChatPanelWrapper panelId="email-chat" promptPrefix={PROMPT_PREFIX} />;
|
||||
|
||||
export const EmailScreen = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { emailId } = useParams<{ emailId?: string }>();
|
||||
@@ -37,6 +42,7 @@ export const EmailScreen = () => {
|
||||
() => ({
|
||||
'email-list': EmailList,
|
||||
'email-reader': EmailReader,
|
||||
'email-chat': EmailChatPanel,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -50,7 +56,6 @@ export const EmailScreen = () => {
|
||||
workspace={workspace}
|
||||
locked
|
||||
components={components}
|
||||
promptPrefix={PROMPT_PREFIX}
|
||||
mobilePanelId={mobilePanelId}
|
||||
onMobilePanelChange={mobilePanelId ? () => onMobileBack() : undefined}
|
||||
/>
|
||||
|
||||
@@ -53,10 +53,13 @@ const ChatPanelInner = ({
|
||||
);
|
||||
};
|
||||
|
||||
type ChatPanelWrapperProps = { panelId: string };
|
||||
// `promptPrefix` is a system prompt only this app understands, so the screen that wants one passes it
|
||||
// here rather than through the workspace context — a screen supplies its own chat panel through
|
||||
// `components`, which is what that prop exists for. The registry mounts this with no prefix.
|
||||
type ChatPanelWrapperProps = { panelId: string; promptPrefix?: string };
|
||||
|
||||
export const ChatPanelWrapper = ({ panelId }: ChatPanelWrapperProps) => {
|
||||
const { workspace, cwd, root, promptPrefix } = useWorkspace();
|
||||
export const ChatPanelWrapper = ({ panelId, promptPrefix }: ChatPanelWrapperProps) => {
|
||||
const { workspace, cwd, root } = useWorkspace();
|
||||
const scoped = cwd !== '~';
|
||||
|
||||
// `contextId` stays the raw workspace key: it is the same string `agent_panels.dashboard_id` holds, and
|
||||
|
||||
@@ -10,6 +10,8 @@ export { QuestionActivity } from './components/QuestionActivity';
|
||||
export { ModelSelector } from './components/ModelSelector';
|
||||
export { InputArea } from './components/InputArea';
|
||||
export { ChatLauncher } from './ChatLauncher';
|
||||
// Exported so a screen can mount the chat panel itself with a system prompt of its own.
|
||||
export { ChatPanelWrapper } from './ChatPanelWrapper';
|
||||
export { AttachmentList } from './components/AttachmentList';
|
||||
export { AttachButton } from './components/AttachButton';
|
||||
export { WebpageDialog } from './components/WebpageDialog';
|
||||
|
||||
@@ -9,7 +9,6 @@ type WorkspaceContextValue = {
|
||||
workspace: WorkspaceIdentity | null;
|
||||
cwd: string;
|
||||
root?: string;
|
||||
promptPrefix?: string;
|
||||
// Per-panel app settings, by panel id. A map rather than a getter so a config change re-renders the
|
||||
// panel that owns it — apps read this through `usePanelConfig`, not directly.
|
||||
panelConfigs: Record<string, PanelConfig>;
|
||||
|
||||
@@ -11,7 +11,6 @@ type WorkspaceLayoutProps = {
|
||||
registry?: AppRegistryMap;
|
||||
components?: PanelComponents;
|
||||
cwd?: string;
|
||||
promptPrefix?: string;
|
||||
noHeader?: boolean;
|
||||
isMobile?: boolean;
|
||||
mobilePanelId?: string;
|
||||
@@ -20,7 +19,7 @@ type WorkspaceLayoutProps = {
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp, components, cwd, promptPrefix, noHeader, isMobile, mobilePanelId, onMobileBack }: WorkspaceLayoutProps) => {
|
||||
export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp, components, cwd, noHeader, isMobile, mobilePanelId, onMobileBack }: WorkspaceLayoutProps) => {
|
||||
const { registry: globalRegistry } = useAppRegistry();
|
||||
const registry = registryProp ?? globalRegistry;
|
||||
|
||||
@@ -37,7 +36,7 @@ export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp
|
||||
// dashboard previews. It is not a dashboard and not a screen, and a panel that asks should be told so
|
||||
// rather than handed something that parses.
|
||||
return (
|
||||
<WorkspaceProvider value={{ workspace: null, cwd: cwd ?? '~', promptPrefix, panelConfigs: collectPanelConfigs(layout), setPanelConfig: noop, swapSourceId: null, setSwapSourceId: noop, onSwap: noop, dragSourceId: null, setDragSourceId: noop, onMove: noop, onSetZoom: noop, maximizedPanelId: null, setMaximizedPanelId: noop, transitioningPanelId: null, isMobile: isMobile ?? false, onMobileBack: onMobileBack ?? null }}>
|
||||
<WorkspaceProvider value={{ workspace: null, cwd: cwd ?? '~', panelConfigs: collectPanelConfigs(layout), setPanelConfig: noop, swapSourceId: null, setSwapSourceId: noop, onSwap: noop, dragSourceId: null, setDragSourceId: noop, onMove: noop, onSetZoom: noop, maximizedPanelId: null, setMaximizedPanelId: noop, transitioningPanelId: null, isMobile: isMobile ?? false, onMobileBack: onMobileBack ?? null }}>
|
||||
<WorkspaceRenderer
|
||||
layout={layout}
|
||||
registry={registry}
|
||||
|
||||
@@ -16,7 +16,6 @@ type WorkspaceViewProps = {
|
||||
locked?: boolean;
|
||||
cwd?: string;
|
||||
root?: string;
|
||||
promptPrefix?: string;
|
||||
components?: PanelComponents;
|
||||
ephemeral?: EphemeralPanels | null;
|
||||
mobilePanelId?: string;
|
||||
@@ -25,7 +24,7 @@ type WorkspaceViewProps = {
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export const WorkspaceView = ({ workspace, locked, cwd = '~', root, promptPrefix, components, ephemeral, mobilePanelId, onMobilePanelChange }: WorkspaceViewProps) => {
|
||||
export const WorkspaceView = ({ workspace, locked, cwd = '~', root, components, ephemeral, mobilePanelId, onMobilePanelChange }: WorkspaceViewProps) => {
|
||||
const { registry } = useAppRegistry();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
@@ -190,7 +189,6 @@ export const WorkspaceView = ({ workspace, locked, cwd = '~', root, promptPrefix
|
||||
workspace: identity,
|
||||
cwd,
|
||||
root,
|
||||
promptPrefix,
|
||||
panelConfigs,
|
||||
setPanelConfig: locked ? noop : handleSetPanelConfig,
|
||||
swapSourceId,
|
||||
|
||||
@@ -13,6 +13,7 @@ export {
|
||||
ModelSelector,
|
||||
InputArea,
|
||||
ChatLauncher,
|
||||
ChatPanelWrapper,
|
||||
AttachmentList,
|
||||
AttachButton,
|
||||
WebpageDialog,
|
||||
|
||||
Reference in New Issue
Block a user