perf(ui): split right panel and secondary viewer chunks (#239)
## Summary - split the right panel, picker, and tool call secondary viewers into smaller deferred chunks - release hidden right-panel file buffers and stop tracking static tool-call scrollers when they are not needed - keep this branch focused on the remaining secondary viewer chunking work now that the Monaco-specific chunking moved into PR 215 ## Testing - npm run build --workspace @codenomad/ui
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
Show,
|
Show,
|
||||||
|
Suspense,
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
createSignal,
|
createSignal,
|
||||||
|
lazy,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
type Accessor,
|
type Accessor,
|
||||||
type Component,
|
type Component,
|
||||||
@@ -20,11 +22,6 @@ import type { Session } from "../../../../types/session"
|
|||||||
import type { DrawerViewState } from "../types"
|
import type { DrawerViewState } from "../types"
|
||||||
import type { DiffContextMode, DiffViewMode, DiffWordWrapMode, RightPanelTab } from "./types"
|
import type { DiffContextMode, DiffViewMode, DiffWordWrapMode, RightPanelTab } from "./types"
|
||||||
|
|
||||||
import ChangesTab from "./tabs/ChangesTab"
|
|
||||||
import FilesTab from "./tabs/FilesTab"
|
|
||||||
import GitChangesTab from "./tabs/GitChangesTab"
|
|
||||||
import StatusTab from "./tabs/StatusTab"
|
|
||||||
|
|
||||||
import { getDefaultWorktreeSlug, getOrCreateWorktreeClient, getWorktreeSlugForSession } from "../../../../stores/worktrees"
|
import { getDefaultWorktreeSlug, getOrCreateWorktreeClient, getWorktreeSlugForSession } from "../../../../stores/worktrees"
|
||||||
import { requestData } from "../../../../lib/opencode-api"
|
import { requestData } from "../../../../lib/opencode-api"
|
||||||
import { buildUnifiedDiffFromSdkPatch, tryReverseApplyUnifiedDiff } from "../../../../lib/unified-diff-reverse"
|
import { buildUnifiedDiffFromSdkPatch, tryReverseApplyUnifiedDiff } from "../../../../lib/unified-diff-reverse"
|
||||||
@@ -49,6 +46,15 @@ import {
|
|||||||
readStoredRightPanelTab,
|
readStoredRightPanelTab,
|
||||||
} from "../storage"
|
} from "../storage"
|
||||||
|
|
||||||
|
const LazyChangesTab = lazy(() => import("./tabs/ChangesTab"))
|
||||||
|
const LazyGitChangesTab = lazy(() => import("./tabs/GitChangesTab"))
|
||||||
|
const LazyFilesTab = lazy(() => import("./tabs/FilesTab"))
|
||||||
|
const LazyStatusTab = lazy(() => import("./tabs/StatusTab"))
|
||||||
|
|
||||||
|
function RightPanelTabFallback() {
|
||||||
|
return <div class="flex-1 min-h-0" />
|
||||||
|
}
|
||||||
|
|
||||||
interface RightPanelProps {
|
interface RightPanelProps {
|
||||||
t: (key: string, vars?: Record<string, any>) => string
|
t: (key: string, vars?: Record<string, any>) => string
|
||||||
|
|
||||||
@@ -565,6 +571,13 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
void loadBrowserEntries(browserPath())
|
void loadBrowserEntries(browserPath())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (rightPanelTab() === "files") return
|
||||||
|
setBrowserSelectedContent(null)
|
||||||
|
setBrowserSelectedLoading(false)
|
||||||
|
setBrowserSelectedError(null)
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (rightPanelTab() !== "git-changes") return
|
if (rightPanelTab() !== "git-changes") return
|
||||||
if (gitStatusLoading()) return
|
if (gitStatusLoading()) return
|
||||||
@@ -572,6 +585,14 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
void loadGitStatus()
|
void loadGitStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (rightPanelTab() === "git-changes") return
|
||||||
|
setGitSelectedBefore(null)
|
||||||
|
setGitSelectedAfter(null)
|
||||||
|
setGitSelectedLoading(false)
|
||||||
|
setGitSelectedError(null)
|
||||||
|
})
|
||||||
|
|
||||||
const handleSelectChangesFile = (file: string, closeList: boolean) => {
|
const handleSelectChangesFile = (file: string, closeList: boolean) => {
|
||||||
setSelectedFile(file)
|
setSelectedFile(file)
|
||||||
if (closeList) {
|
if (closeList) {
|
||||||
@@ -738,101 +759,109 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
|||||||
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<Show when={rightPanelTab() === "changes"}>
|
<Show when={rightPanelTab() === "changes"}>
|
||||||
<ChangesTab
|
<Suspense fallback={<RightPanelTabFallback />}>
|
||||||
t={props.t}
|
<LazyChangesTab
|
||||||
instanceId={props.instanceId}
|
t={props.t}
|
||||||
activeSessionId={props.activeSessionId}
|
instanceId={props.instanceId}
|
||||||
activeSessionDiffs={props.activeSessionDiffs}
|
activeSessionId={props.activeSessionId}
|
||||||
selectedFile={selectedFile}
|
activeSessionDiffs={props.activeSessionDiffs}
|
||||||
onSelectFile={handleSelectChangesFile}
|
selectedFile={selectedFile}
|
||||||
diffViewMode={diffViewMode}
|
onSelectFile={handleSelectChangesFile}
|
||||||
diffContextMode={diffContextMode}
|
diffViewMode={diffViewMode}
|
||||||
diffWordWrapMode={diffWordWrapMode}
|
diffContextMode={diffContextMode}
|
||||||
onViewModeChange={setDiffViewMode}
|
diffWordWrapMode={diffWordWrapMode}
|
||||||
onContextModeChange={setDiffContextMode}
|
onViewModeChange={setDiffViewMode}
|
||||||
onWordWrapModeChange={setDiffWordWrapMode}
|
onContextModeChange={setDiffContextMode}
|
||||||
listOpen={changesListOpen}
|
onWordWrapModeChange={setDiffWordWrapMode}
|
||||||
onToggleList={toggleChangesList}
|
listOpen={changesListOpen}
|
||||||
splitWidth={changesSplitWidth}
|
onToggleList={toggleChangesList}
|
||||||
onResizeMouseDown={handleSplitResizeMouseDown("changes")}
|
splitWidth={changesSplitWidth}
|
||||||
onResizeTouchStart={handleSplitResizeTouchStart("changes")}
|
onResizeMouseDown={handleSplitResizeMouseDown("changes")}
|
||||||
isPhoneLayout={props.isPhoneLayout}
|
onResizeTouchStart={handleSplitResizeTouchStart("changes")}
|
||||||
/>
|
isPhoneLayout={props.isPhoneLayout}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={rightPanelTab() === "git-changes"}>
|
<Show when={rightPanelTab() === "git-changes"}>
|
||||||
<GitChangesTab
|
<Suspense fallback={<RightPanelTabFallback />}>
|
||||||
t={props.t}
|
<LazyGitChangesTab
|
||||||
activeSessionId={props.activeSessionId}
|
t={props.t}
|
||||||
entries={gitStatusEntries}
|
activeSessionId={props.activeSessionId}
|
||||||
statusLoading={gitStatusLoading}
|
entries={gitStatusEntries}
|
||||||
statusError={gitStatusError}
|
statusLoading={gitStatusLoading}
|
||||||
selectedPath={gitSelectedPath}
|
statusError={gitStatusError}
|
||||||
selectedLoading={gitSelectedLoading}
|
selectedPath={gitSelectedPath}
|
||||||
selectedError={gitSelectedError}
|
selectedLoading={gitSelectedLoading}
|
||||||
selectedBefore={gitSelectedBefore}
|
selectedError={gitSelectedError}
|
||||||
selectedAfter={gitSelectedAfter}
|
selectedBefore={gitSelectedBefore}
|
||||||
mostChangedPath={gitMostChangedPath}
|
selectedAfter={gitSelectedAfter}
|
||||||
scopeKey={gitScopeKey}
|
mostChangedPath={gitMostChangedPath}
|
||||||
diffViewMode={diffViewMode}
|
scopeKey={gitScopeKey}
|
||||||
diffContextMode={diffContextMode}
|
diffViewMode={diffViewMode}
|
||||||
diffWordWrapMode={diffWordWrapMode}
|
diffContextMode={diffContextMode}
|
||||||
onViewModeChange={setDiffViewMode}
|
diffWordWrapMode={diffWordWrapMode}
|
||||||
onContextModeChange={setDiffContextMode}
|
onViewModeChange={setDiffViewMode}
|
||||||
onWordWrapModeChange={setDiffWordWrapMode}
|
onContextModeChange={setDiffContextMode}
|
||||||
onOpenFile={(path) => void openGitFile(path)}
|
onWordWrapModeChange={setDiffWordWrapMode}
|
||||||
onRefresh={() => void refreshGitStatus()}
|
onOpenFile={(path: string) => void openGitFile(path)}
|
||||||
listOpen={gitChangesListOpen}
|
onRefresh={() => void refreshGitStatus()}
|
||||||
onToggleList={toggleGitList}
|
listOpen={gitChangesListOpen}
|
||||||
splitWidth={gitChangesSplitWidth}
|
onToggleList={toggleGitList}
|
||||||
onResizeMouseDown={handleSplitResizeMouseDown("git-changes")}
|
splitWidth={gitChangesSplitWidth}
|
||||||
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
|
onResizeMouseDown={handleSplitResizeMouseDown("git-changes")}
|
||||||
isPhoneLayout={props.isPhoneLayout}
|
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
|
||||||
/>
|
isPhoneLayout={props.isPhoneLayout}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={rightPanelTab() === "files"}>
|
<Show when={rightPanelTab() === "files"}>
|
||||||
<FilesTab
|
<Suspense fallback={<RightPanelTabFallback />}>
|
||||||
t={props.t}
|
<LazyFilesTab
|
||||||
browserPath={browserPath}
|
t={props.t}
|
||||||
browserEntries={browserEntries}
|
browserPath={browserPath}
|
||||||
browserLoading={browserLoading}
|
browserEntries={browserEntries}
|
||||||
browserError={browserError}
|
browserLoading={browserLoading}
|
||||||
browserSelectedPath={browserSelectedPath}
|
browserError={browserError}
|
||||||
browserSelectedContent={browserSelectedContent}
|
browserSelectedPath={browserSelectedPath}
|
||||||
browserSelectedLoading={browserSelectedLoading}
|
browserSelectedContent={browserSelectedContent}
|
||||||
browserSelectedError={browserSelectedError}
|
browserSelectedLoading={browserSelectedLoading}
|
||||||
parentPath={browserParentPath}
|
browserSelectedError={browserSelectedError}
|
||||||
scopeKey={browserScopeKey}
|
parentPath={browserParentPath}
|
||||||
onLoadEntries={(path) => void loadBrowserEntries(path)}
|
scopeKey={browserScopeKey}
|
||||||
onOpenFile={(path) => void openBrowserFile(path)}
|
onLoadEntries={(path: string) => void loadBrowserEntries(path)}
|
||||||
onRefresh={() => void refreshFilesTab()}
|
onOpenFile={(path: string) => void openBrowserFile(path)}
|
||||||
listOpen={filesListOpen}
|
onRefresh={() => void refreshFilesTab()}
|
||||||
onToggleList={toggleFilesList}
|
listOpen={filesListOpen}
|
||||||
splitWidth={filesSplitWidth}
|
onToggleList={toggleFilesList}
|
||||||
onResizeMouseDown={handleSplitResizeMouseDown("files")}
|
splitWidth={filesSplitWidth}
|
||||||
onResizeTouchStart={handleSplitResizeTouchStart("files")}
|
onResizeMouseDown={handleSplitResizeMouseDown("files")}
|
||||||
isPhoneLayout={props.isPhoneLayout}
|
onResizeTouchStart={handleSplitResizeTouchStart("files")}
|
||||||
/>
|
isPhoneLayout={props.isPhoneLayout}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={rightPanelTab() === "status"}>
|
<Show when={rightPanelTab() === "status"}>
|
||||||
<StatusTab
|
<Suspense fallback={<RightPanelTabFallback />}>
|
||||||
t={props.t}
|
<LazyStatusTab
|
||||||
instanceId={props.instanceId}
|
t={props.t}
|
||||||
instance={props.instance}
|
instanceId={props.instanceId}
|
||||||
activeSessionId={props.activeSessionId}
|
instance={props.instance}
|
||||||
activeSession={props.activeSession}
|
activeSessionId={props.activeSessionId}
|
||||||
activeSessionDiffs={props.activeSessionDiffs}
|
activeSession={props.activeSession}
|
||||||
latestTodoState={props.latestTodoState}
|
activeSessionDiffs={props.activeSessionDiffs}
|
||||||
backgroundProcessList={props.backgroundProcessList}
|
latestTodoState={props.latestTodoState}
|
||||||
onOpenBackgroundOutput={props.onOpenBackgroundOutput}
|
backgroundProcessList={props.backgroundProcessList}
|
||||||
onStopBackgroundProcess={props.onStopBackgroundProcess}
|
onOpenBackgroundOutput={props.onOpenBackgroundOutput}
|
||||||
onTerminateBackgroundProcess={props.onTerminateBackgroundProcess}
|
onStopBackgroundProcess={props.onStopBackgroundProcess}
|
||||||
expandedItems={rightPanelExpandedItems}
|
onTerminateBackgroundProcess={props.onTerminateBackgroundProcess}
|
||||||
onExpandedItemsChange={handleAccordionChange}
|
expandedItems={rightPanelExpandedItems}
|
||||||
onOpenChangesTab={openChangesTabFromStatus}
|
onExpandedItemsChange={handleAccordionChange}
|
||||||
/>
|
onOpenChangesTab={openChangesTabFromStatus}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js"
|
import { For, Match, Show, Suspense, Switch, createEffect, createMemo, createSignal, lazy, onCleanup, untrack } from "solid-js"
|
||||||
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, ListStart, Trash } from "lucide-solid"
|
import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, ListStart, Trash } from "lucide-solid"
|
||||||
import MessageItem from "./message-item"
|
import MessageItem from "./message-item"
|
||||||
import ToolCall from "./tool-call"
|
|
||||||
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||||
import type { ClientPart, MessageInfo } from "../types/message"
|
import type { ClientPart, MessageInfo } from "../types/message"
|
||||||
import { partHasRenderableText } from "../types/message"
|
import { partHasRenderableText } from "../types/message"
|
||||||
@@ -29,6 +28,12 @@ const USER_BORDER_COLOR = "var(--message-user-border)"
|
|||||||
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
|
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
|
||||||
const TOOL_BORDER_COLOR = "var(--message-tool-border)"
|
const TOOL_BORDER_COLOR = "var(--message-tool-border)"
|
||||||
|
|
||||||
|
const LazyToolCall = lazy(() => import("./tool-call"))
|
||||||
|
|
||||||
|
function ToolCallFallback() {
|
||||||
|
return <div class="tool-call tool-call-loading" />
|
||||||
|
}
|
||||||
|
|
||||||
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
||||||
|
|
||||||
|
|
||||||
@@ -500,16 +505,18 @@ function ToolCallItem(props: ToolCallItemProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToolCall
|
<Suspense fallback={<ToolCallFallback />}>
|
||||||
toolCall={resolvedToolPart()}
|
<LazyToolCall
|
||||||
toolCallId={props.partId}
|
toolCall={resolvedToolPart()}
|
||||||
messageId={props.messageId}
|
toolCallId={props.partId}
|
||||||
messageVersion={messageVersion()}
|
messageId={props.messageId}
|
||||||
partVersion={partVersion()}
|
messageVersion={messageVersion()}
|
||||||
instanceId={props.instanceId}
|
partVersion={partVersion()}
|
||||||
sessionId={props.sessionId}
|
instanceId={props.instanceId}
|
||||||
onContentRendered={props.onContentRendered}
|
sessionId={props.sessionId}
|
||||||
/>
|
onContentRendered={props.onContentRendered}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Show, Match, Switch } from "solid-js"
|
import { Match, Show, Suspense, Switch, lazy } from "solid-js"
|
||||||
import ToolCall from "./tool-call"
|
|
||||||
import { isItemExpanded, toggleItemExpanded } from "../stores/tool-call-state"
|
import { isItemExpanded, toggleItemExpanded } from "../stores/tool-call-state"
|
||||||
import { Markdown } from "./markdown"
|
import { Markdown } from "./markdown"
|
||||||
import { useTheme } from "../lib/theme"
|
import { useTheme } from "../lib/theme"
|
||||||
@@ -7,6 +6,8 @@ import { partHasRenderableText, SDKPart, TextPart, ClientPart } from "../types/m
|
|||||||
|
|
||||||
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
||||||
|
|
||||||
|
const LazyToolCall = lazy(() => import("./tool-call"))
|
||||||
|
|
||||||
interface MessagePartProps {
|
interface MessagePartProps {
|
||||||
part: ClientPart
|
part: ClientPart
|
||||||
messageType?: "user" | "assistant"
|
messageType?: "user" | "assistant"
|
||||||
@@ -153,12 +154,14 @@ export default function MessagePart(props: MessagePartProps) {
|
|||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
<Match when={partType() === "tool"}>
|
<Match when={partType() === "tool"}>
|
||||||
<ToolCall
|
<Suspense fallback={<div class="tool-call tool-call-loading" />}>
|
||||||
toolCall={props.part as ToolCallPart}
|
<LazyToolCall
|
||||||
toolCallId={props.part?.id}
|
toolCall={props.part as ToolCallPart}
|
||||||
instanceId={props.instanceId}
|
toolCallId={props.part?.id}
|
||||||
sessionId={props.sessionId}
|
instanceId={props.instanceId}
|
||||||
/>
|
sessionId={props.sessionId}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { For, Show, createMemo, createSignal, createEffect, onCleanup, type Component } from "solid-js"
|
import { For, Show, Suspense, createMemo, createSignal, createEffect, lazy, onCleanup, type Component } from "solid-js"
|
||||||
import type { PermissionRequestLike } from "../types/permission"
|
import type { PermissionRequestLike } from "../types/permission"
|
||||||
import { getPermissionCallId, getPermissionDisplayTitle, getPermissionKind, getPermissionMessageId, getPermissionSessionId } from "../types/permission"
|
import { getPermissionCallId, getPermissionDisplayTitle, getPermissionKind, getPermissionMessageId, getPermissionSessionId } from "../types/permission"
|
||||||
import { getQuestionCallId, getQuestionMessageId, getQuestionSessionId, type QuestionRequest } from "../types/question"
|
import { getQuestionCallId, getQuestionMessageId, getQuestionSessionId, type QuestionRequest } from "../types/question"
|
||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
} from "../stores/instances"
|
} from "../stores/instances"
|
||||||
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
||||||
import { messageStoreBus } from "../stores/message-v2/bus"
|
import { messageStoreBus } from "../stores/message-v2/bus"
|
||||||
import ToolCall from "./tool-call"
|
|
||||||
|
const LazyToolCall = lazy(() => import("./tool-call"))
|
||||||
|
|
||||||
interface PermissionApprovalModalProps {
|
interface PermissionApprovalModalProps {
|
||||||
instanceId: string
|
instanceId: string
|
||||||
@@ -408,15 +409,17 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(data) => (
|
{(data) => (
|
||||||
<ToolCall
|
<Suspense fallback={<div class="tool-call tool-call-loading" />}>
|
||||||
toolCall={data().toolPart}
|
<LazyToolCall
|
||||||
toolCallId={data().toolPart.id}
|
toolCall={data().toolPart}
|
||||||
messageId={data().messageId}
|
toolCallId={data().toolPart.id}
|
||||||
messageVersion={data().messageVersion}
|
messageId={data().messageId}
|
||||||
partVersion={data().partVersion}
|
messageVersion={data().messageVersion}
|
||||||
instanceId={props.instanceId}
|
partVersion={data().partVersion}
|
||||||
sessionId={data().sessionId}
|
instanceId={props.instanceId}
|
||||||
/>
|
sessionId={data().sessionId}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createSignal, Show, onMount, onCleanup, createEffect, on } from "solid-js"
|
import { Suspense, createEffect, createSignal, lazy, on, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { ArrowBigUp, ArrowBigDown } from "lucide-solid"
|
import { ArrowBigUp, ArrowBigDown } from "lucide-solid"
|
||||||
import UnifiedPicker from "./unified-picker"
|
|
||||||
import ExpandButton from "./expand-button"
|
import ExpandButton from "./expand-button"
|
||||||
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
||||||
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
||||||
@@ -20,6 +19,7 @@ import { usePromptAttachments } from "./prompt-input/usePromptAttachments"
|
|||||||
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
||||||
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
||||||
const log = getLogger("actions")
|
const log = getLogger("actions")
|
||||||
|
const LazyUnifiedPicker = lazy(() => import("./unified-picker"))
|
||||||
|
|
||||||
function getConsumedPastedTextAttachmentIds(text: string, attachments: Attachment[]): string[] {
|
function getConsumedPastedTextAttachmentIds(text: string, attachments: Attachment[]): string[] {
|
||||||
if (!text || attachments.length === 0) return []
|
if (!text || attachments.length === 0) return []
|
||||||
@@ -467,18 +467,20 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
<Show when={showPicker() && instance()}>
|
<Show when={showPicker() && instance()}>
|
||||||
<UnifiedPicker
|
<Suspense fallback={null}>
|
||||||
open={showPicker()}
|
<LazyUnifiedPicker
|
||||||
mode={pickerMode()}
|
open={showPicker()}
|
||||||
onClose={handlePickerClose}
|
mode={pickerMode()}
|
||||||
onSelect={handlePickerSelect}
|
onClose={handlePickerClose}
|
||||||
agents={instanceAgents()}
|
onSelect={handlePickerSelect}
|
||||||
commands={getCommands(props.instanceId)}
|
agents={instanceAgents()}
|
||||||
instanceClient={instance()!.client}
|
commands={getCommands(props.instanceId)}
|
||||||
searchQuery={searchQuery()}
|
instanceClient={instance()!.client}
|
||||||
textareaRef={textareaRef}
|
searchQuery={searchQuery()}
|
||||||
workspaceId={props.instanceId}
|
textareaRef={textareaRef}
|
||||||
/>
|
workspaceId={props.instanceId}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col">
|
||||||
|
|||||||
@@ -514,6 +514,7 @@ function ToolCallDetails(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { renderDiffContent } = createDiffContentRenderer({
|
const { renderDiffContent } = createDiffContentRenderer({
|
||||||
|
toolState: props.toolState,
|
||||||
preferences: props.preferences,
|
preferences: props.preferences,
|
||||||
setDiffViewMode: props.setDiffViewMode,
|
setDiffViewMode: props.setDiffViewMode,
|
||||||
isDark: props.isDark,
|
isDark: props.isDark,
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ export function createAnsiContentRenderer(params: {
|
|||||||
const runningAnsiRenderer = createAnsiStreamRenderer()
|
const runningAnsiRenderer = createAnsiStreamRenderer()
|
||||||
let runningAnsiSource = ""
|
let runningAnsiSource = ""
|
||||||
|
|
||||||
|
const registerTracked = (element: HTMLDivElement | null) => {
|
||||||
|
params.scrollHelpers.registerContainer(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerUntracked = (element: HTMLDivElement | null) => {
|
||||||
|
params.scrollHelpers.registerContainer(element, { disableTracking: true })
|
||||||
|
}
|
||||||
|
|
||||||
const getMode = () => {
|
const getMode = () => {
|
||||||
const version = params.partVersion?.()
|
const version = params.partVersion?.()
|
||||||
return typeof version === "number" ? String(version) : undefined
|
return typeof version === "number" ? String(version) : undefined
|
||||||
@@ -36,6 +44,8 @@ export function createAnsiContentRenderer(params: {
|
|||||||
const cached = cacheHandle.get<AnsiRenderCache>()
|
const cached = cacheHandle.get<AnsiRenderCache>()
|
||||||
const mode = getMode()
|
const mode = getMode()
|
||||||
const isRunningVariant = options.variant === "running"
|
const isRunningVariant = options.variant === "running"
|
||||||
|
const disableScrollTracking = !isRunningVariant
|
||||||
|
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
||||||
|
|
||||||
let nextCache: AnsiRenderCache
|
let nextCache: AnsiRenderCache
|
||||||
|
|
||||||
@@ -87,9 +97,9 @@ export function createAnsiContentRenderer(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={messageClass} ref={params.scrollHelpers.registerContainer} onScroll={params.scrollHelpers.handleScroll}>
|
<div class={messageClass} ref={registerRef} onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}>
|
||||||
<pre class="tool-call-content tool-call-ansi" dir="auto" innerHTML={nextCache.html} />
|
<pre class="tool-call-content tool-call-ansi" dir="auto" innerHTML={nextCache.html} />
|
||||||
{params.scrollHelpers.renderSentinel()}
|
{params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Suspense, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
|
import { Suspense, lazy, onMount, type Accessor, type JSXElement } from "solid-js"
|
||||||
|
import type { ToolState } from "@opencode-ai/sdk/v2"
|
||||||
import type { RenderCache } from "../../types/message"
|
import type { RenderCache } from "../../types/message"
|
||||||
import type { DiffViewMode } from "../../stores/preferences"
|
import type { DiffViewMode } from "../../stores/preferences"
|
||||||
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
||||||
@@ -31,6 +32,7 @@ type DiffPrefs = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createDiffContentRenderer(params: {
|
export function createDiffContentRenderer(params: {
|
||||||
|
toolState: Accessor<ToolState | undefined>
|
||||||
preferences: Accessor<DiffPrefs>
|
preferences: Accessor<DiffPrefs>
|
||||||
setDiffViewMode: (mode: DiffViewMode) => void
|
setDiffViewMode: (mode: DiffViewMode) => void
|
||||||
isDark: Accessor<boolean>
|
isDark: Accessor<boolean>
|
||||||
@@ -58,7 +60,10 @@ export function createDiffContentRenderer(params: {
|
|||||||
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
||||||
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
|
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
|
||||||
const themeKey = params.isDark() ? "dark" : "light"
|
const themeKey = params.isDark() ? "dark" : "light"
|
||||||
const disableScrollTracking = Boolean(options?.disableScrollTracking)
|
const state = params.toolState()
|
||||||
|
const disableScrollTracking = Boolean(
|
||||||
|
options?.disableScrollTracking || (state?.status !== "running" && state?.status !== "pending"),
|
||||||
|
)
|
||||||
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
||||||
|
|
||||||
const baseEntryParams = cacheHandle.params() as any
|
const baseEntryParams = cacheHandle.params() as any
|
||||||
|
|||||||
@@ -31,10 +31,9 @@ export function createMarkdownContentRenderer(params: {
|
|||||||
const size = options.size || "default"
|
const size = options.size || "default"
|
||||||
const disableHighlight = options.disableHighlight || false
|
const disableHighlight = options.disableHighlight || false
|
||||||
const messageClass = `message-text tool-call-markdown${size === "large" ? " tool-call-markdown-large" : ""}`
|
const messageClass = `message-text tool-call-markdown${size === "large" ? " tool-call-markdown-large" : ""}`
|
||||||
const disableScrollTracking = options.disableScrollTracking || false
|
|
||||||
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
|
||||||
|
|
||||||
const state = params.toolState()
|
const state = params.toolState()
|
||||||
|
const disableScrollTracking = options.disableScrollTracking || (state?.status !== "running" && state?.status !== "pending")
|
||||||
|
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
||||||
const shouldDeferMarkdown = Boolean(state && (state.status === "running" || state.status === "pending") && disableHighlight)
|
const shouldDeferMarkdown = Boolean(state && (state.status === "running" || state.status === "pending") && disableHighlight)
|
||||||
if (shouldDeferMarkdown) {
|
if (shouldDeferMarkdown) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user