mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
import type { App, Component } from "obsidian";
|
|
|
|
import type { AppServerClient } from "../../../../app-server/client";
|
|
import type { ChatServerThreadActions } from "../../protocol/client-actions/thread-actions";
|
|
import { ChatComposerController } from "../composer/controller";
|
|
import { activeTurnId, type ChatState, type ChatStateStore } from "../../state/reducer";
|
|
import type { ChatReconnectActions } from "../../connection/reconnect-actions";
|
|
import { PendingRequestController } from "../../pending-requests/controller";
|
|
import type { ChatRuntimeSettingsActions } from "../../runtime/runtime-settings-actions";
|
|
import { createComposerSubmissionActions } from "./composer-submission-actions";
|
|
import { createPlanImplementationActions } from "./plan-implementation-actions";
|
|
import { createSlashCommandActions } from "./slash-command-actions";
|
|
import { TurnSubmissionController } from "./turn-submission-controller";
|
|
import type { ChatThreadActions } from "../../threads/thread-actions";
|
|
import type { ChatThreadGoalActions } from "../../threads/thread-goal-actions";
|
|
import type { ThreadHistoryController } from "../../threads/thread-history-controller";
|
|
import type { ThreadRenameController } from "../../threads/thread-rename-controller";
|
|
import type { ChatInboundController } from "../../protocol/inbound/controller";
|
|
import { currentModel, runtimeConfigOrDefault } from "../../runtime/effective-settings";
|
|
import type { RuntimeSnapshot } from "../../runtime/model";
|
|
import { ChatMessageRenderer } from "../../ui/message-stream/renderer";
|
|
import type { DisplayDetailSection } from "../../display/types";
|
|
import type { ChatMessageScrollIntentController } from "../../panel/message-scroll-intent-controller";
|
|
import type { ChatTurnDiffViewState } from "../../turn-diff/model";
|
|
import type { ComposerMetaViewModel } from "../../panel/view-model/composer";
|
|
import type { CodexPanelSettings } from "../../../../settings/model";
|
|
|
|
interface ConversationSurfaceControllerGroupPorts {
|
|
obsidian: {
|
|
app: App;
|
|
owner: Component;
|
|
viewId: string;
|
|
};
|
|
plugin: {
|
|
openTurnDiff: (state: ChatTurnDiffViewState) => Promise<void>;
|
|
settings: CodexPanelSettings;
|
|
vaultPath: string;
|
|
};
|
|
state: {
|
|
stateStore: ChatStateStore;
|
|
getState: () => ChatState;
|
|
};
|
|
client: {
|
|
getClient: () => AppServerClient | null;
|
|
ensureConnected: () => Promise<void>;
|
|
};
|
|
lifecycle: {
|
|
messageScrollIntent: ChatMessageScrollIntentController;
|
|
};
|
|
render: {
|
|
now: () => void;
|
|
schedule: () => void;
|
|
};
|
|
messages: {
|
|
pendingRequestsSignature: () => string;
|
|
};
|
|
composerView: {
|
|
composerPlaceholder: () => string;
|
|
composerMetaViewModel: () => ComposerMetaViewModel;
|
|
};
|
|
runtime: {
|
|
runtimeSnapshotForState: (state: ChatState) => RuntimeSnapshot;
|
|
statusSummaryLines: () => string[];
|
|
connectionDiagnosticDetails: () => DisplayDetailSection[];
|
|
mcpStatusLines: () => Promise<string[]>;
|
|
modelStatusLines: () => string[];
|
|
effortStatusLines: () => string[];
|
|
};
|
|
thread: {
|
|
ensureRestoredThreadLoaded: () => Promise<boolean>;
|
|
startNewThread: () => Promise<void>;
|
|
selectThread: (threadId: string) => Promise<void>;
|
|
notifyIdentityChanged: () => void;
|
|
resetTurnPresence: (hadTurns: boolean) => void;
|
|
};
|
|
liveState: {
|
|
refresh: () => void;
|
|
};
|
|
status: {
|
|
set: (status: string) => void;
|
|
addSystemMessage: (text: string) => void;
|
|
addStructuredSystemMessage: (text: string, details: DisplayDetailSection[]) => void;
|
|
};
|
|
scroll: {
|
|
forceBottom: () => void;
|
|
followBottom: () => void;
|
|
};
|
|
}
|
|
|
|
export function createConversationSurfaceControllerGroup(
|
|
context: ConversationSurfaceControllerGroupPorts,
|
|
refs: {
|
|
controller: ChatInboundController;
|
|
serverThreads: ChatServerThreadActions;
|
|
runtimeSettings: ChatRuntimeSettingsActions;
|
|
threadActions: ChatThreadActions;
|
|
threadRename: ThreadRenameController;
|
|
reconnectActions: ChatReconnectActions;
|
|
goals: ChatThreadGoalActions;
|
|
history: ThreadHistoryController;
|
|
},
|
|
) {
|
|
const { plugin, state, render, messages, composerView, runtime, thread, liveState, status, lifecycle, client, scroll } = context;
|
|
const { app, owner, viewId } = context.obsidian;
|
|
const stateStore = state.stateStore;
|
|
const currentClient = client.getClient;
|
|
const { messageScrollIntent } = lifecycle;
|
|
|
|
const composerController = new ChatComposerController({
|
|
app,
|
|
stateStore,
|
|
viewId,
|
|
sendShortcut: () => plugin.settings.sendShortcut,
|
|
scrollThreadFromComposerEdges: () => plugin.settings.scrollThreadFromComposerEdges,
|
|
canInterrupt: () => {
|
|
const current = state.getState();
|
|
return current.turn.lifecycle.kind !== "idle" && Boolean(current.activeThread.id && activeTurnId(current));
|
|
},
|
|
composerPlaceholder: composerView.composerPlaceholder,
|
|
composerMeta: composerView.composerMetaViewModel,
|
|
currentModelForSuggestions: () => {
|
|
const current = state.getState();
|
|
return currentModel(runtime.runtimeSnapshotForState(current), runtimeConfigOrDefault(current.connection.runtimeConfig));
|
|
},
|
|
togglePlan: () => void refs.runtimeSettings.toggleCollaborationMode(),
|
|
toggleAutoReview: () => void refs.runtimeSettings.toggleAutoReview(),
|
|
toggleFast: () => void refs.runtimeSettings.toggleFastMode(),
|
|
onDraftChange: liveState.refresh,
|
|
onHeightChange: () => {
|
|
messageRenderer.repinMessagesToBottomIfPinned();
|
|
},
|
|
});
|
|
const pendingRequests = new PendingRequestController({
|
|
stateStore,
|
|
controller: refs.controller,
|
|
composerHasFocus: () => composerController.hasFocus(),
|
|
refreshLiveState: liveState.refresh,
|
|
render: render.now,
|
|
});
|
|
|
|
const turnSubmission = new TurnSubmissionController({
|
|
stateStore,
|
|
connection: {
|
|
vaultPath: plugin.vaultPath,
|
|
currentClient,
|
|
},
|
|
restoredThread: {
|
|
ensureRestoredThreadLoaded: thread.ensureRestoredThreadLoaded,
|
|
},
|
|
thread: {
|
|
startThread: (preview) => refs.serverThreads.startThread(preview),
|
|
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
|
resetThreadTurnPresence: thread.resetTurnPresence,
|
|
},
|
|
runtime: {
|
|
applyPendingThreadSettings: () => refs.runtimeSettings.applyPendingThreadSettings(),
|
|
},
|
|
composer: {
|
|
codexInput: (text) => composerController.codexInput(text),
|
|
setDraft: (text, options) => {
|
|
composerController.setDraft(text, options);
|
|
},
|
|
},
|
|
view: {
|
|
render: render.now,
|
|
scheduleRender: render.schedule,
|
|
},
|
|
status: {
|
|
setStatus: status.set,
|
|
addSystemMessage: status.addSystemMessage,
|
|
},
|
|
});
|
|
const slashCommands = createSlashCommandActions({
|
|
stateStore,
|
|
currentClient,
|
|
codexInput: (text) => composerController.codexInput(text),
|
|
threads: {
|
|
startNewThread: thread.startNewThread,
|
|
startThreadForGoal: async (objective) => {
|
|
const response = await refs.serverThreads.startThread(objective, { syncGoal: false });
|
|
return response?.threadId ?? null;
|
|
},
|
|
resumeThread: thread.selectThread,
|
|
forkThread: (threadId) => refs.threadActions.forkThread(threadId),
|
|
rollbackThread: (threadId) => refs.threadActions.rollbackThread(threadId),
|
|
compactThread: (threadId) => refs.threadActions.compactThread(threadId),
|
|
archiveThread: (threadId) => refs.threadActions.archiveThread(threadId),
|
|
renameThread: (threadId, name) => refs.threadRename.rename(threadId, name),
|
|
reconnect: () => refs.reconnectActions.reconnectPanel(),
|
|
},
|
|
runtime: {
|
|
toggleFastMode: () => refs.runtimeSettings.toggleFastMode(),
|
|
toggleCollaborationMode: () => refs.runtimeSettings.toggleCollaborationMode(),
|
|
toggleAutoReview: () => void refs.runtimeSettings.toggleAutoReview(),
|
|
requestModel: (model) => refs.runtimeSettings.requestModel(model),
|
|
resetModelToConfig: () => refs.runtimeSettings.resetModelToConfig(),
|
|
requestReasoningEffort: (effort) => refs.runtimeSettings.requestReasoningEffort(effort),
|
|
resetReasoningEffortToConfig: () => refs.runtimeSettings.resetReasoningEffortToConfig(),
|
|
},
|
|
goals: {
|
|
activeGoal: () => refs.goals.activeGoal(),
|
|
setObjective: (threadId, objective, tokenBudget) => refs.goals.setObjective(threadId, objective, tokenBudget),
|
|
setStatus: (threadId, goalStatus) => refs.goals.setStatus(threadId, goalStatus),
|
|
clear: (threadId) => refs.goals.clear(threadId),
|
|
},
|
|
status: {
|
|
addSystemMessage: status.addSystemMessage,
|
|
addStructuredSystemMessage: status.addStructuredSystemMessage,
|
|
setStatus: status.set,
|
|
statusSummaryLines: runtime.statusSummaryLines,
|
|
connectionDiagnosticDetails: runtime.connectionDiagnosticDetails,
|
|
mcpStatusLines: runtime.mcpStatusLines,
|
|
modelStatusLines: runtime.modelStatusLines,
|
|
effortStatusLines: runtime.effortStatusLines,
|
|
},
|
|
});
|
|
const planImplementation = createPlanImplementationActions({
|
|
stateStore,
|
|
connection: {
|
|
currentClient,
|
|
ensureConnected: client.ensureConnected,
|
|
},
|
|
submission: {
|
|
sendTurnText: (text) => turnSubmission.sendTurnText(text),
|
|
},
|
|
runtime: {
|
|
requestDefaultCollaborationModeForNextTurn: () => {
|
|
refs.runtimeSettings.requestDefaultCollaborationModeForNextTurn();
|
|
},
|
|
},
|
|
});
|
|
|
|
const messageRenderer = new ChatMessageRenderer({
|
|
obsidian: {
|
|
app,
|
|
owner,
|
|
},
|
|
state: {
|
|
store: stateStore,
|
|
},
|
|
workspace: {
|
|
vaultPath: plugin.vaultPath,
|
|
},
|
|
scroll: {
|
|
consumeIntent: () => messageScrollIntent.consumeIntent(),
|
|
},
|
|
history: {
|
|
loadOlderTurns: () => void refs.history.loadOlder(),
|
|
},
|
|
actions: {
|
|
rollbackThread: (threadId) => void refs.threadActions.rollbackThread(threadId),
|
|
forkThreadFromTurn: (threadId, turnId, archiveSource) => void refs.threadActions.forkThreadFromTurn(threadId, turnId, archiveSource),
|
|
implementPlan: (item) => void planImplementation.implement(item),
|
|
openTurnDiff: (state) => void plugin.openTurnDiff(state),
|
|
},
|
|
requests: {
|
|
pendingSignature: messages.pendingRequestsSignature,
|
|
pendingSnapshot: () => pendingRequests.snapshot(),
|
|
pendingActions: () => pendingRequests.actions(),
|
|
consumePendingAutoFocus: () => pendingRequests.consumeAutoFocus(),
|
|
},
|
|
});
|
|
const composerSubmission = createComposerSubmissionActions({
|
|
stateStore,
|
|
composer: composerController,
|
|
slashCommands,
|
|
turnSubmission,
|
|
connection: {
|
|
currentClient,
|
|
ensureConnected: client.ensureConnected,
|
|
},
|
|
status: {
|
|
setStatus: status.set,
|
|
addSystemMessage: status.addSystemMessage,
|
|
},
|
|
scroll: {
|
|
followBottom: scroll.followBottom,
|
|
},
|
|
});
|
|
composerController.setActionHandlers({
|
|
submit: () => void composerSubmission.submit(),
|
|
threadScrollFromComposer: (action) => {
|
|
messageRenderer.scrollFromComposer(action);
|
|
},
|
|
});
|
|
|
|
return {
|
|
pendingRequests,
|
|
messageRenderer,
|
|
composerController,
|
|
composerSubmission,
|
|
};
|
|
}
|