mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Split chat session controller composition
This commit is contained in:
parent
8f01d579d0
commit
1ca1a33175
6 changed files with 681 additions and 682 deletions
14
src/features/chat/panel/cached-app-server-state.ts
Normal file
14
src/features/chat/panel/cached-app-server-state.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { ChatAppServerMetadataController } from "../app-server/metadata-controller";
|
||||
import type { ChatAppServerThreadController } from "../app-server/thread-controller";
|
||||
import type { ChatPanelContext } from "./context";
|
||||
|
||||
export function applyCachedSharedAppServerState(
|
||||
context: ChatPanelContext,
|
||||
appServerThreads: ChatAppServerThreadController,
|
||||
appServerMetadata: ChatAppServerMetadataController,
|
||||
): void {
|
||||
const threads = context.plugin.cachedThreadList();
|
||||
if (threads) appServerThreads.applyThreadList(threads);
|
||||
const metadata = context.plugin.cachedAppServerMetadata();
|
||||
if (metadata) appServerMetadata.applyAppServerMetadata(metadata);
|
||||
}
|
||||
|
|
@ -1,39 +1,34 @@
|
|||
import { Notice } from "obsidian";
|
||||
|
||||
import { ConnectionManager } from "../../../app-server/connection-manager";
|
||||
import { recoverRolloutTokenUsage } from "../../../app-server/rollout-token-usage";
|
||||
import { currentModel } from "../../../runtime/state";
|
||||
import { ChatAppServerDiagnosticsController } from "../app-server/diagnostics-controller";
|
||||
import { ChatAppServerMetadataController } from "../app-server/metadata-controller";
|
||||
import { ChatAppServerThreadController } from "../app-server/thread-controller";
|
||||
import { ChatComposerController } from "../composer/controller";
|
||||
import { ChatInboundController } from "../inbound/controller";
|
||||
import { activeTurnId } from "../chat-state";
|
||||
import { ChatThreadGoalController } from "../controllers/thread/thread-goal-controller";
|
||||
import { ChatRuntimeSettingsController } from "../controllers/runtime/runtime-settings-controller";
|
||||
import { ChatThreadActionController } from "../controllers/thread/thread-actions-controller";
|
||||
import { ThreadHistoryController } from "../controllers/thread/thread-history-controller";
|
||||
import { ThreadRenameController } from "../controllers/thread/thread-rename-controller";
|
||||
import { ToolbarPanelController } from "./toolbar-controller";
|
||||
import { createAppServerWarmupActions, type AppServerWarmupActions } from "../controllers/connection/app-server-warmup-controller";
|
||||
import { ChatConnectionController } from "../controllers/connection/connection-controller";
|
||||
import { ChatReconnectController } from "../controllers/connection/reconnect-controller";
|
||||
import { PendingRequestController } from "../controllers/requests/pending-request-controller";
|
||||
import type { ChatAppServerDiagnosticsController } from "../app-server/diagnostics-controller";
|
||||
import type { ChatAppServerMetadataController } from "../app-server/metadata-controller";
|
||||
import type { ChatAppServerThreadController } from "../app-server/thread-controller";
|
||||
import type { ChatComposerController } from "../composer/controller";
|
||||
import type { ChatInboundController } from "../inbound/controller";
|
||||
import type { ChatThreadGoalController } from "../controllers/thread/thread-goal-controller";
|
||||
import type { ChatRuntimeSettingsController } from "../controllers/runtime/runtime-settings-controller";
|
||||
import type { ChatThreadActionController } from "../controllers/thread/thread-actions-controller";
|
||||
import type { ThreadHistoryController } from "../controllers/thread/thread-history-controller";
|
||||
import type { ThreadRenameController } from "../controllers/thread/thread-rename-controller";
|
||||
import type { ToolbarPanelController } from "./toolbar-controller";
|
||||
import type { AppServerWarmupActions } from "../controllers/connection/app-server-warmup-controller";
|
||||
import type { ChatConnectionController } from "../controllers/connection/connection-controller";
|
||||
import type { ChatReconnectController } from "../controllers/connection/reconnect-controller";
|
||||
import type { PendingRequestController } from "../controllers/requests/pending-request-controller";
|
||||
import { ServerRequestResponder } from "../controllers/requests/server-request-responder";
|
||||
import { createChatShellRenderPort } from "./shell-render";
|
||||
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 { RestoredThreadController } from "../controllers/thread/restored-thread-controller";
|
||||
import { ThreadIdentityController } from "../controllers/thread/thread-identity-controller";
|
||||
import { ThreadResumeController } from "../controllers/thread/thread-resume-controller";
|
||||
import { createThreadSelectionActions, type ThreadSelectionActions } from "../controllers/thread/thread-selection-controller";
|
||||
import { ChatViewOpenCloseController } from "../controllers/view/view-open-close-controller";
|
||||
import { ChatViewRenderController } from "../controllers/view/view-render-controller";
|
||||
import { createChatViewStateActions, type ChatViewStateActions } from "../controllers/view/view-state-controller";
|
||||
import { ChatMessageRenderer } from "../ui/message-stream";
|
||||
import type { ComposerSubmissionController } from "../controllers/submission/composer-submission-controller";
|
||||
import type { RestoredThreadController } from "../controllers/thread/restored-thread-controller";
|
||||
import type { ThreadIdentityController } from "../controllers/thread/thread-identity-controller";
|
||||
import type { ThreadResumeController } from "../controllers/thread/thread-resume-controller";
|
||||
import type { ThreadSelectionActions } from "../controllers/thread/thread-selection-controller";
|
||||
import type { ChatViewOpenCloseController } from "../controllers/view/view-open-close-controller";
|
||||
import type { ChatViewRenderController } from "../controllers/view/view-render-controller";
|
||||
import type { ChatViewStateActions } from "../controllers/view/view-state-controller";
|
||||
import type { ChatMessageRenderer } from "../ui/message-stream";
|
||||
import type { ChatPanelContext } from "./context";
|
||||
import { createChatAppServerControllers, createChatConnectionControllers, createChatInboundController } from "./session-controllers";
|
||||
import { createThreadControllerGroup } from "./thread-controllers";
|
||||
import { createTurnControllerGroup } from "./turn-controllers";
|
||||
import { createConnectionLifecycleControllerGroup, createViewRenderControllerGroup } from "./ui-controllers";
|
||||
|
||||
export interface ChatViewControllers {
|
||||
connection: {
|
||||
|
|
@ -81,12 +76,9 @@ export interface ChatViewControllers {
|
|||
};
|
||||
}
|
||||
|
||||
type ControllerContext = ReturnType<typeof createControllerContext>;
|
||||
|
||||
export function createChatViewControllers(ports: ChatPanelContext): ChatViewControllers {
|
||||
const context = createControllerContext(ports);
|
||||
const connection = new ConnectionManager(() => context.plugin.settings.codexPath, context.plugin.vaultPath);
|
||||
const { renderController } = createViewRenderControllerGroup(context, { connection });
|
||||
const connection = new ConnectionManager(() => ports.plugin.settings.codexPath, ports.plugin.vaultPath);
|
||||
const { renderController } = createViewRenderControllerGroup(ports, { connection });
|
||||
const {
|
||||
history,
|
||||
threadActions,
|
||||
|
|
@ -100,23 +92,23 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont
|
|||
threadResume,
|
||||
threadIdentity,
|
||||
threadRename,
|
||||
} = createThreadToolbarControllerGroup(context, {
|
||||
} = createThreadControllerGroup(ports, {
|
||||
connection,
|
||||
});
|
||||
const { appServerThreads, appServerMetadata, appServerDiagnostics } = createAppServerControllerGroup(context, {
|
||||
const { appServerThreads, appServerMetadata, appServerDiagnostics } = createChatAppServerControllers(ports, {
|
||||
connection,
|
||||
goals,
|
||||
});
|
||||
const serverRequestResponder = new ServerRequestResponder({
|
||||
currentClient: context.currentClient,
|
||||
currentClient: ports.client.getClient,
|
||||
});
|
||||
const controller = createInboundController(context, {
|
||||
const controller = createChatInboundController(ports, {
|
||||
appServerMetadata,
|
||||
appServerDiagnostics,
|
||||
threadRename,
|
||||
serverRequestResponder,
|
||||
});
|
||||
const { connectionController } = createConnectionControllerGroup(context, {
|
||||
const { connectionController } = createChatConnectionControllers(ports, {
|
||||
connection,
|
||||
appServerMetadata,
|
||||
appServerDiagnostics,
|
||||
|
|
@ -125,24 +117,24 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont
|
|||
connection.setHandlers({
|
||||
onNotification: (notification) => {
|
||||
controller.handleNotification(notification);
|
||||
context.liveState.refresh();
|
||||
context.render.schedule();
|
||||
ports.liveState.refresh();
|
||||
ports.render.schedule();
|
||||
},
|
||||
onServerRequest: (request) => {
|
||||
controller.handleServerRequest(request);
|
||||
context.liveState.refresh();
|
||||
context.render.now();
|
||||
ports.liveState.refresh();
|
||||
ports.render.now();
|
||||
},
|
||||
onLog: (message) => {
|
||||
controller.handleAppServerLog(message);
|
||||
context.render.now();
|
||||
ports.render.now();
|
||||
},
|
||||
onExit: () => {
|
||||
connectionController.handleExit();
|
||||
},
|
||||
});
|
||||
|
||||
const { pendingRequests, messageRenderer, composerController, composerSubmission } = createSubmissionControllerGroup(context, {
|
||||
const { pendingRequests, messageRenderer, composerController, composerSubmission } = createTurnControllerGroup(ports, {
|
||||
controller,
|
||||
appServerThreads,
|
||||
runtimeSettings,
|
||||
|
|
@ -152,7 +144,7 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont
|
|||
goals,
|
||||
history,
|
||||
});
|
||||
const { appServerWarmup, openCloseController } = createConnectionLifecycleControllerGroup(context, {
|
||||
const { appServerWarmup, openCloseController } = createConnectionLifecycleControllerGroup(ports, {
|
||||
connection,
|
||||
composerController,
|
||||
messageRenderer,
|
||||
|
|
@ -206,635 +198,3 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createControllerContext(ports: ChatPanelContext) {
|
||||
const { obsidian, plugin, state, client, lifecycle, render, runtime, thread, liveState, scroll, status, composer } = ports;
|
||||
const { app, owner, viewId } = obsidian;
|
||||
const { stateStore } = state;
|
||||
|
||||
return {
|
||||
ports,
|
||||
obsidian,
|
||||
plugin,
|
||||
state,
|
||||
client,
|
||||
lifecycle,
|
||||
render,
|
||||
runtime,
|
||||
thread,
|
||||
liveState,
|
||||
scroll,
|
||||
status,
|
||||
composer,
|
||||
app,
|
||||
owner,
|
||||
viewId,
|
||||
stateStore,
|
||||
currentClient: client.getClient,
|
||||
};
|
||||
}
|
||||
|
||||
function createViewRenderControllerGroup(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
},
|
||||
) {
|
||||
const { plugin, render, lifecycle, stateStore } = context;
|
||||
const { deferredTasks } = lifecycle;
|
||||
|
||||
return {
|
||||
renderController: new ChatViewRenderController({
|
||||
shell: createChatShellRenderPort(stateStore, {
|
||||
connected: () => refs.connection.isConnected(),
|
||||
showToolbar: () => plugin.settings.showToolbar,
|
||||
pendingRequestsSignature: render.pendingRequestsSignature,
|
||||
activeComposerThreadName: render.activeComposerThreadName,
|
||||
}),
|
||||
panelRoot: render.panelRoot,
|
||||
renderToolbar: render.renderToolbar,
|
||||
renderGoal: render.renderGoal,
|
||||
renderMessages: render.renderMessages,
|
||||
renderComposer: render.renderComposer,
|
||||
clearScheduledRender: () => {
|
||||
deferredTasks.clearRender();
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createSubmissionControllerGroup(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
controller: ChatInboundController;
|
||||
appServerThreads: ChatAppServerThreadController;
|
||||
runtimeSettings: ChatRuntimeSettingsController;
|
||||
threadActions: ChatThreadActionController;
|
||||
threadRename: ThreadRenameController;
|
||||
reconnectActions: ChatReconnectController;
|
||||
goals: ChatThreadGoalController;
|
||||
history: ThreadHistoryController;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
app,
|
||||
owner,
|
||||
plugin,
|
||||
state,
|
||||
stateStore,
|
||||
viewId,
|
||||
render,
|
||||
runtime,
|
||||
thread,
|
||||
liveState,
|
||||
scroll,
|
||||
status,
|
||||
lifecycle,
|
||||
currentClient,
|
||||
client,
|
||||
} = context;
|
||||
const { messageScrollIntent } = lifecycle;
|
||||
|
||||
const composerController = new ChatComposerController({
|
||||
app,
|
||||
stateStore,
|
||||
viewId,
|
||||
sendShortcut: () => plugin.settings.sendShortcut,
|
||||
scrollThreadFromComposerEdges: () => plugin.settings.scrollThreadFromComposerEdges,
|
||||
canInterrupt: () =>
|
||||
state.getState().turn.lifecycle.kind !== "idle" && Boolean(state.getState().activeThread.id && activeTurnId(state.getState())),
|
||||
composerPlaceholder: render.composerPlaceholder,
|
||||
composerMeta: render.composerMetaViewModel,
|
||||
currentModelForSuggestions: () => currentModel(runtime.runtimeSnapshot()),
|
||||
togglePlan: () => void refs.runtimeSettings.toggleCollaborationMode(),
|
||||
toggleAutoReview: () => void refs.runtimeSettings.toggleAutoReview(),
|
||||
toggleFast: () => void refs.runtimeSettings.toggleFastMode(),
|
||||
renderIfDetached: render.now,
|
||||
onDraftChange: liveState.refresh,
|
||||
onComposerResize: () => {
|
||||
scroll.correctAfterLayoutChange();
|
||||
},
|
||||
});
|
||||
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.appServerThreads.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: {
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
render: render.now,
|
||||
scheduleRender: render.schedule,
|
||||
},
|
||||
status: {
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
},
|
||||
});
|
||||
const slashCommands = new SlashCommandController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
codexInput: (text) => composerController.codexInput(text),
|
||||
threads: {
|
||||
startNewThread: thread.startNewThread,
|
||||
startThreadForGoal: async (objective) => {
|
||||
const response = await refs.appServerThreads.startThread(objective, { syncGoal: false });
|
||||
return response?.thread.id ?? 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(),
|
||||
setRequestedModel: (model) => refs.runtimeSettings.setRequestedModel(model),
|
||||
setRequestedReasoningEffort: (effort) => refs.runtimeSettings.setRequestedReasoningEffort(effort),
|
||||
},
|
||||
goals: {
|
||||
activeGoal: () => refs.goals.activeGoal(),
|
||||
setObjective: (threadId, objective, tokenBudget) => refs.goals.setObjective(threadId, objective, tokenBudget),
|
||||
setStatus: (threadId, status) => refs.goals.setStatus(threadId, status),
|
||||
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 = new PlanImplementationController({
|
||||
stateStore,
|
||||
connection: {
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
},
|
||||
submission: {
|
||||
sendTurnText: (text) => turnSubmission.sendTurnText(text),
|
||||
},
|
||||
});
|
||||
|
||||
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: render.pendingRequestsSignature,
|
||||
renderPending: () => pendingRequests.renderNode(),
|
||||
},
|
||||
});
|
||||
const composerSubmission = new ComposerSubmissionController({
|
||||
stateStore,
|
||||
composer: composerController,
|
||||
slashCommands,
|
||||
turnSubmission,
|
||||
connection: {
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
},
|
||||
status: {
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
},
|
||||
});
|
||||
composerController.setActionHandlers({
|
||||
submit: () => void composerSubmission.submit(),
|
||||
threadScrollFromComposer: (action) => {
|
||||
messageRenderer.scrollFromComposer(action);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
pendingRequests,
|
||||
messageRenderer,
|
||||
composerController,
|
||||
composerSubmission,
|
||||
};
|
||||
}
|
||||
|
||||
function createConnectionLifecycleControllerGroup(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
composerController: ChatComposerController;
|
||||
messageRenderer: ChatMessageRenderer;
|
||||
appServerThreads: ChatAppServerThreadController;
|
||||
appServerMetadata: ChatAppServerMetadataController;
|
||||
},
|
||||
) {
|
||||
const { ports, obsidian, lifecycle, render, liveState, scroll, client } = context;
|
||||
const { deferredTasks } = lifecycle;
|
||||
|
||||
return {
|
||||
appServerWarmup: createAppServerWarmupActions({
|
||||
deferredTasks,
|
||||
opened: lifecycle.getOpened,
|
||||
closing: lifecycle.getClosing,
|
||||
connected: () => refs.connection.isConnected(),
|
||||
ensureConnected: client.ensureConnected,
|
||||
}),
|
||||
openCloseController: new ChatViewOpenCloseController({
|
||||
setOpened: lifecycle.setOpened,
|
||||
setClosing: lifecycle.setClosing,
|
||||
registerEvent: obsidian.registerEvent,
|
||||
registerComposerNoteIndexInvalidation: (register) => {
|
||||
refs.composerController.registerNoteIndexInvalidation(register);
|
||||
},
|
||||
registerPointerDown: obsidian.registerPointerDown,
|
||||
registerActiveLeafChange: obsidian.registerActiveLeafChange,
|
||||
isOwnLeaf: obsidian.isOwnLeaf,
|
||||
scrollMessagesToBottomOnFocus: scroll.bottomOnFocus,
|
||||
applyCachedSharedAppServerState: () => {
|
||||
applyCachedSharedAppServerState(ports, refs.appServerThreads, refs.appServerMetadata);
|
||||
},
|
||||
render: render.now,
|
||||
scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup,
|
||||
scheduleDeferredRestoredThreadHydration: lifecycle.scheduleDeferredRestoredThreadHydration,
|
||||
closeToolbarPanelOnOutsidePointer: render.closeToolbarPanelOnOutsidePointer,
|
||||
invalidateConnectionWork: lifecycle.invalidateConnectionWork,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearDeferredTasks: () => {
|
||||
deferredTasks.clearAll();
|
||||
},
|
||||
panelRoot: render.panelRoot,
|
||||
disposeMessages: () => {
|
||||
refs.messageRenderer.dispose();
|
||||
},
|
||||
disposeComposer: () => {
|
||||
refs.composerController.dispose();
|
||||
},
|
||||
disconnect: () => {
|
||||
refs.connection.disconnect();
|
||||
},
|
||||
clearClient: client.clear,
|
||||
refreshLiveState: liveState.refresh,
|
||||
deferRefreshLiveState: liveState.deferRefresh,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createAppServerControllerGroup(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
goals: ChatThreadGoalController;
|
||||
},
|
||||
) {
|
||||
const { plugin, runtime, scroll, stateStore } = context;
|
||||
const appServerBaseHost = {
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
currentClient: () => refs.connection.currentClient(),
|
||||
};
|
||||
const appServerMetadata = new ChatAppServerMetadataController({
|
||||
...appServerBaseHost,
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
plugin.publishAppServerMetadata(metadata);
|
||||
},
|
||||
});
|
||||
const appServerDiagnostics = new ChatAppServerDiagnosticsController({
|
||||
...appServerBaseHost,
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
plugin.publishAppServerMetadata(metadata);
|
||||
},
|
||||
appServerMetadataSnapshot: () => appServerMetadata.appServerMetadataSnapshot(),
|
||||
});
|
||||
const appServerThreads = new ChatAppServerThreadController({
|
||||
...appServerBaseHost,
|
||||
runtimeSnapshot: runtime.runtimeSnapshot,
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
publishThreadList: (threads) => {
|
||||
plugin.applyThreadListSnapshot(threads);
|
||||
},
|
||||
syncThreadGoal: (threadId) => {
|
||||
void refs.goals.syncThreadGoal(threadId);
|
||||
},
|
||||
});
|
||||
|
||||
return { appServerThreads, appServerMetadata, appServerDiagnostics };
|
||||
}
|
||||
|
||||
function createInboundController(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
appServerMetadata: ChatAppServerMetadataController;
|
||||
appServerDiagnostics: ChatAppServerDiagnosticsController;
|
||||
threadRename: ThreadRenameController;
|
||||
serverRequestResponder: ServerRequestResponder;
|
||||
},
|
||||
) {
|
||||
const { plugin, thread, render, stateStore } = context;
|
||||
|
||||
return new ChatInboundController(stateStore, {
|
||||
refreshThreads: () => {
|
||||
void thread.refreshThreads();
|
||||
},
|
||||
refreshRateLimits: () => {
|
||||
void refs.appServerMetadata.refreshPublishedRateLimits();
|
||||
},
|
||||
refreshSkills: (forceReload) => void thread.refreshSkills(forceReload),
|
||||
publishAppServerMetadata: thread.publishAppServerMetadataSnapshot,
|
||||
maybeNameThread: (threadId, turn) => {
|
||||
refs.threadRename.maybeAutoNameThread(threadId, turn);
|
||||
},
|
||||
notifyThreadArchived: plugin.notifyThreadArchived.bind(plugin),
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
recordMcpStartupStatus: (name, status, message) => {
|
||||
refs.appServerDiagnostics.recordMcpStartupStatus(name, status, message);
|
||||
render.schedule();
|
||||
},
|
||||
respondToServerRequest: (requestId, result) => refs.serverRequestResponder.respond(requestId, result),
|
||||
rejectServerRequest: (requestId, code, message) => refs.serverRequestResponder.reject(requestId, code, message),
|
||||
});
|
||||
}
|
||||
|
||||
function createConnectionControllerGroup(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
appServerMetadata: ChatAppServerMetadataController;
|
||||
appServerDiagnostics: ChatAppServerDiagnosticsController;
|
||||
},
|
||||
) {
|
||||
const { plugin, client, thread, status, liveState, render, lifecycle, stateStore } = context;
|
||||
const { connectionWork } = lifecycle;
|
||||
|
||||
return {
|
||||
connectionController: new ChatConnectionController({
|
||||
stateStore,
|
||||
connection: refs.connection,
|
||||
connectionWork,
|
||||
metadata: {
|
||||
refreshPublishedAppServerMetadata: () => refs.appServerMetadata.refreshPublishedAppServerMetadata(),
|
||||
refreshPublishedSkills: (forceReload) => refs.appServerMetadata.refreshPublishedSkills(forceReload),
|
||||
},
|
||||
diagnostics: {
|
||||
refreshPublishedCapabilityDiagnostics: () => refs.appServerDiagnostics.refreshPublishedCapabilityDiagnostics(),
|
||||
},
|
||||
setClient: client.setClient,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
loadSharedThreadList: thread.loadSharedThreadList,
|
||||
scheduleDeferredDiagnostics: lifecycle.scheduleDeferredDiagnostics,
|
||||
clearDeferredDiagnostics: lifecycle.clearDeferredDiagnostics,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
resetThreadTurnPresence: thread.resetTurnPresence,
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
configuredCommand: () => plugin.settings.codexPath,
|
||||
refreshLiveState: liveState.refresh,
|
||||
render: render.now,
|
||||
scheduleRender: render.schedule,
|
||||
notifyConnectionFailed: () => {
|
||||
new Notice("Codex app-server connection failed.");
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createThreadToolbarControllerGroup(
|
||||
context: ControllerContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
obsidian,
|
||||
plugin,
|
||||
state,
|
||||
runtime,
|
||||
thread,
|
||||
status,
|
||||
liveState,
|
||||
scroll,
|
||||
render,
|
||||
client,
|
||||
composer,
|
||||
lifecycle,
|
||||
stateStore,
|
||||
currentClient,
|
||||
} = context;
|
||||
const { deferredTasks, resumeWork } = lifecycle;
|
||||
|
||||
const history = new ThreadHistoryController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
render: render.now,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
keepCurrentScrollPosition: scroll.preservePosition,
|
||||
setThreadTurnPresence: thread.resetTurnPresence,
|
||||
});
|
||||
const threadActions = new ChatThreadActionController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
archiveAdapter: obsidian.archiveAdapter,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient,
|
||||
history,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
setStatus: status.set,
|
||||
setComposerText: composer.setText,
|
||||
openThreadInNewView: (threadId) => plugin.openThreadInNewView(threadId),
|
||||
openThreadInCurrentPanel: thread.selectThread,
|
||||
notifyThreadArchived: plugin.notifyThreadArchived.bind(plugin),
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
plugin.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
refreshThreads: thread.refreshThreads,
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
plugin.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
});
|
||||
const toolbarPanels = new ToolbarPanelController({
|
||||
stateStore,
|
||||
threadActions,
|
||||
scheduleRender: render.schedule,
|
||||
});
|
||||
const threadSelection = createThreadSelectionActions({
|
||||
stateStore,
|
||||
closeForThreadSelection: () => {
|
||||
toolbarPanels.closeForThreadSelection();
|
||||
},
|
||||
focusThreadInOpenView: (threadId) => plugin.focusThreadInOpenView(threadId),
|
||||
resumeThread: thread.resumeThread,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const reconnectActions = new ChatReconnectController({
|
||||
stateStore,
|
||||
invalidateConnectionWork: lifecycle.invalidateConnectionWork,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearDeferredDiagnostics: lifecycle.clearDeferredDiagnostics,
|
||||
reconnect: () => {
|
||||
refs.connection.reconnect();
|
||||
},
|
||||
clearClient: client.clear,
|
||||
setStatus: status.set,
|
||||
render: render.now,
|
||||
ensureConnected: client.ensureConnected,
|
||||
resumeThread: thread.resumeThread,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const runtimeSettings = new ChatRuntimeSettingsController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
runtimeSnapshot: runtime.runtimeSnapshot,
|
||||
collaborationModeLabel: runtime.collaborationModeLabel,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const goals = new ChatThreadGoalController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
addGoalEvent: (item) => {
|
||||
stateStore.dispatch({ type: "transcript/item-upserted", item });
|
||||
},
|
||||
render: render.now,
|
||||
refreshLiveState: liveState.refresh,
|
||||
});
|
||||
const restoredThread = new RestoredThreadController({
|
||||
deferredTasks,
|
||||
opened: lifecycle.getOpened,
|
||||
resumeThread: thread.resumeThread,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
stateStore,
|
||||
systemItem: state.systemItem,
|
||||
setStatus: status.set,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
});
|
||||
const viewStateController = createChatViewStateActions({
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearRestoredThreadLifecycle: thread.clearRestoredLifecycle,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup,
|
||||
restoreThreadPlaceholder: thread.restorePlaceholder,
|
||||
});
|
||||
const threadResume = new ThreadResumeController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
resumeWork,
|
||||
history,
|
||||
restoredThread,
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
closing: lifecycle.getClosing,
|
||||
systemItem: state.systemItem,
|
||||
resetThreadTurnPresence: thread.resetTurnPresence,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
render: render.now,
|
||||
refreshLiveState: liveState.refresh,
|
||||
syncThreadGoal: (threadId) => goals.syncThreadGoal(threadId),
|
||||
recoverTokenUsageFromRollout: (path) =>
|
||||
recoverRolloutTokenUsage(path, async (filePath, options) => {
|
||||
const response = await currentClient()?.readFile(filePath, options);
|
||||
return response?.dataBase64 ?? "";
|
||||
}),
|
||||
});
|
||||
const threadIdentity = new ThreadIdentityController({
|
||||
stateStore,
|
||||
restoredThread,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
resetThreadTurnPresence: thread.resetTurnPresence,
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
refreshLiveState: liveState.refresh,
|
||||
render: render.now,
|
||||
});
|
||||
const threadRename = new ThreadRenameController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient: () => refs.connection.currentClient(),
|
||||
refreshThreads: thread.refreshThreads,
|
||||
render: render.shellSlots,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
});
|
||||
|
||||
return {
|
||||
history,
|
||||
threadActions,
|
||||
toolbarPanels,
|
||||
threadSelection,
|
||||
reconnectActions,
|
||||
runtimeSettings,
|
||||
goals,
|
||||
restoredThread,
|
||||
viewStateController,
|
||||
threadResume,
|
||||
threadIdentity,
|
||||
threadRename,
|
||||
};
|
||||
}
|
||||
|
||||
function applyCachedSharedAppServerState(
|
||||
ports: ChatPanelContext,
|
||||
appServerThreads: ChatAppServerThreadController,
|
||||
appServerMetadata: ChatAppServerMetadataController,
|
||||
): void {
|
||||
const threads = ports.plugin.cachedThreadList();
|
||||
if (threads) appServerThreads.applyThreadList(threads);
|
||||
const metadata = ports.plugin.cachedAppServerMetadata();
|
||||
if (metadata) appServerMetadata.applyAppServerMetadata(metadata);
|
||||
}
|
||||
|
|
|
|||
131
src/features/chat/panel/session-controllers.ts
Normal file
131
src/features/chat/panel/session-controllers.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { Notice } from "obsidian";
|
||||
|
||||
import type { ConnectionManager } from "../../../app-server/connection-manager";
|
||||
import { ChatAppServerDiagnosticsController } from "../app-server/diagnostics-controller";
|
||||
import { ChatAppServerMetadataController } from "../app-server/metadata-controller";
|
||||
import { ChatAppServerThreadController } from "../app-server/thread-controller";
|
||||
import { ChatConnectionController } from "../controllers/connection/connection-controller";
|
||||
import type { ServerRequestResponder } from "../controllers/requests/server-request-responder";
|
||||
import type { ChatThreadGoalController } from "../controllers/thread/thread-goal-controller";
|
||||
import type { ThreadRenameController } from "../controllers/thread/thread-rename-controller";
|
||||
import { ChatInboundController } from "../inbound/controller";
|
||||
import type { ChatPanelContext } from "./context";
|
||||
|
||||
export function createChatAppServerControllers(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
goals: ChatThreadGoalController;
|
||||
},
|
||||
) {
|
||||
const { plugin, runtime, scroll } = context;
|
||||
const stateStore = context.state.stateStore;
|
||||
const appServerBaseHost = {
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
currentClient: () => refs.connection.currentClient(),
|
||||
};
|
||||
const appServerMetadata = new ChatAppServerMetadataController({
|
||||
...appServerBaseHost,
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
plugin.publishAppServerMetadata(metadata);
|
||||
},
|
||||
});
|
||||
const appServerDiagnostics = new ChatAppServerDiagnosticsController({
|
||||
...appServerBaseHost,
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
plugin.publishAppServerMetadata(metadata);
|
||||
},
|
||||
appServerMetadataSnapshot: () => appServerMetadata.appServerMetadataSnapshot(),
|
||||
});
|
||||
const appServerThreads = new ChatAppServerThreadController({
|
||||
...appServerBaseHost,
|
||||
runtimeSnapshot: runtime.runtimeSnapshot,
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
publishThreadList: (threads) => {
|
||||
plugin.applyThreadListSnapshot(threads);
|
||||
},
|
||||
syncThreadGoal: (threadId) => {
|
||||
void refs.goals.syncThreadGoal(threadId);
|
||||
},
|
||||
});
|
||||
|
||||
return { appServerThreads, appServerMetadata, appServerDiagnostics };
|
||||
}
|
||||
|
||||
export function createChatInboundController(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
appServerMetadata: ChatAppServerMetadataController;
|
||||
appServerDiagnostics: ChatAppServerDiagnosticsController;
|
||||
threadRename: ThreadRenameController;
|
||||
serverRequestResponder: ServerRequestResponder;
|
||||
},
|
||||
): ChatInboundController {
|
||||
const { plugin, thread, render } = context;
|
||||
|
||||
return new ChatInboundController(context.state.stateStore, {
|
||||
refreshThreads: () => {
|
||||
void thread.refreshThreads();
|
||||
},
|
||||
refreshRateLimits: () => {
|
||||
void refs.appServerMetadata.refreshPublishedRateLimits();
|
||||
},
|
||||
refreshSkills: (forceReload) => void thread.refreshSkills(forceReload),
|
||||
publishAppServerMetadata: thread.publishAppServerMetadataSnapshot,
|
||||
maybeNameThread: (threadId, turn) => {
|
||||
refs.threadRename.maybeAutoNameThread(threadId, turn);
|
||||
},
|
||||
notifyThreadArchived: plugin.notifyThreadArchived.bind(plugin),
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
recordMcpStartupStatus: (name, status, message) => {
|
||||
refs.appServerDiagnostics.recordMcpStartupStatus(name, status, message);
|
||||
render.schedule();
|
||||
},
|
||||
respondToServerRequest: (requestId, result) => refs.serverRequestResponder.respond(requestId, result),
|
||||
rejectServerRequest: (requestId, code, message) => refs.serverRequestResponder.reject(requestId, code, message),
|
||||
});
|
||||
}
|
||||
|
||||
export function createChatConnectionControllers(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
appServerMetadata: ChatAppServerMetadataController;
|
||||
appServerDiagnostics: ChatAppServerDiagnosticsController;
|
||||
},
|
||||
) {
|
||||
const { plugin, client, thread, status, liveState, render, lifecycle } = context;
|
||||
const { connectionWork } = lifecycle;
|
||||
|
||||
return {
|
||||
connectionController: new ChatConnectionController({
|
||||
stateStore: context.state.stateStore,
|
||||
connection: refs.connection,
|
||||
connectionWork,
|
||||
metadata: {
|
||||
refreshPublishedAppServerMetadata: () => refs.appServerMetadata.refreshPublishedAppServerMetadata(),
|
||||
refreshPublishedSkills: (forceReload) => refs.appServerMetadata.refreshPublishedSkills(forceReload),
|
||||
},
|
||||
diagnostics: {
|
||||
refreshPublishedCapabilityDiagnostics: () => refs.appServerDiagnostics.refreshPublishedCapabilityDiagnostics(),
|
||||
},
|
||||
setClient: client.setClient,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
loadSharedThreadList: thread.loadSharedThreadList,
|
||||
scheduleDeferredDiagnostics: lifecycle.scheduleDeferredDiagnostics,
|
||||
clearDeferredDiagnostics: lifecycle.clearDeferredDiagnostics,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
resetThreadTurnPresence: thread.resetTurnPresence,
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
configuredCommand: () => plugin.settings.codexPath,
|
||||
refreshLiveState: liveState.refresh,
|
||||
render: render.now,
|
||||
scheduleRender: render.schedule,
|
||||
notifyConnectionFailed: () => {
|
||||
new Notice("Codex app-server connection failed.");
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
185
src/features/chat/panel/thread-controllers.ts
Normal file
185
src/features/chat/panel/thread-controllers.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import type { ConnectionManager } from "../../../app-server/connection-manager";
|
||||
import { recoverRolloutTokenUsage } from "../../../app-server/rollout-token-usage";
|
||||
import { ChatRuntimeSettingsController } from "../controllers/runtime/runtime-settings-controller";
|
||||
import { ChatThreadActionController } from "../controllers/thread/thread-actions-controller";
|
||||
import { ChatThreadGoalController } from "../controllers/thread/thread-goal-controller";
|
||||
import { ThreadHistoryController } from "../controllers/thread/thread-history-controller";
|
||||
import { ThreadIdentityController } from "../controllers/thread/thread-identity-controller";
|
||||
import { ThreadRenameController } from "../controllers/thread/thread-rename-controller";
|
||||
import { ThreadResumeController } from "../controllers/thread/thread-resume-controller";
|
||||
import { createThreadSelectionActions } from "../controllers/thread/thread-selection-controller";
|
||||
import { RestoredThreadController } from "../controllers/thread/restored-thread-controller";
|
||||
import { ChatReconnectController } from "../controllers/connection/reconnect-controller";
|
||||
import { createChatViewStateActions } from "../controllers/view/view-state-controller";
|
||||
import { ToolbarPanelController } from "./toolbar-controller";
|
||||
import type { ChatPanelContext } from "./context";
|
||||
|
||||
export function createThreadControllerGroup(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
},
|
||||
) {
|
||||
const { obsidian, plugin, state, runtime, thread, status, liveState, scroll, render, client, composer, lifecycle } = context;
|
||||
const stateStore = state.stateStore;
|
||||
const currentClient = client.getClient;
|
||||
const { deferredTasks, resumeWork } = lifecycle;
|
||||
|
||||
const history = new ThreadHistoryController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
render: render.now,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
keepCurrentScrollPosition: scroll.preservePosition,
|
||||
setThreadTurnPresence: thread.resetTurnPresence,
|
||||
});
|
||||
const threadActions = new ChatThreadActionController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
archiveAdapter: obsidian.archiveAdapter,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient,
|
||||
history,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
setStatus: status.set,
|
||||
setComposerText: composer.setText,
|
||||
openThreadInNewView: (threadId) => plugin.openThreadInNewView(threadId),
|
||||
openThreadInCurrentPanel: thread.selectThread,
|
||||
notifyThreadArchived: plugin.notifyThreadArchived.bind(plugin),
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
plugin.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
refreshThreads: thread.refreshThreads,
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
plugin.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
});
|
||||
const toolbarPanels = new ToolbarPanelController({
|
||||
stateStore,
|
||||
threadActions,
|
||||
scheduleRender: render.schedule,
|
||||
});
|
||||
const threadSelection = createThreadSelectionActions({
|
||||
stateStore,
|
||||
closeForThreadSelection: () => {
|
||||
toolbarPanels.closeForThreadSelection();
|
||||
},
|
||||
focusThreadInOpenView: (threadId) => plugin.focusThreadInOpenView(threadId),
|
||||
resumeThread: thread.resumeThread,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const reconnectActions = new ChatReconnectController({
|
||||
stateStore,
|
||||
invalidateConnectionWork: lifecycle.invalidateConnectionWork,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearDeferredDiagnostics: lifecycle.clearDeferredDiagnostics,
|
||||
reconnect: () => {
|
||||
refs.connection.reconnect();
|
||||
},
|
||||
clearClient: client.clear,
|
||||
setStatus: status.set,
|
||||
render: render.now,
|
||||
ensureConnected: client.ensureConnected,
|
||||
resumeThread: thread.resumeThread,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const runtimeSettings = new ChatRuntimeSettingsController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
runtimeSnapshot: runtime.runtimeSnapshot,
|
||||
collaborationModeLabel: runtime.collaborationModeLabel,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const goals = new ChatThreadGoalController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
addGoalEvent: (item) => {
|
||||
stateStore.dispatch({ type: "transcript/item-upserted", item });
|
||||
},
|
||||
render: render.now,
|
||||
refreshLiveState: liveState.refresh,
|
||||
});
|
||||
const restoredThread = new RestoredThreadController({
|
||||
deferredTasks,
|
||||
opened: lifecycle.getOpened,
|
||||
resumeThread: thread.resumeThread,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
stateStore,
|
||||
systemItem: state.systemItem,
|
||||
setStatus: status.set,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
});
|
||||
const viewStateController = createChatViewStateActions({
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearRestoredThreadLifecycle: thread.clearRestoredLifecycle,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup,
|
||||
restoreThreadPlaceholder: thread.restorePlaceholder,
|
||||
});
|
||||
const threadResume = new ThreadResumeController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
resumeWork,
|
||||
history,
|
||||
restoredThread,
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
closing: lifecycle.getClosing,
|
||||
systemItem: state.systemItem,
|
||||
resetThreadTurnPresence: thread.resetTurnPresence,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
render: render.now,
|
||||
refreshLiveState: liveState.refresh,
|
||||
syncThreadGoal: (threadId) => goals.syncThreadGoal(threadId),
|
||||
recoverTokenUsageFromRollout: (path) =>
|
||||
recoverRolloutTokenUsage(path, async (filePath, options) => {
|
||||
const response = await currentClient()?.readFile(filePath, options);
|
||||
return response?.dataBase64 ?? "";
|
||||
}),
|
||||
});
|
||||
const threadIdentity = new ThreadIdentityController({
|
||||
stateStore,
|
||||
restoredThread,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration,
|
||||
resetThreadTurnPresence: thread.resetTurnPresence,
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
refreshTabHeader: thread.refreshTabHeader,
|
||||
refreshLiveState: liveState.refresh,
|
||||
render: render.now,
|
||||
});
|
||||
const threadRename = new ThreadRenameController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient: () => refs.connection.currentClient(),
|
||||
refreshThreads: thread.refreshThreads,
|
||||
render: render.shellSlots,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
});
|
||||
|
||||
return {
|
||||
history,
|
||||
threadActions,
|
||||
toolbarPanels,
|
||||
threadSelection,
|
||||
reconnectActions,
|
||||
runtimeSettings,
|
||||
goals,
|
||||
restoredThread,
|
||||
viewStateController,
|
||||
threadResume,
|
||||
threadIdentity,
|
||||
threadRename,
|
||||
};
|
||||
}
|
||||
208
src/features/chat/panel/turn-controllers.ts
Normal file
208
src/features/chat/panel/turn-controllers.ts
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
import type { ChatAppServerThreadController } from "../app-server/thread-controller";
|
||||
import { ChatComposerController } from "../composer/controller";
|
||||
import { activeTurnId } from "../chat-state";
|
||||
import type { ChatReconnectController } from "../controllers/connection/reconnect-controller";
|
||||
import { PendingRequestController } from "../controllers/requests/pending-request-controller";
|
||||
import type { ChatRuntimeSettingsController } from "../controllers/runtime/runtime-settings-controller";
|
||||
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 type { ChatThreadActionController } from "../controllers/thread/thread-actions-controller";
|
||||
import type { ChatThreadGoalController } from "../controllers/thread/thread-goal-controller";
|
||||
import type { ThreadHistoryController } from "../controllers/thread/thread-history-controller";
|
||||
import type { ThreadRenameController } from "../controllers/thread/thread-rename-controller";
|
||||
import type { ChatInboundController } from "../inbound/controller";
|
||||
import { currentModel } from "../../../runtime/state";
|
||||
import { ChatMessageRenderer } from "../ui/message-stream";
|
||||
import type { ChatPanelContext } from "./context";
|
||||
|
||||
export function createTurnControllerGroup(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
controller: ChatInboundController;
|
||||
appServerThreads: ChatAppServerThreadController;
|
||||
runtimeSettings: ChatRuntimeSettingsController;
|
||||
threadActions: ChatThreadActionController;
|
||||
threadRename: ThreadRenameController;
|
||||
reconnectActions: ChatReconnectController;
|
||||
goals: ChatThreadGoalController;
|
||||
history: ThreadHistoryController;
|
||||
},
|
||||
) {
|
||||
const { plugin, state, render, runtime, thread, liveState, scroll, status, lifecycle, client } = 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: () =>
|
||||
state.getState().turn.lifecycle.kind !== "idle" && Boolean(state.getState().activeThread.id && activeTurnId(state.getState())),
|
||||
composerPlaceholder: render.composerPlaceholder,
|
||||
composerMeta: render.composerMetaViewModel,
|
||||
currentModelForSuggestions: () => currentModel(runtime.runtimeSnapshot()),
|
||||
togglePlan: () => void refs.runtimeSettings.toggleCollaborationMode(),
|
||||
toggleAutoReview: () => void refs.runtimeSettings.toggleAutoReview(),
|
||||
toggleFast: () => void refs.runtimeSettings.toggleFastMode(),
|
||||
renderIfDetached: render.now,
|
||||
onDraftChange: liveState.refresh,
|
||||
onComposerResize: () => {
|
||||
scroll.correctAfterLayoutChange();
|
||||
},
|
||||
});
|
||||
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.appServerThreads.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: {
|
||||
forceMessagesToBottom: scroll.forceBottom,
|
||||
render: render.now,
|
||||
scheduleRender: render.schedule,
|
||||
},
|
||||
status: {
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
},
|
||||
});
|
||||
const slashCommands = new SlashCommandController({
|
||||
stateStore,
|
||||
currentClient,
|
||||
codexInput: (text) => composerController.codexInput(text),
|
||||
threads: {
|
||||
startNewThread: thread.startNewThread,
|
||||
startThreadForGoal: async (objective) => {
|
||||
const response = await refs.appServerThreads.startThread(objective, { syncGoal: false });
|
||||
return response?.thread.id ?? 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(),
|
||||
setRequestedModel: (model) => refs.runtimeSettings.setRequestedModel(model),
|
||||
setRequestedReasoningEffort: (effort) => refs.runtimeSettings.setRequestedReasoningEffort(effort),
|
||||
},
|
||||
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 = new PlanImplementationController({
|
||||
stateStore,
|
||||
connection: {
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
},
|
||||
submission: {
|
||||
sendTurnText: (text) => turnSubmission.sendTurnText(text),
|
||||
},
|
||||
});
|
||||
|
||||
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: render.pendingRequestsSignature,
|
||||
renderPending: () => pendingRequests.renderNode(),
|
||||
},
|
||||
});
|
||||
const composerSubmission = new ComposerSubmissionController({
|
||||
stateStore,
|
||||
composer: composerController,
|
||||
slashCommands,
|
||||
turnSubmission,
|
||||
connection: {
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
},
|
||||
status: {
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
},
|
||||
});
|
||||
composerController.setActionHandlers({
|
||||
submit: () => void composerSubmission.submit(),
|
||||
threadScrollFromComposer: (action) => {
|
||||
messageRenderer.scrollFromComposer(action);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
pendingRequests,
|
||||
messageRenderer,
|
||||
composerController,
|
||||
composerSubmission,
|
||||
};
|
||||
}
|
||||
101
src/features/chat/panel/ui-controllers.ts
Normal file
101
src/features/chat/panel/ui-controllers.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import type { ConnectionManager } from "../../../app-server/connection-manager";
|
||||
import type { ChatAppServerMetadataController } from "../app-server/metadata-controller";
|
||||
import type { ChatAppServerThreadController } from "../app-server/thread-controller";
|
||||
import type { ChatComposerController } from "../composer/controller";
|
||||
import { createAppServerWarmupActions } from "../controllers/connection/app-server-warmup-controller";
|
||||
import { ChatViewOpenCloseController } from "../controllers/view/view-open-close-controller";
|
||||
import { ChatViewRenderController } from "../controllers/view/view-render-controller";
|
||||
import type { ChatMessageRenderer } from "../ui/message-stream";
|
||||
import { applyCachedSharedAppServerState } from "./cached-app-server-state";
|
||||
import type { ChatPanelContext } from "./context";
|
||||
import { createChatShellRenderPort } from "./shell-render";
|
||||
|
||||
export function createViewRenderControllerGroup(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
},
|
||||
) {
|
||||
const { plugin, render, lifecycle } = context;
|
||||
const { deferredTasks } = lifecycle;
|
||||
|
||||
return {
|
||||
renderController: new ChatViewRenderController({
|
||||
shell: createChatShellRenderPort(context.state.stateStore, {
|
||||
connected: () => refs.connection.isConnected(),
|
||||
showToolbar: () => plugin.settings.showToolbar,
|
||||
pendingRequestsSignature: render.pendingRequestsSignature,
|
||||
activeComposerThreadName: render.activeComposerThreadName,
|
||||
}),
|
||||
panelRoot: render.panelRoot,
|
||||
renderToolbar: render.renderToolbar,
|
||||
renderGoal: render.renderGoal,
|
||||
renderMessages: render.renderMessages,
|
||||
renderComposer: render.renderComposer,
|
||||
clearScheduledRender: () => {
|
||||
deferredTasks.clearRender();
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function createConnectionLifecycleControllerGroup(
|
||||
context: ChatPanelContext,
|
||||
refs: {
|
||||
connection: ConnectionManager;
|
||||
composerController: ChatComposerController;
|
||||
messageRenderer: ChatMessageRenderer;
|
||||
appServerThreads: ChatAppServerThreadController;
|
||||
appServerMetadata: ChatAppServerMetadataController;
|
||||
},
|
||||
) {
|
||||
const { obsidian, lifecycle, render, liveState, scroll, client } = context;
|
||||
const { deferredTasks } = lifecycle;
|
||||
|
||||
return {
|
||||
appServerWarmup: createAppServerWarmupActions({
|
||||
deferredTasks,
|
||||
opened: lifecycle.getOpened,
|
||||
closing: lifecycle.getClosing,
|
||||
connected: () => refs.connection.isConnected(),
|
||||
ensureConnected: client.ensureConnected,
|
||||
}),
|
||||
openCloseController: new ChatViewOpenCloseController({
|
||||
setOpened: lifecycle.setOpened,
|
||||
setClosing: lifecycle.setClosing,
|
||||
registerEvent: obsidian.registerEvent,
|
||||
registerComposerNoteIndexInvalidation: (register) => {
|
||||
refs.composerController.registerNoteIndexInvalidation(register);
|
||||
},
|
||||
registerPointerDown: obsidian.registerPointerDown,
|
||||
registerActiveLeafChange: obsidian.registerActiveLeafChange,
|
||||
isOwnLeaf: obsidian.isOwnLeaf,
|
||||
scrollMessagesToBottomOnFocus: scroll.bottomOnFocus,
|
||||
applyCachedSharedAppServerState: () => {
|
||||
applyCachedSharedAppServerState(context, refs.appServerThreads, refs.appServerMetadata);
|
||||
},
|
||||
render: render.now,
|
||||
scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup,
|
||||
scheduleDeferredRestoredThreadHydration: lifecycle.scheduleDeferredRestoredThreadHydration,
|
||||
closeToolbarPanelOnOutsidePointer: render.closeToolbarPanelOnOutsidePointer,
|
||||
invalidateConnectionWork: lifecycle.invalidateConnectionWork,
|
||||
invalidateResumeWork: lifecycle.invalidateResumeWork,
|
||||
clearDeferredTasks: () => {
|
||||
deferredTasks.clearAll();
|
||||
},
|
||||
panelRoot: render.panelRoot,
|
||||
disposeMessages: () => {
|
||||
refs.messageRenderer.dispose();
|
||||
},
|
||||
disposeComposer: () => {
|
||||
refs.composerController.dispose();
|
||||
},
|
||||
disconnect: () => {
|
||||
refs.connection.disconnect();
|
||||
},
|
||||
clearClient: client.clear,
|
||||
refreshLiveState: liveState.refresh,
|
||||
deferRefreshLiveState: liveState.deferRefresh,
|
||||
}),
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue