mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Tidy chat controller boundaries
This commit is contained in:
parent
34e1289c22
commit
e0182d7a1c
51 changed files with 316 additions and 271 deletions
|
|
@ -9,7 +9,7 @@ import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
|||
import { MarkdownMessageRenderer } from "./markdown-message-renderer";
|
||||
import { isRollbackCandidateItem, rollbackCandidateFromItems } from "./rollback";
|
||||
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
|
||||
import { implementPlanCandidateFromState } from "./plan-implementation-controller";
|
||||
import { implementPlanCandidateFromState } from "./plan-implementation";
|
||||
import { unmountReactRoot } from "../../shared/ui/react-root";
|
||||
|
||||
export interface ChatMessageRendererOptions {
|
||||
|
|
@ -17,7 +17,7 @@ export interface ChatMessageRendererOptions {
|
|||
owner: Component;
|
||||
stateStore: ChatStateStore;
|
||||
vaultPath: string;
|
||||
consumeScrollIntent: () => ChatMessageScrollIntent;
|
||||
consumeScrollIntent: () => MessageScrollIntent;
|
||||
loadOlderTurns: () => void;
|
||||
rollbackThread: (threadId: string) => void;
|
||||
implementPlan: (item: DisplayItem) => void;
|
||||
|
|
@ -26,8 +26,6 @@ export interface ChatMessageRendererOptions {
|
|||
renderPendingRequests: () => ReactNode;
|
||||
}
|
||||
|
||||
export type ChatMessageScrollIntent = MessageScrollIntent;
|
||||
|
||||
export class ChatMessageRenderer {
|
||||
private messagesEl: HTMLElement | null = null;
|
||||
private readonly scrollController: MessageScrollController;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ChatViewDeferredTasks } from "./view-lifecycle";
|
||||
import type { ChatViewDeferredTasks } from "../../view-lifecycle";
|
||||
|
||||
export interface AppServerWarmupControllerHost {
|
||||
deferredTasks: ChatViewDeferredTasks;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { StaleConnectionError } from "../../app-server/connection-manager";
|
||||
import type { AppServerClient } from "../../app-server/client";
|
||||
import type { InitializeResponse } from "../../generated/app-server/InitializeResponse";
|
||||
import type { ChatAction, ChatStateStore } from "./chat-state";
|
||||
import type { ChatAppServerController } from "./chat-app-server-controller";
|
||||
import type { ChatConnectionWorkTracker, ActiveChatConnection } from "./view-lifecycle";
|
||||
import { StaleConnectionError } from "../../../../app-server/connection-manager";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
import type { InitializeResponse } from "../../../../generated/app-server/InitializeResponse";
|
||||
import type { ChatAction, ChatStateStore } from "../../chat-state";
|
||||
import type { ChatAppServerController } from "../../chat-app-server-controller";
|
||||
import type { ChatConnectionWorkTracker, ActiveChatConnection } from "../../view-lifecycle";
|
||||
|
||||
export interface ChatConnectionAdapter {
|
||||
connect(): Promise<InitializeResponse>;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ChatAction, ChatStateStore } from "./chat-state";
|
||||
import type { ChatAction, ChatStateStore } from "../../chat-state";
|
||||
|
||||
export interface ChatReconnectControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import type { ReactNode } from "react";
|
||||
|
||||
import type { ApprovalAction, PendingApproval } from "./approvals/model";
|
||||
import type { ChatAction, ChatState, ChatStateStore } from "./chat-state";
|
||||
import type { ChatController } from "./chat-controller";
|
||||
import { pendingRequestFocusSignature, userInputDraftKey, userInputOtherDraftKey } from "./request-state";
|
||||
import { pendingRequestMessageNode } from "./ui/pending-request-message";
|
||||
import { answersForPendingUserInput, type PendingUserInput } from "./user-input/model";
|
||||
import type { ApprovalAction, PendingApproval } from "../../approvals/model";
|
||||
import type { ChatAction, ChatState, ChatStateStore } from "../../chat-state";
|
||||
import type { ChatController } from "../../chat-controller";
|
||||
import { pendingRequestFocusSignature } from "../../request-state";
|
||||
import { pendingRequestMessageNode } from "../../ui/pending-request-message";
|
||||
import { userInputDraftKey, userInputOtherDraftKey } from "../../user-input/drafts";
|
||||
import { answersForPendingUserInput, type PendingUserInput } from "../../user-input/model";
|
||||
|
||||
export interface PendingRequestControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { AppServerClient } from "../../app-server/client";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
|
||||
type RespondRequestId = Parameters<AppServerClient["respondToServerRequest"]>[0];
|
||||
type RejectRequestId = Parameters<AppServerClient["rejectServerRequest"]>[0];
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { AppServerClient } from "../../app-server/client";
|
||||
import { parseSlashCommand } from "./composer/suggestions";
|
||||
import { activeTurnId, chatTurnBusy, type ChatStateStore } from "./chat-state";
|
||||
import type { ChatComposerController } from "./chat-composer-controller";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
import { parseSlashCommand } from "../../composer/suggestions";
|
||||
import { activeTurnId, chatTurnBusy, type ChatStateStore } from "../../chat-state";
|
||||
import type { ChatComposerController } from "../../chat-composer-controller";
|
||||
import type { SlashCommandController } from "./slash-command-controller";
|
||||
import type { TurnSubmissionController } from "./turn-submission-controller";
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import type { AppServerClient } from "../../app-server/client";
|
||||
import { chatTurnBusy, type ChatState, type ChatStateStore } from "./chat-state";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
import type { ChatState, ChatStateStore } from "../../chat-state";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import { implementPlanCandidateFromState } from "../../plan-implementation";
|
||||
|
||||
const IMPLEMENT_PLAN_PROMPT = "Please implement this plan.";
|
||||
|
||||
|
|
@ -32,20 +33,3 @@ export class PlanImplementationController {
|
|||
return this.host.stateStore.getState();
|
||||
}
|
||||
}
|
||||
|
||||
export function implementPlanCandidateFromState(
|
||||
state: Pick<ChatState, "activeThreadId" | "turnLifecycle" | "composerDraft" | "requestedCollaborationMode" | "displayItems">,
|
||||
): DisplayItem | null {
|
||||
if (
|
||||
!state.activeThreadId ||
|
||||
chatTurnBusy(state) ||
|
||||
state.composerDraft.trim().length > 0 ||
|
||||
state.requestedCollaborationMode !== "plan"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
[...state.displayItems].reverse().find((item) => item.kind === "message" && item.role === "assistant" && item.proposedPlan === true) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import type { AppServerClient } from "../../app-server/client";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
import {
|
||||
referencedThreadInput as buildReferencedThreadInput,
|
||||
referencedThreadTurns,
|
||||
REFERENCED_THREAD_TURN_LIMIT,
|
||||
} from "../../domain/threads/reference";
|
||||
import type { Thread } from "../../generated/app-server/v2/Thread";
|
||||
import { chatTurnBusy, type ChatStateStore } from "./chat-state";
|
||||
import { executeSlashCommand as runSlashCommand, type SlashCommandExecutionResult, type ThreadReferenceInput } from "./slash-commands";
|
||||
import type { SlashCommandName } from "./composer/slash-commands";
|
||||
import type { DisplayDetailSection } from "./display/types";
|
||||
import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort";
|
||||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
} from "../../../../domain/threads/reference";
|
||||
import type { Thread } from "../../../../generated/app-server/v2/Thread";
|
||||
import { chatTurnBusy, type ChatStateStore } from "../../chat-state";
|
||||
import { executeSlashCommand as runSlashCommand, type SlashCommandExecutionResult, type ThreadReferenceInput } from "../../slash-commands";
|
||||
import type { SlashCommandName } from "../../composer/slash-commands";
|
||||
import type { DisplayDetailSection } from "../../display/types";
|
||||
import type { ReasoningEffort } from "../../../../generated/app-server/ReasoningEffort";
|
||||
import type { UserInput } from "../../../../generated/app-server/v2/UserInput";
|
||||
|
||||
export interface SlashCommandControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { AppServerClient } from "../../app-server/client";
|
||||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
import { activeTurnId, chatTurnBusy, pendingTurnStart, type ChatStateStore } from "./chat-state";
|
||||
import type { ReferencedThreadDisplay } from "../../domain/threads/reference";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
import type { UserInput } from "../../../../generated/app-server/v2/UserInput";
|
||||
import { activeTurnId, chatTurnBusy, pendingTurnStart, type ChatStateStore } from "../../chat-state";
|
||||
import type { ReferencedThreadDisplay } from "../../../../domain/threads/reference";
|
||||
import {
|
||||
acknowledgeOptimisticTurnStart,
|
||||
cleanupFailedTurnStart,
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import type { PendingTurnStart } from "./chat-state";
|
||||
import type { DisplayFileMention, DisplayItem, MessageDisplayItem } from "./display/types";
|
||||
import { fileMentionsFromInput } from "./display/thread-items";
|
||||
import { attachHookRunsToTurn } from "./hook-display";
|
||||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
import type { PendingTurnStart } from "../../chat-state";
|
||||
import type { DisplayFileMention, DisplayItem, MessageDisplayItem } from "../../display/types";
|
||||
import { fileMentionsFromInput } from "../../display/thread-items";
|
||||
import { attachHookRunsToTurn } from "../../hook-display";
|
||||
import type { UserInput } from "../../../../generated/app-server/v2/UserInput";
|
||||
|
||||
export interface LocalUserMessageParams {
|
||||
id: string;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import type { ChatAction } from "./chat-state";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
import type { ChatAction } from "../../chat-state";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import {
|
||||
transitionRestoredThreadLifecycle,
|
||||
type RestoredThreadLifecycleState,
|
||||
type RestoredThreadPlaceholderState,
|
||||
type RestoredThreadState,
|
||||
type ChatViewDeferredTasks,
|
||||
} from "./view-lifecycle";
|
||||
} from "../../view-lifecycle";
|
||||
|
||||
export interface RestoredThreadControllerHost {
|
||||
deferredTasks: ChatViewDeferredTasks;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ChatStateStore } from "./chat-state";
|
||||
import type { ChatStateStore } from "../../chat-state";
|
||||
import type { RestoredThreadController } from "./restored-thread-controller";
|
||||
|
||||
export interface ThreadIdentityControllerHost {
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import type { AppServerClient } from "../../app-server/client";
|
||||
import type { ChatStateStore } from "./chat-state";
|
||||
import { chatTurnBusy } from "./chat-state";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
import type { AppServerClient } from "../../../../app-server/client";
|
||||
import type { ChatStateStore } from "../../chat-state";
|
||||
import { chatTurnBusy } from "../../chat-state";
|
||||
import type { DisplayItem } from "../../display/types";
|
||||
import type { RestoredThreadController } from "./restored-thread-controller";
|
||||
import { resumedThreadAction, type ThreadActivationResponse } from "./thread-resume";
|
||||
import type { ThreadHistoryLoader } from "./thread-history";
|
||||
import type { ChatResumeWorkTracker, ActiveChatResume } from "./view-lifecycle";
|
||||
import { resumedThreadAction, type ThreadActivationResponse } from "../../thread-resume";
|
||||
import type { ThreadHistoryLoader } from "../../thread-history";
|
||||
import type { ChatResumeWorkTracker, ActiveChatResume } from "../../view-lifecycle";
|
||||
|
||||
export interface ThreadResumeControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
|
||||
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "../../chat-state";
|
||||
|
||||
export interface ThreadSelectionControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ChatStateStore } from "./chat-state";
|
||||
import type { ChatMessageScrollIntent } from "./chat-message-renderer";
|
||||
import type { ChatStateStore } from "../../chat-state";
|
||||
import type { MessageScrollIntent } from "../../ui/scroll";
|
||||
|
||||
export interface ChatMessageScrollControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -7,11 +7,11 @@ export interface ChatMessageScrollControllerHost {
|
|||
}
|
||||
|
||||
export class ChatMessageScrollController {
|
||||
private nextIntent: ChatMessageScrollIntent = "auto";
|
||||
private nextIntent: MessageScrollIntent = "auto";
|
||||
|
||||
constructor(private readonly host: ChatMessageScrollControllerHost) {}
|
||||
|
||||
consumeIntent(): ChatMessageScrollIntent {
|
||||
consumeIntent(): MessageScrollIntent {
|
||||
const value = this.nextIntent;
|
||||
this.nextIntent = "auto";
|
||||
return value;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { EventRef, WorkspaceLeaf } from "obsidian";
|
||||
|
||||
import { unmountReactRoot } from "../../shared/ui/react-root";
|
||||
import { unmountChatPanelShell } from "./ui/shell";
|
||||
import { unmountReactRoot } from "../../../../shared/ui/react-root";
|
||||
import { unmountChatPanelShell } from "../../ui/shell";
|
||||
|
||||
export interface ChatViewOpenCloseControllerHost {
|
||||
setOpened: (opened: boolean) => void;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ChatState, ChatStateStore } from "./chat-state";
|
||||
import type { ChatViewRenderScheduleOptions } from "./view-lifecycle";
|
||||
import { composerSlotSnapshot, messagesSlotSnapshot, toolbarSlotSnapshot } from "./view-snapshot";
|
||||
import { renderChatPanelShell } from "./ui/shell";
|
||||
import type { ChatState, ChatStateStore } from "../../chat-state";
|
||||
import type { ChatViewRenderScheduleOptions } from "../../view-lifecycle";
|
||||
import { composerSlotSnapshot, messagesSlotSnapshot, toolbarSlotSnapshot } from "../../view-snapshot";
|
||||
import { renderChatPanelShell } from "../../ui/shell";
|
||||
|
||||
export interface ChatViewRenderControllerHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { parseRestoredThreadState } from "./view-snapshot";
|
||||
import type { RestoredThreadState } from "./view-lifecycle";
|
||||
import { parseRestoredThreadState } from "../../view-snapshot";
|
||||
import type { RestoredThreadState } from "../../view-lifecycle";
|
||||
|
||||
export interface ChatViewStateControllerHost {
|
||||
invalidateResumeWork: () => void;
|
||||
|
|
@ -2,7 +2,7 @@ import { MarkdownRenderer, Notice, type App, type Component } from "obsidian";
|
|||
|
||||
import { isAbsoluteFileHref, vaultFileLinkTarget, vaultRelativeFileLinkTarget } from "./markdown-file-links";
|
||||
import { renderTextWithWikiLinks as renderInlineWikiLinks } from "../../shared/ui/dom";
|
||||
import { notifyMessageContentRendered } from "./ui/message-stream";
|
||||
import { notifyMessageContentRendered } from "./ui/message-content-events";
|
||||
|
||||
export interface MarkdownMessageRendererOptions {
|
||||
app: App;
|
||||
|
|
|
|||
19
src/features/chat/plan-implementation.ts
Normal file
19
src/features/chat/plan-implementation.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { chatTurnBusy, type ChatState } from "./chat-state";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
|
||||
export function implementPlanCandidateFromState(
|
||||
state: Pick<ChatState, "activeThreadId" | "turnLifecycle" | "composerDraft" | "requestedCollaborationMode" | "displayItems">,
|
||||
): DisplayItem | null {
|
||||
if (
|
||||
!state.activeThreadId ||
|
||||
chatTurnBusy(state) ||
|
||||
state.composerDraft.trim().length > 0 ||
|
||||
state.requestedCollaborationMode !== "plan"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
[...state.displayItems].reverse().find((item) => item.kind === "message" && item.role === "assistant" && item.proposedPlan === true) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import type { RequestId } from "../../generated/app-server/RequestId";
|
||||
import {
|
||||
approvalActionKind,
|
||||
approvalDetails,
|
||||
|
|
@ -9,16 +8,9 @@ import {
|
|||
} from "./approvals/model";
|
||||
import type { DisplayDetailSection, DisplayItem } from "./display/types";
|
||||
import type { PendingUserInput } from "./user-input/model";
|
||||
import { userInputDraftKey, userInputOtherDraftKey } from "./user-input/drafts";
|
||||
import { definedProp } from "../../utils";
|
||||
|
||||
export function userInputDraftKey(requestId: RequestId, questionId: string): string {
|
||||
return `${String(requestId)}:${questionId}`;
|
||||
}
|
||||
|
||||
export function userInputOtherDraftKey(requestId: RequestId, questionId: string): string {
|
||||
return `${String(requestId)}:${questionId}:other`;
|
||||
}
|
||||
|
||||
export function pendingRequestsSignature(
|
||||
approvals: readonly PendingApproval[],
|
||||
inputs: readonly PendingUserInput[],
|
||||
|
|
|
|||
62
src/features/chat/toolbar-model.ts
Normal file
62
src/features/chat/toolbar-model.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import type { EffectiveConfigSection, RateLimitSummary } from "../../runtime/view";
|
||||
|
||||
export type ToolbarPanelKind = "history" | "status" | "runtime";
|
||||
export type ToolbarStatusState = "offline" | "connected" | "running";
|
||||
export type ToolbarDiagnosticAlertLevel = "normal" | "warning" | "error";
|
||||
|
||||
export interface ToolbarChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ToolbarThreadRow {
|
||||
title: string;
|
||||
threadId: string;
|
||||
selected: boolean;
|
||||
disabled: boolean;
|
||||
canArchive: boolean;
|
||||
archiveConfirm?: { active: boolean; defaultSaveMarkdown: boolean };
|
||||
rename: {
|
||||
draft: string;
|
||||
generating: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface ToolbarDiagnosticRow {
|
||||
label: string;
|
||||
value: string;
|
||||
level?: "normal" | "warning" | "error";
|
||||
}
|
||||
|
||||
export interface ToolbarDiagnosticSection {
|
||||
title: string;
|
||||
rows: ToolbarDiagnosticRow[];
|
||||
}
|
||||
|
||||
export interface ToolbarViewModel {
|
||||
connected: boolean;
|
||||
status: string;
|
||||
statusState: ToolbarStatusState;
|
||||
historyOpen: boolean;
|
||||
statusPanelOpen: boolean;
|
||||
runtimeOpen: boolean;
|
||||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
runtimeSummary: string;
|
||||
runtimeTitle: string;
|
||||
runtimeEmphasized: boolean;
|
||||
context: { level: "ok" | "warn" | "danger"; title: string; label: string; percent: number | null } | null;
|
||||
rateLimit: RateLimitSummary | null;
|
||||
configSections: EffectiveConfigSection[];
|
||||
openPanel: ToolbarPanelKind | null;
|
||||
threads: ToolbarThreadRow[];
|
||||
modelChoices: ToolbarChoice[];
|
||||
effortChoices: ToolbarChoice[];
|
||||
connectLabel: string;
|
||||
diagnostics: ToolbarDiagnosticSection[];
|
||||
diagnosticAlertLevel: ToolbarDiagnosticAlertLevel;
|
||||
}
|
||||
5
src/features/chat/ui/message-content-events.ts
Normal file
5
src/features/chat/ui/message-content-events.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export const MESSAGE_CONTENT_RENDERED_EVENT = "codex-panel:message-content-rendered";
|
||||
|
||||
export function notifyMessageContentRendered(element: HTMLElement): void {
|
||||
element.dispatchEvent(new Event(MESSAGE_CONTENT_RENDERED_EVENT));
|
||||
}
|
||||
|
|
@ -6,13 +6,13 @@ import type { ToolResultDisplayItem } from "../display/tool-view";
|
|||
import type { DisplayBlock, DisplayDetailSection, DisplayItem } from "../display/types";
|
||||
import { activeTurnId, type ChatTurnLifecycleState } from "../chat-state";
|
||||
import { IconButton } from "../../../shared/ui/react-components";
|
||||
import { MESSAGE_CONTENT_RENDERED_EVENT } from "./message-content-events";
|
||||
import { toolResultNode } from "./tool-result";
|
||||
import { activeAgentRunSummaryBlock, agentRunSummaryNode, workItemNode, type WorkItemDisplayItem } from "./work-items";
|
||||
import type { ChatTurnDiffViewState } from "./turn-diff";
|
||||
import { renderReactRoot } from "../../../shared/ui/react-root";
|
||||
|
||||
const USER_MESSAGE_COLLAPSE_HEIGHT_PX = 360;
|
||||
const MESSAGE_CONTENT_RENDERED_EVENT = "codex-panel:message-content-rendered";
|
||||
|
||||
export interface MessageStreamBlock {
|
||||
key: string;
|
||||
|
|
@ -579,10 +579,6 @@ function userMessageCollapseHeight(element: HTMLElement): number {
|
|||
return Math.min(USER_MESSAGE_COLLAPSE_HEIGHT_PX, viewportHeight * 0.45);
|
||||
}
|
||||
|
||||
export function notifyMessageContentRendered(element: HTMLElement): void {
|
||||
element.dispatchEvent(new Event(MESSAGE_CONTENT_RENDERED_EVENT));
|
||||
}
|
||||
|
||||
function displayRoleLabel(item: DisplayItem): string {
|
||||
if (item.kind === "approvalResult") return "Approval";
|
||||
if (item.kind === "userInputResult") return "Input";
|
||||
|
|
|
|||
|
|
@ -3,67 +3,7 @@ import { useLayoutEffect, useRef, type ButtonHTMLAttributes, type KeyboardEvent,
|
|||
import type { EffectiveConfigSection, RateLimitSummary } from "../../../runtime/view";
|
||||
import { IconButton, ObsidianIcon } from "../../../shared/ui/react-components";
|
||||
import { renderReactRoot } from "../../../shared/ui/react-root";
|
||||
|
||||
export type ToolbarPanelKind = "history" | "status" | "runtime";
|
||||
export type ToolbarStatusState = "offline" | "connected" | "running";
|
||||
export type ToolbarDiagnosticAlertLevel = "normal" | "warning" | "error";
|
||||
|
||||
export interface ToolbarChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ToolbarThreadRow {
|
||||
title: string;
|
||||
threadId: string;
|
||||
selected: boolean;
|
||||
disabled: boolean;
|
||||
canArchive: boolean;
|
||||
archiveConfirm?: { active: boolean; defaultSaveMarkdown: boolean };
|
||||
rename: {
|
||||
draft: string;
|
||||
generating: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface ToolbarDiagnosticRow {
|
||||
label: string;
|
||||
value: string;
|
||||
level?: "normal" | "warning" | "error";
|
||||
}
|
||||
|
||||
export interface ToolbarDiagnosticSection {
|
||||
title: string;
|
||||
rows: ToolbarDiagnosticRow[];
|
||||
}
|
||||
|
||||
export interface ToolbarViewModel {
|
||||
connected: boolean;
|
||||
status: string;
|
||||
statusState: ToolbarStatusState;
|
||||
historyOpen: boolean;
|
||||
statusPanelOpen: boolean;
|
||||
runtimeOpen: boolean;
|
||||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
runtimeSummary: string;
|
||||
runtimeTitle: string;
|
||||
runtimeEmphasized: boolean;
|
||||
context: { level: "ok" | "warn" | "danger"; title: string; label: string; percent: number | null } | null;
|
||||
rateLimit: RateLimitSummary | null;
|
||||
configSections: EffectiveConfigSection[];
|
||||
openPanel: ToolbarPanelKind | null;
|
||||
threads: ToolbarThreadRow[];
|
||||
modelChoices: ToolbarChoice[];
|
||||
effortChoices: ToolbarChoice[];
|
||||
connectLabel: string;
|
||||
diagnostics: ToolbarDiagnosticSection[];
|
||||
diagnosticAlertLevel: ToolbarDiagnosticAlertLevel;
|
||||
}
|
||||
import type { ToolbarDiagnosticSection, ToolbarThreadRow, ToolbarViewModel } from "../toolbar-model";
|
||||
|
||||
export interface ToolbarActions {
|
||||
toggleHistory: () => void;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,16 @@ import { useLayoutEffect, useState, type ReactNode } from "react";
|
|||
|
||||
import { activeAgentRunSummary } from "../display/agent";
|
||||
import { executionState } from "../display/state";
|
||||
import type { AgentDisplayItem, AgentRunSummary, AgentRunSummaryAgent, TaskProgressDisplayItem, ToolDisplayItem } from "../display/types";
|
||||
import type {
|
||||
AgentDisplayItem,
|
||||
AgentRunSummary,
|
||||
AgentRunSummaryAgent,
|
||||
DisplayItem,
|
||||
TaskProgressDisplayItem,
|
||||
ToolDisplayItem,
|
||||
} from "../display/types";
|
||||
import { agentActivityMetaLabel, agentMessagePreview, agentRunSummaryLabel, taskStatusMarker } from "../display/labels";
|
||||
import { messageStreamActiveTurnId, type MessageStreamContext } from "./message-stream";
|
||||
import { activeTurnId, type ChatTurnLifecycleState } from "../chat-state";
|
||||
import { createWorkMessageClassName } from "./work-message";
|
||||
import { shortThreadId } from "../../../utils";
|
||||
|
||||
|
|
@ -13,15 +20,26 @@ const AGENT_ROW_MESSAGE_PREVIEW_LIMIT = 120;
|
|||
type ReasoningDisplayItem = ToolDisplayItem & { kind: "reasoning" };
|
||||
export type WorkItemDisplayItem = TaskProgressDisplayItem | AgentDisplayItem | ReasoningDisplayItem;
|
||||
|
||||
export function activeAgentRunSummaryBlock(context: MessageStreamContext): AgentRunSummary | null {
|
||||
return activeAgentRunSummary(context.displayItems, messageStreamActiveTurnId(context));
|
||||
export interface WorkItemContext {
|
||||
turnLifecycle: ChatTurnLifecycleState;
|
||||
displayItems: readonly DisplayItem[];
|
||||
openDetails: ReadonlySet<string>;
|
||||
onDetailsToggle?: (key: string, open: boolean) => void;
|
||||
}
|
||||
|
||||
export function workItemsActiveTurnId(context: Pick<WorkItemContext, "turnLifecycle">): string | null {
|
||||
return activeTurnId(context);
|
||||
}
|
||||
|
||||
export function activeAgentRunSummaryBlock(context: WorkItemContext): AgentRunSummary | null {
|
||||
return activeAgentRunSummary(context.displayItems, workItemsActiveTurnId(context));
|
||||
}
|
||||
|
||||
export function agentRunSummaryNode(summary: AgentRunSummary): ReactNode {
|
||||
return <AgentRunSummaryItem summary={summary} />;
|
||||
}
|
||||
|
||||
export function workItemNode(item: WorkItemDisplayItem, context: MessageStreamContext): ReactNode {
|
||||
export function workItemNode(item: WorkItemDisplayItem, context: WorkItemContext): ReactNode {
|
||||
if (item.kind === "taskProgress") return <TaskProgressItem item={item} />;
|
||||
if (item.kind === "agent") return <AgentItem item={item} context={context} />;
|
||||
return <ReasoningItem item={item} context={context} />;
|
||||
|
|
@ -56,7 +74,7 @@ function TaskProgressItem({ item }: { item: TaskProgressDisplayItem }): ReactNod
|
|||
);
|
||||
}
|
||||
|
||||
function AgentItem({ item, context }: { item: AgentDisplayItem; context: MessageStreamContext }): ReactNode {
|
||||
function AgentItem({ item, context }: { item: AgentDisplayItem; context: WorkItemContext }): ReactNode {
|
||||
return (
|
||||
<WorkMessage label="agent" className="codex-panel__agent-activity" state={executionState(item)}>
|
||||
<div className="codex-panel__tool-summary">{agentSummaryText(item)}</div>
|
||||
|
|
@ -107,7 +125,7 @@ function AgentItem({ item, context }: { item: AgentDisplayItem; context: Message
|
|||
);
|
||||
}
|
||||
|
||||
function ReasoningItem({ item, context }: { item: ReasoningDisplayItem; context: MessageStreamContext }): ReactNode {
|
||||
function ReasoningItem({ item, context }: { item: ReasoningDisplayItem; context: WorkItemContext }): ReactNode {
|
||||
const active = isReasoningActive(item, context);
|
||||
return (
|
||||
<div className={`codex-panel__reasoning${active ? " is-active" : ""}`}>
|
||||
|
|
@ -158,7 +176,7 @@ function RememberedDetails({
|
|||
detailsClassName: string;
|
||||
detailsKey: string;
|
||||
summary: string;
|
||||
context: MessageStreamContext;
|
||||
context: WorkItemContext;
|
||||
children: ReactNode;
|
||||
}): ReactNode {
|
||||
const [open, setOpen] = useState(context.openDetails.has(detailsKey));
|
||||
|
|
@ -228,8 +246,8 @@ function isLongAgentMessage(message: string): boolean {
|
|||
return message.length > AGENT_ROW_MESSAGE_PREVIEW_LIMIT || message.includes("\n");
|
||||
}
|
||||
|
||||
function isReasoningActive(item: ReasoningDisplayItem, context: MessageStreamContext): boolean {
|
||||
const activeTurn = messageStreamActiveTurnId(context);
|
||||
function isReasoningActive(item: ReasoningDisplayItem, context: WorkItemContext): boolean {
|
||||
const activeTurn = workItemsActiveTurnId(context);
|
||||
if (!activeTurn || item.turnId !== activeTurn) return false;
|
||||
if (executionState(item) === "completed") return false;
|
||||
const latestActiveTurnItem = [...context.displayItems].reverse().find((candidate) => candidate.turnId === activeTurn);
|
||||
|
|
|
|||
9
src/features/chat/user-input/drafts.ts
Normal file
9
src/features/chat/user-input/drafts.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { RequestId } from "../../../generated/app-server/RequestId";
|
||||
|
||||
export function userInputDraftKey(requestId: RequestId, questionId: string): string {
|
||||
return `${String(requestId)}:${questionId}`;
|
||||
}
|
||||
|
||||
export function userInputOtherDraftKey(requestId: RequestId, questionId: string): string {
|
||||
return `${String(requestId)}:${questionId}:other`;
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import type { ServerRequest } from "../../../generated/app-server/ServerRequest"
|
|||
import type { ToolRequestUserInputParams } from "../../../generated/app-server/v2/ToolRequestUserInputParams";
|
||||
import type { ToolRequestUserInputQuestion } from "../../../generated/app-server/v2/ToolRequestUserInputQuestion";
|
||||
import type { ToolRequestUserInputResponse } from "../../../generated/app-server/v2/ToolRequestUserInputResponse";
|
||||
import { userInputDraftKey } from "../request-state";
|
||||
import { userInputDraftKey } from "./drafts";
|
||||
|
||||
export type UserInputRequest = Extract<ServerRequest, { method: "item/tool/requestUserInput" }>;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { codexPanelDisplayTitle, explicitThreadName, getThreadTitle } from "../.
|
|||
import { connectionDiagnosticSections, diagnosticAlertLevel } from "./diagnostics";
|
||||
import type { ChatState } from "./chat-state";
|
||||
import { statusValue, usageLimitStatusLines } from "./status-lines";
|
||||
import type { ToolbarChoice, ToolbarThreadRow, ToolbarViewModel } from "./ui/toolbar";
|
||||
import type { ToolbarChoice, ToolbarThreadRow, ToolbarViewModel } from "./toolbar-model";
|
||||
|
||||
export interface RuntimeSnapshotInput {
|
||||
state: ChatState;
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@ import { pendingRequestsSignature as requestStateSignature } from "./request-sta
|
|||
import type { CodexPanelSettings } from "../../settings/model";
|
||||
import { ChatComposerController } from "./chat-composer-controller";
|
||||
import { activeTurnId, chatTurnBusy, createChatStateStore, type ChatState, type ChatAction } from "./chat-state";
|
||||
import { renderToolbar, type ToolbarViewModel } from "./ui/toolbar";
|
||||
import { renderToolbar } from "./ui/toolbar";
|
||||
import type { ToolbarViewModel } from "./toolbar-model";
|
||||
import type { ChatTurnDiffViewState } from "./ui/turn-diff";
|
||||
import { ChatMessageRenderer } from "./chat-message-renderer";
|
||||
import type { OpenCodexPanelSnapshot } from "../../runtime/open-panel-snapshot";
|
||||
import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state";
|
||||
import { ChatThreadActionController } from "./thread-actions";
|
||||
import { ChatRuntimeSettingsController } from "./runtime-settings-controller";
|
||||
import { RestoredThreadController } from "./restored-thread-controller";
|
||||
import { RestoredThreadController } from "./controllers/thread/restored-thread-controller";
|
||||
import {
|
||||
activeComposerThreadName as buildActiveComposerThreadName,
|
||||
activeThreadTitle as buildActiveThreadTitle,
|
||||
|
|
@ -39,30 +40,30 @@ import {
|
|||
statusSummaryLines as buildStatusSummaryLines,
|
||||
toolbarViewModel as buildToolbarViewModel,
|
||||
} from "./view-model";
|
||||
import { composerSlotSnapshot, openPanelTurnLifecycle } from "./view-snapshot";
|
||||
import { openPanelTurnLifecycle } from "./view-snapshot";
|
||||
import {
|
||||
ChatConnectionWorkTracker,
|
||||
ChatResumeWorkTracker,
|
||||
ChatViewDeferredTasks,
|
||||
type ChatViewRenderScheduleOptions,
|
||||
} from "./view-lifecycle";
|
||||
import { PendingRequestController } from "./pending-request-controller";
|
||||
import { ChatConnectionController } from "./controllers/connection/connection-controller";
|
||||
import { ChatReconnectController } from "./controllers/connection/reconnect-controller";
|
||||
import { AppServerWarmupController } from "./controllers/connection/app-server-warmup-controller";
|
||||
import { PendingRequestController } from "./controllers/requests/pending-request-controller";
|
||||
import { ServerRequestResponder } from "./controllers/requests/server-request-responder";
|
||||
import { ComposerSubmissionController } from "./controllers/submission/composer-submission-controller";
|
||||
import { PlanImplementationController } from "./controllers/submission/plan-implementation-controller";
|
||||
import { SlashCommandController } from "./controllers/submission/slash-command-controller";
|
||||
import { TurnSubmissionController } from "./controllers/submission/turn-submission-controller";
|
||||
import { ThreadIdentityController } from "./controllers/thread/thread-identity-controller";
|
||||
import { ThreadResumeController } from "./controllers/thread/thread-resume-controller";
|
||||
import { ThreadSelectionController } from "./controllers/thread/thread-selection-controller";
|
||||
import { ChatMessageScrollController } from "./controllers/view/message-scroll-controller";
|
||||
import { ChatViewOpenCloseController } from "./controllers/view/view-open-close-controller";
|
||||
import { ChatViewRenderController } from "./controllers/view/view-render-controller";
|
||||
import { ChatViewStateController } from "./controllers/view/view-state-controller";
|
||||
import { ToolbarPanelController } from "./toolbar-panel-controller";
|
||||
import { ChatReconnectController } from "./reconnect-controller";
|
||||
import { ChatMessageScrollController } from "./message-scroll-controller";
|
||||
import { TurnSubmissionController } from "./turn-submission-controller";
|
||||
import { SlashCommandController } from "./slash-command-controller";
|
||||
import { ComposerSubmissionController } from "./composer-submission-controller";
|
||||
import { ChatConnectionController } from "./connection-controller";
|
||||
import { ThreadIdentityController } from "./thread-identity-controller";
|
||||
import { ThreadResumeController } from "./thread-resume-controller";
|
||||
import { ChatViewRenderController } from "./view-render-controller";
|
||||
import { ChatViewOpenCloseController } from "./view-open-close-controller";
|
||||
import { PlanImplementationController } from "./plan-implementation-controller";
|
||||
import { ThreadSelectionController } from "./thread-selection-controller";
|
||||
import { ChatViewStateController } from "./view-state-controller";
|
||||
import { AppServerWarmupController } from "./app-server-warmup-controller";
|
||||
import { ServerRequestResponder } from "./server-request-responder";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settings: CodexPanelSettings;
|
||||
|
|
@ -959,8 +960,6 @@ export class CodexChatView extends ItemView {
|
|||
return buildActiveComposerThreadName(this.state, this.restoredThreadPlaceholder());
|
||||
}
|
||||
|
||||
private readonly composerSnapshot = (state: ChatState) => composerSlotSnapshot(state, this.activeComposerThreadName());
|
||||
|
||||
private render(options: ChatViewRenderScheduleOptions = {}): void {
|
||||
this.renderController.render(options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { AppServerWarmupController } from "../../../src/features/chat/app-server-warmup-controller";
|
||||
import { ChatViewDeferredTasks } from "../../../src/features/chat/view-lifecycle";
|
||||
import { AppServerWarmupController } from "../../../../../src/features/chat/controllers/connection/app-server-warmup-controller";
|
||||
import { ChatViewDeferredTasks } from "../../../../../src/features/chat/view-lifecycle";
|
||||
|
||||
function createController({
|
||||
opened = true,
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ChatConnectionController, type ChatConnectionAdapter } from "../../../src/features/chat/connection-controller";
|
||||
import { ChatConnectionWorkTracker } from "../../../src/features/chat/view-lifecycle";
|
||||
import type { ChatAppServerController } from "../../../src/features/chat/chat-app-server-controller";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import {
|
||||
ChatConnectionController,
|
||||
type ChatConnectionAdapter,
|
||||
} from "../../../../../src/features/chat/controllers/connection/connection-controller";
|
||||
import { ChatConnectionWorkTracker } from "../../../../../src/features/chat/view-lifecycle";
|
||||
import type { ChatAppServerController } from "../../../../../src/features/chat/chat-app-server-controller";
|
||||
|
||||
function createController({ connected = false, client = {} as AppServerClient } = {}) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ChatReconnectController, type ChatReconnectControllerHost } from "../../../src/features/chat/reconnect-controller";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import {
|
||||
ChatReconnectController,
|
||||
type ChatReconnectControllerHost,
|
||||
} from "../../../../../src/features/chat/controllers/connection/reconnect-controller";
|
||||
|
||||
function createHost(overrides: Partial<ChatReconnectControllerHost> = {}) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ChatController } from "../../../src/features/chat/chat-controller";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { PendingRequestController } from "../../../src/features/chat/pending-request-controller";
|
||||
import { toPendingUserInput } from "../../../src/features/chat/user-input/model";
|
||||
import type { ServerRequest } from "../../../src/generated/app-server/ServerRequest";
|
||||
import { ChatController } from "../../../../../src/features/chat/chat-controller";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import { PendingRequestController } from "../../../../../src/features/chat/controllers/requests/pending-request-controller";
|
||||
import { toPendingUserInput } from "../../../../../src/features/chat/user-input/model";
|
||||
import type { ServerRequest } from "../../../../../src/generated/app-server/ServerRequest";
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { ServerRequestResponder } from "../../../src/features/chat/server-request-responder";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { ServerRequestResponder } from "../../../../../src/features/chat/controllers/requests/server-request-responder";
|
||||
|
||||
describe("ServerRequestResponder", () => {
|
||||
it("responds through the current app-server client", () => {
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ComposerSubmissionController } from "../../../src/features/chat/composer-submission-controller";
|
||||
import type { ChatComposerController } from "../../../src/features/chat/chat-composer-controller";
|
||||
import type { SlashCommandController } from "../../../src/features/chat/slash-command-controller";
|
||||
import type { TurnSubmissionController } from "../../../src/features/chat/turn-submission-controller";
|
||||
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import { ComposerSubmissionController } from "../../../../../src/features/chat/controllers/submission/composer-submission-controller";
|
||||
import type { ChatComposerController } from "../../../../../src/features/chat/chat-composer-controller";
|
||||
import type { SlashCommandController } from "../../../../../src/features/chat/controllers/submission/slash-command-controller";
|
||||
import type { TurnSubmissionController } from "../../../../../src/features/chat/controllers/submission/turn-submission-controller";
|
||||
import type { Thread } from "../../../../../src/generated/app-server/v2/Thread";
|
||||
|
||||
function thread(id: string): Thread {
|
||||
return {
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/plan-implementation";
|
||||
import {
|
||||
implementPlanCandidateFromState,
|
||||
PlanImplementationController,
|
||||
type PlanImplementationControllerHost,
|
||||
} from "../../../src/features/chat/plan-implementation-controller";
|
||||
import type { DisplayItem } from "../../../src/features/chat/display/types";
|
||||
} from "../../../../../src/features/chat/controllers/submission/plan-implementation-controller";
|
||||
import type { DisplayItem } from "../../../../../src/features/chat/display/types";
|
||||
|
||||
const planItem = (id: string): DisplayItem => ({
|
||||
id,
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { SlashCommandController, type SlashCommandControllerHost } from "../../../src/features/chat/slash-command-controller";
|
||||
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
|
||||
import type { UserInput } from "../../../src/generated/app-server/v2/UserInput";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import {
|
||||
SlashCommandController,
|
||||
type SlashCommandControllerHost,
|
||||
} from "../../../../../src/features/chat/controllers/submission/slash-command-controller";
|
||||
import type { Thread } from "../../../../../src/generated/app-server/v2/Thread";
|
||||
import type { UserInput } from "../../../../../src/generated/app-server/v2/UserInput";
|
||||
|
||||
const textInput = (text: string): UserInput[] => [{ type: "text", text, text_elements: [] }];
|
||||
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { TurnSubmissionController, type TurnSubmissionControllerHost } from "../../../src/features/chat/turn-submission-controller";
|
||||
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
|
||||
import type { UserInput } from "../../../src/generated/app-server/v2/UserInput";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import {
|
||||
TurnSubmissionController,
|
||||
type TurnSubmissionControllerHost,
|
||||
} from "../../../../../src/features/chat/controllers/submission/turn-submission-controller";
|
||||
import type { Thread } from "../../../../../src/generated/app-server/v2/Thread";
|
||||
import type { UserInput } from "../../../../../src/generated/app-server/v2/UserInput";
|
||||
|
||||
const textInput = (text: string): UserInput[] => [{ type: "text", text, text_elements: [] }];
|
||||
|
||||
|
|
@ -7,8 +7,8 @@ import {
|
|||
localUserMessageItemFromInput,
|
||||
optimisticTurnStart,
|
||||
shouldAcknowledgeTurnStart,
|
||||
} from "../../../src/features/chat/turn-submission";
|
||||
import type { DisplayItem } from "../../../src/features/chat/display/types";
|
||||
} from "../../../../../src/features/chat/controllers/submission/turn-submission";
|
||||
import type { DisplayItem } from "../../../../../src/features/chat/display/types";
|
||||
|
||||
describe("chat turn submission helpers", () => {
|
||||
it("builds local user messages without sharing mentioned file arrays", () => {
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { RestoredThreadController } from "../../../src/features/chat/restored-thread-controller";
|
||||
import { ChatViewDeferredTasks } from "../../../src/features/chat/view-lifecycle";
|
||||
import type { ChatAction } from "../../../src/features/chat/chat-state";
|
||||
import { RestoredThreadController } from "../../../../../src/features/chat/controllers/thread/restored-thread-controller";
|
||||
import { ChatViewDeferredTasks } from "../../../../../src/features/chat/view-lifecycle";
|
||||
import type { ChatAction } from "../../../../../src/features/chat/chat-state";
|
||||
|
||||
describe("RestoredThreadController", () => {
|
||||
it("restores a placeholder and schedules deferred hydration", () => {
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ThreadIdentityController } from "../../../src/features/chat/thread-identity-controller";
|
||||
import type { RestoredThreadController } from "../../../src/features/chat/restored-thread-controller";
|
||||
import type { RestoredThreadPlaceholderState } from "../../../src/features/chat/view-lifecycle";
|
||||
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import { ThreadIdentityController } from "../../../../../src/features/chat/controllers/thread/thread-identity-controller";
|
||||
import type { RestoredThreadController } from "../../../../../src/features/chat/controllers/thread/restored-thread-controller";
|
||||
import type { RestoredThreadPlaceholderState } from "../../../../../src/features/chat/view-lifecycle";
|
||||
import type { Thread } from "../../../../../src/generated/app-server/v2/Thread";
|
||||
|
||||
function thread(id: string, name: string | null = null): Thread {
|
||||
return {
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import type { RestoredThreadController } from "../../../src/features/chat/restored-thread-controller";
|
||||
import type { ThreadActivationResponse } from "../../../src/features/chat/thread-resume";
|
||||
import { ThreadResumeController } from "../../../src/features/chat/thread-resume-controller";
|
||||
import type { ThreadHistoryLoader } from "../../../src/features/chat/thread-history";
|
||||
import { ChatResumeWorkTracker } from "../../../src/features/chat/view-lifecycle";
|
||||
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
|
||||
import type { AppServerClient } from "../../../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import type { RestoredThreadController } from "../../../../../src/features/chat/controllers/thread/restored-thread-controller";
|
||||
import type { ThreadActivationResponse } from "../../../../../src/features/chat/thread-resume";
|
||||
import { ThreadResumeController } from "../../../../../src/features/chat/controllers/thread/thread-resume-controller";
|
||||
import type { ThreadHistoryLoader } from "../../../../../src/features/chat/thread-history";
|
||||
import { ChatResumeWorkTracker } from "../../../../../src/features/chat/view-lifecycle";
|
||||
import type { Thread } from "../../../../../src/generated/app-server/v2/Thread";
|
||||
|
||||
function thread(id: string): Thread {
|
||||
return {
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ThreadSelectionController, type ThreadSelectionControllerHost } from "../../../src/features/chat/thread-selection-controller";
|
||||
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import {
|
||||
ThreadSelectionController,
|
||||
type ThreadSelectionControllerHost,
|
||||
} from "../../../../../src/features/chat/controllers/thread/thread-selection-controller";
|
||||
|
||||
function resumeThreadState(stateStore: ChatStateStore, threadId: string): void {
|
||||
stateStore.dispatch({
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ChatMessageScrollController } from "../../../src/features/chat/message-scroll-controller";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import { ChatMessageScrollController } from "../../../../../src/features/chat/controllers/view/message-scroll-controller";
|
||||
|
||||
describe("ChatMessageScrollController", () => {
|
||||
it("consumes one-shot scroll intents", () => {
|
||||
|
|
@ -3,15 +3,18 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { EventRef } from "obsidian";
|
||||
|
||||
import { ChatViewOpenCloseController, type ChatViewOpenCloseControllerHost } from "../../../src/features/chat/view-open-close-controller";
|
||||
import { unmountChatPanelShell } from "../../../src/features/chat/ui/shell";
|
||||
import { unmountReactRoot } from "../../../src/shared/ui/react-root";
|
||||
import {
|
||||
ChatViewOpenCloseController,
|
||||
type ChatViewOpenCloseControllerHost,
|
||||
} from "../../../../../src/features/chat/controllers/view/view-open-close-controller";
|
||||
import { unmountChatPanelShell } from "../../../../../src/features/chat/ui/shell";
|
||||
import { unmountReactRoot } from "../../../../../src/shared/ui/react-root";
|
||||
|
||||
vi.mock("../../../src/features/chat/ui/shell", () => ({
|
||||
vi.mock("../../../../../src/features/chat/ui/shell", () => ({
|
||||
unmountChatPanelShell: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/shared/ui/react-root", () => ({
|
||||
vi.mock("../../../../../src/shared/ui/react-root", () => ({
|
||||
unmountReactRoot: vi.fn(),
|
||||
}));
|
||||
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ChatViewRenderController } from "../../../src/features/chat/view-render-controller";
|
||||
import { renderChatPanelShell } from "../../../src/features/chat/ui/shell";
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
|
||||
import { ChatViewRenderController } from "../../../../../src/features/chat/controllers/view/view-render-controller";
|
||||
import { renderChatPanelShell } from "../../../../../src/features/chat/ui/shell";
|
||||
|
||||
vi.mock("../../../src/features/chat/ui/shell", () => ({
|
||||
vi.mock("../../../../../src/features/chat/ui/shell", () => ({
|
||||
renderChatPanelShell: vi.fn(),
|
||||
}));
|
||||
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ChatViewStateController, type ChatViewStateControllerHost } from "../../../src/features/chat/view-state-controller";
|
||||
import {
|
||||
ChatViewStateController,
|
||||
type ChatViewStateControllerHost,
|
||||
} from "../../../../../src/features/chat/controllers/view/view-state-controller";
|
||||
|
||||
function createController(overrides: Partial<ChatViewStateControllerHost> = {}) {
|
||||
const host: ChatViewStateControllerHost = {
|
||||
|
|
@ -9,7 +9,8 @@ import {
|
|||
scrollComposerSuggestionIntoView,
|
||||
syncComposerHeight,
|
||||
} from "../../../../src/features/chat/ui/composer";
|
||||
import { renderToolbar, type ToolbarViewModel } from "../../../../src/features/chat/ui/toolbar";
|
||||
import type { ToolbarViewModel } from "../../../../src/features/chat/toolbar-model";
|
||||
import { renderToolbar } from "../../../../src/features/chat/ui/toolbar";
|
||||
import { CodexChatTurnDiffView } from "../../../../src/features/chat/chat-turn-diff-view";
|
||||
import { persistedChatTurnDiffViewState, renderChatTurnDiffView } from "../../../../src/features/chat/ui/turn-diff";
|
||||
import { displayDiffLines } from "../../../../src/shared/diff/unified";
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { DEFAULT_SETTINGS } from "../../../src/settings/model";
|
|||
import type { CodexChatHost } from "../../../src/features/chat/view";
|
||||
import { createAppServerDiagnostics } from "../../../src/app-server/compatibility";
|
||||
import { createChatState, type ChatState } from "../../../src/features/chat/chat-state";
|
||||
import { composerSlotSnapshot } from "../../../src/features/chat/view-snapshot";
|
||||
import type { ServerNotification } from "../../../src/generated/app-server/ServerNotification";
|
||||
import { notices } from "../../mocks/obsidian";
|
||||
import { installObsidianDomShims } from "./ui/dom-test-helpers";
|
||||
|
|
@ -325,17 +326,16 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
});
|
||||
|
||||
it("tracks composer slot dependencies for model and skill suggestions", async () => {
|
||||
const view = await chatView();
|
||||
const composerSnapshot = (view as unknown as { composerSnapshot: (state: ChatState) => string }).composerSnapshot;
|
||||
await chatView();
|
||||
const state = createChatState();
|
||||
state.effectiveConfig = effectiveConfig("gpt-configured");
|
||||
state.availableSkills = [skillFixture("writer")];
|
||||
|
||||
const base = composerSnapshot(state);
|
||||
const base = composerSlotSnapshot(state, null);
|
||||
|
||||
expect(composerSnapshot({ ...state, effectiveConfig: effectiveConfig("gpt-updated") })).not.toBe(base);
|
||||
expect(composerSnapshot({ ...state, requestedModel: { kind: "set", value: "gpt-requested" } })).not.toBe(base);
|
||||
expect(composerSnapshot({ ...state, availableSkills: [skillFixture("reader")] })).not.toBe(base);
|
||||
expect(composerSlotSnapshot({ ...state, effectiveConfig: effectiveConfig("gpt-updated") }, null)).not.toBe(base);
|
||||
expect(composerSlotSnapshot({ ...state, requestedModel: { kind: "set", value: "gpt-requested" } }, null)).not.toBe(base);
|
||||
expect(composerSlotSnapshot({ ...state, availableSkills: [skillFixture("reader")] }, null)).not.toBe(base);
|
||||
});
|
||||
|
||||
it("hydrates a focused restored thread immediately", async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue