Compare commits
2 Commits
v0.12.3-de
...
v0.12.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bad0afd7d | ||
|
|
8567d49178 |
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
Show,
|
||||
Suspense,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
lazy,
|
||||
onCleanup,
|
||||
type Accessor,
|
||||
type Component,
|
||||
@@ -20,11 +22,6 @@ import type { Session } from "../../../../types/session"
|
||||
import type { DrawerViewState } 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 { requestData } from "../../../../lib/opencode-api"
|
||||
import { buildUnifiedDiffFromSdkPatch, tryReverseApplyUnifiedDiff } from "../../../../lib/unified-diff-reverse"
|
||||
@@ -49,6 +46,15 @@ import {
|
||||
readStoredRightPanelTab,
|
||||
} 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 {
|
||||
t: (key: string, vars?: Record<string, any>) => string
|
||||
|
||||
@@ -565,6 +571,13 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
||||
void loadBrowserEntries(browserPath())
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (rightPanelTab() === "files") return
|
||||
setBrowserSelectedContent(null)
|
||||
setBrowserSelectedLoading(false)
|
||||
setBrowserSelectedError(null)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (rightPanelTab() !== "git-changes") return
|
||||
if (gitStatusLoading()) return
|
||||
@@ -572,6 +585,14 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
||||
void loadGitStatus()
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (rightPanelTab() === "git-changes") return
|
||||
setGitSelectedBefore(null)
|
||||
setGitSelectedAfter(null)
|
||||
setGitSelectedLoading(false)
|
||||
setGitSelectedError(null)
|
||||
})
|
||||
|
||||
const handleSelectChangesFile = (file: string, closeList: boolean) => {
|
||||
setSelectedFile(file)
|
||||
if (closeList) {
|
||||
@@ -738,101 +759,109 @@ const RightPanel: Component<RightPanelProps> = (props) => {
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<Show when={rightPanelTab() === "changes"}>
|
||||
<ChangesTab
|
||||
t={props.t}
|
||||
instanceId={props.instanceId}
|
||||
activeSessionId={props.activeSessionId}
|
||||
activeSessionDiffs={props.activeSessionDiffs}
|
||||
selectedFile={selectedFile}
|
||||
onSelectFile={handleSelectChangesFile}
|
||||
diffViewMode={diffViewMode}
|
||||
diffContextMode={diffContextMode}
|
||||
diffWordWrapMode={diffWordWrapMode}
|
||||
onViewModeChange={setDiffViewMode}
|
||||
onContextModeChange={setDiffContextMode}
|
||||
onWordWrapModeChange={setDiffWordWrapMode}
|
||||
listOpen={changesListOpen}
|
||||
onToggleList={toggleChangesList}
|
||||
splitWidth={changesSplitWidth}
|
||||
onResizeMouseDown={handleSplitResizeMouseDown("changes")}
|
||||
onResizeTouchStart={handleSplitResizeTouchStart("changes")}
|
||||
isPhoneLayout={props.isPhoneLayout}
|
||||
/>
|
||||
<Suspense fallback={<RightPanelTabFallback />}>
|
||||
<LazyChangesTab
|
||||
t={props.t}
|
||||
instanceId={props.instanceId}
|
||||
activeSessionId={props.activeSessionId}
|
||||
activeSessionDiffs={props.activeSessionDiffs}
|
||||
selectedFile={selectedFile}
|
||||
onSelectFile={handleSelectChangesFile}
|
||||
diffViewMode={diffViewMode}
|
||||
diffContextMode={diffContextMode}
|
||||
diffWordWrapMode={diffWordWrapMode}
|
||||
onViewModeChange={setDiffViewMode}
|
||||
onContextModeChange={setDiffContextMode}
|
||||
onWordWrapModeChange={setDiffWordWrapMode}
|
||||
listOpen={changesListOpen}
|
||||
onToggleList={toggleChangesList}
|
||||
splitWidth={changesSplitWidth}
|
||||
onResizeMouseDown={handleSplitResizeMouseDown("changes")}
|
||||
onResizeTouchStart={handleSplitResizeTouchStart("changes")}
|
||||
isPhoneLayout={props.isPhoneLayout}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
|
||||
<Show when={rightPanelTab() === "git-changes"}>
|
||||
<GitChangesTab
|
||||
t={props.t}
|
||||
activeSessionId={props.activeSessionId}
|
||||
entries={gitStatusEntries}
|
||||
statusLoading={gitStatusLoading}
|
||||
statusError={gitStatusError}
|
||||
selectedPath={gitSelectedPath}
|
||||
selectedLoading={gitSelectedLoading}
|
||||
selectedError={gitSelectedError}
|
||||
selectedBefore={gitSelectedBefore}
|
||||
selectedAfter={gitSelectedAfter}
|
||||
mostChangedPath={gitMostChangedPath}
|
||||
scopeKey={gitScopeKey}
|
||||
diffViewMode={diffViewMode}
|
||||
diffContextMode={diffContextMode}
|
||||
diffWordWrapMode={diffWordWrapMode}
|
||||
onViewModeChange={setDiffViewMode}
|
||||
onContextModeChange={setDiffContextMode}
|
||||
onWordWrapModeChange={setDiffWordWrapMode}
|
||||
onOpenFile={(path) => void openGitFile(path)}
|
||||
onRefresh={() => void refreshGitStatus()}
|
||||
listOpen={gitChangesListOpen}
|
||||
onToggleList={toggleGitList}
|
||||
splitWidth={gitChangesSplitWidth}
|
||||
onResizeMouseDown={handleSplitResizeMouseDown("git-changes")}
|
||||
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
|
||||
isPhoneLayout={props.isPhoneLayout}
|
||||
/>
|
||||
<Suspense fallback={<RightPanelTabFallback />}>
|
||||
<LazyGitChangesTab
|
||||
t={props.t}
|
||||
activeSessionId={props.activeSessionId}
|
||||
entries={gitStatusEntries}
|
||||
statusLoading={gitStatusLoading}
|
||||
statusError={gitStatusError}
|
||||
selectedPath={gitSelectedPath}
|
||||
selectedLoading={gitSelectedLoading}
|
||||
selectedError={gitSelectedError}
|
||||
selectedBefore={gitSelectedBefore}
|
||||
selectedAfter={gitSelectedAfter}
|
||||
mostChangedPath={gitMostChangedPath}
|
||||
scopeKey={gitScopeKey}
|
||||
diffViewMode={diffViewMode}
|
||||
diffContextMode={diffContextMode}
|
||||
diffWordWrapMode={diffWordWrapMode}
|
||||
onViewModeChange={setDiffViewMode}
|
||||
onContextModeChange={setDiffContextMode}
|
||||
onWordWrapModeChange={setDiffWordWrapMode}
|
||||
onOpenFile={(path: string) => void openGitFile(path)}
|
||||
onRefresh={() => void refreshGitStatus()}
|
||||
listOpen={gitChangesListOpen}
|
||||
onToggleList={toggleGitList}
|
||||
splitWidth={gitChangesSplitWidth}
|
||||
onResizeMouseDown={handleSplitResizeMouseDown("git-changes")}
|
||||
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
|
||||
isPhoneLayout={props.isPhoneLayout}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
|
||||
<Show when={rightPanelTab() === "files"}>
|
||||
<FilesTab
|
||||
t={props.t}
|
||||
browserPath={browserPath}
|
||||
browserEntries={browserEntries}
|
||||
browserLoading={browserLoading}
|
||||
browserError={browserError}
|
||||
browserSelectedPath={browserSelectedPath}
|
||||
browserSelectedContent={browserSelectedContent}
|
||||
browserSelectedLoading={browserSelectedLoading}
|
||||
browserSelectedError={browserSelectedError}
|
||||
parentPath={browserParentPath}
|
||||
scopeKey={browserScopeKey}
|
||||
onLoadEntries={(path) => void loadBrowserEntries(path)}
|
||||
onOpenFile={(path) => void openBrowserFile(path)}
|
||||
onRefresh={() => void refreshFilesTab()}
|
||||
listOpen={filesListOpen}
|
||||
onToggleList={toggleFilesList}
|
||||
splitWidth={filesSplitWidth}
|
||||
onResizeMouseDown={handleSplitResizeMouseDown("files")}
|
||||
onResizeTouchStart={handleSplitResizeTouchStart("files")}
|
||||
isPhoneLayout={props.isPhoneLayout}
|
||||
/>
|
||||
<Suspense fallback={<RightPanelTabFallback />}>
|
||||
<LazyFilesTab
|
||||
t={props.t}
|
||||
browserPath={browserPath}
|
||||
browserEntries={browserEntries}
|
||||
browserLoading={browserLoading}
|
||||
browserError={browserError}
|
||||
browserSelectedPath={browserSelectedPath}
|
||||
browserSelectedContent={browserSelectedContent}
|
||||
browserSelectedLoading={browserSelectedLoading}
|
||||
browserSelectedError={browserSelectedError}
|
||||
parentPath={browserParentPath}
|
||||
scopeKey={browserScopeKey}
|
||||
onLoadEntries={(path: string) => void loadBrowserEntries(path)}
|
||||
onOpenFile={(path: string) => void openBrowserFile(path)}
|
||||
onRefresh={() => void refreshFilesTab()}
|
||||
listOpen={filesListOpen}
|
||||
onToggleList={toggleFilesList}
|
||||
splitWidth={filesSplitWidth}
|
||||
onResizeMouseDown={handleSplitResizeMouseDown("files")}
|
||||
onResizeTouchStart={handleSplitResizeTouchStart("files")}
|
||||
isPhoneLayout={props.isPhoneLayout}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
|
||||
<Show when={rightPanelTab() === "status"}>
|
||||
<StatusTab
|
||||
t={props.t}
|
||||
instanceId={props.instanceId}
|
||||
instance={props.instance}
|
||||
activeSessionId={props.activeSessionId}
|
||||
activeSession={props.activeSession}
|
||||
activeSessionDiffs={props.activeSessionDiffs}
|
||||
latestTodoState={props.latestTodoState}
|
||||
backgroundProcessList={props.backgroundProcessList}
|
||||
onOpenBackgroundOutput={props.onOpenBackgroundOutput}
|
||||
onStopBackgroundProcess={props.onStopBackgroundProcess}
|
||||
onTerminateBackgroundProcess={props.onTerminateBackgroundProcess}
|
||||
expandedItems={rightPanelExpandedItems}
|
||||
onExpandedItemsChange={handleAccordionChange}
|
||||
onOpenChangesTab={openChangesTabFromStatus}
|
||||
/>
|
||||
<Suspense fallback={<RightPanelTabFallback />}>
|
||||
<LazyStatusTab
|
||||
t={props.t}
|
||||
instanceId={props.instanceId}
|
||||
instance={props.instance}
|
||||
activeSessionId={props.activeSessionId}
|
||||
activeSession={props.activeSession}
|
||||
activeSessionDiffs={props.activeSessionDiffs}
|
||||
latestTodoState={props.latestTodoState}
|
||||
backgroundProcessList={props.backgroundProcessList}
|
||||
onOpenBackgroundOutput={props.onOpenBackgroundOutput}
|
||||
onStopBackgroundProcess={props.onStopBackgroundProcess}
|
||||
onTerminateBackgroundProcess={props.onTerminateBackgroundProcess}
|
||||
expandedItems={rightPanelExpandedItems}
|
||||
onExpandedItemsChange={handleAccordionChange}
|
||||
onOpenChangesTab={openChangesTabFromStatus}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
</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 MessageItem from "./message-item"
|
||||
import ToolCall from "./tool-call"
|
||||
import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
|
||||
import type { ClientPart, MessageInfo } 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 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" }>
|
||||
|
||||
|
||||
@@ -500,16 +505,18 @@ function ToolCallItem(props: ToolCallItemProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToolCall
|
||||
toolCall={resolvedToolPart()}
|
||||
toolCallId={props.partId}
|
||||
messageId={props.messageId}
|
||||
messageVersion={messageVersion()}
|
||||
partVersion={partVersion()}
|
||||
instanceId={props.instanceId}
|
||||
sessionId={props.sessionId}
|
||||
onContentRendered={props.onContentRendered}
|
||||
/>
|
||||
<Suspense fallback={<ToolCallFallback />}>
|
||||
<LazyToolCall
|
||||
toolCall={resolvedToolPart()}
|
||||
toolCallId={props.partId}
|
||||
messageId={props.messageId}
|
||||
messageVersion={messageVersion()}
|
||||
partVersion={partVersion()}
|
||||
instanceId={props.instanceId}
|
||||
sessionId={props.sessionId}
|
||||
onContentRendered={props.onContentRendered}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Show, Match, Switch } from "solid-js"
|
||||
import ToolCall from "./tool-call"
|
||||
import { Match, Show, Suspense, Switch, lazy } from "solid-js"
|
||||
import { isItemExpanded, toggleItemExpanded } from "../stores/tool-call-state"
|
||||
import { Markdown } from "./markdown"
|
||||
import { useTheme } from "../lib/theme"
|
||||
@@ -7,6 +6,8 @@ import { partHasRenderableText, SDKPart, TextPart, ClientPart } from "../types/m
|
||||
|
||||
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
|
||||
|
||||
const LazyToolCall = lazy(() => import("./tool-call"))
|
||||
|
||||
interface MessagePartProps {
|
||||
part: ClientPart
|
||||
messageType?: "user" | "assistant"
|
||||
@@ -153,12 +154,14 @@ export default function MessagePart(props: MessagePartProps) {
|
||||
</Match>
|
||||
|
||||
<Match when={partType() === "tool"}>
|
||||
<ToolCall
|
||||
toolCall={props.part as ToolCallPart}
|
||||
toolCallId={props.part?.id}
|
||||
instanceId={props.instanceId}
|
||||
sessionId={props.sessionId}
|
||||
/>
|
||||
<Suspense fallback={<div class="tool-call tool-call-loading" />}>
|
||||
<LazyToolCall
|
||||
toolCall={props.part as ToolCallPart}
|
||||
toolCallId={props.part?.id}
|
||||
instanceId={props.instanceId}
|
||||
sessionId={props.sessionId}
|
||||
/>
|
||||
</Suspense>
|
||||
</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 { getPermissionCallId, getPermissionDisplayTitle, getPermissionKind, getPermissionMessageId, getPermissionSessionId } from "../types/permission"
|
||||
import { getQuestionCallId, getQuestionMessageId, getQuestionSessionId, type QuestionRequest } from "../types/question"
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
} from "../stores/instances"
|
||||
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
||||
import { messageStoreBus } from "../stores/message-v2/bus"
|
||||
import ToolCall from "./tool-call"
|
||||
|
||||
const LazyToolCall = lazy(() => import("./tool-call"))
|
||||
|
||||
interface PermissionApprovalModalProps {
|
||||
instanceId: string
|
||||
@@ -408,15 +409,17 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
||||
}
|
||||
>
|
||||
{(data) => (
|
||||
<ToolCall
|
||||
toolCall={data().toolPart}
|
||||
toolCallId={data().toolPart.id}
|
||||
messageId={data().messageId}
|
||||
messageVersion={data().messageVersion}
|
||||
partVersion={data().partVersion}
|
||||
instanceId={props.instanceId}
|
||||
sessionId={data().sessionId}
|
||||
/>
|
||||
<Suspense fallback={<div class="tool-call tool-call-loading" />}>
|
||||
<LazyToolCall
|
||||
toolCall={data().toolPart}
|
||||
toolCallId={data().toolPart.id}
|
||||
messageId={data().messageId}
|
||||
messageVersion={data().messageVersion}
|
||||
partVersion={data().partVersion}
|
||||
instanceId={props.instanceId}
|
||||
sessionId={data().sessionId}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</Show>
|
||||
</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 UnifiedPicker from "./unified-picker"
|
||||
import ExpandButton from "./expand-button"
|
||||
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
||||
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
||||
@@ -20,6 +19,7 @@ import { usePromptAttachments } from "./prompt-input/usePromptAttachments"
|
||||
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
||||
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
||||
const log = getLogger("actions")
|
||||
const LazyUnifiedPicker = lazy(() => import("./unified-picker"))
|
||||
|
||||
function getConsumedPastedTextAttachmentIds(text: string, attachments: Attachment[]): string[] {
|
||||
if (!text || attachments.length === 0) return []
|
||||
@@ -467,18 +467,20 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<Show when={showPicker() && instance()}>
|
||||
<UnifiedPicker
|
||||
open={showPicker()}
|
||||
mode={pickerMode()}
|
||||
onClose={handlePickerClose}
|
||||
onSelect={handlePickerSelect}
|
||||
agents={instanceAgents()}
|
||||
commands={getCommands(props.instanceId)}
|
||||
instanceClient={instance()!.client}
|
||||
searchQuery={searchQuery()}
|
||||
textareaRef={textareaRef}
|
||||
workspaceId={props.instanceId}
|
||||
/>
|
||||
<Suspense fallback={null}>
|
||||
<LazyUnifiedPicker
|
||||
open={showPicker()}
|
||||
mode={pickerMode()}
|
||||
onClose={handlePickerClose}
|
||||
onSelect={handlePickerSelect}
|
||||
agents={instanceAgents()}
|
||||
commands={getCommands(props.instanceId)}
|
||||
instanceClient={instance()!.client}
|
||||
searchQuery={searchQuery()}
|
||||
textareaRef={textareaRef}
|
||||
workspaceId={props.instanceId}
|
||||
/>
|
||||
</Suspense>
|
||||
</Show>
|
||||
|
||||
<div class="flex flex-1 flex-col">
|
||||
|
||||
@@ -514,6 +514,7 @@ function ToolCallDetails(props: {
|
||||
})
|
||||
|
||||
const { renderDiffContent } = createDiffContentRenderer({
|
||||
toolState: props.toolState,
|
||||
preferences: props.preferences,
|
||||
setDiffViewMode: props.setDiffViewMode,
|
||||
isDark: props.isDark,
|
||||
|
||||
@@ -20,6 +20,14 @@ export function createAnsiContentRenderer(params: {
|
||||
const runningAnsiRenderer = createAnsiStreamRenderer()
|
||||
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 version = params.partVersion?.()
|
||||
return typeof version === "number" ? String(version) : undefined
|
||||
@@ -36,6 +44,8 @@ export function createAnsiContentRenderer(params: {
|
||||
const cached = cacheHandle.get<AnsiRenderCache>()
|
||||
const mode = getMode()
|
||||
const isRunningVariant = options.variant === "running"
|
||||
const disableScrollTracking = !isRunningVariant
|
||||
const registerRef = disableScrollTracking ? registerUntracked : registerTracked
|
||||
|
||||
let nextCache: AnsiRenderCache
|
||||
|
||||
@@ -87,9 +97,9 @@ export function createAnsiContentRenderer(params: {
|
||||
}
|
||||
|
||||
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} />
|
||||
{params.scrollHelpers.renderSentinel()}
|
||||
{params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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 { DiffViewMode } from "../../stores/preferences"
|
||||
import type { DiffPayload, DiffRenderOptions, ToolScrollHelpers } from "./types"
|
||||
@@ -31,6 +32,7 @@ type DiffPrefs = {
|
||||
}
|
||||
|
||||
export function createDiffContentRenderer(params: {
|
||||
toolState: Accessor<ToolState | undefined>
|
||||
preferences: Accessor<DiffPrefs>
|
||||
setDiffViewMode: (mode: DiffViewMode) => void
|
||||
isDark: Accessor<boolean>
|
||||
@@ -58,7 +60,10 @@ export function createDiffContentRenderer(params: {
|
||||
const cacheHandle = selectedVariant === "permission-diff" ? params.permissionDiffCache : params.diffCache
|
||||
const diffMode = () => (params.preferences().diffViewMode || "split") as DiffViewMode
|
||||
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 baseEntryParams = cacheHandle.params() as any
|
||||
|
||||
@@ -31,10 +31,9 @@ export function createMarkdownContentRenderer(params: {
|
||||
const size = options.size || "default"
|
||||
const disableHighlight = options.disableHighlight || false
|
||||
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 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)
|
||||
if (shouldDeferMarkdown) {
|
||||
return (
|
||||
|
||||
@@ -2,11 +2,6 @@ import { createContext, createEffect, createMemo, createSignal, onCleanup, onMou
|
||||
import type { ParentComponent } from "solid-js"
|
||||
import { useConfig } from "../../stores/preferences"
|
||||
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>
|
||||
|
||||
@@ -15,14 +10,18 @@ export type TranslateParams = Record<string, unknown>
|
||||
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_BY_LOWER = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
||||
|
||||
const messagesByLocale: Record<Locale, Messages> = {
|
||||
en: enMessages,
|
||||
es: esMessages,
|
||||
fr: frMessages,
|
||||
ru: ruMessages,
|
||||
ja: jaMessages,
|
||||
"zh-Hans": zhHansMessages,
|
||||
const localeMessagesCache = new Map<Locale, Messages>([["en", enMessages]])
|
||||
const localeMessagesPromises = new Map<Locale, Promise<Messages>>()
|
||||
|
||||
const localeLoaders: Record<Locale, () => Promise<Messages>> = {
|
||||
en: async () => enMessages,
|
||||
es: async () => (await import("./messages/es")).esMessages,
|
||||
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 {
|
||||
@@ -34,8 +33,7 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
||||
|
||||
const normalized = normalizeLocaleTag(value)
|
||||
const lower = normalized.toLowerCase()
|
||||
const supportedLower = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
||||
const exact = supportedLower.get(lower)
|
||||
const exact = SUPPORTED_LOCALES_BY_LOWER.get(lower)
|
||||
if (exact) return exact
|
||||
|
||||
const parts = lower.split("-")
|
||||
@@ -43,11 +41,11 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
||||
if (!base) return null
|
||||
|
||||
if (base === "zh") {
|
||||
const zhHans = supportedLower.get("zh-hans")
|
||||
const zhHans = SUPPORTED_LOCALES_BY_LOWER.get("zh-hans")
|
||||
return zhHans ?? null
|
||||
}
|
||||
|
||||
const baseMatch = supportedLower.get(base)
|
||||
const baseMatch = SUPPORTED_LOCALES_BY_LOWER.get(base)
|
||||
return baseMatch ?? null
|
||||
}
|
||||
|
||||
@@ -84,8 +82,54 @@ function translateFrom(messages: Messages, key: string, params?: TranslateParams
|
||||
}
|
||||
|
||||
const [globalRevision, setGlobalRevision] = createSignal(0)
|
||||
const initialGlobalLocale: Locale = detectNavigatorLocale() ?? "en"
|
||||
let globalMessages: Messages = messagesByLocale[initialGlobalLocale]
|
||||
let globalMessages: Messages = enMessages
|
||||
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 {
|
||||
globalRevision()
|
||||
@@ -101,9 +145,10 @@ const I18nContext = createContext<I18nContextValue>()
|
||||
|
||||
export const I18nProvider: ParentComponent = (props) => {
|
||||
const { preferences } = useConfig()
|
||||
const [detectedLocale, setDetectedLocale] = createSignal<Locale>("en")
|
||||
|
||||
const previousMessages = globalMessages
|
||||
const [detectedLocale, setDetectedLocale] = createSignal<Locale>(globalLocale)
|
||||
const [resolvedLocale, setResolvedLocale] = createSignal<Locale>(globalLocale)
|
||||
const previousGlobalMessages = globalMessages
|
||||
const previousGlobalLocale = globalLocale
|
||||
|
||||
onMount(() => {
|
||||
const detected = detectNavigatorLocale()
|
||||
@@ -115,19 +160,44 @@ export const I18nProvider: ParentComponent = (props) => {
|
||||
return configured ?? detectedLocale() ?? "en"
|
||||
})
|
||||
|
||||
const messages = createMemo<Messages>(() => messagesByLocale[locale()])
|
||||
const messages = createMemo<Messages>(() => getMessagesForLocale(resolvedLocale()))
|
||||
|
||||
function t(key: string, params?: TranslateParams): string {
|
||||
return translateFrom(messages(), key, params)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
globalMessages = messages()
|
||||
setGlobalRevision((value) => value + 1)
|
||||
const nextLocale = locale()
|
||||
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(() => {
|
||||
globalMessages = previousMessages
|
||||
globalMessages = previousGlobalMessages
|
||||
globalLocale = previousGlobalLocale
|
||||
setGlobalRevision((value) => value + 1)
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ThemeProvider } from "./lib/theme"
|
||||
import { ConfigProvider } from "./stores/preferences"
|
||||
import { InstanceConfigProvider } from "./stores/instance-config"
|
||||
import { runtimeEnv } from "./lib/runtime-env"
|
||||
import { I18nProvider } from "./lib/i18n"
|
||||
import { I18nProvider, preloadLocaleMessages } from "./lib/i18n"
|
||||
import { storage } from "./lib/storage"
|
||||
import "./index.css"
|
||||
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||
@@ -31,15 +31,19 @@ async function bootstrap() {
|
||||
|
||||
try {
|
||||
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") {
|
||||
document.documentElement.removeAttribute("data-theme")
|
||||
} else {
|
||||
if (theme === "light" || theme === "dark") {
|
||||
document.documentElement.setAttribute("data-theme", theme)
|
||||
} else {
|
||||
document.documentElement.removeAttribute("data-theme")
|
||||
}
|
||||
|
||||
await preloadLocaleMessages(locale)
|
||||
} catch {
|
||||
// If config fails to load, fall back to CSS defaults.
|
||||
await preloadLocaleMessages()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user