@@ -334,41 +356,45 @@ export function PromptProjectSelector(props: {
-
1}
- fallback={
-
-
- {(project) => (
-
- )}
-
-
- }
- >
-
- props.controller.projects().some((project) => project.server?.key === server!.key),
- )}
+
+
1}
+ fallback={
+
+
+ {(project) => (
+
+ )}
+
+
+ }
>
- {(server) => (
-
-
- {server!.name}
+
+ props.controller.projects().some((project) => project.server?.key === server!.key),
+ )}
+ >
+ {(server) => (
+
+
+ {server!.name}
+
+
+ project.server?.key === server!.key)}
+ >
+ {(project) => (
+
+ )}
+
+
-
- project.server?.key === server!.key)}>
- {(project) => (
-
- )}
-
-
-
- )}
-
-
+ )}
+
+
+
diff --git a/packages/app/src/components/prompt-workspace-selector.tsx b/packages/app/src/components/prompt-workspace-selector.tsx
index 27d8c97ca2..a7a3537434 100644
--- a/packages/app/src/components/prompt-workspace-selector.tsx
+++ b/packages/app/src/components/prompt-workspace-selector.tsx
@@ -1,5 +1,6 @@
import { For, Show } from "solid-js"
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
+import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { Icon } from "@opencode-ai/ui/icon"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { getFilename } from "@opencode-ai/core/util/path"
@@ -92,17 +93,36 @@ export function PromptWorkspaceSelector(props: {
-
- {(branch) => (
- <>
- /
-
-
- {branch()}
-
- >
- )}
-
+
>
)
}
+
+export function PromptGitStatus(props: { branch?: string; noGit?: boolean }) {
+ const language = useLanguage()
+ const label = () => {
+ if (props.noGit) return language.t("session.new.git.none")
+ return props.branch
+ }
+
+ return (
+
+ {(value) => (
+ <>
+ /
+
+
+
+ {value()}
+
+
+ >
+ )}
+
+ )
+}
diff --git a/packages/app/src/components/server/server-row-menu.tsx b/packages/app/src/components/server/server-row-menu.tsx
index 9d5f5e5a32..0a2920dec7 100644
--- a/packages/app/src/components/server/server-row-menu.tsx
+++ b/packages/app/src/components/server/server-row-menu.tsx
@@ -15,9 +15,47 @@ export const ServerRowMenu: Component<{
}> = (props) => {
const language = useLanguage()
const key = ServerConnection.key(props.server)
- const builtin = ServerConnection.builtin(props.server)
- const isDefault = () => props.controller.defaultKey() === key
+ return (
+
props.controller.setDefault(key)}
+ onRemoveDefault={() => props.controller.setDefault(null)}
+ onRemove={() => props.controller.handleRemove(key)}
+ open={props.open}
+ onOpenChange={props.onOpenChange}
+ />
+ )
+}
+export function serverMenuLabels(language: ReturnType) {
+ return {
+ more: language.t("common.moreOptions"),
+ server: language.t("settings.section.server"),
+ edit: language.t("dialog.server.menu.edit"),
+ default: language.t("dialog.server.menu.default"),
+ defaultRemove: language.t("dialog.server.menu.defaultRemove"),
+ delete: language.t("dialog.server.menu.delete"),
+ }
+}
+
+export const ServerRowMenuView: Component<{
+ server: ServerConnection.Any
+ labels: ReturnType
+ canDefault: boolean
+ isDefault: boolean
+ onEdit: (server: ServerConnection.Http) => void
+ onSetDefault: () => void
+ onRemoveDefault: () => void
+ onRemove: () => void
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}> = (props) => {
+ const builtin = () => ServerConnection.builtin(props.server)
+ const httpServer = () => (props.server.type === "http" ? props.server : undefined)
return (
}
- aria-label={language.t("common.moreOptions")}
+ aria-label={props.labels.more}
/>
- {language.t("settings.section.server")}
+ {props.labels.server}
props.onEdit(props.server as ServerConnection.Http)}
+ disabled={builtin() || !httpServer()}
+ onSelect={() => {
+ const server = httpServer()
+ if (server) props.onEdit(server)
+ }}
>
- {language.t("dialog.server.menu.edit")}
+ {props.labels.edit}
-
- props.controller.setDefault(key)}>
- {language.t("dialog.server.menu.default")}
-
+
+ {props.labels.default}
-
- props.controller.setDefault(null)}>
- {language.t("dialog.server.menu.defaultRemove")}
-
+
+ {props.labels.defaultRemove}
- props.controller.handleRemove(key)}>
- {language.t("dialog.server.menu.delete")}
+
+ {props.labels.delete}
diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx
index 7ac1aa9965..c1e0ccde05 100644
--- a/packages/app/src/components/session-context-usage.tsx
+++ b/packages/app/src/components/session-context-usage.tsx
@@ -4,6 +4,7 @@ import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
import { Button } from "@opencode-ai/ui/button"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
+import { createMediaQuery } from "@solid-primitives/media"
import { useFile } from "@/context/file"
import { useLayout } from "@/context/layout"
@@ -11,9 +12,10 @@ import { useSync } from "@/context/sync"
import { useLanguage } from "@/context/language"
import { useProviders } from "@/hooks/use-providers"
import { useSDK } from "@/context/sdk"
-import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics"
+import { getSessionContext } from "@/components/session/session-context-metrics"
import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
+import { useSettings } from "@/context/settings"
interface SessionContextUsageProps {
variant?: "button" | "indicator"
@@ -47,8 +49,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const layout = useLayout()
const language = useLanguage()
const sdk = useSDK()
+ const settings = useSettings()
const providers = useProviders(() => sdk().directory)
const { params, tabs, view } = useSessionLayout()
+ const isDesktop = createMediaQuery("(min-width: 768px)")
const variant = createMemo(() => props.variant ?? "button")
const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default")
@@ -56,6 +60,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
tabs,
pathFromTab: file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
+ fileBrowser: () => settings.general.newLayoutDesigns() && isDesktop() && !!params.id,
})
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
@@ -69,7 +74,6 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
)
const context = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
- const tokens = createMemo(() => info()?.tokens)
const cost = createMemo(() => {
return usd().format(info()?.cost ?? 0)
})
@@ -127,7 +131,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
)
diff --git a/packages/app/src/components/session/index.ts b/packages/app/src/components/session/index.ts
index 8e424f0f36..c4fdb2ff38 100644
--- a/packages/app/src/components/session/index.ts
+++ b/packages/app/src/components/session/index.ts
@@ -1,6 +1,6 @@
export { SessionHeader } from "./session-header"
export { SessionContextTab } from "./session-context-tab"
export { SortableTab, FileVisual } from "./session-sortable-tab"
+export { SortableTabV2 } from "./session-sortable-tab-v2"
export { SortableTerminalTab } from "./session-sortable-terminal-tab"
export { NewSessionView } from "./session-new-view"
-export { NewSessionDesignView } from "./session-new-design-view"
diff --git a/packages/app/src/components/session/open-in-app-v2.tsx b/packages/app/src/components/session/open-in-app-v2.tsx
new file mode 100644
index 0000000000..e26ff2e0ea
--- /dev/null
+++ b/packages/app/src/components/session/open-in-app-v2.tsx
@@ -0,0 +1,98 @@
+import { For, Show } from "solid-js"
+import { AppIcon } from "@opencode-ai/ui/app-icon"
+import { Icon } from "@opencode-ai/ui/icon"
+import { Spinner } from "@opencode-ai/ui/spinner"
+import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
+import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
+import { SplitButtonV2, SplitButtonV2Action, SplitButtonV2MenuTrigger } from "@opencode-ai/ui/v2/split-button-v2"
+import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
+import { useLanguage } from "@/context/language"
+import { type OpenApp, useOpenInApp } from "@/components/session/open-in-app"
+
+export function OpenInAppV2(props: { directory: () => string }) {
+ const language = useLanguage()
+ const state = useOpenInApp(props)
+
+ return (
+
+ event.stopPropagation()}>
+
+ event.stopPropagation()}
+ onClick={(event) => {
+ event.stopPropagation()
+ if (state.opening()) return
+ state.openDir(state.current().id)
+ }}
+ disabled={state.opening()}
+ aria-label={language.t("session.header.open.ariaLabel", { app: state.current().label })}
+ >
+ }>
+
+
+
+
+ state.setMenu("open", open)}
+ >
+ event.stopPropagation()}
+ >
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/app/src/components/session/open-in-app.tsx b/packages/app/src/components/session/open-in-app.tsx
new file mode 100644
index 0000000000..cd363d00c4
--- /dev/null
+++ b/packages/app/src/components/session/open-in-app.tsx
@@ -0,0 +1,229 @@
+import { createEffect, createMemo } from "solid-js"
+import { createStore } from "solid-js/store"
+import { useLanguage } from "@/context/language"
+import { usePlatform } from "@/context/platform"
+import { useServer } from "@/context/server"
+import { Persist, persisted } from "@/utils/persist"
+import { showToast } from "@/utils/toast"
+
+export const OPEN_APPS = [
+ "vscode",
+ "cursor",
+ "zed",
+ "textmate",
+ "antigravity",
+ "finder",
+ "terminal",
+ "iterm2",
+ "ghostty",
+ "warp",
+ "xcode",
+ "android-studio",
+ "powershell",
+ "sublime-text",
+] as const
+
+export type OpenApp = (typeof OPEN_APPS)[number]
+export type OpenAppOS = "macos" | "windows" | "linux" | "unknown"
+
+export const MAC_OPEN_APPS = [
+ {
+ id: "vscode",
+ label: "session.header.open.app.vscode",
+ icon: "vscode",
+ openWith: "Visual Studio Code",
+ },
+ { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "Cursor" },
+ { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "Zed" },
+ { id: "textmate", label: "session.header.open.app.textmate", icon: "textmate", openWith: "TextMate" },
+ {
+ id: "antigravity",
+ label: "session.header.open.app.antigravity",
+ icon: "antigravity",
+ openWith: "Antigravity",
+ },
+ { id: "terminal", label: "session.header.open.app.terminal", icon: "terminal", openWith: "Terminal" },
+ { id: "iterm2", label: "session.header.open.app.iterm2", icon: "iterm2", openWith: "iTerm" },
+ { id: "ghostty", label: "session.header.open.app.ghostty", icon: "ghostty", openWith: "Ghostty" },
+ { id: "warp", label: "session.header.open.app.warp", icon: "warp", openWith: "Warp" },
+ { id: "xcode", label: "session.header.open.app.xcode", icon: "xcode", openWith: "Xcode" },
+ {
+ id: "android-studio",
+ label: "session.header.open.app.androidStudio",
+ icon: "android-studio",
+ openWith: "Android Studio",
+ },
+ {
+ id: "sublime-text",
+ label: "session.header.open.app.sublimeText",
+ icon: "sublime-text",
+ openWith: "Sublime Text",
+ },
+] as const
+
+export const WINDOWS_OPEN_APPS = [
+ { id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" },
+ { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" },
+ { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" },
+ {
+ id: "powershell",
+ label: "session.header.open.app.powershell",
+ icon: "powershell",
+ openWith: "powershell",
+ },
+ {
+ id: "sublime-text",
+ label: "session.header.open.app.sublimeText",
+ icon: "sublime-text",
+ openWith: "Sublime Text",
+ },
+] as const
+
+export const LINUX_OPEN_APPS = [
+ { id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" },
+ { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" },
+ { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" },
+ {
+ id: "sublime-text",
+ label: "session.header.open.app.sublimeText",
+ icon: "sublime-text",
+ openWith: "Sublime Text",
+ },
+] as const
+
+export function detectOpenAppOS(platform: ReturnType
): OpenAppOS {
+ if (platform.platform === "desktop" && platform.os) return platform.os
+ if (typeof navigator !== "object") return "unknown"
+ const value = navigator.platform || navigator.userAgent
+ if (/Mac/i.test(value)) return "macos"
+ if (/Win/i.test(value)) return "windows"
+ if (/Linux/i.test(value)) return "linux"
+ return "unknown"
+}
+
+export function openAppFileManager(os: OpenAppOS) {
+ if (os === "macos") return { label: "session.header.open.finder", icon: "finder" as const }
+ if (os === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const }
+ return { label: "session.header.open.fileManager", icon: "finder" as const }
+}
+
+export function openAppsForOS(os: OpenAppOS) {
+ if (os === "macos") return MAC_OPEN_APPS
+ if (os === "windows") return WINDOWS_OPEN_APPS
+ return LINUX_OPEN_APPS
+}
+
+const showRequestError = (language: ReturnType, err: unknown) => {
+ showToast({
+ variant: "error",
+ title: language.t("common.requestFailed"),
+ description: err instanceof Error ? err.message : String(err),
+ })
+}
+
+export function useOpenInApp(input: { directory: () => string }) {
+ const platform = usePlatform()
+ const server = useServer()
+ const language = useLanguage()
+
+ const os = createMemo(() => detectOpenAppOS(platform))
+ const apps = createMemo(() => openAppsForOS(os()))
+ const fileManager = createMemo(() => openAppFileManager(os()))
+
+ const [exists, setExists] = createStore>>({
+ finder: true,
+ })
+
+ createEffect(() => {
+ if (platform.platform !== "desktop") return
+ if (!platform.checkAppExists) return
+
+ const list = apps()
+
+ setExists(Object.fromEntries(list.map((app) => [app.id, undefined])) as Partial>)
+
+ void Promise.all(
+ list.map((app) =>
+ Promise.resolve(platform.checkAppExists?.(app.openWith))
+ .then((value) => Boolean(value))
+ .catch(() => false)
+ .then((ok) => [app.id, ok] as const),
+ ),
+ ).then((entries) => {
+ setExists(Object.fromEntries(entries) as Partial>)
+ })
+ })
+
+ const options = createMemo(() => {
+ return [
+ { id: "finder", label: language.t(fileManager().label), icon: fileManager().icon },
+ ...apps()
+ .filter((app) => exists[app.id])
+ .map((app) => ({ ...app, label: language.t(app.label) })),
+ ] as const
+ })
+
+ const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp | "finder" }))
+ const [menu, setMenu] = createStore({ open: false })
+ const [openRequest, setOpenRequest] = createStore({
+ app: undefined as OpenApp | undefined,
+ })
+
+ const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
+ const current = createMemo(
+ () =>
+ options().find((o) => o.id === prefs.app) ??
+ options()[0] ??
+ ({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
+ )
+ const opening = createMemo(() => openRequest.app !== undefined)
+
+ const selectApp = (app: OpenApp | "finder") => {
+ if (!options().some((item) => item.id === app)) return
+ setPrefs("app", app)
+ }
+
+ const openDir = (app: OpenApp | "finder") => {
+ if (opening() || !canOpen() || !platform.openPath) return
+ const directory = input.directory()
+ if (!directory) return
+
+ const item = options().find((o) => o.id === app)
+ const openWith = item && "openWith" in item ? item.openWith : undefined
+ setOpenRequest("app", app)
+ platform
+ .openPath(directory, openWith)
+ .catch((err: unknown) => showRequestError(language, err))
+ .finally(() => {
+ setOpenRequest("app", undefined)
+ })
+ }
+
+ const copyPath = () => {
+ const directory = input.directory()
+ if (!directory) return
+ navigator.clipboard
+ .writeText(directory)
+ .then(() => {
+ showToast({
+ variant: "success",
+ icon: "circle-check",
+ title: language.t("session.share.copy.copied"),
+ description: directory,
+ })
+ })
+ .catch((err: unknown) => showRequestError(language, err))
+ }
+
+ return {
+ canOpen,
+ opening,
+ current,
+ options,
+ menu,
+ setMenu,
+ openDir,
+ selectApp,
+ copyPath,
+ }
+}
diff --git a/packages/app/src/components/session/session-context-metrics.test.ts b/packages/app/src/components/session/session-context-metrics.test.ts
index 5b11bf1433..37cc0d5b5d 100644
--- a/packages/app/src/components/session/session-context-metrics.test.ts
+++ b/packages/app/src/components/session/session-context-metrics.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import type { Message } from "@opencode-ai/sdk/v2/client"
-import { getSessionContext, getSessionTokenTotal } from "./session-context-metrics"
+import { getSessionContext } from "./session-context-metrics"
const assistant = (
id: string,
@@ -38,10 +38,10 @@ const user = (id: string) => {
}
describe("getSessionContext", () => {
- test("computes usage from latest assistant with tokens", () => {
+ test("computes token totals and usage from latest assistant with tokens", () => {
const messages = [
user("u1"),
- assistant("a1", { input: 0, output: 0, reasoning: 0, read: 0, write: 0 }, 0.5),
+ assistant("a1", { input: 600, output: 200, reasoning: 100, read: 50, write: 50 }, 0.5),
assistant("a2", { input: 300, output: 100, reasoning: 50, read: 25, write: 25 }, 1.25),
]
const providers = [
@@ -60,6 +60,8 @@ describe("getSessionContext", () => {
const ctx = getSessionContext(messages, providers)
expect(ctx?.message.id).toBe("a2")
+ expect(ctx?.total).toBe(500)
+ expect(ctx?.input).toBe(300)
expect(ctx?.usage).toBe(50)
expect(ctx?.providerLabel).toBe("OpenAI")
expect(ctx?.modelLabel).toBe("GPT-4.1")
@@ -94,15 +96,4 @@ describe("getSessionContext", () => {
expect(ctx).toBeUndefined()
})
-
- test("computes stored session token totals", () => {
- expect(
- getSessionTokenTotal({
- input: 10,
- output: 20,
- reasoning: 30,
- cache: { read: 40, write: 50 },
- }),
- ).toBe(150)
- })
})
diff --git a/packages/app/src/components/session/session-context-metrics.ts b/packages/app/src/components/session/session-context-metrics.ts
index 84cc984abf..30dc9e958b 100644
--- a/packages/app/src/components/session/session-context-metrics.ts
+++ b/packages/app/src/components/session/session-context-metrics.ts
@@ -1,4 +1,4 @@
-import type { AssistantMessage, Message, Session } from "@opencode-ai/sdk/v2/client"
+import type { AssistantMessage, Message } from "@opencode-ai/sdk/v2/client"
type Provider = {
id: string
@@ -21,6 +21,7 @@ type Context = {
modelLabel: string
limit: number | undefined
input: number
+ total: number
usage: number | null
}
@@ -54,6 +55,7 @@ const build = (messages: Message[] = [], providers: Provider[] = []): Context |
modelLabel: model?.name ?? message.modelID,
limit,
input: message.tokens.input,
+ total,
usage: limit ? Math.round((total / limit) * 100) : null,
}
}
@@ -61,8 +63,3 @@ const build = (messages: Message[] = [], providers: Provider[] = []): Context |
export function getSessionContext(messages: Message[] = [], providers: Provider[] = []) {
return build(messages, providers)
}
-
-export function getSessionTokenTotal(tokens: Session["tokens"] | undefined) {
- if (!tokens) return undefined
- return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
-}
diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx
index 8757c69367..25ad5ab230 100644
--- a/packages/app/src/components/session/session-context-tab.tsx
+++ b/packages/app/src/components/session/session-context-tab.tsx
@@ -15,7 +15,7 @@ import { useLanguage } from "@/context/language"
import { useProviders } from "@/hooks/use-providers"
import { useSDK } from "@/context/sdk"
import { useSessionLayout } from "@/pages/session/session-layout"
-import { getSessionContext, getSessionTokenTotal } from "./session-context-metrics"
+import { getSessionContext } from "./session-context-metrics"
import { estimateSessionContextBreakdown, type SessionContextBreakdownKey } from "./session-context-breakdown"
import { createSessionContextFormatter } from "./session-context-format"
@@ -135,7 +135,6 @@ export function SessionContextTab() {
)
const ctx = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
- const tokens = createMemo(() => info()?.tokens)
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
const cost = createMemo(() => {
@@ -204,14 +203,15 @@ export function SessionContextTab() {
{ label: "context.stats.provider", value: providerLabel },
{ label: "context.stats.model", value: modelLabel },
{ label: "context.stats.limit", value: () => formatter().number(ctx()?.limit) },
- { label: "context.stats.totalTokens", value: () => formatter().number(getSessionTokenTotal(tokens())) },
+ { label: "context.stats.totalTokens", value: () => formatter().number(ctx()?.total) },
{ label: "context.stats.usage", value: () => formatter().percent(ctx()?.usage) },
- { label: "context.stats.inputTokens", value: () => formatter().number(tokens()?.input) },
- { label: "context.stats.outputTokens", value: () => formatter().number(tokens()?.output) },
- { label: "context.stats.reasoningTokens", value: () => formatter().number(tokens()?.reasoning) },
+ { label: "context.stats.inputTokens", value: () => formatter().number(ctx()?.input) },
+ { label: "context.stats.outputTokens", value: () => formatter().number(ctx()?.message.tokens.output) },
+ { label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.message.tokens.reasoning) },
{
label: "context.stats.cacheTokens",
- value: () => `${formatter().number(tokens()?.cache.read)} / ${formatter().number(tokens()?.cache.write)}`,
+ value: () =>
+ `${formatter().number(ctx()?.message.tokens.cache.read)} / ${formatter().number(ctx()?.message.tokens.cache.write)}`,
},
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index 256d34969b..dc6cdaa6f3 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -24,6 +24,7 @@ import { focusTerminalById } from "@/pages/session/helpers"
import { useSessionLayout } from "@/pages/session/session-layout"
import { messageAgentColor } from "@/utils/agent"
import { decode64 } from "@/utils/base64"
+import { fileManagerApp } from "@/utils/file-manager"
import { Persist, persisted } from "@/utils/persist"
import { StatusPopover, StatusPopoverV2 } from "../status-popover"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
@@ -175,11 +176,7 @@ export function SessionHeader() {
return LINUX_APPS
})
- const fileManager = createMemo(() => {
- if (os() === "macos") return { label: "session.header.open.finder", icon: "finder" as const }
- if (os() === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const }
- return { label: "session.header.open.fileManager", icon: "finder" as const }
- })
+ const fileManager = createMemo(() => fileManagerApp(os()))
createEffect(() => {
if (platform.platform !== "desktop") return
@@ -229,7 +226,7 @@ export function SessionHeader() {
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
const current = createMemo(
() =>
- options().find((o) => o.id === prefs().app) ??
+ options().find((o) => o.id === prefs.app) ??
options()[0] ??
({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
)
@@ -292,9 +289,9 @@ export function SessionHeader() {
return (
<>
-
+
{(mount) => (
-
+
-