mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Extract chat state utilities
This commit is contained in:
parent
d38bdaf32c
commit
f474ff49c7
7 changed files with 76 additions and 91 deletions
|
|
@ -7,6 +7,7 @@ import {
|
|||
type PendingRequestId,
|
||||
type PendingUserInput,
|
||||
} from "../../../../domain/pending-requests/model";
|
||||
import { patchObject } from "../state/patch";
|
||||
|
||||
export interface ChatRequestState {
|
||||
readonly approvals: readonly PendingApproval[];
|
||||
|
|
@ -107,8 +108,3 @@ function setMcpElicitationDraftSlice(state: ChatRequestState, key: string, value
|
|||
if (state.mcpElicitationDrafts.get(key) === value) return state;
|
||||
return patchObject(state, { mcpElicitationDrafts: new Map([...state.mcpElicitationDrafts, [key, value]]) });
|
||||
}
|
||||
|
||||
function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
|
|
|
|||
61
src/features/chat/application/state/clone.ts
Normal file
61
src/features/chat/application/state/clone.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import type { ChatState } from "./root-reducer";
|
||||
import { cloneDisclosureUiState } from "./ui-state";
|
||||
import type { ChatMessageStreamActiveSegment, ChatMessageStreamState } from "./message-stream";
|
||||
|
||||
export function cloneChatState(state: ChatState): ChatState {
|
||||
return {
|
||||
connection: {
|
||||
...state.connection,
|
||||
availableModels: [...state.connection.availableModels],
|
||||
availableSkills: [...state.connection.availableSkills],
|
||||
},
|
||||
threadList: {
|
||||
listedThreads: [...state.threadList.listedThreads],
|
||||
threadsLoaded: state.threadList.threadsLoaded,
|
||||
},
|
||||
activeThread: { ...state.activeThread },
|
||||
runtime: { ...state.runtime },
|
||||
turn: { lifecycle: state.turn.lifecycle },
|
||||
messageStream: cloneMessageStreamState(state.messageStream),
|
||||
requests: {
|
||||
approvals: [...state.requests.approvals],
|
||||
pendingUserInputs: [...state.requests.pendingUserInputs],
|
||||
pendingMcpElicitations: [...state.requests.pendingMcpElicitations],
|
||||
userInputDrafts: new Map(state.requests.userInputDrafts),
|
||||
mcpElicitationDrafts: new Map(state.requests.mcpElicitationDrafts),
|
||||
},
|
||||
composer: {
|
||||
...state.composer,
|
||||
suggestions: [...state.composer.suggestions],
|
||||
},
|
||||
ui: {
|
||||
toolbarPanel: state.ui.toolbarPanel,
|
||||
archiveConfirmThreadId: state.ui.archiveConfirmThreadId,
|
||||
rename: { ...state.ui.rename },
|
||||
goalEditor: { ...state.ui.goalEditor },
|
||||
messageActionMenu: { ...state.ui.messageActionMenu },
|
||||
disclosures: cloneDisclosureUiState(state.ui.disclosures),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function cloneMessageStreamState(state: ChatMessageStreamState): ChatMessageStreamState {
|
||||
return {
|
||||
stableItems: [...state.stableItems],
|
||||
activeSegment: cloneActiveSegment(state.activeSegment),
|
||||
turnDiffs: new Map(state.turnDiffs),
|
||||
historyCursor: state.historyCursor,
|
||||
loadingHistory: state.loadingHistory,
|
||||
reportedLogs: new Set(state.reportedLogs),
|
||||
};
|
||||
}
|
||||
|
||||
function cloneActiveSegment(segment: ChatMessageStreamActiveSegment | null): ChatMessageStreamActiveSegment | null {
|
||||
if (!segment) return null;
|
||||
return {
|
||||
turnId: segment.turnId,
|
||||
items: [...segment.items],
|
||||
indexById: new Map(segment.indexById),
|
||||
indexBySourceItemId: new Map(segment.indexBySourceItemId),
|
||||
};
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import {
|
|||
streamedTextMessageStreamItem,
|
||||
streamedToolOutputMessageStreamItem,
|
||||
} from "../../domain/message-stream/factories/streaming-items";
|
||||
import { definedPatch, patchObject } from "./patch";
|
||||
|
||||
export interface ChatMessageStreamActiveSegment {
|
||||
readonly turnId: string | null;
|
||||
|
|
@ -545,12 +546,3 @@ function promptMessageForTurn(items: readonly MessageStreamItem[], turnId: strin
|
|||
const item = classification?.item;
|
||||
return item?.kind === "message" ? item : null;
|
||||
}
|
||||
|
||||
function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
|
||||
function definedPatch<Key extends string, Value>(key: Key, value: Value | undefined): Partial<Record<Key, Value>> {
|
||||
return value === undefined ? {} : ({ [key]: value } as Partial<Record<Key, Value>>);
|
||||
}
|
||||
|
|
|
|||
8
src/features/chat/application/state/patch.ts
Normal file
8
src/features/chat/application/state/patch.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
|
||||
export function definedPatch<Key extends string, Value>(key: Key, value: Value | undefined): Partial<Record<Key, Value>> {
|
||||
return value === undefined ? {} : ({ [key]: value } as Partial<Record<Key, Value>>);
|
||||
}
|
||||
|
|
@ -49,7 +49,6 @@ import {
|
|||
messageStreamWithActiveTurnItems,
|
||||
messageStreamWithItems,
|
||||
reduceMessageStreamSlice,
|
||||
type ChatMessageStreamActiveSegment,
|
||||
type ChatMessageStreamState,
|
||||
type MessageStreamAction,
|
||||
} from "./message-stream";
|
||||
|
|
@ -70,7 +69,6 @@ import {
|
|||
import {
|
||||
clearAllRequestDisclosures,
|
||||
clearResolvedRequestDisclosures,
|
||||
cloneDisclosureUiState,
|
||||
initialUiState,
|
||||
isUiAction,
|
||||
maybeClearGoalObjectiveExpansion,
|
||||
|
|
@ -79,6 +77,7 @@ import {
|
|||
type UiAction,
|
||||
} from "./ui-state";
|
||||
import { STATUS_TURN_RUNNING } from "./status-text";
|
||||
import { definedPatch, patchObject } from "./patch";
|
||||
|
||||
export { activeTurnId, chatTurnBusy, pendingTurnStart, type ChatTurnState, type PendingTurnStart } from "../conversation/turn-state";
|
||||
export type { ChatMessageStreamState } from "./message-stream";
|
||||
|
|
@ -658,64 +657,6 @@ function initialComposerState(): ChatComposerState {
|
|||
};
|
||||
}
|
||||
|
||||
export function cloneChatState(state: ChatState): ChatState {
|
||||
return {
|
||||
connection: {
|
||||
...state.connection,
|
||||
availableModels: [...state.connection.availableModels],
|
||||
availableSkills: [...state.connection.availableSkills],
|
||||
},
|
||||
threadList: {
|
||||
listedThreads: [...state.threadList.listedThreads],
|
||||
threadsLoaded: state.threadList.threadsLoaded,
|
||||
},
|
||||
activeThread: { ...state.activeThread },
|
||||
runtime: { ...state.runtime },
|
||||
turn: { lifecycle: state.turn.lifecycle },
|
||||
messageStream: cloneMessageStreamState(state.messageStream),
|
||||
requests: {
|
||||
approvals: [...state.requests.approvals],
|
||||
pendingUserInputs: [...state.requests.pendingUserInputs],
|
||||
pendingMcpElicitations: [...state.requests.pendingMcpElicitations],
|
||||
userInputDrafts: new Map(state.requests.userInputDrafts),
|
||||
mcpElicitationDrafts: new Map(state.requests.mcpElicitationDrafts),
|
||||
},
|
||||
composer: {
|
||||
...state.composer,
|
||||
suggestions: [...state.composer.suggestions],
|
||||
},
|
||||
ui: {
|
||||
toolbarPanel: state.ui.toolbarPanel,
|
||||
archiveConfirmThreadId: state.ui.archiveConfirmThreadId,
|
||||
rename: { ...state.ui.rename },
|
||||
goalEditor: { ...state.ui.goalEditor },
|
||||
messageActionMenu: { ...state.ui.messageActionMenu },
|
||||
disclosures: cloneDisclosureUiState(state.ui.disclosures),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function cloneMessageStreamState(state: ChatMessageStreamState): ChatMessageStreamState {
|
||||
return {
|
||||
stableItems: [...state.stableItems],
|
||||
activeSegment: cloneActiveSegment(state.activeSegment),
|
||||
turnDiffs: new Map(state.turnDiffs),
|
||||
historyCursor: state.historyCursor,
|
||||
loadingHistory: state.loadingHistory,
|
||||
reportedLogs: new Set(state.reportedLogs),
|
||||
};
|
||||
}
|
||||
|
||||
function cloneActiveSegment(segment: ChatMessageStreamActiveSegment | null): ChatMessageStreamActiveSegment | null {
|
||||
if (!segment) return null;
|
||||
return {
|
||||
turnId: segment.turnId,
|
||||
items: [...segment.items],
|
||||
indexById: new Map(segment.indexById),
|
||||
indexBySourceItemId: new Map(segment.indexBySourceItemId),
|
||||
};
|
||||
}
|
||||
|
||||
function setComposerSuggestionsSlice(
|
||||
state: ChatComposerState,
|
||||
suggestions: readonly ComposerSuggestion[],
|
||||
|
|
@ -751,16 +692,6 @@ function composerSuggestionsEqual(left: readonly ComposerSuggestion[], right: re
|
|||
});
|
||||
}
|
||||
|
||||
function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
|
||||
function patchChatState(state: ChatState, patch: Partial<ChatState>): ChatState {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(state[key as keyof ChatState], value))) return state;
|
||||
return { ...state, ...patch };
|
||||
}
|
||||
|
||||
function definedPatch<Key extends string, Value>(key: Key, value: Value | undefined): Partial<Record<Key, Value>> {
|
||||
return value === undefined ? {} : ({ [key]: value } as Partial<Record<Key, Value>>);
|
||||
return patchObject(state, patch);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { chatReducer, cloneChatState, createChatState, type ChatAction, type ChatState } from "./root-reducer";
|
||||
import { cloneChatState } from "./clone";
|
||||
import { chatReducer, createChatState, type ChatAction, type ChatState } from "./root-reducer";
|
||||
|
||||
export interface ChatStateStore {
|
||||
getState(): ChatState;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
type ThreadRenameLifecycleState,
|
||||
} from "../../../threads/rename-lifecycle";
|
||||
import type { DisclosureSetAction } from "./actions";
|
||||
import { patchObject } from "./patch";
|
||||
|
||||
export type ChatRenameUiState = { readonly kind: "idle" } | (ThreadRenameActiveState & { readonly threadId: string });
|
||||
|
||||
|
|
@ -397,8 +398,3 @@ function filterStringSet(values: ReadonlySet<string>, keep: (value: string) => b
|
|||
}
|
||||
return next ?? values;
|
||||
}
|
||||
|
||||
function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue