mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Extract runtime toolbar choices
This commit is contained in:
parent
7364f5db85
commit
3f7c9e29ec
3 changed files with 80 additions and 39 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort";
|
||||
import type { RuntimeSnapshot } from "../../runtime/state";
|
||||
import {
|
||||
autoReviewActive,
|
||||
|
|
@ -11,6 +12,7 @@ import {
|
|||
supportedReasoningEfforts,
|
||||
} from "../../runtime/state";
|
||||
import { readRuntimeConfig } from "../../runtime/config";
|
||||
import { sortedAvailableModels } from "../../runtime/model";
|
||||
import { compactContextLabel } from "../../runtime/settings";
|
||||
import { contextSummary, effectiveConfigSections, rateLimitSummary } from "../../runtime/view";
|
||||
import { getThreadTitle } from "../../domain/threads/model";
|
||||
|
|
@ -43,6 +45,13 @@ export interface ConnectionDiagnosticsModelInput {
|
|||
configuredCommand: string;
|
||||
}
|
||||
|
||||
export interface RuntimeToolbarChoicesInput {
|
||||
state: ChatState;
|
||||
snapshot: RuntimeSnapshot;
|
||||
setRequestedModel: (model: string | null) => void;
|
||||
setRequestedReasoningEffort: (effort: ReasoningEffort | null) => void;
|
||||
}
|
||||
|
||||
export function runtimeSnapshotForChatState({ state }: RuntimeSnapshotInput): RuntimeSnapshot {
|
||||
return {
|
||||
effectiveConfig: state.effectiveConfig,
|
||||
|
|
@ -66,6 +75,37 @@ export function runtimeSnapshotForChatState({ state }: RuntimeSnapshotInput): Ru
|
|||
};
|
||||
}
|
||||
|
||||
export function runtimeToolbarChoices(input: RuntimeToolbarChoicesInput): Pick<ToolbarViewModel, "modelChoices" | "effortChoices"> {
|
||||
const config = readRuntimeConfig(input.state.effectiveConfig);
|
||||
const activeModel = currentModel(input.snapshot, config);
|
||||
const models = sortedAvailableModels(input.state.availableModels);
|
||||
const modelChoices: ToolbarChoice[] = models.slice(0, 12).map((model) => ({
|
||||
label: model.model,
|
||||
selected: activeModel === model.model,
|
||||
onClick: () => {
|
||||
input.setRequestedModel(model.model);
|
||||
},
|
||||
}));
|
||||
if (models.length === 0) {
|
||||
modelChoices.push({
|
||||
label: "No model list available.",
|
||||
disabled: true,
|
||||
onClick: () => undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const activeEffort = currentReasoningEffort(input.snapshot, config);
|
||||
const effortChoices: ToolbarChoice[] = supportedReasoningEfforts(input.snapshot).map((effort) => ({
|
||||
label: effort,
|
||||
selected: activeEffort === effort,
|
||||
onClick: () => {
|
||||
input.setRequestedReasoningEffort(effort);
|
||||
},
|
||||
}));
|
||||
|
||||
return { modelChoices, effortChoices };
|
||||
}
|
||||
|
||||
export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel {
|
||||
const { state, snapshot } = input;
|
||||
const config = readRuntimeConfig(state.effectiveConfig);
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@ import type { ThreadResumeResponse } from "../../generated/app-server/v2/ThreadR
|
|||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../../runtime/collaboration-mode";
|
||||
import { ChatController } from "./chat-controller";
|
||||
import { currentModel, currentReasoningEffort, supportedReasoningEfforts, type RuntimeSnapshot } from "../../runtime/state";
|
||||
import { readRuntimeConfig } from "../../runtime/config";
|
||||
import { sortedAvailableModels } from "../../runtime/model";
|
||||
import { currentModel, type RuntimeSnapshot } from "../../runtime/state";
|
||||
import { executeSlashCommand as runSlashCommand, type SlashCommandExecutionResult } from "./slash-commands";
|
||||
import type { ThreadReferenceInput } from "./slash-commands";
|
||||
import { mcpStatusLines } from "./mcp-status";
|
||||
|
|
@ -48,7 +46,7 @@ import {
|
|||
type ReferencedThreadDisplay,
|
||||
} from "../../domain/threads/reference";
|
||||
import { pendingRequestMessageNode } from "./ui/pending-request-message";
|
||||
import { renderToolbar, type ToolbarChoice, type ToolbarViewModel } from "./ui/toolbar";
|
||||
import { renderToolbar, type ToolbarViewModel } from "./ui/toolbar";
|
||||
import { renderChatPanelShell, unmountChatPanelShell } from "./ui/shell";
|
||||
import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
||||
import { ChatMessageRenderer, type ChatMessageScrollIntent } from "./chat-message-renderer";
|
||||
|
|
@ -62,6 +60,7 @@ import {
|
|||
connectionDiagnosticsModel,
|
||||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
runtimeToolbarChoices,
|
||||
runtimeSnapshotForChatState,
|
||||
statusSummaryLines as buildStatusSummaryLines,
|
||||
toolbarViewModel as buildToolbarViewModel,
|
||||
|
|
@ -1168,8 +1167,12 @@ export class CodexChatView extends ItemView {
|
|||
configuredCommand: this.plugin.settings.codexPath,
|
||||
archiveConfirmThreadId: this.archiveConfirmThreadId,
|
||||
archiveExportEnabled: this.plugin.settings.archiveExportEnabled,
|
||||
modelChoices: this.modelToolbarChoices(),
|
||||
effortChoices: this.effortToolbarChoices(),
|
||||
...runtimeToolbarChoices({
|
||||
state: this.state,
|
||||
snapshot: this.runtimeSnapshot(),
|
||||
setRequestedModel: (model) => void this.setRequestedModelFromUi(model),
|
||||
setRequestedReasoningEffort: (effort) => void this.setRequestedReasoningEffortFromUi(effort),
|
||||
}),
|
||||
renameState: (threadId) => this.threadRename.editState(threadId),
|
||||
});
|
||||
}
|
||||
|
|
@ -1195,39 +1198,6 @@ export class CodexChatView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
private modelToolbarChoices(): ToolbarChoice[] {
|
||||
const snapshot = this.runtimeSnapshot();
|
||||
const config = readRuntimeConfig(this.state.effectiveConfig);
|
||||
const activeModel = currentModel(snapshot, config);
|
||||
const models = sortedAvailableModels(this.state.availableModels);
|
||||
const choices: ToolbarChoice[] = [
|
||||
...models.slice(0, 12).map((model) => ({
|
||||
label: model.model,
|
||||
selected: activeModel === model.model,
|
||||
onClick: () => void this.setRequestedModelFromUi(model.model),
|
||||
})),
|
||||
];
|
||||
if (models.length === 0) {
|
||||
choices.push({
|
||||
label: "No model list available.",
|
||||
disabled: true,
|
||||
onClick: () => undefined,
|
||||
});
|
||||
}
|
||||
return choices;
|
||||
}
|
||||
|
||||
private effortToolbarChoices(): ToolbarChoice[] {
|
||||
const snapshot = this.runtimeSnapshot();
|
||||
const config = readRuntimeConfig(this.state.effectiveConfig);
|
||||
const activeEffort = currentReasoningEffort(snapshot, config);
|
||||
return supportedReasoningEfforts(snapshot).map((effort) => ({
|
||||
label: effort,
|
||||
selected: activeEffort === effort,
|
||||
onClick: () => void this.setRequestedReasoningEffortFromUi(effort),
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleHistoryPanel(): void {
|
||||
this.dispatch({ type: "ui/panel-set", panel: "history", toggle: true });
|
||||
this.scheduleRender();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { createAppServerDiagnostics } from "../../../src/app-server/compatibilit
|
|||
import { createChatState } from "../../../src/features/chat/chat-state";
|
||||
import {
|
||||
effortStatusLines,
|
||||
runtimeToolbarChoices,
|
||||
modelStatusLines,
|
||||
runtimeSnapshotForChatState,
|
||||
statusSummaryLines,
|
||||
|
|
@ -63,6 +64,36 @@ describe("chat view model", () => {
|
|||
expect(modelStatusLines(state, snapshot, "Default")).toContain("Mode: Default");
|
||||
expect(effortStatusLines(state, snapshot)).toContain("Supported: high");
|
||||
});
|
||||
|
||||
it("builds runtime toolbar choices from immutable chat state snapshots", () => {
|
||||
const state = createChatState();
|
||||
state.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
|
||||
state.availableModels = [modelFixture("gpt-5.5"), modelFixture("gpt-5-mini")];
|
||||
const selectedModels: (string | null)[] = [];
|
||||
const selectedEfforts: string[] = [];
|
||||
|
||||
const choices = runtimeToolbarChoices({
|
||||
state,
|
||||
snapshot: runtimeSnapshotForChatState({ state }),
|
||||
setRequestedModel: (model) => {
|
||||
selectedModels.push(model);
|
||||
},
|
||||
setRequestedReasoningEffort: (effort) => {
|
||||
if (effort) selectedEfforts.push(effort);
|
||||
},
|
||||
});
|
||||
|
||||
expect(choices.modelChoices).toMatchObject([
|
||||
{ label: "gpt-5-mini", selected: false },
|
||||
{ label: "gpt-5.5", selected: true },
|
||||
]);
|
||||
expect(choices.effortChoices).toMatchObject([{ label: "high", selected: true }]);
|
||||
|
||||
choices.modelChoices[0]?.onClick();
|
||||
choices.effortChoices[0]?.onClick();
|
||||
expect(selectedModels).toEqual(["gpt-5-mini"]);
|
||||
expect(selectedEfforts).toEqual(["high"]);
|
||||
});
|
||||
});
|
||||
|
||||
function effectiveConfigFixture(config: Record<string, unknown>): ConfigReadResponse {
|
||||
|
|
|
|||
Loading…
Reference in a new issue