mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Move chat panel shell and surface contracts out of UI
This commit is contained in:
parent
cfa693c8ba
commit
647ba5d05f
34 changed files with 240 additions and 157 deletions
|
|
@ -25,7 +25,7 @@ const removedChatStateEscapeHatchRestrictions = [
|
|||
const chatSignalAdapterRestrictions = [
|
||||
{
|
||||
selector: "ImportDeclaration[source.value='@preact/signals']",
|
||||
message: "Keep chat signals in ui/shell-state.tsx as a reducer-to-Preact notification adapter.",
|
||||
message: "Keep chat signals in panel/shell-state.tsx as a reducer-to-Preact notification adapter.",
|
||||
},
|
||||
];
|
||||
const uiRootImportRestrictions = [
|
||||
|
|
@ -154,12 +154,12 @@ const chatPreactDomBridgeFiles = [
|
|||
"src/features/chat/ui/message-stream/tool-result.tsx",
|
||||
"src/features/chat/ui/message-stream/viewport.tsx",
|
||||
"src/features/chat/ui/composer-dom.ts",
|
||||
"src/features/chat/ui/shell.tsx",
|
||||
"src/features/chat/panel/shell.tsx",
|
||||
"src/features/chat/ui/turn-diff/render.tsx",
|
||||
];
|
||||
const chatImperativeDomBridgeFiles = [...chatExternalDomBridgeFiles, ...chatPreactDomBridgeFiles];
|
||||
const uiRootBridgeFiles = [
|
||||
"src/features/chat/ui/shell.tsx",
|
||||
"src/features/chat/panel/shell.tsx",
|
||||
"src/features/chat/ui/turn-diff/render.tsx",
|
||||
"src/features/chat/ui/turn-diff/view.ts",
|
||||
"src/features/selection-rewrite/popover.tsx",
|
||||
|
|
@ -639,7 +639,7 @@ export default defineConfig([
|
|||
},
|
||||
{
|
||||
files: ["src/features/chat/**/*.{ts,tsx}"],
|
||||
ignores: ["src/features/chat/ui/shell-state.tsx", ...chatImperativeDomBridgeFiles],
|
||||
ignores: ["src/features/chat/panel/shell-state.tsx", ...chatImperativeDomBridgeFiles],
|
||||
rules: {
|
||||
...restrictedSyntaxRule(chatSourceSyntaxRestrictions),
|
||||
"codex-panel/no-imperative-dom": "error",
|
||||
|
|
@ -647,7 +647,7 @@ export default defineConfig([
|
|||
},
|
||||
},
|
||||
{
|
||||
files: ["src/features/chat/ui/shell-state.tsx"],
|
||||
files: ["src/features/chat/panel/shell-state.tsx"],
|
||||
rules: {
|
||||
...restrictedSyntaxRule(baseSourceSyntaxRestrictions),
|
||||
"codex-panel/no-imperative-dom": "error",
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ export {
|
|||
chatTurnBusy,
|
||||
pendingTurnStart,
|
||||
transitionChatTurnLifecycleState,
|
||||
type ChatTurnLifecycleState,
|
||||
type ChatTurnState,
|
||||
type PendingTurnStart,
|
||||
} from "../conversation/turn-state";
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { MessageStreamScrollBridge } from "../panel/surface/message-stream-scrol
|
|||
import { currentModel, runtimeConfigOrDefault } from "../domain/runtime/effective";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import { activeTurnId, type ChatStateStore } from "../application/state/reducer";
|
||||
import type { ComposerMetaViewModel } from "../ui/composer";
|
||||
import type { ChatPanelComposerShellState } from "../ui/shell-state";
|
||||
import type { ChatPanelComposerShellState } from "../panel/shell-state";
|
||||
import type { CodexChatHost } from "../application/chat-host";
|
||||
import type { ChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import { ChatComposerController } from "../ui/composer-controller";
|
||||
import { ChatComposerController } from "../panel/composer-controller";
|
||||
import type { ChatPanelComposerProjection } from "../panel/surface/model";
|
||||
|
||||
export interface ConversationComposerContext {
|
||||
app: App;
|
||||
|
|
@ -17,10 +17,7 @@ export interface ConversationComposerContext {
|
|||
stateStore: ChatStateStore;
|
||||
viewId: string;
|
||||
surface: {
|
||||
composerProjection: (state: ChatPanelComposerShellState) => {
|
||||
placeholder: string;
|
||||
meta: ComposerMetaViewModel;
|
||||
};
|
||||
composerProjection: (state: ChatPanelComposerShellState) => ChatPanelComposerProjection;
|
||||
};
|
||||
liveState: {
|
||||
refresh: () => void;
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import type { GoalActions } from "../application/threads/goal-actions";
|
|||
import type { HistoryController } from "../application/threads/history-controller";
|
||||
import type { ChatInboundController } from "../app-server/inbound/controller";
|
||||
import type { MessageStreamItem, MessageStreamNoticeSection } from "../domain/message-stream/items";
|
||||
import type { ChatMessageScrollIntentState } from "../ui/message-stream/scroll-intent-state";
|
||||
import type { ComposerMetaViewModel } from "../ui/composer";
|
||||
import type { ChatPanelComposerShellState } from "../ui/shell-state";
|
||||
import type { ChatPanelComposerShellState } from "../panel/shell-state";
|
||||
import type { CodexChatHost } from "../application/chat-host";
|
||||
import { MessageStreamPresenter } from "../panel/surface/message-stream-presenter";
|
||||
import type { ChatMessageScrollIntentState } from "../panel/surface/message-stream-scroll-intent";
|
||||
import type { ChatPanelComposerProjection } from "../panel/surface/model";
|
||||
import { createConversationComposer } from "./composer";
|
||||
import { createConversationTurnActions } from "../application/conversation/composition";
|
||||
|
||||
|
|
@ -33,10 +33,7 @@ interface ConversationPartsContext {
|
|||
};
|
||||
surface: {
|
||||
pendingRequestsSignature: () => string;
|
||||
composerProjection: (state: ChatPanelComposerShellState) => {
|
||||
placeholder: string;
|
||||
meta: ComposerMetaViewModel;
|
||||
};
|
||||
composerProjection: (state: ChatPanelComposerShellState) => ChatPanelComposerProjection;
|
||||
};
|
||||
runtime: {
|
||||
connectionDiagnosticDetails: () => MessageStreamNoticeSection[];
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { createChatReconnectActions } from "../application/connection/reconnect-
|
|||
import { createChatServerDiagnosticsActions, type ChatServerDiagnosticsActions } from "../app-server/actions/diagnostics";
|
||||
import { createChatServerMetadataActions, type ChatServerMetadataActions } from "../app-server/actions/metadata";
|
||||
import { createChatServerThreadActions, type ChatServerThreadActions } from "../app-server/actions/threads";
|
||||
import type { ChatComposerController } from "../ui/composer-controller";
|
||||
import type { ChatComposerController } from "../panel/composer-controller";
|
||||
import { createConversationParts } from "./conversation";
|
||||
import type { ComposerSubmitActions } from "../application/conversation/composer-submit-actions";
|
||||
import { codexPanelDisplayTitle } from "../application/threads/title-display";
|
||||
|
|
@ -36,16 +36,16 @@ import {
|
|||
import { applyChatViewState } from "../panel/view-state";
|
||||
import { closeChatView, openChatView, type ChatViewLifecycleHost } from "../panel/view-lifecycle";
|
||||
import { createToolbarPanelActions, type ToolbarPanelActions } from "../panel/toolbar-actions";
|
||||
import { connectionDiagnosticsModel } from "../panel/surface/toolbar";
|
||||
import { connectionDiagnosticsModel } from "../panel/surface/toolbar-projection";
|
||||
import { openPanelTurnLifecycle } from "../panel/snapshot";
|
||||
import { ChatInboundController } from "../app-server/inbound/controller";
|
||||
import { rejectServerRequest, respondToServerRequest } from "../app-server/requests/responder";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../presentation/runtime/messages";
|
||||
import { createChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import { runtimeSnapshotForChatState, type RuntimeSnapshot } from "../application/runtime/snapshot";
|
||||
import { chatPanelComposerProjection } from "../panel/surface/composer";
|
||||
import { createChatMessageScrollIntentState, type ChatMessageScrollIntentState } from "../ui/message-stream/scroll-intent-state";
|
||||
import { renderChatPanelShell } from "../ui/shell";
|
||||
import { chatPanelComposerProjection } from "../panel/surface/composer-projection";
|
||||
import { createChatMessageScrollIntentState, type ChatMessageScrollIntentState } from "../panel/surface/message-stream-scroll-intent";
|
||||
import { renderChatPanelShell } from "../panel/shell";
|
||||
import {
|
||||
chatTurnBusy,
|
||||
createChatStateStore,
|
||||
|
|
|
|||
|
|
@ -4,14 +4,11 @@ import type { CodexInput } from "../../../domain/chat/input";
|
|||
import { isComposerSendKey, type SendShortcut } from "../../../shared/ui/keyboard";
|
||||
import { textareaCursorAtVisualBoundary } from "../../../shared/ui/textarea-caret";
|
||||
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "../application/state/reducer";
|
||||
import type { ComposerMetaViewModel, ComposerShellProps } from "./composer";
|
||||
import { syncComposerHeight, type ComposerCallbacks } from "./composer";
|
||||
import type { ComposerShellProps } from "../ui/composer";
|
||||
import { syncComposerHeight, type ComposerCallbacks } from "../ui/composer";
|
||||
import type { ChatPanelComposerShellState } from "./shell-state";
|
||||
import { composerBoundaryScrollDirection, type ComposerBoundaryScrollAction } from "../application/composer/boundary-scroll";
|
||||
import {
|
||||
noteCandidates as appNoteCandidates,
|
||||
resolveWikiLinkMention as resolveAppWikiLinkMention,
|
||||
} from "../application/composer/obsidian-context";
|
||||
import { noteCandidates as appNoteCandidates, resolveWikiLinkMention as resolveAppWikiLinkMention } from "./composer-obsidian-context";
|
||||
import {
|
||||
activeComposerSuggestions,
|
||||
applyComposerSuggestionInsertion,
|
||||
|
|
@ -22,6 +19,7 @@ import {
|
|||
type NoteCandidate,
|
||||
} from "../application/composer/suggestions";
|
||||
import { userInputWithWikiLinkMentionsAndSkills } from "../application/composer/wikilink-context";
|
||||
import type { ChatPanelComposerProjection } from "./surface/model";
|
||||
|
||||
export interface ChatComposerControllerOptions {
|
||||
app: App;
|
||||
|
|
@ -30,10 +28,7 @@ export interface ChatComposerControllerOptions {
|
|||
sendShortcut: () => SendShortcut;
|
||||
scrollThreadFromComposerEdges: () => boolean;
|
||||
canInterrupt: (state: ChatPanelComposerShellState) => boolean;
|
||||
composerProjection: (state: ChatPanelComposerShellState) => {
|
||||
placeholder: string;
|
||||
meta: ComposerMetaViewModel;
|
||||
};
|
||||
composerProjection: (state: ChatPanelComposerShellState) => ChatPanelComposerProjection;
|
||||
currentModelForSuggestions: () => string | null;
|
||||
threadScrollFromComposer: (action: ComposerBoundaryScrollAction) => void;
|
||||
togglePlan: () => void;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { App } from "obsidian";
|
||||
import { stripHeadingForLink, TFile } from "obsidian";
|
||||
|
||||
import type { NoteCandidate } from "./suggestions";
|
||||
import type { NoteCandidate } from "../application/composer/suggestions";
|
||||
|
||||
export interface WikiLinkMention {
|
||||
name: string;
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import type { ComponentChild as UiNode } from "preact";
|
||||
import { renderUiRoot, unmountUiRoot } from "../../../shared/ui/ui-root";
|
||||
import type { ChatStateStore } from "../application/state/reducer";
|
||||
import type { ChatPanelGoalSurface, ChatPanelToolbarSurface } from "../panel/surface/model";
|
||||
import { ChatPanelToolbar } from "../panel/surface/toolbar";
|
||||
import { ChatPanelGoal } from "../panel/surface/goal";
|
||||
import { ChatPanelMessageStream, type ChatPanelMessageStreamPresenter } from "../panel/surface/message-stream-presenter";
|
||||
import { ChatPanelComposer, type ChatPanelComposerActions, type ChatPanelComposerController } from "../panel/surface/composer";
|
||||
import type { ChatPanelGoalSurface, ChatPanelToolbarSurface } from "./surface/model";
|
||||
import { ChatPanelToolbar } from "./surface/toolbar-projection";
|
||||
import { ChatPanelGoal } from "./surface/goal-projection";
|
||||
import { ChatPanelMessageStream, type ChatPanelMessageStreamPresenter } from "./surface/message-stream-presenter";
|
||||
import { ChatPanelComposer, type ChatPanelComposerActions, type ChatPanelComposerController } from "./surface/composer-projection";
|
||||
import { ChatPanelShellStateContext, createChatPanelShellState, syncChatPanelShellState, type ChatPanelShellState } from "./shell-state";
|
||||
|
||||
export interface ChatPanelShellParts {
|
||||
|
|
@ -15,28 +15,22 @@ import { sortedModelMetadata } from "../../../../domain/catalog/metadata";
|
|||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { RuntimeSnapshot } from "../../application/runtime/snapshot";
|
||||
import type { ChatState } from "../../application/state/reducer";
|
||||
import type {
|
||||
ComposerContextMeterCellViewModel,
|
||||
ComposerContextMeterViewModel,
|
||||
ComposerMetaViewModel,
|
||||
RuntimeChoice,
|
||||
} from "../../ui/composer";
|
||||
import { ComposerShell, type ComposerShellProps } from "../../ui/composer";
|
||||
import { composerStateFromShellState, useChatPanelShellState, type ChatPanelComposerShellState } from "../../ui/shell-state";
|
||||
import { composerStateFromShellState, useChatPanelShellState, type ChatPanelComposerShellState } from "../shell-state";
|
||||
import { explicitThreadName } from "../../../../domain/threads/model";
|
||||
import type { ChatPanelComposerSurface, RestoredThreadTitleSnapshot } from "./model";
|
||||
import type {
|
||||
ChatPanelComposerContextMeter,
|
||||
ChatPanelComposerContextMeterCell,
|
||||
ChatPanelComposerMeta,
|
||||
ChatPanelComposerProjection,
|
||||
ChatPanelComposerRuntimeChoice,
|
||||
ChatPanelComposerSurface,
|
||||
RestoredThreadTitleSnapshot,
|
||||
} from "./model";
|
||||
import { runtimeSnapshotForShellState } from "./runtime-snapshot";
|
||||
|
||||
type ComposerMetaState = Pick<ChatState, "connection" | "runtime">;
|
||||
|
||||
export interface ChatPanelComposerProjection {
|
||||
placeholder: string;
|
||||
meta: ComposerMetaViewModel & {
|
||||
modelChoices: RuntimeChoice[];
|
||||
effortChoices: RuntimeChoice[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChatPanelComposerController {
|
||||
renderState(state: ChatPanelComposerShellState, actions: ChatPanelComposerActions): ComposerShellProps;
|
||||
}
|
||||
|
|
@ -88,7 +82,10 @@ export function chatPanelComposerProjection(
|
|||
};
|
||||
}
|
||||
|
||||
export function composerMetaViewModel(state: ComposerMetaState, snapshot: RuntimeSnapshot): ComposerMetaViewModel {
|
||||
export function composerMetaViewModel(
|
||||
state: ComposerMetaState,
|
||||
snapshot: RuntimeSnapshot,
|
||||
): Omit<ChatPanelComposerMeta, "modelChoices" | "effortChoices"> {
|
||||
if (state.connection.phase.kind === "failed" || state.connection.phase.kind === "disconnected") {
|
||||
return {
|
||||
fatal: "Codex app-server disconnected",
|
||||
|
|
@ -131,13 +128,13 @@ export function composerMetaViewModel(state: ComposerMetaState, snapshot: Runtim
|
|||
}
|
||||
|
||||
export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
|
||||
modelChoices: RuntimeChoice[];
|
||||
effortChoices: RuntimeChoice[];
|
||||
modelChoices: ChatPanelComposerRuntimeChoice[];
|
||||
effortChoices: ChatPanelComposerRuntimeChoice[];
|
||||
} {
|
||||
const config = runtimeConfigOrDefault(input.state.connection.runtimeConfig);
|
||||
const activeModel = currentModel(input.snapshot, config);
|
||||
const models = sortedModelMetadata(input.state.connection.availableModels);
|
||||
const modelChoices: RuntimeChoice[] = models.slice(0, 12).map((model) => ({
|
||||
const modelChoices: ChatPanelComposerRuntimeChoice[] = models.slice(0, 12).map((model) => ({
|
||||
label: model.model,
|
||||
selected: activeModel === model.model,
|
||||
onClick: () => {
|
||||
|
|
@ -153,7 +150,7 @@ export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
|
|||
}
|
||||
|
||||
const activeEffort = currentReasoningEffort(input.snapshot, config);
|
||||
const effortChoices: RuntimeChoice[] = [
|
||||
const effortChoices: ChatPanelComposerRuntimeChoice[] = [
|
||||
{
|
||||
label: "Codex default",
|
||||
selected: activeEffort === null,
|
||||
|
|
@ -174,7 +171,7 @@ export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
|
|||
}
|
||||
|
||||
function composerStatusSummary(input: {
|
||||
context: ComposerContextMeterViewModel;
|
||||
context: ChatPanelComposerContextMeter;
|
||||
model: string;
|
||||
effort: string | null;
|
||||
planActive: boolean;
|
||||
|
|
@ -214,7 +211,7 @@ const CONTEXT_PARTIAL_DOTS = ["", "⣀", "⣤", "⣶", "⣿"] as const;
|
|||
const CONTEXT_FULL_DOT = "⣿";
|
||||
const CONTEXT_EMPTY_DOT = "⣀";
|
||||
|
||||
function contextComposerMeter(percent: number | null): ComposerContextMeterViewModel {
|
||||
function contextComposerMeter(percent: number | null): ChatPanelComposerContextMeter {
|
||||
const percentLabel = percent === null ? "--%" : `${String(Math.round(Math.max(0, Math.min(100, percent)))).padStart(2, " ")}%`;
|
||||
return {
|
||||
cells: contextBrailleCells(percent),
|
||||
|
|
@ -222,10 +219,10 @@ function contextComposerMeter(percent: number | null): ComposerContextMeterViewM
|
|||
};
|
||||
}
|
||||
|
||||
function contextBrailleCells(percent: number | null): ComposerContextMeterCellViewModel[] {
|
||||
function contextBrailleCells(percent: number | null): ChatPanelComposerContextMeterCell[] {
|
||||
if (percent === null) return Array.from({ length: CONTEXT_DOT_WIDTH }, () => ({ text: CONTEXT_EMPTY_DOT, placeholder: true }));
|
||||
const clamped = Math.max(0, Math.min(100, percent));
|
||||
const cells: ComposerContextMeterCellViewModel[] = [];
|
||||
const cells: ChatPanelComposerContextMeterCell[] = [];
|
||||
for (let index = 0; index < CONTEXT_DOT_WIDTH; index += 1) {
|
||||
const remaining = clamped - index * CONTEXT_CELL_PERCENT;
|
||||
if (remaining <= 0) {
|
||||
|
|
@ -3,7 +3,7 @@ import { h } from "preact";
|
|||
|
||||
import type { GoalPanelActions, GoalPanelDisplayState, GoalPanelEditorState, GoalPanelOptions } from "../../ui/goal";
|
||||
import { GoalPanel } from "../../ui/goal";
|
||||
import { goalStateFromShellState, useChatPanelShellState, type ChatPanelGoalShellState } from "../../ui/shell-state";
|
||||
import { goalStateFromShellState, useChatPanelShellState, type ChatPanelGoalShellState } from "../shell-state";
|
||||
import type { ChatPanelGoalSurface } from "./model";
|
||||
|
||||
export interface ChatPanelGoalProjection {
|
||||
|
|
@ -8,7 +8,7 @@ import { MarkdownMessageRenderer } from "../../ui/message-stream/markdown-render
|
|||
import { MessageStreamViewport, type MessageStreamViewportState } from "../../ui/message-stream/viewport";
|
||||
import type { MessageStreamItem } from "../../domain/message-stream/items";
|
||||
import { messageStreamBlocks } from "../../ui/message-stream/stream-blocks";
|
||||
import { messageStreamStateFromShellState, useChatPanelShellState, type ChatPanelMessageStreamShellState } from "../../ui/shell-state";
|
||||
import { messageStreamStateFromShellState, useChatPanelShellState, type ChatPanelMessageStreamShellState } from "../shell-state";
|
||||
import type { PendingRequestBlockActions, PendingRequestBlockState } from "../../application/pending-requests/block";
|
||||
import type { ChatTurnDiffViewState } from "../../domain/turn-diff";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
type MessageStreamRollbackCandidate,
|
||||
} from "../../application/state/message-stream";
|
||||
import type { MessageStreamContext } from "../../ui/message-stream/context";
|
||||
import type { ChatPanelMessageStreamShellState } from "../../ui/shell-state";
|
||||
import type { ChatPanelMessageStreamShellState } from "../shell-state";
|
||||
import { pendingRequestBlockSnapshotFromState } from "../../presentation/pending-requests/snapshot";
|
||||
import type { PendingRequestBlockActions, PendingRequestBlockState } from "../../application/pending-requests/block";
|
||||
import type { ChatTurnDiffViewState } from "../../domain/turn-diff";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { MessageStreamScrollIntent } from "./virtualizer";
|
||||
import type { MessageStreamScrollIntent } from "../../ui/message-stream/virtualizer";
|
||||
|
||||
export interface ChatMessageScrollIntentState {
|
||||
consumeIntent(): MessageStreamScrollIntent;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { SendShortcut } from "../../../../shared/ui/keyboard";
|
||||
import type { ToolbarActions } from "../../ui/toolbar";
|
||||
|
||||
export interface RestoredThreadTitleSnapshot {
|
||||
threadId: string;
|
||||
|
|
@ -8,7 +7,60 @@ export interface RestoredThreadTitleSnapshot {
|
|||
explicitName: string | null;
|
||||
}
|
||||
|
||||
type ChatPanelToolbarActions = ToolbarActions;
|
||||
export interface ChatPanelComposerContextMeterCell {
|
||||
text: string;
|
||||
placeholder: boolean;
|
||||
}
|
||||
|
||||
export interface ChatPanelComposerContextMeter {
|
||||
cells: ChatPanelComposerContextMeterCell[];
|
||||
percent: string;
|
||||
}
|
||||
|
||||
export interface ChatPanelComposerRuntimeChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
meta?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ChatPanelComposerMeta {
|
||||
fatal: string | null;
|
||||
context: ChatPanelComposerContextMeter;
|
||||
statusSummary: string;
|
||||
model: string;
|
||||
effort: string | null;
|
||||
planActive: boolean;
|
||||
autoReviewActive: boolean;
|
||||
fastActive: boolean;
|
||||
modelChoices?: ChatPanelComposerRuntimeChoice[];
|
||||
effortChoices?: ChatPanelComposerRuntimeChoice[];
|
||||
}
|
||||
|
||||
export interface ChatPanelComposerProjection {
|
||||
placeholder: string;
|
||||
meta: ChatPanelComposerMeta;
|
||||
}
|
||||
|
||||
interface ChatPanelToolbarActions {
|
||||
startNewThread: () => void;
|
||||
toggleChatActions: () => void;
|
||||
compactConversation: () => void;
|
||||
setGoal: () => void;
|
||||
toggleHistory: () => void;
|
||||
toggleStatusPanel: () => void;
|
||||
connect: () => void;
|
||||
refreshStatus: () => void;
|
||||
resumeThread: (threadId: string) => void;
|
||||
startArchiveThread: (threadId: string) => void;
|
||||
archiveThread: (threadId: string, saveMarkdown: boolean) => void;
|
||||
startRenameThread: (threadId: string) => void;
|
||||
updateRenameDraft: (threadId: string, value: string) => void;
|
||||
saveRenameThread: (threadId: string, value: string) => void;
|
||||
cancelRenameThread: (threadId: string) => void;
|
||||
autoNameThread: (threadId: string) => void;
|
||||
}
|
||||
|
||||
interface ChatPanelGoalActions {
|
||||
saveObjective: (objective: string, tokenBudget: number | null) => Promise<void>;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { runtimeConfigSections, rateLimitSummary } from "../../presentation/runt
|
|||
import { connectionDiagnosticSections } from "../../application/connection/diagnostics-display";
|
||||
import type { RuntimeSnapshot } from "../../application/runtime/snapshot";
|
||||
import { chatTurnBusy, type ChatState } from "../../application/state/reducer";
|
||||
import { toolbarStateFromShellState, useChatPanelShellState, type ChatPanelToolbarShellState } from "../../ui/shell-state";
|
||||
import { toolbarStateFromShellState, useChatPanelShellState, type ChatPanelToolbarShellState } from "../shell-state";
|
||||
import { Toolbar, type ToolbarThreadRow, type ToolbarViewModel } from "../../ui/toolbar";
|
||||
import type { ChatPanelToolbarSurface } from "./model";
|
||||
import { runtimeSnapshotForToolbarShellState } from "./runtime-snapshot";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { EventRef } from "obsidian";
|
||||
|
||||
import { unmountChatPanelShell } from "../ui/shell";
|
||||
import { unmountChatPanelShell } from "./shell";
|
||||
|
||||
export interface ChatViewLifecycleHost {
|
||||
lifecycle: {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
import type { ButtonHTMLAttributes, ComponentChild as UiNode, Ref } from "preact";
|
||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import type { ComposerSuggestion } from "../application/composer/suggestions";
|
||||
import { IconButton } from "../../../shared/ui/components";
|
||||
import { disposeDomListeners, listenDomEvent } from "../../../shared/ui/dom-events";
|
||||
import { syncTextareaHeight } from "../../../shared/ui/textarea-autogrow";
|
||||
import { renderComposerMetaIcon, scrollComposerSuggestionIntoView, updateComposerMetaStatusOverflow } from "./composer-dom";
|
||||
|
||||
export interface ComposerSuggestion {
|
||||
display: string;
|
||||
detail: string;
|
||||
replacement: string;
|
||||
start: number;
|
||||
appendSpaceOnInsert?: boolean;
|
||||
tabCursorOffset?: number;
|
||||
suffixOnInsert?: string;
|
||||
}
|
||||
|
||||
export interface ComposerMetaViewModel {
|
||||
fatal: string | null;
|
||||
context: ComposerContextMeterViewModel;
|
||||
|
|
@ -20,7 +29,7 @@ export interface ComposerMetaViewModel {
|
|||
effortChoices?: RuntimeChoice[];
|
||||
}
|
||||
|
||||
export interface RuntimeChoice {
|
||||
interface RuntimeChoice {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
|
|
@ -28,12 +37,12 @@ export interface RuntimeChoice {
|
|||
onClick: () => void;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterCellViewModel {
|
||||
interface ComposerContextMeterCellViewModel {
|
||||
text: string;
|
||||
placeholder: boolean;
|
||||
}
|
||||
|
||||
export interface ComposerContextMeterViewModel {
|
||||
interface ComposerContextMeterViewModel {
|
||||
cells: ComposerContextMeterCellViewModel[];
|
||||
percent: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { ComponentChild as UiNode } from "preact";
|
||||
|
||||
import type { ChatDisclosureBucket, ChatDisclosureUiState, ChatTurnLifecycleState } from "../../application/state/reducer";
|
||||
import type { PendingRequestBlockActions } from "../../application/pending-requests/block";
|
||||
import type { ApprovalAction, PendingRequestId } from "../../domain/pending-requests/model";
|
||||
import type { PendingRequestBlockSnapshot } from "../../presentation/pending-requests/snapshot";
|
||||
import type { MessageStreamItem } from "../../domain/message-stream/items";
|
||||
import type { TextMessageStreamItem } from "../../presentation/message-stream/text-view";
|
||||
|
|
@ -14,9 +13,41 @@ export interface MessageStreamBlock {
|
|||
|
||||
export type { TextMessageStreamItem };
|
||||
|
||||
type MessageStreamDisclosureBucket =
|
||||
| "toolResults"
|
||||
| "activityGroups"
|
||||
| "agentDetails"
|
||||
| "textDetails"
|
||||
| "userMessageExpanded"
|
||||
| "goalObjectiveExpanded"
|
||||
| "approvalDetails";
|
||||
|
||||
export interface MessageStreamDisclosureState {
|
||||
toolResults: ReadonlySet<string>;
|
||||
activityGroups: ReadonlySet<string>;
|
||||
agentDetails: ReadonlySet<string>;
|
||||
textDetails: ReadonlySet<string>;
|
||||
userMessageExpanded: ReadonlySet<string>;
|
||||
goalObjectiveExpanded: ReadonlySet<string>;
|
||||
approvalDetails: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
export type MessageStreamTurnLifecycleState =
|
||||
| { kind: "idle" }
|
||||
| { kind: "starting"; pendingTurnStart: unknown }
|
||||
| { kind: "running"; turnId: string };
|
||||
|
||||
export interface PendingRequestBlockActions {
|
||||
resolveApproval: (requestId: PendingRequestId, action: ApprovalAction) => void;
|
||||
resolveUserInput: (requestId: PendingRequestId) => void;
|
||||
cancelUserInput: (requestId: PendingRequestId) => void;
|
||||
setApprovalDetailsExpanded?: (requestId: PendingRequestId, expanded: boolean) => void;
|
||||
setUserInputDraft: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
export interface TextItemDetailStateContext {
|
||||
disclosures: ChatDisclosureUiState;
|
||||
onDisclosureToggle?: (bucket: ChatDisclosureBucket, id: string, open: boolean) => void;
|
||||
disclosures: MessageStreamDisclosureState;
|
||||
onDisclosureToggle?: (bucket: MessageStreamDisclosureBucket, id: string, open: boolean) => void;
|
||||
}
|
||||
|
||||
export interface TextItemContentContext extends TextItemDetailStateContext {
|
||||
|
|
@ -24,7 +55,7 @@ export interface TextItemContentContext extends TextItemDetailStateContext {
|
|||
}
|
||||
|
||||
export interface TextItemActionContext extends TextItemDetailStateContext {
|
||||
turnLifecycle: ChatTurnLifecycleState;
|
||||
turnLifecycle: MessageStreamTurnLifecycleState;
|
||||
forkActionsItemId: string | null;
|
||||
onForkActionsToggle?: (itemId: string | null) => void;
|
||||
copyText?: (text: string) => void;
|
||||
|
|
@ -44,7 +75,7 @@ export interface TextItemMetadataContext extends TextItemDetailStateContext {
|
|||
|
||||
interface MessageStreamRenderContext {
|
||||
activeThreadId: string | null;
|
||||
turnLifecycle: ChatTurnLifecycleState;
|
||||
turnLifecycle: MessageStreamTurnLifecycleState;
|
||||
workspaceRoot?: string | null;
|
||||
loadOlderTurns: () => void;
|
||||
pendingRequests?: PendingRequestBlockContext;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
type PendingUserInputQuestionViewModel,
|
||||
type PendingUserInputViewModel,
|
||||
} from "../../presentation/pending-requests/view-model";
|
||||
import type { PendingRequestBlockActions } from "../../application/pending-requests/block";
|
||||
import type { PendingRequestBlockActions } from "./context";
|
||||
import { createWorkMessageClassName } from "./work-message";
|
||||
|
||||
export function pendingRequestBlockNode(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { type ComponentChild as UiNode } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import { activeTurnId } from "../../application/state/reducer";
|
||||
import type { MessageStreamItem } from "../../domain/message-stream/items";
|
||||
import { IconButton } from "../../../../shared/ui/components";
|
||||
import { listenDomEvent } from "../../../../shared/ui/dom-events";
|
||||
|
|
@ -117,6 +116,6 @@ function displayRoleLabel(item: MessageStreamItem): string {
|
|||
|
||||
function isMessageCopyActionVisible(item: MessageStreamItem, context: Pick<TextItemActionContext, "turnLifecycle">): boolean {
|
||||
if (item.kind !== "message" || item.copyText === undefined) return false;
|
||||
const activeTurn = activeTurnId({ lifecycle: context.turnLifecycle });
|
||||
const activeTurn = context.turnLifecycle.kind === "running" ? context.turnLifecycle.turnId : null;
|
||||
return !(activeTurn && item.role === "assistant" && item.turnId === activeTurn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { useLayoutEffect, useRef } from "preact/hooks";
|
|||
|
||||
import { type ToolResultDetailSection, type ToolResultView } from "../../presentation/message-stream/tool-result-view";
|
||||
import { renderRawDiffLines } from "../../../../shared/diff/render";
|
||||
import type { ChatDisclosureUiState } from "../../application/state/reducer";
|
||||
import type { MessageStreamDisclosureState } from "./context";
|
||||
|
||||
export interface ToolResultRenderContext {
|
||||
disclosures: ChatDisclosureUiState;
|
||||
disclosures: MessageStreamDisclosureState;
|
||||
onDisclosureToggle?: (bucket: "toolResults", id: string, open: boolean) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import type { ComponentChild as UiNode } from "preact";
|
|||
|
||||
import type { ExecutionState, TaskProgressMessageStreamItem } from "../../domain/message-stream/items";
|
||||
import type { AgentRunSummaryView, MessageStreamWorkView } from "../../presentation/message-stream/work-view";
|
||||
import type { ChatDisclosureUiState } from "../../application/state/reducer";
|
||||
import type { MessageStreamDisclosureState } from "./context";
|
||||
import { createWorkMessageClassName } from "./work-message";
|
||||
|
||||
export interface WorkItemContext {
|
||||
disclosures: ChatDisclosureUiState;
|
||||
disclosures: MessageStreamDisclosureState;
|
||||
onDisclosureToggle?: (bucket: "agentDetails", id: string, open: boolean) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import type { App } from "obsidian";
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { h } from "preact";
|
||||
|
||||
import { ChatComposerController, type ChatComposerRenderActions } from "../../../../../src/features/chat/ui/composer-controller";
|
||||
import { ChatComposerController, type ChatComposerRenderActions } from "../../../../../src/features/chat/panel/composer-controller";
|
||||
import { ComposerShell } from "../../../../../src/features/chat/ui/composer";
|
||||
import { createChatStateStore, type ChatStateStore } from "../../../../../src/features/chat/application/state/reducer";
|
||||
import { renderUiRoot, unmountUiRoot } from "../../../../../src/shared/ui/ui-root";
|
||||
import type { SkillMetadata } from "../../../../../src/domain/catalog/metadata";
|
||||
import { installObsidianDomShims } from "../../../../support/dom";
|
||||
import type { ChatPanelComposerShellState } from "../../../../../src/features/chat/ui/shell-state";
|
||||
import type { ChatPanelComposerShellState } from "../../../../../src/features/chat/panel/shell-state";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { TFile, type App } from "obsidian";
|
||||
|
||||
import { noteCandidates, resolveWikiLinkMention } from "../../../../../src/features/chat/application/composer/obsidian-context";
|
||||
import { noteCandidates, resolveWikiLinkMention } from "../../../../src/features/chat/panel/composer-obsidian-context";
|
||||
|
||||
describe("Obsidian composer context", () => {
|
||||
it("builds note candidates from markdown files", () => {
|
||||
|
|
@ -5,8 +5,8 @@ import { act } from "preact/test-utils";
|
|||
|
||||
import { createChatStateStore } from "../../../../src/features/chat/application/state/reducer";
|
||||
import { messageStreamItems } from "../../../../src/features/chat/application/state/message-stream";
|
||||
import { renderChatPanelShell, unmountChatPanelShell, type ChatPanelShellParts } from "../../../../src/features/chat/ui/shell";
|
||||
import type { ChatPanelComposerShellState, ChatPanelMessageStreamShellState } from "../../../../src/features/chat/ui/shell-state";
|
||||
import { renderChatPanelShell, unmountChatPanelShell, type ChatPanelShellParts } from "../../../../src/features/chat/panel/shell";
|
||||
import type { ChatPanelComposerShellState, ChatPanelMessageStreamShellState } from "../../../../src/features/chat/panel/shell-state";
|
||||
import type { ChatPanelSurface } from "../../../../src/features/chat/panel/surface/model";
|
||||
import { installObsidianDomShims } from "../../../support/dom";
|
||||
|
||||
|
|
@ -1,33 +1,30 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createChatMessageScrollIntentState } from "../../../../../src/features/chat/ui/message-stream/scroll-intent-state";
|
||||
import { createChatMessageScrollIntentState } from "../../../../../src/features/chat/panel/surface/message-stream-scroll-intent";
|
||||
|
||||
describe("createChatMessageScrollIntentState", () => {
|
||||
it("consumes one-shot scroll intents", () => {
|
||||
it("starts with automatic scrolling", () => {
|
||||
const scrollIntent = createChatMessageScrollIntentState();
|
||||
|
||||
expect(scrollIntent.consumeIntent()).toBe("auto");
|
||||
expect(scrollIntent.consumeIntent()).toBe("auto");
|
||||
});
|
||||
|
||||
it("consumes force bottom intent once", () => {
|
||||
const scrollIntent = createChatMessageScrollIntentState();
|
||||
|
||||
scrollIntent.forceBottom();
|
||||
|
||||
expect(scrollIntent.consumeIntent()).toBe("force-bottom");
|
||||
expect(scrollIntent.consumeIntent()).toBe("auto");
|
||||
});
|
||||
|
||||
it("keeps the latest requested intent", () => {
|
||||
const scrollIntent = createChatMessageScrollIntentState();
|
||||
|
||||
scrollIntent.followBottom();
|
||||
scrollIntent.preservePosition();
|
||||
|
||||
expect(scrollIntent.consumeIntent()).toBe("preserve");
|
||||
expect(scrollIntent.consumeIntent()).toBe("auto");
|
||||
});
|
||||
|
||||
it("emits a one-shot force-bottom intent", () => {
|
||||
const scrollIntent = createChatMessageScrollIntentState();
|
||||
|
||||
scrollIntent.forceBottom();
|
||||
|
||||
expect(scrollIntent.consumeIntent()).toBe("force-bottom");
|
||||
});
|
||||
|
||||
it("uses the latest scroll intent before consumption", () => {
|
||||
const scrollIntent = createChatMessageScrollIntentState();
|
||||
|
||||
scrollIntent.preservePosition();
|
||||
scrollIntent.followBottom();
|
||||
scrollIntent.forceBottom();
|
||||
|
||||
expect(scrollIntent.consumeIntent()).toBe("force-bottom");
|
||||
expect(scrollIntent.consumeIntent()).toBe("auto");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,26 +1,30 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createServerDiagnostics } from "../../../src/domain/server/diagnostics";
|
||||
import { createServerDiagnostics } from "../../../../../src/domain/server/diagnostics";
|
||||
import {
|
||||
runtimeConfigSnapshotFromAppServerConfig,
|
||||
type ConfigReadResult,
|
||||
type RuntimeConfigSnapshot,
|
||||
} from "../../../src/app-server/protocol/runtime-config";
|
||||
import { createChatState } from "../../../src/features/chat/application/state/reducer";
|
||||
import { chatPanelComposerProjection, composerMetaViewModel, composerPlaceholder } from "../../../src/features/chat/panel/surface/composer";
|
||||
import { effortStatusLines, modelStatusLines, statusSummaryLines } from "../../../src/features/chat/presentation/runtime/status";
|
||||
import { runtimeComposerChoices } from "../../../src/features/chat/panel/surface/composer";
|
||||
import { runtimeSnapshotForChatState } from "../../../src/features/chat/application/runtime/snapshot";
|
||||
import { chatPanelToolbarProjection, toolbarStateProjection } from "../../../src/features/chat/panel/surface/toolbar";
|
||||
import type { ChatState } from "../../../src/features/chat/application/state/reducer";
|
||||
import type { ModelMetadata } from "../../../src/domain/catalog/metadata";
|
||||
import type { Thread } from "../../../src/domain/threads/model";
|
||||
import { chatPanelGoalProjection, chatPanelGoalViewModel } from "../../../src/features/chat/panel/surface/goal";
|
||||
import type { ChatPanelComposerSurface, ChatPanelGoalSurface } from "../../../src/features/chat/panel/surface/model";
|
||||
import type { ThreadGoal } from "../../../src/domain/threads/goal";
|
||||
import { setChatStateMessageStreamItems } from "./support/message-stream";
|
||||
} from "../../../../../src/app-server/protocol/runtime-config";
|
||||
import { createChatState } from "../../../../../src/features/chat/application/state/reducer";
|
||||
import {
|
||||
chatPanelComposerProjection,
|
||||
composerMetaViewModel,
|
||||
composerPlaceholder,
|
||||
} from "../../../../../src/features/chat/panel/surface/composer-projection";
|
||||
import { effortStatusLines, modelStatusLines, statusSummaryLines } from "../../../../../src/features/chat/presentation/runtime/status";
|
||||
import { runtimeComposerChoices } from "../../../../../src/features/chat/panel/surface/composer-projection";
|
||||
import { runtimeSnapshotForChatState } from "../../../../../src/features/chat/application/runtime/snapshot";
|
||||
import { chatPanelToolbarProjection, toolbarStateProjection } from "../../../../../src/features/chat/panel/surface/toolbar-projection";
|
||||
import type { ChatState } from "../../../../../src/features/chat/application/state/reducer";
|
||||
import type { ModelMetadata } from "../../../../../src/domain/catalog/metadata";
|
||||
import type { Thread } from "../../../../../src/domain/threads/model";
|
||||
import { chatPanelGoalProjection, chatPanelGoalViewModel } from "../../../../../src/features/chat/panel/surface/goal-projection";
|
||||
import type { ChatPanelComposerSurface, ChatPanelGoalSurface } from "../../../../../src/features/chat/panel/surface/model";
|
||||
import type { ThreadGoal } from "../../../../../src/domain/threads/goal";
|
||||
import { setChatStateMessageStreamItems } from "../../support/message-stream";
|
||||
|
||||
describe("chat view model", () => {
|
||||
describe("chat panel surface projections", () => {
|
||||
it("builds toolbar rows from immutable chat state snapshots", () => {
|
||||
const state = createChatState();
|
||||
state.activeThread.id = "thread-1";
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/application/state/reducer";
|
||||
import { createToolbarPanelActions } from "../../../../../src/features/chat/panel/toolbar-actions";
|
||||
import type { ThreadManagementActions } from "../../../../../src/features/chat/application/threads/thread-management-actions";
|
||||
import { createChatState, createChatStateStore } from "../../../../src/features/chat/application/state/reducer";
|
||||
import { createToolbarPanelActions } from "../../../../src/features/chat/panel/toolbar-actions";
|
||||
import type { ThreadManagementActions } from "../../../../src/features/chat/application/threads/thread-management-actions";
|
||||
|
||||
describe("createToolbarPanelActions", () => {
|
||||
it("tracks archive confirmation and delegates archive actions", async () => {
|
||||
|
|
@ -8,7 +8,7 @@ import { createChatStateStore } from "../../../../src/features/chat/application/
|
|||
import { createToolbarPanelActions, type ToolbarPanelActions } from "../../../../src/features/chat/panel/toolbar-actions";
|
||||
import type { ChatPanelSurface, ChatPanelToolbarSurface } from "../../../../src/features/chat/panel/surface/model";
|
||||
import type { ThreadManagementActions } from "../../../../src/features/chat/application/threads/thread-management-actions";
|
||||
import { renderChatPanelShell, unmountChatPanelShell, type ChatPanelShellParts } from "../../../../src/features/chat/ui/shell";
|
||||
import { renderChatPanelShell, unmountChatPanelShell, type ChatPanelShellParts } from "../../../../src/features/chat/panel/shell";
|
||||
import { installObsidianDomShims } from "../../../support/dom";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
|
@ -4,9 +4,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||
import type { EventRef } from "obsidian";
|
||||
|
||||
import { closeChatView, openChatView, type ChatViewLifecycleHost } from "../../../../src/features/chat/panel/view-lifecycle";
|
||||
import { unmountChatPanelShell } from "../../../../src/features/chat/ui/shell";
|
||||
import { unmountChatPanelShell } from "../../../../src/features/chat/panel/shell";
|
||||
|
||||
vi.mock("../../../../src/features/chat/ui/shell", () => ({
|
||||
vi.mock("../../../../src/features/chat/panel/shell", () => ({
|
||||
unmountChatPanelShell: vi.fn(),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ import { act } from "preact/test-utils";
|
|||
|
||||
import type { PendingApproval, PendingUserInput } from "../../../../../src/features/chat/domain/pending-requests/model";
|
||||
import { pendingRequestBlockSnapshotFromState } from "../../../../../src/features/chat/presentation/pending-requests/snapshot";
|
||||
import type { PendingRequestBlockActions } from "../../../../../src/features/chat/application/pending-requests/block";
|
||||
import { pendingRequestBlockNode } from "../../../../../src/features/chat/ui/message-stream/pending-request-block";
|
||||
import {
|
||||
activeTurnId,
|
||||
type ChatDisclosureUiState,
|
||||
type ChatTurnLifecycleState,
|
||||
} from "../../../../../src/features/chat/application/state/reducer";
|
||||
import { messageStreamBlocks as rawMessageStreamBlocks } from "../../../../../src/features/chat/ui/message-stream/stream-blocks";
|
||||
import type { MessageStreamBlock, MessageStreamContext } from "../../../../../src/features/chat/ui/message-stream/context";
|
||||
import type {
|
||||
MessageStreamBlock,
|
||||
MessageStreamContext,
|
||||
MessageStreamDisclosureState,
|
||||
MessageStreamTurnLifecycleState,
|
||||
PendingRequestBlockActions,
|
||||
} from "../../../../../src/features/chat/ui/message-stream/context";
|
||||
import type { MessageStreamItem } from "../../../../../src/features/chat/domain/message-stream/items";
|
||||
import { messageStreamViewBlocks } from "../../../../../src/features/chat/presentation/message-stream/view-model";
|
||||
import { MessageStreamViewport } from "../../../../../src/features/chat/ui/message-stream/viewport";
|
||||
|
|
@ -24,7 +24,7 @@ export function messageStreamBlocks(
|
|||
const normalized = normalizeMessageStreamContext(context);
|
||||
const viewBlocks = messageStreamViewBlocks({
|
||||
activeThreadId: normalized.activeThreadId,
|
||||
activeTurnId: activeTurnId({ lifecycle: normalized.turnLifecycle }),
|
||||
activeTurnId: activeTurnIdForMessageStream(normalized.turnLifecycle),
|
||||
historyCursor: context.historyCursor,
|
||||
loadingHistory: context.loadingHistory,
|
||||
items: context.items,
|
||||
|
|
@ -48,11 +48,13 @@ type TestMessageStreamContext = Omit<MessageStreamContext, "disclosures" | "fork
|
|||
turnDiffs?: ReadonlyMap<string, string>;
|
||||
};
|
||||
|
||||
export function emptyDisclosures(): ChatDisclosureUiState {
|
||||
export function emptyDisclosures(): MessageStreamDisclosureState {
|
||||
return testDisclosures();
|
||||
}
|
||||
|
||||
export function testDisclosures(overrides: Partial<Record<keyof ChatDisclosureUiState, readonly string[]>> = {}): ChatDisclosureUiState {
|
||||
export function testDisclosures(
|
||||
overrides: Partial<Record<keyof MessageStreamDisclosureState, readonly string[]>> = {},
|
||||
): MessageStreamDisclosureState {
|
||||
return {
|
||||
toolResults: new Set(overrides.toolResults),
|
||||
activityGroups: new Set(overrides.activityGroups),
|
||||
|
|
@ -194,18 +196,22 @@ export function pendingRequestActions(overrides: Partial<PendingRequestBlockActi
|
|||
};
|
||||
}
|
||||
|
||||
export function idleTurnLifecycle(): ChatTurnLifecycleState {
|
||||
export function idleTurnLifecycle(): MessageStreamTurnLifecycleState {
|
||||
return { kind: "idle" };
|
||||
}
|
||||
|
||||
export function runningTurnLifecycle(turnId = "turn"): ChatTurnLifecycleState {
|
||||
export function runningTurnLifecycle(turnId = "turn"): MessageStreamTurnLifecycleState {
|
||||
return { kind: "running", turnId };
|
||||
}
|
||||
|
||||
export function startingTurnLifecycle(): ChatTurnLifecycleState {
|
||||
export function startingTurnLifecycle(): MessageStreamTurnLifecycleState {
|
||||
return { kind: "starting", pendingTurnStart: { anchorItemId: "local-user", promptSubmitHookItemIds: [] } };
|
||||
}
|
||||
|
||||
function activeTurnIdForMessageStream(lifecycle: MessageStreamTurnLifecycleState): string | null {
|
||||
return lifecycle.kind === "running" ? lifecycle.turnId : null;
|
||||
}
|
||||
|
||||
export function withMessageContentScrollHeight<T>(scrollHeight: number, fn: () => T): T {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scrollHeight");
|
||||
Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import {
|
|||
scrollComposerSuggestionIntoView,
|
||||
syncComposerHeight,
|
||||
type ComposerCallbacks,
|
||||
type ComposerSuggestion,
|
||||
} from "../../../../../src/features/chat/ui/composer";
|
||||
import type { ComposerSuggestion } from "../../../../../src/features/chat/application/composer/suggestions";
|
||||
import type { ComposerMetaViewModel } from "../../../../../src/features/chat/ui/composer";
|
||||
import { renderUiRoot } from "../../../../../src/shared/ui/ui-root";
|
||||
import { waitForAsyncWork } from "../../../../support/async";
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export function setBoxValue(box: Box): void {
|
|||
|
||||
it("does not confuse signal value writes with DOM writes", async () => {
|
||||
const messages = await lintSource(
|
||||
"src/features/chat/ui/shell-state.tsx",
|
||||
"src/features/chat/panel/shell-state.tsx",
|
||||
`
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
|
|
@ -247,7 +247,7 @@ export function mutateState(store: ChatStateStore): void {
|
|||
|
||||
it("allows signals in the chat shell-state adapter", async () => {
|
||||
const messages = await lintSource(
|
||||
"src/features/chat/ui/shell-state.tsx",
|
||||
"src/features/chat/panel/shell-state.tsx",
|
||||
`
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
|
|
@ -339,7 +339,7 @@ export const render = renderUiRoot;
|
|||
|
||||
it("allows Preact root adapter imports in explicit root bridge files", async () => {
|
||||
const messages = await lintSource(
|
||||
"src/features/chat/ui/shell.tsx",
|
||||
"src/features/chat/panel/shell.tsx",
|
||||
`
|
||||
import { renderUiRoot } from "../../../shared/ui/ui-root";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue