mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Group toolbar actions by UI region
This commit is contained in:
parent
5abc9cf2c3
commit
b47a43e763
9 changed files with 324 additions and 186 deletions
|
|
@ -11,7 +11,7 @@ import type { ChatPanelGoalSurface } from "../panel/surface/goal-projection";
|
|||
import { MessageStreamPresenter } from "../panel/surface/message-stream-presenter";
|
||||
import type { ChatMessageScrollController } from "../panel/surface/message-stream-scroll";
|
||||
import type { ChatPanelToolbarSurface } from "../panel/surface/toolbar-projection";
|
||||
import { createChatPanelToolbarActions, type ToolbarPanelActions } from "../panel/toolbar-actions";
|
||||
import { createToolbarUiActions, type ToolbarPanelActions } from "../panel/toolbar-actions";
|
||||
import type { ToolbarActions } from "../ui/toolbar";
|
||||
import type { ChatPanelConnectionBundle } from "./connection-bundle";
|
||||
import type { ChatPanelEnvironment } from "./environment";
|
||||
|
|
@ -33,7 +33,7 @@ export interface ChatPanelSurfacesInput {
|
|||
goals: ChatPanelGoalActions;
|
||||
rename: ThreadRenameEditorActions;
|
||||
threadActions: ChatPanelThreadActions;
|
||||
toolbarPanels: ToolbarPanelActions;
|
||||
toolbarPanelActions: ToolbarPanelActions;
|
||||
navigation: ChatPanelThreadNavigationActions;
|
||||
reconnect: () => Promise<void>;
|
||||
history: HistoryController;
|
||||
|
|
@ -55,7 +55,7 @@ export function createChatPanelSurfaces(host: ChatPanelSurfacesHost, input: Chat
|
|||
goals,
|
||||
rename,
|
||||
threadActions,
|
||||
toolbarPanels,
|
||||
toolbarPanelActions,
|
||||
navigation,
|
||||
reconnect,
|
||||
history,
|
||||
|
|
@ -63,12 +63,12 @@ export function createChatPanelSurfaces(host: ChatPanelSurfacesHost, input: Chat
|
|||
turnActions,
|
||||
} = input;
|
||||
const { environment, stateStore } = host;
|
||||
const toolbarActions = createChatPanelToolbarActions({
|
||||
const toolbarActions = createToolbarUiActions({
|
||||
connectionController,
|
||||
reconnectPanel: reconnect,
|
||||
threadActions,
|
||||
goals,
|
||||
toolbarPanels,
|
||||
toolbarPanel: toolbarPanelActions,
|
||||
rename,
|
||||
navigation,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ type ChatPanelThreadNavigationActions = ReturnType<typeof createThreadNavigation
|
|||
|
||||
interface ChatPanelThreadActionParts {
|
||||
actions: ChatPanelThreadActions;
|
||||
toolbarPanels: ToolbarPanelActions;
|
||||
toolbarPanelActions: ToolbarPanelActions;
|
||||
navigation: ChatPanelThreadNavigationActions;
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +281,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
goals,
|
||||
rename,
|
||||
threadActions: threadActionParts.actions,
|
||||
toolbarPanels: threadActionParts.toolbarPanels,
|
||||
toolbarPanelActions: threadActionParts.toolbarPanelActions,
|
||||
navigation: threadActionParts.navigation,
|
||||
reconnect: composerAndTurn.reconnect,
|
||||
history,
|
||||
|
|
@ -324,7 +324,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
rename,
|
||||
},
|
||||
toolbar: {
|
||||
panels: threadActionParts.toolbarPanels,
|
||||
panels: threadActionParts.toolbarPanelActions,
|
||||
actions: surfaces.toolbarActions,
|
||||
},
|
||||
composer: {
|
||||
|
|
@ -667,7 +667,7 @@ function createThreadActionParts(
|
|||
},
|
||||
};
|
||||
const actions = createThreadManagementActions(threadManagementHost);
|
||||
const toolbarPanels = createToolbarPanelActions({
|
||||
const toolbarPanelActions = createToolbarPanelActions({
|
||||
stateStore,
|
||||
threadActions: actions,
|
||||
});
|
||||
|
|
@ -675,7 +675,7 @@ function createThreadActionParts(
|
|||
stateStore,
|
||||
identity,
|
||||
closeForThreadSelection: () => {
|
||||
toolbarPanels.closeForThreadSelection();
|
||||
toolbarPanelActions.closeForThreadSelection();
|
||||
},
|
||||
focusThreadInOpenView: (threadId) => environment.plugin.workspace.focusThreadInOpenView(threadId),
|
||||
resumeThread: (threadId) => resume.resumeThread(threadId),
|
||||
|
|
@ -684,7 +684,7 @@ function createThreadActionParts(
|
|||
composerController.focus();
|
||||
},
|
||||
});
|
||||
return { actions, toolbarPanels, navigation };
|
||||
return { actions, toolbarPanelActions, navigation };
|
||||
}
|
||||
|
||||
function createComposerAndTurnActions(
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ export interface ToolbarPanelActions {
|
|||
closeOnOutsidePointer(context: ToolbarOutsidePointerContext): void;
|
||||
}
|
||||
|
||||
export interface ChatPanelToolbarActionDependencies {
|
||||
export interface ToolbarUiActionDependencies {
|
||||
connectionController: ChatConnectionController;
|
||||
reconnectPanel: () => Promise<void>;
|
||||
threadActions: ThreadManagementActions;
|
||||
goals: GoalActions;
|
||||
toolbarPanels: ToolbarPanelActions;
|
||||
toolbarPanel: ToolbarPanelActions;
|
||||
rename: ThreadRenameEditorActions;
|
||||
navigation: ThreadNavigationActions;
|
||||
}
|
||||
|
|
@ -120,58 +120,70 @@ export function createToolbarPanelActions(host: ToolbarPanelActionsHost): Toolba
|
|||
};
|
||||
}
|
||||
|
||||
export function createChatPanelToolbarActions(deps: ChatPanelToolbarActionDependencies): ToolbarActions {
|
||||
export function createToolbarUiActions(deps: ToolbarUiActionDependencies): ToolbarActions {
|
||||
return {
|
||||
startNewThread: () => {
|
||||
void deps.navigation.startNewThread();
|
||||
primary: {
|
||||
toggleChatActions: () => {
|
||||
deps.toolbarPanel.toggleChatActions();
|
||||
},
|
||||
toggleHistory: () => {
|
||||
deps.toolbarPanel.toggleHistory();
|
||||
},
|
||||
toggleStatusPanel: () => {
|
||||
deps.toolbarPanel.toggleStatus();
|
||||
},
|
||||
},
|
||||
toggleChatActions: () => {
|
||||
deps.toolbarPanels.toggleChatActions();
|
||||
chat: {
|
||||
startNewThread: () => {
|
||||
void deps.navigation.startNewThread();
|
||||
},
|
||||
compactConversation: () => {
|
||||
void deps.threadActions.compactActiveThread();
|
||||
},
|
||||
setGoal: () => {
|
||||
deps.goals.startEditingCurrent();
|
||||
},
|
||||
},
|
||||
compactConversation: () => {
|
||||
void deps.threadActions.compactActiveThread();
|
||||
status: {
|
||||
connect: () => {
|
||||
void deps.reconnectPanel();
|
||||
},
|
||||
refreshStatus: () => {
|
||||
void deps.connectionController.refreshStatusPanel();
|
||||
},
|
||||
copyDebugDetails: (details) => {
|
||||
void copyTextWithNotice(details, "Copied debug details.", "Could not copy debug details.");
|
||||
},
|
||||
},
|
||||
setGoal: () => {
|
||||
deps.goals.startEditingCurrent();
|
||||
},
|
||||
toggleHistory: () => {
|
||||
deps.toolbarPanels.toggleHistory();
|
||||
},
|
||||
toggleStatusPanel: () => {
|
||||
deps.toolbarPanels.toggleStatus();
|
||||
},
|
||||
connect: () => {
|
||||
void deps.reconnectPanel();
|
||||
},
|
||||
refreshStatus: () => {
|
||||
void deps.connectionController.refreshStatusPanel();
|
||||
},
|
||||
copyDebugDetails: (details) => {
|
||||
void copyTextWithNotice(details, "Copied debug details.", "Could not copy debug details.");
|
||||
},
|
||||
resumeThread: (threadId) => {
|
||||
void deps.navigation.selectThreadFromToolbar(threadId);
|
||||
},
|
||||
startArchiveThread: (threadId) => {
|
||||
deps.toolbarPanels.startArchive(threadId);
|
||||
},
|
||||
archiveThread: (threadId, saveMarkdown) => {
|
||||
void deps.toolbarPanels.archiveThread(threadId, saveMarkdown);
|
||||
},
|
||||
startRenameThread: (threadId) => {
|
||||
deps.rename.start(threadId);
|
||||
},
|
||||
updateRenameDraft: (threadId, value) => {
|
||||
deps.rename.updateDraft(threadId, value);
|
||||
},
|
||||
saveRenameThread: (threadId, value) => {
|
||||
void deps.rename.save(threadId, value);
|
||||
},
|
||||
cancelRenameThread: (threadId) => {
|
||||
deps.rename.cancel(threadId);
|
||||
},
|
||||
autoNameThread: (threadId) => {
|
||||
void deps.rename.autoNameDraft(threadId);
|
||||
threads: {
|
||||
resume: (threadId) => {
|
||||
void deps.navigation.selectThreadFromToolbar(threadId);
|
||||
},
|
||||
archive: {
|
||||
start: (threadId) => {
|
||||
deps.toolbarPanel.startArchive(threadId);
|
||||
},
|
||||
confirm: (threadId, saveMarkdown) => {
|
||||
void deps.toolbarPanel.archiveThread(threadId, saveMarkdown);
|
||||
},
|
||||
},
|
||||
rename: {
|
||||
start: (threadId) => {
|
||||
deps.rename.start(threadId);
|
||||
},
|
||||
updateDraft: (threadId, value) => {
|
||||
deps.rename.updateDraft(threadId, value);
|
||||
},
|
||||
save: (threadId, value) => {
|
||||
void deps.rename.save(threadId, value);
|
||||
},
|
||||
cancel: (threadId) => {
|
||||
deps.rename.cancel(threadId);
|
||||
},
|
||||
autoName: (threadId) => {
|
||||
void deps.rename.autoNameDraft(threadId);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,24 +45,44 @@ export interface ToolbarViewModel {
|
|||
toolInventory: ToolbarDiagnosticSection[];
|
||||
}
|
||||
|
||||
export interface ToolbarActions {
|
||||
startNewThread: () => void;
|
||||
interface ToolbarPrimaryActions {
|
||||
toggleHistory: () => void;
|
||||
toggleChatActions: () => void;
|
||||
toggleStatusPanel: () => void;
|
||||
}
|
||||
|
||||
interface ToolbarChatActions {
|
||||
startNewThread: () => void;
|
||||
compactConversation: () => void;
|
||||
setGoal: () => void;
|
||||
toggleHistory: () => void;
|
||||
toggleStatusPanel: () => void;
|
||||
}
|
||||
|
||||
interface ToolbarStatusActions {
|
||||
connect: () => void;
|
||||
refreshStatus: () => void;
|
||||
copyDebugDetails: (details: string) => void;
|
||||
resumeThread: (threadId: string) => void;
|
||||
startArchiveThread: (threadId: string) => void;
|
||||
archiveThread: (threadId: string, saveMarkdown: boolean) => void;
|
||||
startRenameThread: (threadId: string) => void;
|
||||
updateRenameDraft: (threadId: string, value: string) => void;
|
||||
saveRenameThread: (threadId: string, value: string) => void;
|
||||
cancelRenameThread: (threadId: string) => void;
|
||||
autoNameThread: (threadId: string) => void;
|
||||
}
|
||||
|
||||
interface ToolbarThreadActions {
|
||||
resume: (threadId: string) => void;
|
||||
archive: {
|
||||
start: (threadId: string) => void;
|
||||
confirm: (threadId: string, saveMarkdown: boolean) => void;
|
||||
};
|
||||
rename: {
|
||||
start: (threadId: string) => void;
|
||||
updateDraft: (threadId: string, value: string) => void;
|
||||
save: (threadId: string, value: string) => void;
|
||||
cancel: (threadId: string) => void;
|
||||
autoName: (threadId: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToolbarActions {
|
||||
primary: ToolbarPrimaryActions;
|
||||
chat: ToolbarChatActions;
|
||||
status: ToolbarStatusActions;
|
||||
threads: ToolbarThreadActions;
|
||||
}
|
||||
|
||||
export function Toolbar({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode {
|
||||
|
|
@ -74,16 +94,16 @@ export function Toolbar({ model, actions }: { model: ToolbarViewModel; actions:
|
|||
icon="history"
|
||||
label={model.historyOpen ? "Hide thread list" : "Show thread list"}
|
||||
className={["codex-panel__history-toggle", model.historyOpen ? "is-active" : ""].filter(Boolean).join(" ")}
|
||||
onClick={actions.toggleHistory}
|
||||
onClick={actions.primary.toggleHistory}
|
||||
/>
|
||||
<ToolbarIconButton
|
||||
icon="messages-square"
|
||||
label={model.chatActionsOpen ? "Hide chat actions" : "Show chat actions"}
|
||||
className={["codex-panel__new-chat", model.chatActionsOpen ? "is-active" : ""].filter(Boolean).join(" ")}
|
||||
disabled={model.newChatDisabled}
|
||||
onClick={actions.toggleChatActions}
|
||||
onClick={actions.primary.toggleChatActions}
|
||||
/>
|
||||
<StatusButton model={model} actions={actions} />
|
||||
<StatusButton model={model} actions={actions.primary} />
|
||||
</div>
|
||||
</div>
|
||||
<ToolbarPanel model={model} actions={actions} />
|
||||
|
|
@ -111,7 +131,7 @@ function ToolbarIconButton({
|
|||
);
|
||||
}
|
||||
|
||||
function StatusButton({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode {
|
||||
function StatusButton({ model, actions }: { model: ToolbarViewModel; actions: ToolbarPrimaryActions }): UiNode {
|
||||
return (
|
||||
<ToolbarIconButton
|
||||
icon="waypoints"
|
||||
|
|
@ -131,14 +151,14 @@ function ToolbarPanel({ model, actions }: { model: ToolbarViewModel; actions: To
|
|||
.join(" ")}
|
||||
data-codex-panel-toolbar-panel={model.openPanel}
|
||||
>
|
||||
{model.openPanel === "history" ? <ThreadList threads={model.threads} actions={actions} /> : null}
|
||||
{model.openPanel === "chat-actions" ? <ChatActionsPanel model={model} actions={actions} /> : null}
|
||||
{model.openPanel === "status" ? <StatusPanel model={model} actions={actions} /> : null}
|
||||
{model.openPanel === "history" ? <ThreadList threads={model.threads} actions={actions.threads} /> : null}
|
||||
{model.openPanel === "chat-actions" ? <ChatActionsPanel model={model} actions={actions.chat} /> : null}
|
||||
{model.openPanel === "status" ? <StatusPanel model={model} actions={actions.status} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatActionsPanel({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode {
|
||||
function ChatActionsPanel({ model, actions }: { model: ToolbarViewModel; actions: ToolbarChatActions }): UiNode {
|
||||
return (
|
||||
<div className="codex-panel__chat-actions-panel-items">
|
||||
<ToolbarPanelItem
|
||||
|
|
@ -157,7 +177,7 @@ function ChatActionsPanel({ model, actions }: { model: ToolbarViewModel; actions
|
|||
);
|
||||
}
|
||||
|
||||
function StatusPanel({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode {
|
||||
function StatusPanel({ model, actions }: { model: ToolbarViewModel; actions: ToolbarStatusActions }): UiNode {
|
||||
return (
|
||||
<>
|
||||
<div className="codex-panel__status-panel-items">
|
||||
|
|
@ -270,7 +290,7 @@ function diagnosticRowClassName(level: NonNullable<ToolbarDiagnosticRow["level"]
|
|||
return "codex-panel__connection-diagnostics-row";
|
||||
}
|
||||
|
||||
function ThreadList({ threads, actions }: { threads: ToolbarThreadRow[]; actions: ToolbarActions }): UiNode {
|
||||
function ThreadList({ threads, actions }: { threads: ToolbarThreadRow[]; actions: ToolbarThreadActions }): UiNode {
|
||||
if (threads.length === 0) {
|
||||
return (
|
||||
<div className="codex-panel__threads">
|
||||
|
|
@ -292,7 +312,7 @@ function ThreadList({ threads, actions }: { threads: ToolbarThreadRow[]; actions
|
|||
);
|
||||
}
|
||||
|
||||
function ThreadListRow({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarActions }): UiNode {
|
||||
function ThreadListRow({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarThreadActions }): UiNode {
|
||||
const archiveConfirm = archiveConfirmState(thread);
|
||||
return (
|
||||
<div
|
||||
|
|
@ -319,7 +339,7 @@ function ThreadListRow({ thread, actions }: { thread: ToolbarThreadRow; actions:
|
|||
disabled={thread.disabled}
|
||||
className="codex-panel__thread"
|
||||
onClick={() => {
|
||||
actions.resumeThread(thread.threadId);
|
||||
actions.resume(thread.threadId);
|
||||
}}
|
||||
/>
|
||||
{!archiveConfirm.active ? (
|
||||
|
|
@ -330,7 +350,7 @@ function ThreadListRow({ thread, actions }: { thread: ToolbarThreadRow; actions:
|
|||
disabled={thread.disabled}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
actions.startRenameThread(thread.threadId);
|
||||
actions.rename.start(thread.threadId);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
|
@ -341,7 +361,7 @@ function ThreadListRow({ thread, actions }: { thread: ToolbarThreadRow; actions:
|
|||
);
|
||||
}
|
||||
|
||||
function ArchiveControls({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarActions }): UiNode {
|
||||
function ArchiveControls({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarThreadActions }): UiNode {
|
||||
const archiveConfirm = archiveConfirmState(thread);
|
||||
if (!archiveConfirm.active) {
|
||||
return (
|
||||
|
|
@ -352,7 +372,7 @@ function ArchiveControls({ thread, actions }: { thread: ToolbarThreadRow; action
|
|||
disabled={thread.disabled}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
actions.startArchiveThread(thread.threadId);
|
||||
actions.archive.start(thread.threadId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -375,7 +395,7 @@ function ArchiveModeButton({
|
|||
thread: ToolbarThreadRow;
|
||||
saveMarkdown: boolean;
|
||||
primary: boolean;
|
||||
actions: ToolbarActions;
|
||||
actions: ToolbarThreadActions;
|
||||
}): UiNode {
|
||||
const label = saveMarkdown ? "Save and archive thread" : "Archive thread without saving";
|
||||
return (
|
||||
|
|
@ -386,13 +406,13 @@ function ArchiveModeButton({
|
|||
disabled={thread.disabled}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
actions.archiveThread(thread.threadId, saveMarkdown);
|
||||
actions.archive.confirm(thread.threadId, saveMarkdown);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarActions }): UiNode {
|
||||
function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarThreadActions }): UiNode {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const generating = thread.rename?.generating ?? false;
|
||||
const draft = thread.rename?.draft ?? thread.title;
|
||||
|
|
@ -419,21 +439,21 @@ function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; action
|
|||
type="text"
|
||||
value={draft}
|
||||
onInput={(event) => {
|
||||
actions.updateRenameDraft(thread.threadId, event.currentTarget.value);
|
||||
actions.rename.updateDraft(thread.threadId, event.currentTarget.value);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
if (!event.isComposing && !generating) actions.saveRenameThread(thread.threadId, event.currentTarget.value);
|
||||
if (!event.isComposing && !generating) actions.rename.save(thread.threadId, event.currentTarget.value);
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
actions.cancelRenameThread(thread.threadId);
|
||||
actions.rename.cancel(thread.threadId);
|
||||
}
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
if (!generating) actions.saveRenameThread(thread.threadId, event.currentTarget.value);
|
||||
if (!generating) actions.rename.save(thread.threadId, event.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -450,7 +470,7 @@ function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; action
|
|||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
actions.autoNameThread(thread.threadId);
|
||||
actions.rename.autoName(thread.threadId);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -470,23 +470,35 @@ const noOpMessageStreamScrollController: MessageStreamScrollControllerBinding =
|
|||
|
||||
function toolbarActionsFixture(): ChatPanelShellParts["toolbar"]["actions"] {
|
||||
return {
|
||||
startNewThread: vi.fn(),
|
||||
toggleChatActions: vi.fn(),
|
||||
compactConversation: vi.fn(),
|
||||
setGoal: vi.fn(),
|
||||
toggleHistory: vi.fn(),
|
||||
toggleStatusPanel: vi.fn(),
|
||||
connect: vi.fn(),
|
||||
refreshStatus: vi.fn(),
|
||||
copyDebugDetails: vi.fn(),
|
||||
resumeThread: vi.fn(),
|
||||
startArchiveThread: vi.fn(),
|
||||
archiveThread: vi.fn(),
|
||||
startRenameThread: vi.fn(),
|
||||
updateRenameDraft: vi.fn(),
|
||||
saveRenameThread: vi.fn(),
|
||||
cancelRenameThread: vi.fn(),
|
||||
autoNameThread: vi.fn(),
|
||||
primary: {
|
||||
toggleHistory: vi.fn(),
|
||||
toggleChatActions: vi.fn(),
|
||||
toggleStatusPanel: vi.fn(),
|
||||
},
|
||||
chat: {
|
||||
startNewThread: vi.fn(),
|
||||
compactConversation: vi.fn(),
|
||||
setGoal: vi.fn(),
|
||||
},
|
||||
status: {
|
||||
connect: vi.fn(),
|
||||
refreshStatus: vi.fn(),
|
||||
copyDebugDetails: vi.fn(),
|
||||
},
|
||||
threads: {
|
||||
resume: vi.fn(),
|
||||
archive: {
|
||||
start: vi.fn(),
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
rename: {
|
||||
start: vi.fn(),
|
||||
updateDraft: vi.fn(),
|
||||
save: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
autoName: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -445,26 +445,41 @@ function toolbarSurfaceFixture(overrides: { archiveExportEnabled?: boolean } = {
|
|||
};
|
||||
}
|
||||
|
||||
function toolbarActionsFixture(overrides: Partial<ToolbarActions> = {}): ToolbarActions {
|
||||
interface ToolbarActionOverrides {
|
||||
copyDebugDetails?: (details: string) => void;
|
||||
}
|
||||
|
||||
function toolbarActionsFixture(overrides: ToolbarActionOverrides = {}): ToolbarActions {
|
||||
return {
|
||||
startNewThread: () => undefined,
|
||||
toggleChatActions: () => undefined,
|
||||
compactConversation: () => undefined,
|
||||
setGoal: () => undefined,
|
||||
toggleHistory: () => undefined,
|
||||
toggleStatusPanel: () => undefined,
|
||||
connect: () => undefined,
|
||||
refreshStatus: () => undefined,
|
||||
copyDebugDetails: () => undefined,
|
||||
resumeThread: () => undefined,
|
||||
startArchiveThread: () => undefined,
|
||||
archiveThread: () => undefined,
|
||||
startRenameThread: () => undefined,
|
||||
updateRenameDraft: () => undefined,
|
||||
saveRenameThread: () => undefined,
|
||||
cancelRenameThread: () => undefined,
|
||||
autoNameThread: () => undefined,
|
||||
...overrides,
|
||||
primary: {
|
||||
toggleHistory: () => undefined,
|
||||
toggleChatActions: () => undefined,
|
||||
toggleStatusPanel: () => undefined,
|
||||
},
|
||||
chat: {
|
||||
startNewThread: () => undefined,
|
||||
compactConversation: () => undefined,
|
||||
setGoal: () => undefined,
|
||||
},
|
||||
status: {
|
||||
connect: () => undefined,
|
||||
refreshStatus: () => undefined,
|
||||
copyDebugDetails: overrides.copyDebugDetails ?? (() => undefined),
|
||||
},
|
||||
threads: {
|
||||
resume: () => undefined,
|
||||
archive: {
|
||||
start: () => undefined,
|
||||
confirm: () => undefined,
|
||||
},
|
||||
rename: {
|
||||
start: () => undefined,
|
||||
updateDraft: () => undefined,
|
||||
save: () => undefined,
|
||||
cancel: () => undefined,
|
||||
autoName: () => undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
import { createChatState } from "../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../src/features/chat/application/state/store";
|
||||
import type { ThreadManagementActions } from "../../../../src/features/chat/application/threads/thread-management-actions";
|
||||
import { createChatPanelToolbarActions, createToolbarPanelActions } from "../../../../src/features/chat/panel/toolbar-actions";
|
||||
import { createToolbarPanelActions, createToolbarUiActions } from "../../../../src/features/chat/panel/toolbar-actions";
|
||||
|
||||
describe("createToolbarPanelActions", () => {
|
||||
it("tracks archive confirmation and delegates archive actions", async () => {
|
||||
|
|
@ -45,39 +45,75 @@ describe("createToolbarPanelActions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("createChatPanelToolbarActions", () => {
|
||||
describe("createToolbarUiActions", () => {
|
||||
it("maps primary toolbar controls to panel actions", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.primary.toggleHistory();
|
||||
actions.primary.toggleChatActions();
|
||||
actions.primary.toggleStatusPanel();
|
||||
|
||||
expect(vi.mocked(deps.toolbarPanel.toggleHistory)).toHaveBeenCalledOnce();
|
||||
expect(vi.mocked(deps.toolbarPanel.toggleChatActions)).toHaveBeenCalledOnce();
|
||||
expect(vi.mocked(deps.toolbarPanel.toggleStatus)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("starts a new thread through thread navigation", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createChatPanelToolbarActions(deps);
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.startNewThread();
|
||||
actions.chat.startNewThread();
|
||||
|
||||
expect(vi.mocked(deps.navigation.startNewThread)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("delegates compacting the active thread", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createChatPanelToolbarActions(deps);
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.compactConversation();
|
||||
actions.chat.compactConversation();
|
||||
|
||||
expect(vi.mocked(deps.threadActions.compactActiveThread)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("starts the goal editor from the active goal", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createChatPanelToolbarActions(deps);
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.setGoal();
|
||||
actions.chat.setGoal();
|
||||
|
||||
expect(vi.mocked(deps.goals.startEditingCurrent)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("maps thread list actions to navigation, archive, and rename actions", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.threads.resume("thread");
|
||||
actions.threads.archive.start("thread");
|
||||
actions.threads.archive.confirm("thread", true);
|
||||
actions.threads.rename.start("thread");
|
||||
actions.threads.rename.updateDraft("thread", "Draft");
|
||||
actions.threads.rename.save("thread", "Saved");
|
||||
actions.threads.rename.cancel("thread");
|
||||
actions.threads.rename.autoName("thread");
|
||||
|
||||
expect(vi.mocked(deps.navigation.selectThreadFromToolbar)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.toolbarPanel.startArchive)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.toolbarPanel.archiveThread)).toHaveBeenCalledWith("thread", true);
|
||||
expect(vi.mocked(deps.rename.start)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.rename.updateDraft)).toHaveBeenCalledWith("thread", "Draft");
|
||||
expect(vi.mocked(deps.rename.save)).toHaveBeenCalledWith("thread", "Saved");
|
||||
expect(vi.mocked(deps.rename.cancel)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.rename.autoNameDraft)).toHaveBeenCalledWith("thread");
|
||||
});
|
||||
});
|
||||
|
||||
function toolbarActionDeps(): Parameters<typeof createChatPanelToolbarActions>[0] {
|
||||
function toolbarActionDeps(): Parameters<typeof createToolbarUiActions>[0] {
|
||||
return {
|
||||
connectionController: { refreshStatusPanel: vi.fn() } as unknown as Parameters<
|
||||
typeof createChatPanelToolbarActions
|
||||
typeof createToolbarUiActions
|
||||
>[0]["connectionController"],
|
||||
reconnectPanel: vi.fn(),
|
||||
threadActions: {
|
||||
|
|
@ -87,24 +123,24 @@ function toolbarActionDeps(): Parameters<typeof createChatPanelToolbarActions>[0
|
|||
} as unknown as ThreadManagementActions,
|
||||
goals: {
|
||||
startEditingCurrent: vi.fn(),
|
||||
} as unknown as Parameters<typeof createChatPanelToolbarActions>[0]["goals"],
|
||||
toolbarPanels: {
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["goals"],
|
||||
toolbarPanel: {
|
||||
toggleChatActions: vi.fn(),
|
||||
toggleHistory: vi.fn(),
|
||||
toggleStatus: vi.fn(),
|
||||
startArchive: vi.fn(),
|
||||
archiveThread: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Parameters<typeof createChatPanelToolbarActions>[0]["toolbarPanels"],
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["toolbarPanel"],
|
||||
rename: {
|
||||
start: vi.fn(),
|
||||
updateDraft: vi.fn(),
|
||||
save: vi.fn().mockResolvedValue(undefined),
|
||||
cancel: vi.fn(),
|
||||
autoNameDraft: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Parameters<typeof createChatPanelToolbarActions>[0]["rename"],
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["rename"],
|
||||
navigation: {
|
||||
startNewThread: vi.fn().mockResolvedValue(undefined),
|
||||
selectThreadFromToolbar: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Parameters<typeof createChatPanelToolbarActions>[0]["navigation"],
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["navigation"],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,31 +68,43 @@ function toolbarSurface(_store: ReturnType<typeof createChatStateStore>, _toolba
|
|||
};
|
||||
}
|
||||
|
||||
function shellParts(store: ReturnType<typeof createChatStateStore>, toolbarActions: ToolbarPanelActions): ChatPanelShellParts {
|
||||
const surface = surfaceFixture(store, toolbarActions);
|
||||
function shellParts(store: ReturnType<typeof createChatStateStore>, toolbarPanelActions: ToolbarPanelActions): ChatPanelShellParts {
|
||||
const surface = surfaceFixture(store, toolbarPanelActions);
|
||||
return {
|
||||
toolbar: {
|
||||
surface: surface.toolbar,
|
||||
actions: {
|
||||
startNewThread: vi.fn(),
|
||||
toggleChatActions: vi.fn(),
|
||||
compactConversation: vi.fn(),
|
||||
setGoal: vi.fn(),
|
||||
toggleHistory: vi.fn(),
|
||||
toggleStatusPanel: vi.fn(),
|
||||
connect: vi.fn(),
|
||||
refreshStatus: vi.fn(),
|
||||
copyDebugDetails: vi.fn(),
|
||||
resumeThread: vi.fn(),
|
||||
startArchiveThread: (threadId) => {
|
||||
toolbarActions.startArchive(threadId);
|
||||
primary: {
|
||||
toggleHistory: vi.fn(),
|
||||
toggleChatActions: vi.fn(),
|
||||
toggleStatusPanel: vi.fn(),
|
||||
},
|
||||
chat: {
|
||||
startNewThread: vi.fn(),
|
||||
compactConversation: vi.fn(),
|
||||
setGoal: vi.fn(),
|
||||
},
|
||||
status: {
|
||||
connect: vi.fn(),
|
||||
refreshStatus: vi.fn(),
|
||||
copyDebugDetails: vi.fn(),
|
||||
},
|
||||
threads: {
|
||||
resume: vi.fn(),
|
||||
archive: {
|
||||
start: (threadId) => {
|
||||
toolbarPanelActions.startArchive(threadId);
|
||||
},
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
rename: {
|
||||
start: vi.fn(),
|
||||
updateDraft: vi.fn(),
|
||||
save: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
autoName: vi.fn(),
|
||||
},
|
||||
},
|
||||
archiveThread: vi.fn(),
|
||||
startRenameThread: vi.fn(),
|
||||
updateRenameDraft: vi.fn(),
|
||||
saveRenameThread: vi.fn(),
|
||||
cancelRenameThread: vi.fn(),
|
||||
autoNameThread: vi.fn(),
|
||||
},
|
||||
},
|
||||
goal: surface.goal,
|
||||
|
|
|
|||
|
|
@ -368,25 +368,56 @@ function toolbarModel(overrides: Partial<ToolbarViewModel> = {}): ToolbarViewMod
|
|||
};
|
||||
}
|
||||
|
||||
function toolbarActions(overrides: Partial<ToolbarActions> = {}): ToolbarActions {
|
||||
interface ToolbarActionOverrides {
|
||||
toggleHistory?: () => void;
|
||||
startNewThread?: () => void;
|
||||
toggleChatActions?: () => void;
|
||||
compactConversation?: () => void;
|
||||
setGoal?: () => void;
|
||||
toggleStatusPanel?: () => void;
|
||||
connect?: () => void;
|
||||
refreshStatus?: () => void;
|
||||
copyDebugDetails?: (details: string) => void;
|
||||
resumeThread?: (threadId: string) => void;
|
||||
startArchiveThread?: (threadId: string) => void;
|
||||
archiveThread?: (threadId: string, saveMarkdown: boolean) => void;
|
||||
startRenameThread?: (threadId: string) => void;
|
||||
updateRenameDraft?: (threadId: string, value: string) => void;
|
||||
saveRenameThread?: (threadId: string, value: string) => void;
|
||||
cancelRenameThread?: (threadId: string) => void;
|
||||
autoNameThread?: (threadId: string) => void;
|
||||
}
|
||||
|
||||
function toolbarActions(overrides: ToolbarActionOverrides = {}): ToolbarActions {
|
||||
return {
|
||||
toggleHistory: vi.fn(),
|
||||
startNewThread: vi.fn(),
|
||||
toggleChatActions: vi.fn(),
|
||||
compactConversation: vi.fn(),
|
||||
setGoal: vi.fn(),
|
||||
toggleStatusPanel: vi.fn(),
|
||||
connect: vi.fn(),
|
||||
refreshStatus: vi.fn(),
|
||||
copyDebugDetails: vi.fn(),
|
||||
resumeThread: vi.fn(),
|
||||
startArchiveThread: vi.fn(),
|
||||
archiveThread: vi.fn(),
|
||||
startRenameThread: vi.fn(),
|
||||
updateRenameDraft: vi.fn(),
|
||||
saveRenameThread: vi.fn(),
|
||||
cancelRenameThread: vi.fn(),
|
||||
autoNameThread: vi.fn(),
|
||||
...overrides,
|
||||
primary: {
|
||||
toggleHistory: overrides.toggleHistory ?? vi.fn(),
|
||||
toggleChatActions: overrides.toggleChatActions ?? vi.fn(),
|
||||
toggleStatusPanel: overrides.toggleStatusPanel ?? vi.fn(),
|
||||
},
|
||||
chat: {
|
||||
startNewThread: overrides.startNewThread ?? vi.fn(),
|
||||
compactConversation: overrides.compactConversation ?? vi.fn(),
|
||||
setGoal: overrides.setGoal ?? vi.fn(),
|
||||
},
|
||||
status: {
|
||||
connect: overrides.connect ?? vi.fn(),
|
||||
refreshStatus: overrides.refreshStatus ?? vi.fn(),
|
||||
copyDebugDetails: overrides.copyDebugDetails ?? vi.fn(),
|
||||
},
|
||||
threads: {
|
||||
resume: overrides.resumeThread ?? vi.fn(),
|
||||
archive: {
|
||||
start: overrides.startArchiveThread ?? vi.fn(),
|
||||
confirm: overrides.archiveThread ?? vi.fn(),
|
||||
},
|
||||
rename: {
|
||||
start: overrides.startRenameThread ?? vi.fn(),
|
||||
updateDraft: overrides.updateRenameDraft ?? vi.fn(),
|
||||
save: overrides.saveRenameThread ?? vi.fn(),
|
||||
cancel: overrides.cancelRenameThread ?? vi.fn(),
|
||||
autoName: overrides.autoNameThread ?? vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue