Compare commits
2 Commits
v0.12.3-de
...
v0.12.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bad0afd7d | ||
|
|
8567d49178 |
@@ -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 (
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import { createContext, createEffect, createMemo, createSignal, onCleanup, onMou
|
|||||||
import type { ParentComponent } from "solid-js"
|
import type { ParentComponent } from "solid-js"
|
||||||
import { useConfig } from "../../stores/preferences"
|
import { useConfig } from "../../stores/preferences"
|
||||||
import { enMessages } from "./messages/en"
|
import { enMessages } from "./messages/en"
|
||||||
import { esMessages } from "./messages/es"
|
|
||||||
import { frMessages } from "./messages/fr"
|
|
||||||
import { ruMessages } from "./messages/ru"
|
|
||||||
import { jaMessages } from "./messages/ja"
|
|
||||||
import { zhHansMessages } from "./messages/zh-Hans"
|
|
||||||
|
|
||||||
type Messages = Record<string, string>
|
type Messages = Record<string, string>
|
||||||
|
|
||||||
@@ -15,14 +10,18 @@ export type TranslateParams = Record<string, unknown>
|
|||||||
export type Locale = "en" | "es" | "fr" | "ru" | "ja" | "zh-Hans"
|
export type Locale = "en" | "es" | "fr" | "ru" | "ja" | "zh-Hans"
|
||||||
|
|
||||||
const SUPPORTED_LOCALES: readonly Locale[] = ["en", "es", "fr", "ru", "ja", "zh-Hans"] as const
|
const SUPPORTED_LOCALES: readonly Locale[] = ["en", "es", "fr", "ru", "ja", "zh-Hans"] as const
|
||||||
|
const SUPPORTED_LOCALES_BY_LOWER = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
||||||
|
|
||||||
const messagesByLocale: Record<Locale, Messages> = {
|
const localeMessagesCache = new Map<Locale, Messages>([["en", enMessages]])
|
||||||
en: enMessages,
|
const localeMessagesPromises = new Map<Locale, Promise<Messages>>()
|
||||||
es: esMessages,
|
|
||||||
fr: frMessages,
|
const localeLoaders: Record<Locale, () => Promise<Messages>> = {
|
||||||
ru: ruMessages,
|
en: async () => enMessages,
|
||||||
ja: jaMessages,
|
es: async () => (await import("./messages/es")).esMessages,
|
||||||
"zh-Hans": zhHansMessages,
|
fr: async () => (await import("./messages/fr")).frMessages,
|
||||||
|
ru: async () => (await import("./messages/ru")).ruMessages,
|
||||||
|
ja: async () => (await import("./messages/ja")).jaMessages,
|
||||||
|
"zh-Hans": async () => (await import("./messages/zh-Hans")).zhHansMessages,
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLocaleTag(value: string): string {
|
function normalizeLocaleTag(value: string): string {
|
||||||
@@ -34,8 +33,7 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
|||||||
|
|
||||||
const normalized = normalizeLocaleTag(value)
|
const normalized = normalizeLocaleTag(value)
|
||||||
const lower = normalized.toLowerCase()
|
const lower = normalized.toLowerCase()
|
||||||
const supportedLower = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
const exact = SUPPORTED_LOCALES_BY_LOWER.get(lower)
|
||||||
const exact = supportedLower.get(lower)
|
|
||||||
if (exact) return exact
|
if (exact) return exact
|
||||||
|
|
||||||
const parts = lower.split("-")
|
const parts = lower.split("-")
|
||||||
@@ -43,11 +41,11 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
|||||||
if (!base) return null
|
if (!base) return null
|
||||||
|
|
||||||
if (base === "zh") {
|
if (base === "zh") {
|
||||||
const zhHans = supportedLower.get("zh-hans")
|
const zhHans = SUPPORTED_LOCALES_BY_LOWER.get("zh-hans")
|
||||||
return zhHans ?? null
|
return zhHans ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMatch = supportedLower.get(base)
|
const baseMatch = SUPPORTED_LOCALES_BY_LOWER.get(base)
|
||||||
return baseMatch ?? null
|
return baseMatch ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +82,54 @@ function translateFrom(messages: Messages, key: string, params?: TranslateParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [globalRevision, setGlobalRevision] = createSignal(0)
|
const [globalRevision, setGlobalRevision] = createSignal(0)
|
||||||
const initialGlobalLocale: Locale = detectNavigatorLocale() ?? "en"
|
let globalMessages: Messages = enMessages
|
||||||
let globalMessages: Messages = messagesByLocale[initialGlobalLocale]
|
let globalLocale: Locale = "en"
|
||||||
|
|
||||||
|
function getMessagesForLocale(locale: Locale): Messages {
|
||||||
|
return localeMessagesCache.get(locale) ?? enMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLocaleMessages(locale: Locale): Promise<Messages> {
|
||||||
|
const cached = localeMessagesCache.get(locale)
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = localeMessagesPromises.get(locale)
|
||||||
|
if (pending) {
|
||||||
|
return pending
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = localeLoaders[locale]
|
||||||
|
const promise = loader()
|
||||||
|
.then((messages) => {
|
||||||
|
localeMessagesCache.set(locale, messages)
|
||||||
|
localeMessagesPromises.delete(locale)
|
||||||
|
return messages
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
localeMessagesPromises.delete(locale)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
|
||||||
|
localeMessagesPromises.set(locale, promise)
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preloadLocaleMessages(preferredLocale?: string | null): Promise<Locale> {
|
||||||
|
const resolvedLocale = matchSupportedLocale(preferredLocale ?? undefined) ?? detectNavigatorLocale() ?? "en"
|
||||||
|
try {
|
||||||
|
globalMessages = await loadLocaleMessages(resolvedLocale)
|
||||||
|
globalLocale = resolvedLocale
|
||||||
|
setGlobalRevision((value) => value + 1)
|
||||||
|
return resolvedLocale
|
||||||
|
} catch {
|
||||||
|
globalMessages = enMessages
|
||||||
|
globalLocale = "en"
|
||||||
|
setGlobalRevision((value) => value + 1)
|
||||||
|
return "en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function tGlobal(key: string, params?: TranslateParams): string {
|
export function tGlobal(key: string, params?: TranslateParams): string {
|
||||||
globalRevision()
|
globalRevision()
|
||||||
@@ -101,9 +145,10 @@ const I18nContext = createContext<I18nContextValue>()
|
|||||||
|
|
||||||
export const I18nProvider: ParentComponent = (props) => {
|
export const I18nProvider: ParentComponent = (props) => {
|
||||||
const { preferences } = useConfig()
|
const { preferences } = useConfig()
|
||||||
const [detectedLocale, setDetectedLocale] = createSignal<Locale>("en")
|
const [detectedLocale, setDetectedLocale] = createSignal<Locale>(globalLocale)
|
||||||
|
const [resolvedLocale, setResolvedLocale] = createSignal<Locale>(globalLocale)
|
||||||
const previousMessages = globalMessages
|
const previousGlobalMessages = globalMessages
|
||||||
|
const previousGlobalLocale = globalLocale
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const detected = detectNavigatorLocale()
|
const detected = detectNavigatorLocale()
|
||||||
@@ -115,19 +160,44 @@ export const I18nProvider: ParentComponent = (props) => {
|
|||||||
return configured ?? detectedLocale() ?? "en"
|
return configured ?? detectedLocale() ?? "en"
|
||||||
})
|
})
|
||||||
|
|
||||||
const messages = createMemo<Messages>(() => messagesByLocale[locale()])
|
const messages = createMemo<Messages>(() => getMessagesForLocale(resolvedLocale()))
|
||||||
|
|
||||||
function t(key: string, params?: TranslateParams): string {
|
function t(key: string, params?: TranslateParams): string {
|
||||||
return translateFrom(messages(), key, params)
|
return translateFrom(messages(), key, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
globalMessages = messages()
|
const nextLocale = locale()
|
||||||
setGlobalRevision((value) => value + 1)
|
let cancelled = false
|
||||||
|
|
||||||
|
void loadLocaleMessages(nextLocale)
|
||||||
|
.then((loadedMessages) => {
|
||||||
|
if (cancelled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setResolvedLocale(nextLocale)
|
||||||
|
globalLocale = nextLocale
|
||||||
|
globalMessages = loadedMessages
|
||||||
|
setGlobalRevision((value) => value + 1)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (cancelled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setResolvedLocale("en")
|
||||||
|
globalMessages = enMessages
|
||||||
|
globalLocale = "en"
|
||||||
|
setGlobalRevision((value) => value + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
cancelled = true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
globalMessages = previousMessages
|
globalMessages = previousGlobalMessages
|
||||||
|
globalLocale = previousGlobalLocale
|
||||||
setGlobalRevision((value) => value + 1)
|
setGlobalRevision((value) => value + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ThemeProvider } from "./lib/theme"
|
|||||||
import { ConfigProvider } from "./stores/preferences"
|
import { ConfigProvider } from "./stores/preferences"
|
||||||
import { InstanceConfigProvider } from "./stores/instance-config"
|
import { InstanceConfigProvider } from "./stores/instance-config"
|
||||||
import { runtimeEnv } from "./lib/runtime-env"
|
import { runtimeEnv } from "./lib/runtime-env"
|
||||||
import { I18nProvider } from "./lib/i18n"
|
import { I18nProvider, preloadLocaleMessages } from "./lib/i18n"
|
||||||
import { storage } from "./lib/storage"
|
import { storage } from "./lib/storage"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||||
@@ -31,15 +31,19 @@ async function bootstrap() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const uiConfig = await storage.loadConfigOwner("ui")
|
const uiConfig = await storage.loadConfigOwner("ui")
|
||||||
const theme = (uiConfig as any)?.theme ?? "system"
|
const theme = (uiConfig as any)?.theme
|
||||||
|
const locale = typeof (uiConfig as any)?.settings?.locale === "string" ? (uiConfig as any).settings.locale : undefined
|
||||||
|
|
||||||
if (theme === "system") {
|
if (theme === "light" || theme === "dark") {
|
||||||
document.documentElement.removeAttribute("data-theme")
|
|
||||||
} else {
|
|
||||||
document.documentElement.setAttribute("data-theme", theme)
|
document.documentElement.setAttribute("data-theme", theme)
|
||||||
|
} else {
|
||||||
|
document.documentElement.removeAttribute("data-theme")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await preloadLocaleMessages(locale)
|
||||||
} catch {
|
} catch {
|
||||||
// If config fails to load, fall back to CSS defaults.
|
// If config fails to load, fall back to CSS defaults.
|
||||||
|
await preloadLocaleMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user