Compare commits

...

14 Commits

Author SHA1 Message Date
usrnk1 4100018ec8 Merge remote-tracking branch 'origin/dev' into tools-menu
# Conflicts:
#	packages/app/src/components/status-popover.tsx
2026-07-14 11:14:28 +02:00
usrnk1 f69c29b38c fix(app): simplify server status indicators 2026-07-14 10:55:15 +02:00
usrnk1 11d2eb3ec8 feat(app): add server connection indicator 2026-07-14 10:33:01 +02:00
usrnk1 014696db63 Wip commit 2026-07-10 14:09:37 +02:00
usrnk1 351172dd1b docs(app): clarify tools menu setting 2026-07-10 11:40:34 +02:00
usrnk1 50f8938960 fix(app): respect tools visibility setting 2026-07-10 11:33:54 +02:00
usrnk1 23bc29fde5 fix(app): preserve mcp switch transition 2026-07-10 11:10:48 +02:00
usrnk1 2a1565951f fix(app): avoid disabled cursor during mcp toggle 2026-07-10 11:08:23 +02:00
usrnk1 4db321512f fix(app): hide tools menu on new session 2026-07-10 11:05:10 +02:00
usrnk1 ecaaceeecf Merge branch 'dev' into tools-menu 2026-07-10 10:38:25 +02:00
usrnk1 249bbd599c fix(ui): prevent segmented label clipping 2026-07-09 16:04:49 +02:00
usrnk1 2aeeca5243 fix(ui): respect reduced motion in popovers 2026-07-09 16:04:49 +02:00
usrnk1 c8f4c383cd test(app): add tools menu stories 2026-07-09 16:04:41 +02:00
usrnk1 25ee2435f6 feat(app): replace status menu with tools 2026-07-09 16:04:35 +02:00
35 changed files with 794 additions and 38 deletions
@@ -0,0 +1,21 @@
[data-component="server-status-icon"] {
color: var(--v2-icon-icon-muted);
[data-slot="server-status-left"],
[data-slot="server-status-right"] {
fill: none;
stroke: currentColor;
}
[data-slot="server-status-dot"] {
fill: var(--server-status-color);
}
&[data-state="reconnecting"] {
--server-status-color: var(--v2-state-fg-warning);
}
&[data-state="disconnected"] {
--server-status-color: var(--v2-state-fg-danger);
}
}
@@ -0,0 +1,33 @@
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { ServerStatusIcon, type ServerStatusIconState } from "./server-status-icon"
export default {
title: "Desktop V2/Server Status Icon",
id: "desktop-v2-server-status-icon",
component: ServerStatusIcon,
tags: ["autodocs"],
}
function Preview(props: { state: ServerStatusIconState; label: string }) {
return (
<div class="flex items-center gap-3">
<IconButtonV2
type="button"
variant="ghost-muted"
size="large"
class="!w-9 shrink-0"
aria-label={props.label}
icon={<ServerStatusIcon state={props.state} />}
/>
<span class="text-[13px] text-v2-text-text-muted">{props.label}</span>
</div>
)
}
export const Reconnecting = {
render: () => <Preview state="reconnecting" label="Retrying automatically..." />,
}
export const Disconnected = {
render: () => <Preview state="disconnected" label="Server disconnected" />,
}
@@ -0,0 +1,17 @@
import { describe, expect, test } from "bun:test"
import { resolveServerStatus } from "./server-status-icon"
describe("resolveServerStatus", () => {
test("prioritizes a disconnected server over stream reconnection", () => {
expect(resolveServerStatus(false, "reconnecting")).toBe("disconnected")
})
test("shows reconnection while the server remains reachable", () => {
expect(resolveServerStatus(true, "reconnecting")).toBe("reconnecting")
})
test("stays hidden for healthy and initial connections", () => {
expect(resolveServerStatus(true, "connected")).toBeUndefined()
expect(resolveServerStatus(undefined, "connecting")).toBeUndefined()
})
})
@@ -0,0 +1,35 @@
import "./server-status-icon.css"
export type ServerStatusIconState = "reconnecting" | "disconnected"
export type ServerConnectionState = "connecting" | "connected" | "reconnecting"
export function resolveServerStatus(healthy: boolean | undefined, connection: ServerConnectionState) {
if (healthy === false) return "disconnected" as const
if (connection === "reconnecting") return "reconnecting" as const
}
export function ServerStatusIcon(props: { state: ServerStatusIconState }) {
return (
<svg
data-component="server-status-icon"
data-state={props.state}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M13.3141 6.00001V14.4572H2.68555V1.54285H9.5M13.3141 14.4572V9.54285H2.68555V14.4572"
stroke="currentColor"
/>
<path data-slot="server-status-left" d="M8.5 11.75H8V12.25H8.5Z" />
<path data-slot="server-status-right" d="M11 11.75H10.5V12.25H11Z" />
<path
data-slot="server-status-dot"
d="M13 5C14.3807 5 15.5 3.88071 15.5 2.5C15.5 1.11929 14.3807 0 13 0C11.6193 0 10.5 1.11929 10.5 2.5C10.5 3.88071 11.6193 5 13 5Z"
/>
</svg>
)
}
@@ -1,3 +1,4 @@
// @refresh reload
import { AppIcon } from "@opencode-ai/ui/app-icon" import { AppIcon } from "@opencode-ai/ui/app-icon"
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
@@ -16,7 +17,9 @@ import { useCommand } from "@/context/command"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useServer } from "@/context/server" import { ServerConnection, serverName, useServer } from "@/context/server"
import { useServerSDK } from "@/context/server-sdk"
import { useGlobal } from "@/context/global"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync" import { useSync } from "@/context/sync"
import { useTerminal } from "@/context/terminal" import { useTerminal } from "@/context/terminal"
@@ -33,6 +36,8 @@ import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { reviewTooltipKeybind } from "../command-tooltip-keybind" import { reviewTooltipKeybind } from "../command-tooltip-keybind"
import { useTitlebarRightMount } from "../titlebar" import { useTitlebarRightMount } from "../titlebar"
import { useSettingsDialog } from "../settings-dialog"
import { resolveServerStatus, ServerStatusIcon, type ServerStatusIconState } from "../server-status-icon"
const OPEN_APPS = [ const OPEN_APPS = [
"vscode", "vscode",
@@ -144,6 +149,8 @@ export function SessionHeader() {
const platform = usePlatform() const platform = usePlatform()
const language = useLanguage() const language = useLanguage()
const settings = useSettings() const settings = useSettings()
const global = useGlobal()
const serverSDK = useServerSDK()
const sync = useSync() const sync = useSync()
const terminal = useTerminal() const terminal = useTerminal()
const { params, view } = useSessionLayout() const { params, view } = useSessionLayout()
@@ -165,6 +172,7 @@ export function SessionHeader() {
const search = settings.visibility.search const search = settings.visibility.search
const status = settings.visibility.status const status = settings.visibility.status
const isDesktop = createMediaQuery("(min-width: 768px)") const isDesktop = createMediaQuery("(min-width: 768px)")
const openServerSettings = useSettingsDialog("servers")
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({
finder: true, finder: true,
@@ -234,9 +242,25 @@ export function SessionHeader() {
const tint = createMemo(() => const tint = createMemo(() =>
messageAgentColor(params.id ? sync().data.message[params.id] : undefined, sync().data.agent), messageAgentColor(params.id ? sync().data.message[params.id] : undefined, sync().data.agent),
) )
const serverStatus = createMemo<ServerStatusIconState | undefined>(() => {
const current = serverSDK()
return resolveServerStatus(
global.servers.health[ServerConnection.key(current.server)]?.healthy,
current.connection.status,
)
})
const serverStatusLabel = createMemo(() => {
if (serverStatus() === "disconnected") {
return language.t("app.server.status.disconnected", { server: serverName(serverSDK().server) })
}
return language.t("app.server.status.reconnecting", { server: serverName(serverSDK().server) })
})
const v2ActionsState = createMemo<SessionHeaderV2ActionsState>(() => ({ const v2ActionsState = createMemo<SessionHeaderV2ActionsState>(() => ({
statusVisible: status(), serverStatus: isDesktop() ? serverStatus() : undefined,
statusLabel: language.t("status.popover.trigger"), serverStatusLabel: serverStatusLabel(),
onServerStatusClick: openServerSettings,
toolsVisible: isDesktop() && status(),
statusLabel: language.t("status.popover.tools.trigger"),
reviewLabel: language.t("command.review.toggle"), reviewLabel: language.t("command.review.toggle"),
reviewKeybind: reviewTooltipKeybind(command), reviewKeybind: reviewTooltipKeybind(command),
reviewVisible: isDesktop(), reviewVisible: isDesktop(),
@@ -517,7 +541,10 @@ export function SessionHeader() {
} }
type SessionHeaderV2ActionsState = { type SessionHeaderV2ActionsState = {
statusVisible: boolean serverStatus?: ServerStatusIconState
serverStatusLabel: string
onServerStatusClick: () => void
toolsVisible: boolean
statusLabel: string statusLabel: string
reviewLabel: string reviewLabel: string
reviewKeybind: string[] reviewKeybind: string[]
@@ -530,11 +557,26 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) {
const language = useLanguage() const language = useLanguage()
return ( return (
<div class="flex items-center gap-2"> <div class="flex items-center gap-[6px]">
<Show when={props.state.statusVisible}> <Show when={props.state.serverStatus}>
<Tooltip placement="bottom" value={props.state.statusLabel}> {(status) => (
<TooltipV2 placement="bottom" value={props.state.serverStatusLabel}>
<IconButtonV2
type="button"
variant="ghost-muted"
size="large"
class="!w-9 shrink-0"
onClick={props.state.onServerStatusClick}
aria-label={props.state.serverStatusLabel}
icon={<ServerStatusIcon state={status()} />}
/>
</TooltipV2>
)}
</Show>
<Show when={props.state.toolsVisible}>
<TooltipV2 placement="bottom" value={props.state.statusLabel}>
<StatusPopoverV2 /> <StatusPopoverV2 />
</Tooltip> </TooltipV2>
</Show> </Show>
<Show when={props.state.reviewVisible}> <Show when={props.state.reviewVisible}>
<TooltipV2 <TooltipV2
@@ -377,8 +377,8 @@ export const SettingsGeneralV2: Component<{
</SettingsRowV2> </SettingsRowV2>
<SettingsRowV2 <SettingsRowV2
title={language.t("settings.general.row.showStatus.title")} title={language.t("settings.general.row.showTools.title")}
description={language.t("settings.general.row.showStatus.description")} description={language.t("settings.general.row.showTools.description")}
> >
<div data-action="settings-show-status"> <div data-action="settings-show-status">
<Switch <Switch
@@ -16,6 +16,7 @@ import { type ServerHealth } from "@/utils/server-health"
import { useGlobal } from "@/context/global" import { useGlobal } from "@/context/global"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useMcpToggle } from "@/context/mcp" import { useMcpToggle } from "@/context/mcp"
import { ToolsMenu } from "./tools-menu"
const pluginEmptyMessage = (value: string, file: string): JSXElement => { const pluginEmptyMessage = (value: string, file: string): JSXElement => {
const parts = value.split(file) const parts = value.split(file)
@@ -280,7 +281,14 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
const toggleMcp = useMcpToggle() const toggleMcp = useMcpToggle()
const defaultServer = useDefaultServerKey(platform.getDefaultServer) const defaultServer = useDefaultServerKey(platform.getDefaultServer)
const mcpNames = createMemo(() => Object.keys(sync().data.mcp ?? {}).sort((a, b) => a.localeCompare(b))) const mcpNames = createMemo(() => Object.keys(sync().data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
const configuredMcpNames = createMemo(() =>
Object.keys(sync().data.config.mcp ?? {}).sort((a, b) => a.localeCompare(b)),
)
const mcpStatus = (name: string) => sync().data.mcp?.[name]?.status const mcpStatus = (name: string) => sync().data.mcp?.[name]?.status
const mcpError = (name: string) => {
const status = sync().data.mcp?.[name]
if (status?.status === "failed" || status?.status === "needs_client_registration") return status.error
}
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length) const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
const lspItems = createMemo(() => sync().data.lsp ?? []) const lspItems = createMemo(() => sync().data.lsp ?? [])
const lspCount = createMemo(() => lspItems().length) const lspCount = createMemo(() => lspItems().length)
@@ -289,6 +297,42 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
) )
const pluginCount = createMemo(() => plugins().length) const pluginCount = createMemo(() => plugins().length)
const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "opencode.json")) const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "opencode.json"))
if (settings.general.newLayoutDesigns()) {
return (
<ToolsMenu
labels={{
menu: language.t("status.popover.tools.trigger"),
mcp: language.t("status.popover.tab.mcp"),
lsp: language.t("status.popover.tab.lsp"),
plugins: language.t("status.popover.tab.plugins"),
mcpDescription: language.t("status.popover.tools.mcp.description"),
lspDescription: language.t("status.popover.tools.lsp.description"),
pluginsDescription: language.t("status.popover.tools.plugins.description"),
disabled: language.t("mcp.status.disabled"),
failed: language.t("mcp.status.failed"),
reauthenticate: language.t("status.popover.tools.reauthenticate"),
}}
empty={{
mcp: language.t("status.popover.tools.mcp.empty"),
lsp: language.t("status.popover.tools.lsp.empty"),
plugins: language.t("status.popover.tools.plugins.empty"),
}}
mcp={configuredMcpNames().map((name) => ({
name,
status: mcpStatus(name),
error: mcpError(name),
loading: !mcpStatus(name),
pending: toggleMcp.isPending && toggleMcp.variables === name,
onToggle: () => {
if (toggleMcp.isPending) return
toggleMcp.mutate(name)
},
}))}
lsp={lspItems().map((item) => ({ name: item.name || item.id, status: item.status }))}
plugins={plugins()}
/>
)
}
return ( return (
<div class="flex items-center gap-1 w-[360px] rounded-xl shadow-[var(--shadow-lg-border-base)]"> <div class="flex items-center gap-1 w-[360px] rounded-xl shadow-[var(--shadow-lg-border-base)]">
+8 -11
View File
@@ -1,7 +1,7 @@
// @refresh reload
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon" import { Icon } from "@opencode-ai/ui/icon"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { Popover } from "@opencode-ai/ui/popover" import { Popover } from "@opencode-ai/ui/popover"
import { Suspense, createMemo, createSignal, lazy, Show, type JSX } from "solid-js" import { Suspense, createMemo, createSignal, lazy, Show, type JSX } from "solid-js"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
@@ -9,6 +9,7 @@ import { ServerConnection, useServer } from "@/context/server"
import { useServerSDK } from "@/context/server-sdk" import { useServerSDK } from "@/context/server-sdk"
import { useSync } from "@/context/sync" import { useSync } from "@/context/sync"
import { useGlobal } from "@/context/global" import { useGlobal } from "@/context/global"
import { ToolsMenuIcon } from "./tools-menu"
import { hasNonBlockingServiceIssue, serverStatusDotClass } from "./status-popover-indicator" import { hasNonBlockingServiceIssue, serverStatusDotClass } from "./status-popover-indicator"
const Body = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverBody }))) const Body = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverBody })))
@@ -87,7 +88,10 @@ function DirectoryStatusPopover() {
const ready = createMemo(() => serverHealth() === false || (sync().data.mcp_ready && sync().data.lsp_ready)) const ready = createMemo(() => serverHealth() === false || (sync().data.mcp_ready && sync().data.lsp_ready))
const issue = createMemo(() => const issue = createMemo(() =>
hasNonBlockingServiceIssue({ hasNonBlockingServiceIssue({
mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status), mcp: Object.keys(sync().data.config.mcp ?? {}).flatMap((name) => {
const status = sync().data.mcp?.[name]
return status ? [status.status] : []
}),
lsp: (sync().data.lsp ?? []).map((item) => item.status), lsp: (sync().data.lsp ?? []).map((item) => item.status),
}), }),
) )
@@ -96,7 +100,7 @@ function DirectoryStatusPopover() {
ready: ready(), ready: ready(),
serverHealth: serverHealth(), serverHealth: serverHealth(),
issue: issue(), issue: issue(),
label: language.t("status.popover.trigger"), label: language.t("status.popover.tools.trigger"),
onOpenChange: setShown, onOpenChange: setShown,
body: () => ( body: () => (
<StatusPopoverBody shown={shown()}> <StatusPopoverBody shown={shown()}>
@@ -174,14 +178,7 @@ function StatusPopoverView(props: { state: StatusPopoverState }) {
state: props.state.shown ? "pressed" : undefined, state: props.state.shown ? "pressed" : undefined,
"aria-label": props.state.label, "aria-label": props.state.label,
}} }}
trigger={ trigger={<ToolsMenuIcon warning={props.state.issue} />}
<div class="relative size-4">
<IconV2 name={props.state.shown ? "status-active" : "status"} />
<div
class={`absolute -top-1 -right-1 size-2 rounded-full border border-[var(--v2-background-bg-deep)] ${serverStatusDotClass(props.state)}`}
/>
</div>
}
{...popoverProps} {...popoverProps}
> >
{props.state.body()} {props.state.body()}
@@ -0,0 +1,136 @@
import { For, type JSXElement } from "solid-js"
import { createStore } from "solid-js/store"
import { ToolsMenu, ToolsMenuIcon, type ToolsMenuProps, type ToolsMenuStatus, type ToolsMenuTab } from "./tools-menu"
export default {
title: "Desktop V2/Tools Menu",
id: "desktop-v2-tools-menu",
component: ToolsMenu,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: `The desktop V2 Tools menu shown in the titlebar. Stories cover every MCP status, both LSP states, populated plugin rows, and empty states for all tabs.`,
},
},
},
}
const labels = {
menu: "Tools",
mcp: "MCP",
lsp: "LSP",
plugins: "Plugins",
mcpDescription: "Add MCP servers in opencode.json",
lspDescription: "LSPs are auto-detected as you open files",
pluginsDescription: "Add plugins in opencode.json",
disabled: "Disabled",
failed: "Failed",
reauthenticate: "Reauthenticate",
}
const empty = {
mcp: "No MCPs configured yet",
lsp: "No active language servers yet",
plugins: "No plugins configured yet",
}
const mcp = [
{ name: "ui-docs", status: "needs_auth" },
{ name: "browser-tools", status: "needs_client_registration" },
{ name: "brave-search", status: "disabled" },
{ name: "fastmail-mcp", status: "connected" },
{ name: "puppeteer", status: "failed" },
{ name: "figma", status: "connected" },
{ name: "playwright", status: "failed" },
] satisfies Array<{ name: string; status: ToolsMenuStatus }>
const lsp = [
{ name: "typescript", status: "connected" },
{ name: "eslint", status: "connected" },
{ name: "rust-analyzer", status: "error" },
] satisfies ToolsMenuProps["lsp"]
const plugins = ["opencode-devcontainers", "opencode-daytona", "~/.config/opencode/plugins/ping.ts"]
const populated = {
labels,
empty,
mcp,
lsp,
plugins,
} satisfies Omit<ToolsMenuProps, "defaultTab">
function InteractiveMenu() {
const [state, setState] = createStore({ mcp: mcp.map((item) => ({ ...item, pending: false })) })
return (
<MenuPreview warning>
<ToolsMenu
{...populated}
mcp={state.mcp.map((item, index) => ({
...item,
onToggle: () => {
if (item.status === "needs_auth" || item.status === "needs_client_registration") return
setState("mcp", index, "status", item.status === "connected" ? "disabled" : "connected")
},
}))}
/>
</MenuPreview>
)
}
function MenuPreview(props: { warning?: boolean; children: JSXElement }) {
return (
<div class="flex flex-col gap-2">
<div class="flex h-7 items-center gap-2 text-[11px] font-[530] uppercase tracking-[0.05px] text-v2-text-text-faint">
<ToolsMenuIcon warning={props.warning} />
{props.warning ? "warning" : "normal"}
</div>
{props.children}
</div>
)
}
function Gallery(props: { empty?: boolean }) {
const tabs: ToolsMenuTab[] = ["mcp", "lsp", "plugins"]
return (
<div class="flex flex-wrap items-start gap-6">
<For each={tabs}>
{(tab) => (
<MenuPreview warning={!props.empty && tab !== "plugins"}>
<ToolsMenu
{...populated}
defaultTab={tab}
mcp={props.empty ? [] : populated.mcp}
lsp={props.empty ? [] : populated.lsp}
plugins={props.empty ? [] : populated.plugins}
/>
</MenuPreview>
)}
</For>
</div>
)
}
export const Interactive = {
render: () => <InteractiveMenu />,
}
export const AllTabs = {
name: "All populated tabs",
render: () => <Gallery />,
}
export const EmptyStates = {
name: "All empty tabs",
render: () => <Gallery empty />,
}
export const McpPending = {
name: "MCP pending toggle",
render: () => (
<MenuPreview warning>
<ToolsMenu {...populated} mcp={populated.mcp.map((item) => ({ ...item, pending: item.name === "figma" }))} />
</MenuPreview>
),
}
+202
View File
@@ -0,0 +1,202 @@
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { Icon } from "@opencode-ai/ui/v2/icon"
import { SegmentedControlItemV2, SegmentedControlV2 } from "@opencode-ai/ui/v2/segmented-control-v2"
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
import { For, Index, Show, type JSXElement } from "solid-js"
import { createStore } from "solid-js/store"
export type ToolsMenuTab = "mcp" | "lsp" | "plugins"
export type ToolsMenuStatus = "connected" | "failed" | "error" | "needs_auth" | "needs_client_registration" | "disabled"
export type ToolsMenuProps = {
defaultTab?: ToolsMenuTab
labels: {
menu: string
mcp: string
lsp: string
plugins: string
mcpDescription: string
lspDescription: string
pluginsDescription: string
disabled: string
failed: string
reauthenticate: string
}
empty: {
mcp: JSXElement
lsp: JSXElement
plugins: JSXElement
}
mcp: Array<{
name: string
status?: ToolsMenuStatus
error?: string
loading?: boolean
pending?: boolean
onToggle?: () => void
}>
lsp: Array<{
name: string
status: "connected" | "error"
}>
plugins: string[]
}
export function ToolsMenuIcon(props: { warning?: boolean }) {
return (
<div class="relative size-4 text-v2-icon-icon-muted">
<Icon name={props.warning ? "tools-warning" : "tools"} />
<Show when={props.warning}>
<div class="absolute right-0 top-0 size-[5px] rounded-full bg-icon-warning-base" />
</Show>
</div>
)
}
export function ToolsMenu(props: ToolsMenuProps) {
const [state, setState] = createStore({ tab: props.defaultTab ?? ("mcp" as ToolsMenuTab) })
return (
<div class="w-[360px] max-w-[calc(100vw-40px)] rounded-xl bg-v2-background-bg-base p-4 [font-family:var(--v2-font-family-sans)] shadow-[var(--v2-elevation-floating)]">
<SegmentedControlV2
value={state.tab}
onChange={(value) => value && setState("tab", value as ToolsMenuTab)}
class="segmented-control-v2--full-width"
aria-label={props.labels.menu}
>
<SegmentedControlItemV2 value="mcp">{props.labels.mcp}</SegmentedControlItemV2>
<SegmentedControlItemV2 value="lsp">{props.labels.lsp}</SegmentedControlItemV2>
<SegmentedControlItemV2 value="plugins">{props.labels.plugins}</SegmentedControlItemV2>
</SegmentedControlV2>
<Show when={state.tab === "mcp"}>
<Show
when={props.mcp.length > 0}
fallback={<ToolsEmpty title={props.empty.mcp} description={props.labels.mcpDescription} />}
>
<ToolsList description={props.labels.mcpDescription}>
<Index each={props.mcp}>
{(item) => (
<div class="flex h-8 items-center gap-2 px-2">
<StatusDot status={item().status} />
<span class="min-w-0 flex-1 truncate text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base">
{item().name}
</span>
<Show
when={item().status === "needs_auth"}
fallback={
<>
<Show when={item().status === "needs_client_registration"}>
<span
class="max-w-40 truncate text-[11px] font-[440] leading-4 tracking-[0.05px] text-v2-text-text-faint"
title={item().error}
>
{item().error ?? props.labels.failed}
</span>
</Show>
<Show when={item().status !== "needs_client_registration"}>
<Show when={item().status === "disabled" || item().status === "failed"}>
<span class="text-[11px] font-[440] capitalize leading-4 tracking-[0.05px] text-v2-text-text-faint">
{item().status === "disabled" ? props.labels.disabled : props.labels.failed}
</span>
</Show>
<Switch
checked={item().status === "connected"}
disabled={item().loading}
hideLabel
onChange={item().onToggle}
readOnly={item().pending}
>
{item().name}
</Switch>
</Show>
</>
}
>
<ButtonV2 size="small" variant="outline" disabled={item().pending} onClick={item().onToggle}>
{props.labels.reauthenticate}
</ButtonV2>
</Show>
</div>
)}
</Index>
</ToolsList>
</Show>
</Show>
<Show when={state.tab === "lsp"}>
<Show
when={props.lsp.length > 0}
fallback={<ToolsEmpty title={props.empty.lsp} description={props.labels.lspDescription} />}
>
<ToolsList description={props.labels.lspDescription}>
<For each={props.lsp}>
{(item) => (
<div class="flex h-8 items-center gap-2 px-2">
<StatusDot status={item.status} />
<span class="min-w-0 flex-1 truncate text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base">
{item.name}
</span>
</div>
)}
</For>
</ToolsList>
</Show>
</Show>
<Show when={state.tab === "plugins"}>
<Show
when={props.plugins.length > 0}
fallback={<ToolsEmpty title={props.empty.plugins} description={props.labels.pluginsDescription} />}
>
<ToolsList description={props.labels.pluginsDescription}>
<For each={props.plugins}>
{(plugin) => (
<div class="flex h-8 items-center gap-2 px-2">
<StatusDot status="connected" />
<span class="min-w-0 flex-1 truncate text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base">
{plugin}
</span>
</div>
)}
</For>
</ToolsList>
</Show>
</Show>
</div>
)
}
function ToolsList(props: { description: string; children: JSXElement }) {
return (
<div class="mt-2 flex flex-col">
<div class="flex h-8 items-center px-2 text-[11px] font-[440] leading-4 tracking-[0.05px] text-v2-text-text-faint">
{props.description}
</div>
{props.children}
</div>
)
}
function ToolsEmpty(props: { title: JSXElement; description: string }) {
return (
<div class="mt-4 flex flex-col gap-2 text-center text-[13px] leading-4 tracking-[-0.04px]">
<div class="h-4 font-[530] text-v2-text-text-muted">{props.title}</div>
<div class="h-4 font-[440] text-v2-text-text-faint">{props.description}</div>
</div>
)
}
function StatusDot(props: { status?: ToolsMenuStatus }) {
return (
<div
classList={{
"size-1.5 shrink-0 rounded-full": true,
"bg-v2-state-fg-success": props.status === "connected",
"bg-v2-state-fg-danger": props.status === "failed" || props.status === "error",
"bg-v2-state-fg-warning": props.status === "needs_auth" || props.status === "needs_client_registration",
"bg-v2-border-border-base": props.status === "disabled",
}}
/>
)
}
+7
View File
@@ -3,6 +3,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { createGlobalEmitter } from "@solid-primitives/event-bus" import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { makeEventListener } from "@solid-primitives/event-listener" import { makeEventListener } from "@solid-primitives/event-listener"
import { type Accessor, batch, createMemo, onCleanup, onMount } from "solid-js" import { type Accessor, batch, createMemo, onCleanup, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { createSdkForServer } from "@/utils/server" import { createSdkForServer } from "@/utils/server"
import { useLanguage } from "./language" import { useLanguage } from "./language"
import { usePlatform } from "./platform" import { usePlatform } from "./platform"
@@ -142,6 +143,9 @@ function createServerSdkContextBase(server: ServerConnection.Any, scope: ServerS
let run: Promise<void> | undefined let run: Promise<void> | undefined
let started = false let started = false
let generation = 0 let generation = 0
const [connection, setConnection] = createStore({
status: "connecting" as "connecting" | "connected" | "reconnecting",
})
const HEARTBEAT_TIMEOUT_MS = 15_000 const HEARTBEAT_TIMEOUT_MS = 15_000
let lastEventAt = Date.now() let lastEventAt = Date.now()
let heartbeat: ReturnType<typeof setTimeout> | undefined let heartbeat: ReturnType<typeof setTimeout> | undefined
@@ -187,6 +191,7 @@ function createServerSdkContextBase(server: ServerConnection.Any, scope: ServerS
}) })
}, },
}) })
setConnection("status", "connected")
let yielded = Date.now() let yielded = Date.now()
resetHeartbeat() resetHeartbeat()
for await (const event of events.stream) { for await (const event of events.stream) {
@@ -218,6 +223,7 @@ function createServerSdkContextBase(server: ServerConnection.Any, scope: ServerS
} }
if (abort.signal.aborted || !started || generation !== active) return if (abort.signal.aborted || !started || generation !== active) return
setConnection("status", "reconnecting")
await wait(RECONNECT_DELAY_MS) await wait(RECONNECT_DELAY_MS)
} }
})().finally(() => { })().finally(() => {
@@ -269,6 +275,7 @@ function createServerSdkContextBase(server: ServerConnection.Any, scope: ServerS
listen: emitter.listen.bind(emitter), listen: emitter.listen.bind(emitter),
start, start,
}, },
connection,
createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) { createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
return createSdkForServer({ return createSdkForServer({
server: server.http, server: server.http,
+12
View File
@@ -621,6 +621,14 @@ export const dict = {
"session.header.open.menu": "خيارات الفتح", "session.header.open.menu": "خيارات الفتح",
"session.header.open.copyPath": "نسخ المسار", "session.header.open.copyPath": "نسخ المسار",
"status.popover.trigger": "الحالة", "status.popover.trigger": "الحالة",
"status.popover.tools.trigger": "الأدوات",
"status.popover.tools.mcp.description": "أضف خوادم MCP في opencode.json",
"status.popover.tools.lsp.description": "يتم اكتشاف خوادم LSP تلقائيًا عند فتح الملفات",
"status.popover.tools.plugins.description": "أضف الإضافات في opencode.json",
"status.popover.tools.mcp.empty": "لم يتم إعداد أي خوادم MCP بعد",
"status.popover.tools.lsp.empty": "لا توجد خوادم لغة نشطة بعد",
"status.popover.tools.plugins.empty": "لم يتم إعداد أي إضافات بعد",
"status.popover.tools.reauthenticate": "إعادة المصادقة",
"status.popover.ariaLabel": "إعدادات الخوادم", "status.popover.ariaLabel": "إعدادات الخوادم",
"status.popover.tab.servers": "الخوادم", "status.popover.tab.servers": "الخوادم",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -722,6 +730,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "إظهار زر المحطة الطرفية في شريط عنوان سطح المكتب", "settings.general.row.showTerminal.description": "إظهار زر المحطة الطرفية في شريط عنوان سطح المكتب",
"settings.general.row.showStatus.title": "حالة الخادم", "settings.general.row.showStatus.title": "حالة الخادم",
"settings.general.row.showStatus.description": "إظهار زر حالة الخادم في شريط العنوان", "settings.general.row.showStatus.description": "إظهار زر حالة الخادم في شريط العنوان",
"settings.general.row.showTools.title": "قائمة الأدوات",
"settings.general.row.showTools.description": "إظهار وصول سريع إلى MCP وLSP والإضافات في شريط العنوان",
"settings.general.row.mobileTitlebarBottom.title": "التنقل السفلي", "settings.general.row.mobileTitlebarBottom.title": "التنقل السفلي",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"وضع شريط العنوان وعلامات تبويب الجلسات أسفل الشاشة على الأجهزة المحمولة", "وضع شريط العنوان وعلامات تبويب الجلسات أسفل الشاشة على الأجهزة المحمولة",
@@ -929,6 +939,8 @@ export const dict = {
"app.server.unreachable": "تعذر الوصول إلى {{server}}", "app.server.unreachable": "تعذر الوصول إلى {{server}}",
"app.server.retrying": "جاري إعادة المحاولة تلقائيًا...", "app.server.retrying": "جاري إعادة المحاولة تلقائيًا...",
"app.server.status.reconnecting": "جارٍ إعادة الاتصال بـ {{server}}...",
"app.server.status.disconnected": "تعذر الاتصال بـ {{server}}",
"app.server.otherServers": "خوادم أخرى", "app.server.otherServers": "خوادم أخرى",
"dialog.server.add.usernamePlaceholder": "اسم المستخدم", "dialog.server.add.usernamePlaceholder": "اسم المستخدم",
"dialog.server.add.passwordPlaceholder": "كلمة المرور", "dialog.server.add.passwordPlaceholder": "كلمة المرور",
+12
View File
@@ -627,6 +627,14 @@ export const dict = {
"session.header.open.menu": "Opções de abertura", "session.header.open.menu": "Opções de abertura",
"session.header.open.copyPath": "Copiar caminho", "session.header.open.copyPath": "Copiar caminho",
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.tools.trigger": "Ferramentas",
"status.popover.tools.mcp.description": "Adicione servidores MCP no opencode.json",
"status.popover.tools.lsp.description": "Os LSPs são detectados automaticamente ao abrir arquivos",
"status.popover.tools.plugins.description": "Adicione plugins no opencode.json",
"status.popover.tools.mcp.empty": "Nenhum MCP configurado ainda",
"status.popover.tools.lsp.empty": "Nenhum servidor de linguagem ativo ainda",
"status.popover.tools.plugins.empty": "Nenhum plugin configurado ainda",
"status.popover.tools.reauthenticate": "Autenticar novamente",
"status.popover.ariaLabel": "Configurações de servidores", "status.popover.ariaLabel": "Configurações de servidores",
"status.popover.tab.servers": "Servidores", "status.popover.tab.servers": "Servidores",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -733,6 +741,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Mostrar o botão do terminal na barra de título do desktop", "settings.general.row.showTerminal.description": "Mostrar o botão do terminal na barra de título do desktop",
"settings.general.row.showStatus.title": "Status do servidor", "settings.general.row.showStatus.title": "Status do servidor",
"settings.general.row.showStatus.description": "Mostrar o botão de status do servidor na barra de título", "settings.general.row.showStatus.description": "Mostrar o botão de status do servidor na barra de título",
"settings.general.row.showTools.title": "Menu de ferramentas",
"settings.general.row.showTools.description": "Mostrar acesso rápido a MCPs, LSPs e plugins na barra de título",
"settings.general.row.mobileTitlebarBottom.title": "Navegação inferior", "settings.general.row.mobileTitlebarBottom.title": "Navegação inferior",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Posicionar a barra de título e as abas da sessão na parte inferior da tela em dispositivos móveis", "Posicionar a barra de título e as abas da sessão na parte inferior da tela em dispositivos móveis",
@@ -943,6 +953,8 @@ export const dict = {
"app.server.unreachable": "Não foi possível conectar a {{server}}", "app.server.unreachable": "Não foi possível conectar a {{server}}",
"app.server.retrying": "Tentando novamente automaticamente...", "app.server.retrying": "Tentando novamente automaticamente...",
"app.server.status.reconnecting": "Reconectando a {{server}}...",
"app.server.status.disconnected": "Não foi possível conectar a {{server}}",
"app.server.otherServers": "Outros servidores", "app.server.otherServers": "Outros servidores",
"dialog.server.add.usernamePlaceholder": "nome de usuário", "dialog.server.add.usernamePlaceholder": "nome de usuário",
"dialog.server.add.passwordPlaceholder": "senha", "dialog.server.add.passwordPlaceholder": "senha",
+12
View File
@@ -685,6 +685,14 @@ export const dict = {
"session.header.open.copyPath": "Kopiraj putanju", "session.header.open.copyPath": "Kopiraj putanju",
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.tools.trigger": "Alati",
"status.popover.tools.mcp.description": "Dodajte MCP servere u opencode.json",
"status.popover.tools.lsp.description": "LSP serveri se automatski otkrivaju dok otvarate datoteke",
"status.popover.tools.plugins.description": "Dodajte dodatke u opencode.json",
"status.popover.tools.mcp.empty": "Još nema konfigurisanih MCP servera",
"status.popover.tools.lsp.empty": "Još nema aktivnih jezičkih servera",
"status.popover.tools.plugins.empty": "Još nema konfigurisanih dodataka",
"status.popover.tools.reauthenticate": "Ponovo se autentificiraj",
"status.popover.ariaLabel": "Konfiguracije servera", "status.popover.ariaLabel": "Konfiguracije servera",
"status.popover.tab.servers": "Serveri", "status.popover.tab.servers": "Serveri",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -797,6 +805,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Prikaži dugme terminala u naslovnoj traci desktop aplikacije", "settings.general.row.showTerminal.description": "Prikaži dugme terminala u naslovnoj traci desktop aplikacije",
"settings.general.row.showStatus.title": "Status servera", "settings.general.row.showStatus.title": "Status servera",
"settings.general.row.showStatus.description": "Prikaži dugme statusa servera u naslovnoj traci", "settings.general.row.showStatus.description": "Prikaži dugme statusa servera u naslovnoj traci",
"settings.general.row.showTools.title": "Meni alata",
"settings.general.row.showTools.description": "Prikaži brzi pristup MCP, LSP i dodacima u naslovnoj traci",
"settings.general.row.mobileTitlebarBottom.title": "Donja navigacija", "settings.general.row.mobileTitlebarBottom.title": "Donja navigacija",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Postavi naslovnu traku i kartice sesije na dno ekrana na mobilnim uređajima", "Postavi naslovnu traku i kartice sesije na dno ekrana na mobilnim uređajima",
@@ -1019,6 +1029,8 @@ export const dict = {
"app.server.unreachable": "Nije moguće pristupiti {{server}}", "app.server.unreachable": "Nije moguće pristupiti {{server}}",
"app.server.retrying": "Automatski ponovni pokušaj...", "app.server.retrying": "Automatski ponovni pokušaj...",
"app.server.status.reconnecting": "Ponovno povezivanje sa {{server}}...",
"app.server.status.disconnected": "Nije moguće povezati se sa {{server}}",
"app.server.otherServers": "Drugi serveri", "app.server.otherServers": "Drugi serveri",
"dialog.server.add.usernamePlaceholder": "korisničko ime", "dialog.server.add.usernamePlaceholder": "korisničko ime",
"dialog.server.add.passwordPlaceholder": "lozinka", "dialog.server.add.passwordPlaceholder": "lozinka",
+12
View File
@@ -680,6 +680,14 @@ export const dict = {
"session.header.open.copyPath": "Kopier sti", "session.header.open.copyPath": "Kopier sti",
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.tools.trigger": "Værktøjer",
"status.popover.tools.mcp.description": "Tilføj MCP-servere i opencode.json",
"status.popover.tools.lsp.description": "LSP'er registreres automatisk, når du åbner filer",
"status.popover.tools.plugins.description": "Tilføj plugins i opencode.json",
"status.popover.tools.mcp.empty": "Ingen MCP'er konfigureret endnu",
"status.popover.tools.lsp.empty": "Ingen aktive sprogservere endnu",
"status.popover.tools.plugins.empty": "Ingen plugins konfigureret endnu",
"status.popover.tools.reauthenticate": "Godkend igen",
"status.popover.ariaLabel": "Serverkonfigurationer", "status.popover.ariaLabel": "Serverkonfigurationer",
"status.popover.tab.servers": "Servere", "status.popover.tab.servers": "Servere",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -791,6 +799,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Vis terminalknappen i desktopappens titellinje", "settings.general.row.showTerminal.description": "Vis terminalknappen i desktopappens titellinje",
"settings.general.row.showStatus.title": "Serverstatus", "settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Vis serverstatusknappen i titellinjen", "settings.general.row.showStatus.description": "Vis serverstatusknappen i titellinjen",
"settings.general.row.showTools.title": "Værktøjsmenu",
"settings.general.row.showTools.description": "Vis hurtig adgang til MCP'er, LSP'er og plugins i titellinjen",
"settings.general.row.mobileTitlebarBottom.title": "Navigation nederst", "settings.general.row.mobileTitlebarBottom.title": "Navigation nederst",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Placer titellinjen og sessionsfanerne nederst på skærmen på mobilenheder", "Placer titellinjen og sessionsfanerne nederst på skærmen på mobilenheder",
@@ -1011,6 +1021,8 @@ export const dict = {
"app.server.unreachable": "Kunne ikke nå {{server}}", "app.server.unreachable": "Kunne ikke nå {{server}}",
"app.server.retrying": "Prøver igen automatisk...", "app.server.retrying": "Prøver igen automatisk...",
"app.server.status.reconnecting": "Opretter forbindelse til {{server}} igen...",
"app.server.status.disconnected": "Kunne ikke oprette forbindelse til {{server}}",
"app.server.otherServers": "Andre servere", "app.server.otherServers": "Andre servere",
"dialog.server.add.usernamePlaceholder": "brugernavn", "dialog.server.add.usernamePlaceholder": "brugernavn",
"dialog.server.add.passwordPlaceholder": "adgangskode", "dialog.server.add.passwordPlaceholder": "adgangskode",
+12
View File
@@ -636,6 +636,14 @@ export const dict = {
"session.header.open.menu": "Öffnen-Optionen", "session.header.open.menu": "Öffnen-Optionen",
"session.header.open.copyPath": "Pfad kopieren", "session.header.open.copyPath": "Pfad kopieren",
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.tools.trigger": "Werkzeuge",
"status.popover.tools.mcp.description": "MCP-Server in opencode.json hinzufügen",
"status.popover.tools.lsp.description": "LSPs werden beim Öffnen von Dateien automatisch erkannt",
"status.popover.tools.plugins.description": "Plugins in opencode.json hinzufügen",
"status.popover.tools.mcp.empty": "Noch keine MCPs konfiguriert",
"status.popover.tools.lsp.empty": "Noch keine aktiven Sprachserver",
"status.popover.tools.plugins.empty": "Noch keine Plugins konfiguriert",
"status.popover.tools.reauthenticate": "Erneut authentifizieren",
"status.popover.ariaLabel": "Serverkonfigurationen", "status.popover.ariaLabel": "Serverkonfigurationen",
"status.popover.tab.servers": "Server", "status.popover.tab.servers": "Server",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -745,6 +753,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Terminal-Schaltfläche in der Desktop-Titelleiste anzeigen", "settings.general.row.showTerminal.description": "Terminal-Schaltfläche in der Desktop-Titelleiste anzeigen",
"settings.general.row.showStatus.title": "Serverstatus", "settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Serverstatus-Schaltfläche in der Titelleiste anzeigen", "settings.general.row.showStatus.description": "Serverstatus-Schaltfläche in der Titelleiste anzeigen",
"settings.general.row.showTools.title": "Werkzeugmenü",
"settings.general.row.showTools.description": "Schnellzugriff auf MCPs, LSPs und Plugins in der Titelleiste anzeigen",
"settings.general.row.mobileTitlebarBottom.title": "Navigation unten", "settings.general.row.mobileTitlebarBottom.title": "Navigation unten",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Titelleiste und Sitzungs-Tabs auf Mobilgeräten am unteren Bildschirmrand platzieren", "Titelleiste und Sitzungs-Tabs auf Mobilgeräten am unteren Bildschirmrand platzieren",
@@ -955,6 +965,8 @@ export const dict = {
"app.server.unreachable": "Konnte {{server}} nicht erreichen", "app.server.unreachable": "Konnte {{server}} nicht erreichen",
"app.server.retrying": "Automatische erneute Verbindung...", "app.server.retrying": "Automatische erneute Verbindung...",
"app.server.status.reconnecting": "Verbindung zu {{server}} wird wiederhergestellt...",
"app.server.status.disconnected": "Verbindung zu {{server}} konnte nicht hergestellt werden",
"app.server.otherServers": "Andere Server", "app.server.otherServers": "Andere Server",
"dialog.server.add.usernamePlaceholder": "Benutzername", "dialog.server.add.usernamePlaceholder": "Benutzername",
"dialog.server.add.passwordPlaceholder": "Passwort", "dialog.server.add.passwordPlaceholder": "Passwort",
+12
View File
@@ -332,6 +332,8 @@ export const dict = {
"app.server.unreachable": "Could not reach {{server}}", "app.server.unreachable": "Could not reach {{server}}",
"app.server.retrying": "Retrying automatically...", "app.server.retrying": "Retrying automatically...",
"app.server.status.reconnecting": "Reconnecting to {{server}}...",
"app.server.status.disconnected": "Couldn't connect to {{server}}",
"app.server.otherServers": "Other servers", "app.server.otherServers": "Other servers",
"dialog.server.title": "Servers", "dialog.server.title": "Servers",
@@ -728,6 +730,14 @@ export const dict = {
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.ariaLabel": "Server configurations", "status.popover.ariaLabel": "Server configurations",
"status.popover.tools.trigger": "Tools",
"status.popover.tools.mcp.description": "Add MCP servers in opencode.json",
"status.popover.tools.lsp.description": "LSPs are auto-detected as you open files",
"status.popover.tools.plugins.description": "Add plugins in opencode.json",
"status.popover.tools.mcp.empty": "No MCPs configured yet",
"status.popover.tools.lsp.empty": "No active language servers yet",
"status.popover.tools.plugins.empty": "No plugins configured yet",
"status.popover.tools.reauthenticate": "Reauthenticate",
"status.popover.tab.servers": "Servers", "status.popover.tab.servers": "Servers",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
"status.popover.tab.lsp": "LSP", "status.popover.tab.lsp": "LSP",
@@ -886,6 +896,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Show the terminal button in the desktop title bar", "settings.general.row.showTerminal.description": "Show the terminal button in the desktop title bar",
"settings.general.row.showStatus.title": "Server status", "settings.general.row.showStatus.title": "Server status",
"settings.general.row.showStatus.description": "Show the server status button in the title bar", "settings.general.row.showStatus.description": "Show the server status button in the title bar",
"settings.general.row.showTools.title": "Tools menu",
"settings.general.row.showTools.description": "Show quick access to MCPs, LSPs, and plugins in the title bar",
"settings.general.row.mobileTitlebarBottom.title": "Bottom navigation", "settings.general.row.mobileTitlebarBottom.title": "Bottom navigation",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Place the title bar and session tabs at the bottom of the screen on mobile", "Place the title bar and session tabs at the bottom of the screen on mobile",
+12
View File
@@ -686,6 +686,14 @@ export const dict = {
"session.header.open.copyPath": "Copiar ruta", "session.header.open.copyPath": "Copiar ruta",
"status.popover.trigger": "Estado", "status.popover.trigger": "Estado",
"status.popover.tools.trigger": "Herramientas",
"status.popover.tools.mcp.description": "Añade servidores MCP en opencode.json",
"status.popover.tools.lsp.description": "Los LSP se detectan automáticamente al abrir archivos",
"status.popover.tools.plugins.description": "Añade plugins en opencode.json",
"status.popover.tools.mcp.empty": "Aún no hay MCP configurados",
"status.popover.tools.lsp.empty": "Aún no hay servidores de lenguaje activos",
"status.popover.tools.plugins.empty": "Aún no hay plugins configurados",
"status.popover.tools.reauthenticate": "Volver a autenticar",
"status.popover.ariaLabel": "Configuraciones del servidor", "status.popover.ariaLabel": "Configuraciones del servidor",
"status.popover.tab.servers": "Servidores", "status.popover.tab.servers": "Servidores",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -802,6 +810,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Mostrar el botón del terminal en la barra de título", "settings.general.row.showTerminal.description": "Mostrar el botón del terminal en la barra de título",
"settings.general.row.showStatus.title": "Estado del servidor", "settings.general.row.showStatus.title": "Estado del servidor",
"settings.general.row.showStatus.description": "Mostrar el botón de estado del servidor en la barra de título", "settings.general.row.showStatus.description": "Mostrar el botón de estado del servidor en la barra de título",
"settings.general.row.showTools.title": "Menú de herramientas",
"settings.general.row.showTools.description": "Mostrar acceso rápido a MCP, LSP y plugins en la barra de título",
"settings.general.row.mobileTitlebarBottom.title": "Navegación inferior", "settings.general.row.mobileTitlebarBottom.title": "Navegación inferior",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Colocar la barra de título y las pestañas de sesión en la parte inferior de la pantalla en dispositivos móviles", "Colocar la barra de título y las pestañas de sesión en la parte inferior de la pantalla en dispositivos móviles",
@@ -1027,6 +1037,8 @@ export const dict = {
"app.server.unreachable": "No se pudo conectar con {{server}}", "app.server.unreachable": "No se pudo conectar con {{server}}",
"app.server.retrying": "Reintentando automáticamente...", "app.server.retrying": "Reintentando automáticamente...",
"app.server.status.reconnecting": "Reconectando con {{server}}...",
"app.server.status.disconnected": "No se pudo conectar con {{server}}",
"app.server.otherServers": "Otros servidores", "app.server.otherServers": "Otros servidores",
"dialog.server.add.usernamePlaceholder": "usuario", "dialog.server.add.usernamePlaceholder": "usuario",
"dialog.server.add.passwordPlaceholder": "contraseña", "dialog.server.add.passwordPlaceholder": "contraseña",
+13
View File
@@ -632,6 +632,14 @@ export const dict = {
"session.header.open.menu": "Options d'ouverture", "session.header.open.menu": "Options d'ouverture",
"session.header.open.copyPath": "Copier le chemin", "session.header.open.copyPath": "Copier le chemin",
"status.popover.trigger": "Statut", "status.popover.trigger": "Statut",
"status.popover.tools.trigger": "Outils",
"status.popover.tools.mcp.description": "Ajouter des serveurs MCP dans opencode.json",
"status.popover.tools.lsp.description": "Les LSP sont détectés automatiquement à louverture des fichiers",
"status.popover.tools.plugins.description": "Ajouter des plugins dans opencode.json",
"status.popover.tools.mcp.empty": "Aucun MCP configuré pour le moment",
"status.popover.tools.lsp.empty": "Aucun serveur de langage actif pour le moment",
"status.popover.tools.plugins.empty": "Aucun plugin configuré pour le moment",
"status.popover.tools.reauthenticate": "Sauthentifier à nouveau",
"status.popover.ariaLabel": "Configurations des serveurs", "status.popover.ariaLabel": "Configurations des serveurs",
"status.popover.tab.servers": "Serveurs", "status.popover.tab.servers": "Serveurs",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -742,6 +750,9 @@ export const dict = {
"Afficher le bouton du terminal dans la barre de titre de l'application desktop", "Afficher le bouton du terminal dans la barre de titre de l'application desktop",
"settings.general.row.showStatus.title": "État du serveur", "settings.general.row.showStatus.title": "État du serveur",
"settings.general.row.showStatus.description": "Afficher le bouton d'état du serveur dans la barre de titre", "settings.general.row.showStatus.description": "Afficher le bouton d'état du serveur dans la barre de titre",
"settings.general.row.showTools.title": "Menu Outils",
"settings.general.row.showTools.description":
"Afficher un accès rapide aux MCP, LSP et plugins dans la barre de titre",
"settings.general.row.mobileTitlebarBottom.title": "Navigation en bas", "settings.general.row.mobileTitlebarBottom.title": "Navigation en bas",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Placer la barre de titre et les onglets de session en bas de l'écran sur mobile", "Placer la barre de titre et les onglets de session en bas de l'écran sur mobile",
@@ -954,6 +965,8 @@ export const dict = {
"app.server.unreachable": "Impossible de joindre {{server}}", "app.server.unreachable": "Impossible de joindre {{server}}",
"app.server.retrying": "Nouvelle tentative automatique...", "app.server.retrying": "Nouvelle tentative automatique...",
"app.server.status.reconnecting": "Reconnexion à {{server}}...",
"app.server.status.disconnected": "Impossible de se connecter à {{server}}",
"app.server.otherServers": "Autres serveurs", "app.server.otherServers": "Autres serveurs",
"dialog.server.add.usernamePlaceholder": "nom d'utilisateur", "dialog.server.add.usernamePlaceholder": "nom d'utilisateur",
"dialog.server.add.passwordPlaceholder": "mot de passe", "dialog.server.add.passwordPlaceholder": "mot de passe",
+12
View File
@@ -623,6 +623,14 @@ export const dict = {
"session.header.open.menu": "開くオプション", "session.header.open.menu": "開くオプション",
"session.header.open.copyPath": "パスをコピー", "session.header.open.copyPath": "パスをコピー",
"status.popover.trigger": "ステータス", "status.popover.trigger": "ステータス",
"status.popover.tools.trigger": "ツール",
"status.popover.tools.mcp.description": "opencode.json に MCP サーバーを追加",
"status.popover.tools.lsp.description": "ファイルを開くと LSP が自動検出されます",
"status.popover.tools.plugins.description": "opencode.json にプラグインを追加",
"status.popover.tools.mcp.empty": "MCP はまだ設定されていません",
"status.popover.tools.lsp.empty": "アクティブな言語サーバーはまだありません",
"status.popover.tools.plugins.empty": "プラグインはまだ設定されていません",
"status.popover.tools.reauthenticate": "再認証",
"status.popover.ariaLabel": "サーバー設定", "status.popover.ariaLabel": "サーバー設定",
"status.popover.tab.servers": "サーバー", "status.popover.tab.servers": "サーバー",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -728,6 +736,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "デスクトップのタイトルバーにターミナルボタンを表示します", "settings.general.row.showTerminal.description": "デスクトップのタイトルバーにターミナルボタンを表示します",
"settings.general.row.showStatus.title": "サーバーステータス", "settings.general.row.showStatus.title": "サーバーステータス",
"settings.general.row.showStatus.description": "タイトルバーにサーバーステータスボタンを表示します", "settings.general.row.showStatus.description": "タイトルバーにサーバーステータスボタンを表示します",
"settings.general.row.showTools.title": "ツールメニュー",
"settings.general.row.showTools.description": "タイトルバーに MCP、LSP、プラグインへのクイックアクセスを表示します",
"settings.general.row.mobileTitlebarBottom.title": "下部ナビゲーション", "settings.general.row.mobileTitlebarBottom.title": "下部ナビゲーション",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"モバイルではタイトルバーとセッションタブを画面下部に配置します", "モバイルではタイトルバーとセッションタブを画面下部に配置します",
@@ -936,6 +946,8 @@ export const dict = {
"app.server.unreachable": "{{server}} に到達できませんでした", "app.server.unreachable": "{{server}} に到達できませんでした",
"app.server.retrying": "自動的に再試行中...", "app.server.retrying": "自動的に再試行中...",
"app.server.status.reconnecting": "{{server}} に再接続しています...",
"app.server.status.disconnected": "{{server}} に接続できませんでした",
"app.server.otherServers": "その他のサーバー", "app.server.otherServers": "その他のサーバー",
"dialog.server.add.usernamePlaceholder": "ユーザー名", "dialog.server.add.usernamePlaceholder": "ユーザー名",
"dialog.server.add.passwordPlaceholder": "パスワード", "dialog.server.add.passwordPlaceholder": "パスワード",
+12
View File
@@ -503,6 +503,14 @@ export const dict = {
"session.header.open.menu": "열기 옵션", "session.header.open.menu": "열기 옵션",
"session.header.open.copyPath": "경로 복사", "session.header.open.copyPath": "경로 복사",
"status.popover.trigger": "상태", "status.popover.trigger": "상태",
"status.popover.tools.trigger": "도구",
"status.popover.tools.mcp.description": "opencode.json에 MCP 서버 추가",
"status.popover.tools.lsp.description": "파일을 열면 LSP가 자동으로 감지됩니다",
"status.popover.tools.plugins.description": "opencode.json에 플러그인 추가",
"status.popover.tools.mcp.empty": "아직 구성된 MCP가 없습니다",
"status.popover.tools.lsp.empty": "아직 활성 언어 서버가 없습니다",
"status.popover.tools.plugins.empty": "아직 구성된 플러그인이 없습니다",
"status.popover.tools.reauthenticate": "다시 인증",
"status.popover.ariaLabel": "서버 구성", "status.popover.ariaLabel": "서버 구성",
"status.popover.tab.servers": "서버", "status.popover.tab.servers": "서버",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -782,6 +790,8 @@ export const dict = {
"app.server.unreachable": "{{server}}에 연결할 수 없습니다", "app.server.unreachable": "{{server}}에 연결할 수 없습니다",
"app.server.retrying": "자동으로 재시도 중...", "app.server.retrying": "자동으로 재시도 중...",
"app.server.status.reconnecting": "{{server}}에 다시 연결하는 중...",
"app.server.status.disconnected": "{{server}}에 연결할 수 없습니다",
"app.server.otherServers": "다른 서버", "app.server.otherServers": "다른 서버",
"dialog.server.add.usernamePlaceholder": "사용자 이름", "dialog.server.add.usernamePlaceholder": "사용자 이름",
"dialog.server.add.passwordPlaceholder": "비밀번호", "dialog.server.add.passwordPlaceholder": "비밀번호",
@@ -995,6 +1005,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "데스크톱 제목 표시줄에 터미널 버튼 표시", "settings.general.row.showTerminal.description": "데스크톱 제목 표시줄에 터미널 버튼 표시",
"settings.general.row.showStatus.title": "서버 상태", "settings.general.row.showStatus.title": "서버 상태",
"settings.general.row.showStatus.description": "제목 표시줄에 서버 상태 버튼 표시", "settings.general.row.showStatus.description": "제목 표시줄에 서버 상태 버튼 표시",
"settings.general.row.showTools.title": "도구 메뉴",
"settings.general.row.showTools.description": "제목 표시줄에 MCP, LSP 및 플러그인 빠른 접근 표시",
"settings.general.row.mobileTitlebarBottom.title": "하단 탐색", "settings.general.row.mobileTitlebarBottom.title": "하단 탐색",
"settings.general.row.mobileTitlebarBottom.description": "모바일에서 제목 표시줄과 세션 탭을 화면 하단에 배치", "settings.general.row.mobileTitlebarBottom.description": "모바일에서 제목 표시줄과 세션 탭을 화면 하단에 배치",
"settings.general.row.showCustomAgents.title": "사용자 지정 에이전트", "settings.general.row.showCustomAgents.title": "사용자 지정 에이전트",
+12
View File
@@ -567,6 +567,14 @@ export const dict = {
"session.header.open.copyPath": "Kopier bane", "session.header.open.copyPath": "Kopier bane",
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.tools.trigger": "Verktøy",
"status.popover.tools.mcp.description": "Legg til MCP-servere i opencode.json",
"status.popover.tools.lsp.description": "LSP-er oppdages automatisk når du åpner filer",
"status.popover.tools.plugins.description": "Legg til plugins i opencode.json",
"status.popover.tools.mcp.empty": "Ingen MCP-er konfigurert ennå",
"status.popover.tools.lsp.empty": "Ingen aktive språkservere ennå",
"status.popover.tools.plugins.empty": "Ingen plugins konfigurert ennå",
"status.popover.tools.reauthenticate": "Autentiser på nytt",
"status.popover.ariaLabel": "Serverkonfigurasjoner", "status.popover.ariaLabel": "Serverkonfigurasjoner",
"status.popover.tab.servers": "Servere", "status.popover.tab.servers": "Servere",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -869,6 +877,8 @@ export const dict = {
"app.server.unreachable": "Kunne ikke nå {{server}}", "app.server.unreachable": "Kunne ikke nå {{server}}",
"app.server.retrying": "Prøver på nytt automatisk...", "app.server.retrying": "Prøver på nytt automatisk...",
"app.server.status.reconnecting": "Kobler til {{server}} på nytt...",
"app.server.status.disconnected": "Kunne ikke koble til {{server}}",
"app.server.otherServers": "Andre servere", "app.server.otherServers": "Andre servere",
"dialog.server.add.usernamePlaceholder": "brukernavn", "dialog.server.add.usernamePlaceholder": "brukernavn",
"dialog.server.add.passwordPlaceholder": "passord", "dialog.server.add.passwordPlaceholder": "passord",
@@ -1090,6 +1100,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Vis terminalknappen på tittellinjen i skrivebordsappen", "settings.general.row.showTerminal.description": "Vis terminalknappen på tittellinjen i skrivebordsappen",
"settings.general.row.showStatus.title": "Serverstatus", "settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Vis serverstatusknappen på tittellinjen", "settings.general.row.showStatus.description": "Vis serverstatusknappen på tittellinjen",
"settings.general.row.showTools.title": "Verktøymeny",
"settings.general.row.showTools.description": "Vis hurtigtilgang til MCP-er, LSP-er og plugins på tittellinjen",
"settings.general.row.mobileTitlebarBottom.title": "Navigasjon nederst", "settings.general.row.mobileTitlebarBottom.title": "Navigasjon nederst",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Plasser tittellinjen og sesjonsfanene nederst på mobilskjermen", "Plasser tittellinjen og sesjonsfanene nederst på mobilskjermen",
+12
View File
@@ -627,6 +627,14 @@ export const dict = {
"session.header.open.menu": "Opcje otwierania", "session.header.open.menu": "Opcje otwierania",
"session.header.open.copyPath": "Kopiuj ścieżkę", "session.header.open.copyPath": "Kopiuj ścieżkę",
"status.popover.trigger": "Status", "status.popover.trigger": "Status",
"status.popover.tools.trigger": "Narzędzia",
"status.popover.tools.mcp.description": "Dodaj serwery MCP w opencode.json",
"status.popover.tools.lsp.description": "LSP są wykrywane automatycznie podczas otwierania plików",
"status.popover.tools.plugins.description": "Dodaj wtyczki w opencode.json",
"status.popover.tools.mcp.empty": "Nie skonfigurowano jeszcze MCP",
"status.popover.tools.lsp.empty": "Brak aktywnych serwerów językowych",
"status.popover.tools.plugins.empty": "Nie skonfigurowano jeszcze wtyczek",
"status.popover.tools.reauthenticate": "Uwierzytelnij ponownie",
"status.popover.ariaLabel": "Konfiguracje serwerów", "status.popover.ariaLabel": "Konfiguracje serwerów",
"status.popover.tab.servers": "Serwery", "status.popover.tab.servers": "Serwery",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -733,6 +741,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Pokaż przycisk terminala na pasku tytułu aplikacji komputerowej", "settings.general.row.showTerminal.description": "Pokaż przycisk terminala na pasku tytułu aplikacji komputerowej",
"settings.general.row.showStatus.title": "Status serwera", "settings.general.row.showStatus.title": "Status serwera",
"settings.general.row.showStatus.description": "Pokaż przycisk statusu serwera na pasku tytułu", "settings.general.row.showStatus.description": "Pokaż przycisk statusu serwera na pasku tytułu",
"settings.general.row.showTools.title": "Menu narzędzi",
"settings.general.row.showTools.description": "Pokaż szybki dostęp do MCP, LSP i wtyczek na pasku tytułu",
"settings.general.row.mobileTitlebarBottom.title": "Dolna nawigacja", "settings.general.row.mobileTitlebarBottom.title": "Dolna nawigacja",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Umieść pasek tytułu i karty sesji u dołu ekranu na urządzeniach mobilnych", "Umieść pasek tytułu i karty sesji u dołu ekranu na urządzeniach mobilnych",
@@ -942,6 +952,8 @@ export const dict = {
"app.server.unreachable": "Nie można połączyć z {{server}}", "app.server.unreachable": "Nie można połączyć z {{server}}",
"app.server.retrying": "Ponawianie automatycznie...", "app.server.retrying": "Ponawianie automatycznie...",
"app.server.status.reconnecting": "Ponowne łączenie z {{server}}...",
"app.server.status.disconnected": "Nie można połączyć z {{server}}",
"app.server.otherServers": "Inne serwery", "app.server.otherServers": "Inne serwery",
"dialog.server.add.usernamePlaceholder": "nazwa użytkownika", "dialog.server.add.usernamePlaceholder": "nazwa użytkownika",
"dialog.server.add.passwordPlaceholder": "hasło", "dialog.server.add.passwordPlaceholder": "hasło",
+12
View File
@@ -683,6 +683,14 @@ export const dict = {
"session.header.open.copyPath": "Копировать путь", "session.header.open.copyPath": "Копировать путь",
"status.popover.trigger": "Статус", "status.popover.trigger": "Статус",
"status.popover.tools.trigger": "Инструменты",
"status.popover.tools.mcp.description": "Добавьте серверы MCP в opencode.json",
"status.popover.tools.lsp.description": "LSP определяются автоматически при открытии файлов",
"status.popover.tools.plugins.description": "Добавьте плагины в opencode.json",
"status.popover.tools.mcp.empty": "MCP пока не настроены",
"status.popover.tools.lsp.empty": "Активных языковых серверов пока нет",
"status.popover.tools.plugins.empty": "Плагины пока не настроены",
"status.popover.tools.reauthenticate": "Повторно авторизоваться",
"status.popover.ariaLabel": "Настройки серверов", "status.popover.ariaLabel": "Настройки серверов",
"status.popover.tab.servers": "Серверы", "status.popover.tab.servers": "Серверы",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -798,6 +806,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Показывать кнопку терминала в строке заголовка приложения", "settings.general.row.showTerminal.description": "Показывать кнопку терминала в строке заголовка приложения",
"settings.general.row.showStatus.title": "Состояние сервера", "settings.general.row.showStatus.title": "Состояние сервера",
"settings.general.row.showStatus.description": "Показывать кнопку состояния сервера в строке заголовка", "settings.general.row.showStatus.description": "Показывать кнопку состояния сервера в строке заголовка",
"settings.general.row.showTools.title": "Меню инструментов",
"settings.general.row.showTools.description": "Показывать быстрый доступ к MCP, LSP и плагинам в строке заголовка",
"settings.general.row.mobileTitlebarBottom.title": "Нижняя навигация", "settings.general.row.mobileTitlebarBottom.title": "Нижняя навигация",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"На мобильных устройствах размещать строку заголовка и вкладки сессии внизу экрана", "На мобильных устройствах размещать строку заголовка и вкладки сессии внизу экрана",
@@ -1022,6 +1032,8 @@ export const dict = {
"app.server.unreachable": "Не удалось связаться с {{server}}", "app.server.unreachable": "Не удалось связаться с {{server}}",
"app.server.retrying": "Автоматическая повторная попытка...", "app.server.retrying": "Автоматическая повторная попытка...",
"app.server.status.reconnecting": "Повторное подключение к {{server}}...",
"app.server.status.disconnected": "Не удалось подключиться к {{server}}",
"app.server.otherServers": "Другие серверы", "app.server.otherServers": "Другие серверы",
"dialog.server.add.usernamePlaceholder": "имя пользователя", "dialog.server.add.usernamePlaceholder": "имя пользователя",
"dialog.server.add.passwordPlaceholder": "пароль", "dialog.server.add.passwordPlaceholder": "пароль",
+12
View File
@@ -679,6 +679,14 @@ export const dict = {
"session.header.open.copyPath": "คัดลอกเส้นทาง", "session.header.open.copyPath": "คัดลอกเส้นทาง",
"status.popover.trigger": "สถานะ", "status.popover.trigger": "สถานะ",
"status.popover.tools.trigger": "เครื่องมือ",
"status.popover.tools.mcp.description": "เพิ่มเซิร์ฟเวอร์ MCP ใน opencode.json",
"status.popover.tools.lsp.description": "ระบบจะตรวจพบ LSP อัตโนมัติเมื่อคุณเปิดไฟล์",
"status.popover.tools.plugins.description": "เพิ่มปลั๊กอินใน opencode.json",
"status.popover.tools.mcp.empty": "ยังไม่ได้กำหนดค่า MCP",
"status.popover.tools.lsp.empty": "ยังไม่มีเซิร์ฟเวอร์ภาษาที่ใช้งานอยู่",
"status.popover.tools.plugins.empty": "ยังไม่ได้กำหนดค่าปลั๊กอิน",
"status.popover.tools.reauthenticate": "ยืนยันตัวตนอีกครั้ง",
"status.popover.ariaLabel": "การกำหนดค่าเซิร์ฟเวอร์", "status.popover.ariaLabel": "การกำหนดค่าเซิร์ฟเวอร์",
"status.popover.tab.servers": "เซิร์ฟเวอร์", "status.popover.tab.servers": "เซิร์ฟเวอร์",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -789,6 +797,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "แสดงปุ่มเทอร์มินัลในแถบชื่อเรื่องเดสก์ท็อป", "settings.general.row.showTerminal.description": "แสดงปุ่มเทอร์มินัลในแถบชื่อเรื่องเดสก์ท็อป",
"settings.general.row.showStatus.title": "สถานะเซิร์ฟเวอร์", "settings.general.row.showStatus.title": "สถานะเซิร์ฟเวอร์",
"settings.general.row.showStatus.description": "แสดงปุ่มสถานะเซิร์ฟเวอร์ในแถบชื่อเรื่อง", "settings.general.row.showStatus.description": "แสดงปุ่มสถานะเซิร์ฟเวอร์ในแถบชื่อเรื่อง",
"settings.general.row.showTools.title": "เมนูเครื่องมือ",
"settings.general.row.showTools.description": "แสดงทางลัดไปยัง MCP, LSP และปลั๊กอินในแถบชื่อเรื่อง",
"settings.general.row.mobileTitlebarBottom.title": "การนำทางด้านล่าง", "settings.general.row.mobileTitlebarBottom.title": "การนำทางด้านล่าง",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"วางแถบชื่อเรื่องและแท็บเซสชันไว้ด้านล่างของหน้าจอบนอุปกรณ์เคลื่อนที่", "วางแถบชื่อเรื่องและแท็บเซสชันไว้ด้านล่างของหน้าจอบนอุปกรณ์เคลื่อนที่",
@@ -1006,6 +1016,8 @@ export const dict = {
"app.server.unreachable": "ไม่สามารถติดต่อ {{server}}", "app.server.unreachable": "ไม่สามารถติดต่อ {{server}}",
"app.server.retrying": "กำลังลองใหม่โดยอัตโนมัติ...", "app.server.retrying": "กำลังลองใหม่โดยอัตโนมัติ...",
"app.server.status.reconnecting": "กำลังเชื่อมต่อกับ {{server}} อีกครั้ง...",
"app.server.status.disconnected": "ไม่สามารถเชื่อมต่อกับ {{server}}",
"app.server.otherServers": "เซิร์ฟเวอร์อื่น ๆ", "app.server.otherServers": "เซิร์ฟเวอร์อื่น ๆ",
"dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้", "dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
"dialog.server.add.passwordPlaceholder": "รหัสผ่าน", "dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
+12
View File
@@ -689,6 +689,14 @@ export const dict = {
"session.header.open.copyPath": "Yolu kopyala", "session.header.open.copyPath": "Yolu kopyala",
"status.popover.trigger": "Durum", "status.popover.trigger": "Durum",
"status.popover.tools.trigger": "Araçlar",
"status.popover.tools.mcp.description": "opencode.json dosyasına MCP sunucuları ekleyin",
"status.popover.tools.lsp.description": "Dosyaları açtığınızda LSP'ler otomatik olarak algılanır",
"status.popover.tools.plugins.description": "opencode.json dosyasına eklentiler ekleyin",
"status.popover.tools.mcp.empty": "Henüz yapılandırılmış MCP yok",
"status.popover.tools.lsp.empty": "Henüz etkin dil sunucusu yok",
"status.popover.tools.plugins.empty": "Henüz yapılandırılmış eklenti yok",
"status.popover.tools.reauthenticate": "Yeniden kimlik doğrula",
"status.popover.ariaLabel": "Sunucu yapılandırmaları", "status.popover.ariaLabel": "Sunucu yapılandırmaları",
"status.popover.tab.servers": "Sunucular", "status.popover.tab.servers": "Sunucular",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -803,6 +811,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Masaüstü başlık çubuğunda terminal düğmesini göster", "settings.general.row.showTerminal.description": "Masaüstü başlık çubuğunda terminal düğmesini göster",
"settings.general.row.showStatus.title": "Sunucu durumu", "settings.general.row.showStatus.title": "Sunucu durumu",
"settings.general.row.showStatus.description": "Başlık çubuğunda sunucu durumu düğmesini göster", "settings.general.row.showStatus.description": "Başlık çubuğunda sunucu durumu düğmesini göster",
"settings.general.row.showTools.title": "Araçlar menüsü",
"settings.general.row.showTools.description": "Başlık çubuğunda MCP, LSP ve eklentilere hızlı erişim göster",
"settings.general.row.mobileTitlebarBottom.title": "Alt gezinme", "settings.general.row.mobileTitlebarBottom.title": "Alt gezinme",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Mobil cihazlarda başlık çubuğunu ve oturum sekmelerini ekranın altına yerleştir", "Mobil cihazlarda başlık çubuğunu ve oturum sekmelerini ekranın altına yerleştir",
@@ -1026,6 +1036,8 @@ export const dict = {
"app.server.unreachable": "{{server}} sunucusuna ulaşılamadı", "app.server.unreachable": "{{server}} sunucusuna ulaşılamadı",
"app.server.retrying": "Otomatik olarak tekrar deneniyor...", "app.server.retrying": "Otomatik olarak tekrar deneniyor...",
"app.server.status.reconnecting": "{{server}} sunucusuna yeniden bağlanılıyor...",
"app.server.status.disconnected": "{{server}} sunucusuna bağlanılamadı",
"app.server.otherServers": "Diğer sunucular", "app.server.otherServers": "Diğer sunucular",
"dialog.server.add.usernamePlaceholder": "kullanıcı adı", "dialog.server.add.usernamePlaceholder": "kullanıcı adı",
"dialog.server.add.passwordPlaceholder": "parola", "dialog.server.add.passwordPlaceholder": "parola",
+12
View File
@@ -333,6 +333,8 @@ export const dict = {
"app.server.unreachable": "Не вдалося досягти {{server}}", "app.server.unreachable": "Не вдалося досягти {{server}}",
"app.server.retrying": "Автоматичне повторення...", "app.server.retrying": "Автоматичне повторення...",
"app.server.status.reconnecting": "Повторне підключення до {{server}}...",
"app.server.status.disconnected": "Не вдалося підключитися до {{server}}",
"app.server.otherServers": "Інші сервери", "app.server.otherServers": "Інші сервери",
"dialog.server.title": "Сервери", "dialog.server.title": "Сервери",
@@ -729,6 +731,14 @@ export const dict = {
"session.header.open.app.sublimeText": "Sublime Text", "session.header.open.app.sublimeText": "Sublime Text",
"status.popover.trigger": "Статус", "status.popover.trigger": "Статус",
"status.popover.tools.trigger": "Інструменти",
"status.popover.tools.mcp.description": "Додайте сервери MCP в opencode.json",
"status.popover.tools.lsp.description": "LSP визначаються автоматично під час відкриття файлів",
"status.popover.tools.plugins.description": "Додайте плагіни в opencode.json",
"status.popover.tools.mcp.empty": "MCP ще не налаштовано",
"status.popover.tools.lsp.empty": "Активних мовних серверів ще немає",
"status.popover.tools.plugins.empty": "Плагіни ще не налаштовано",
"status.popover.tools.reauthenticate": "Повторно автентифікуватися",
"status.popover.ariaLabel": "Конфігурації серверів", "status.popover.ariaLabel": "Конфігурації серверів",
"status.popover.tab.servers": "Сервери", "status.popover.tab.servers": "Сервери",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -889,6 +899,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу", "settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу",
"settings.general.row.showStatus.title": "Статус сервера", "settings.general.row.showStatus.title": "Статус сервера",
"settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку", "settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку",
"settings.general.row.showTools.title": "Меню інструментів",
"settings.general.row.showTools.description": "Показувати швидкий доступ до MCP, LSP і плагінів у заголовку",
"settings.general.row.mobileTitlebarBottom.title": "Нижня навігація", "settings.general.row.mobileTitlebarBottom.title": "Нижня навігація",
"settings.general.row.mobileTitlebarBottom.description": "settings.general.row.mobileTitlebarBottom.description":
"Розмістити заголовок і вкладки сесії внизу екрана на мобільних пристроях", "Розмістити заголовок і вкладки сесії внизу екрана на мобільних пристроях",
+12
View File
@@ -675,6 +675,14 @@ export const dict = {
"session.header.open.copyPath": "复制路径", "session.header.open.copyPath": "复制路径",
"status.popover.trigger": "状态", "status.popover.trigger": "状态",
"status.popover.tools.trigger": "工具",
"status.popover.tools.mcp.description": "在 opencode.json 中添加 MCP 服务器",
"status.popover.tools.lsp.description": "打开文件时会自动检测 LSP",
"status.popover.tools.plugins.description": "在 opencode.json 中添加插件",
"status.popover.tools.mcp.empty": "尚未配置 MCP",
"status.popover.tools.lsp.empty": "尚无活动的语言服务器",
"status.popover.tools.plugins.empty": "尚未配置插件",
"status.popover.tools.reauthenticate": "重新认证",
"status.popover.ariaLabel": "服务器配置", "status.popover.ariaLabel": "服务器配置",
"status.popover.tab.servers": "服务器", "status.popover.tab.servers": "服务器",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -786,6 +794,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "在桌面版标题栏中显示终端按钮", "settings.general.row.showTerminal.description": "在桌面版标题栏中显示终端按钮",
"settings.general.row.showStatus.title": "服务器状态", "settings.general.row.showStatus.title": "服务器状态",
"settings.general.row.showStatus.description": "在标题栏中显示服务器状态按钮", "settings.general.row.showStatus.description": "在标题栏中显示服务器状态按钮",
"settings.general.row.showTools.title": "工具菜单",
"settings.general.row.showTools.description": "在标题栏中显示 MCP、LSP 和插件的快捷入口",
"settings.general.row.mobileTitlebarBottom.title": "底部导航", "settings.general.row.mobileTitlebarBottom.title": "底部导航",
"settings.general.row.mobileTitlebarBottom.description": "在移动设备上将标题栏和会话标签页置于屏幕底部", "settings.general.row.mobileTitlebarBottom.description": "在移动设备上将标题栏和会话标签页置于屏幕底部",
"settings.general.row.showCustomAgents.title": "自定义智能体", "settings.general.row.showCustomAgents.title": "自定义智能体",
@@ -999,6 +1009,8 @@ export const dict = {
"app.server.unreachable": "无法连接到 {{server}}", "app.server.unreachable": "无法连接到 {{server}}",
"app.server.retrying": "正在自动重试...", "app.server.retrying": "正在自动重试...",
"app.server.status.reconnecting": "正在重新连接到 {{server}}...",
"app.server.status.disconnected": "无法连接到 {{server}}",
"app.server.otherServers": "其他服务器", "app.server.otherServers": "其他服务器",
"dialog.server.add.usernamePlaceholder": "用户名", "dialog.server.add.usernamePlaceholder": "用户名",
"dialog.server.add.passwordPlaceholder": "密码", "dialog.server.add.passwordPlaceholder": "密码",
+12
View File
@@ -673,6 +673,14 @@ export const dict = {
"session.header.open.copyPath": "複製路徑", "session.header.open.copyPath": "複製路徑",
"status.popover.trigger": "狀態", "status.popover.trigger": "狀態",
"status.popover.tools.trigger": "工具",
"status.popover.tools.mcp.description": "在 opencode.json 中新增 MCP 伺服器",
"status.popover.tools.lsp.description": "開啟檔案時會自動偵測 LSP",
"status.popover.tools.plugins.description": "在 opencode.json 中新增外掛程式",
"status.popover.tools.mcp.empty": "尚未設定 MCP",
"status.popover.tools.lsp.empty": "尚無作用中的語言伺服器",
"status.popover.tools.plugins.empty": "尚未設定外掛程式",
"status.popover.tools.reauthenticate": "重新驗證",
"status.popover.ariaLabel": "伺服器設定", "status.popover.ariaLabel": "伺服器設定",
"status.popover.tab.servers": "伺服器", "status.popover.tab.servers": "伺服器",
"status.popover.tab.mcp": "MCP", "status.popover.tab.mcp": "MCP",
@@ -781,6 +789,8 @@ export const dict = {
"settings.general.row.showTerminal.description": "在桌面標題列中顯示終端機按鈕", "settings.general.row.showTerminal.description": "在桌面標題列中顯示終端機按鈕",
"settings.general.row.showStatus.title": "伺服器狀態", "settings.general.row.showStatus.title": "伺服器狀態",
"settings.general.row.showStatus.description": "在標題列中顯示伺服器狀態按鈕", "settings.general.row.showStatus.description": "在標題列中顯示伺服器狀態按鈕",
"settings.general.row.showTools.title": "工具選單",
"settings.general.row.showTools.description": "在標題列中顯示 MCP、LSP 和外掛程式的快速入口",
"settings.general.row.mobileTitlebarBottom.title": "底部導覽", "settings.general.row.mobileTitlebarBottom.title": "底部導覽",
"settings.general.row.mobileTitlebarBottom.description": "在行動裝置上將標題列和工作階段分頁置於畫面底部", "settings.general.row.mobileTitlebarBottom.description": "在行動裝置上將標題列和工作階段分頁置於畫面底部",
"settings.general.row.showCustomAgents.title": "自訂代理程式", "settings.general.row.showCustomAgents.title": "自訂代理程式",
@@ -995,6 +1005,8 @@ export const dict = {
"app.server.unreachable": "無法連線至 {{server}}", "app.server.unreachable": "無法連線至 {{server}}",
"app.server.retrying": "正在自動重試...", "app.server.retrying": "正在自動重試...",
"app.server.status.reconnecting": "正在重新連線至 {{server}}...",
"app.server.status.disconnected": "無法連線至 {{server}}",
"app.server.otherServers": "其他伺服器", "app.server.otherServers": "其他伺服器",
"dialog.server.add.usernamePlaceholder": "使用者名稱", "dialog.server.add.usernamePlaceholder": "使用者名稱",
"dialog.server.add.passwordPlaceholder": "密碼", "dialog.server.add.passwordPlaceholder": "密碼",
-16
View File
@@ -1,13 +1,10 @@
import { Show, createEffect, createMemo, createResource, createSignal, onCleanup, untrack } from "solid-js" import { Show, createEffect, createMemo, createResource, createSignal, onCleanup, untrack } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import { useSearchParams } from "@solidjs/router" import { useSearchParams } from "@solidjs/router"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { NewSessionDesignView } from "@/components/session" import { NewSessionDesignView } from "@/components/session"
import { PromptInput } from "@/components/prompt-input" import { PromptInput } from "@/components/prompt-input"
import { StatusPopoverV2 } from "@/components/status-popover"
import { import {
PromptProjectAddButton, PromptProjectAddButton,
PromptProjectSelector, PromptProjectSelector,
@@ -25,7 +22,6 @@ import { useSessionKey } from "@/pages/session/session-layout"
import { useComposerCommands } from "@/pages/session/use-composer-commands" import { useComposerCommands } from "@/pages/session/use-composer-commands"
import { NEW_SESSION_CONTENT_WIDTH } from "@/pages/session/new-session-layout" import { NEW_SESSION_CONTENT_WIDTH } from "@/pages/session/new-session-layout"
import { PromptWorkspaceSelector } from "@/components/prompt-workspace-selector" import { PromptWorkspaceSelector } from "@/components/prompt-workspace-selector"
import { useTitlebarRightMount } from "@/components/titlebar"
import { useCommand } from "@/context/command" import { useCommand } from "@/context/command"
import { useProviders } from "@/hooks/use-providers" import { useProviders } from "@/hooks/use-providers"
import { useSettingsDialog } from "@/components/settings-dialog" import { useSettingsDialog } from "@/components/settings-dialog"
@@ -86,7 +82,6 @@ export default function NewSessionPage() {
]) ])
const [store, setStore] = createStore<{ worktree?: string }>({}) const [store, setStore] = createStore<{ worktree?: string }>({})
const rightMount = useTitlebarRightMount()
const showWorkspaceBar = createMemo(() => workspaceBarEnabled && sync().project?.vcs === "git") const showWorkspaceBar = createMemo(() => workspaceBarEnabled && sync().project?.vcs === "git")
const newSessionWorktree = createMemo(() => { const newSessionWorktree = createMemo(() => {
@@ -126,17 +121,6 @@ export default function NewSessionPage() {
return ( return (
<div class="relative size-full overflow-hidden flex flex-col"> <div class="relative size-full overflow-hidden flex flex-col">
<Show when={rightMount()}>
{(mount) => (
<Portal mount={mount()}>
<Show when={settings.visibility.status()}>
<Tooltip placement="bottom" value={language.t("status.popover.trigger")}>
<StatusPopoverV2 />
</Tooltip>
</Show>
</Portal>
)}
</Show>
<div class="flex-1 min-h-0 flex flex-col gap-2 p-2"> <div class="flex-1 min-h-0 flex flex-col gap-2 p-2">
<div class="@container relative flex flex-col min-h-0 h-full flex-1"> <div class="@container relative flex flex-col min-h-0 h-full flex-1">
<div class="flex-1 min-h-0 overflow-hidden rounded-[10px]"> <div class="flex-1 min-h-0 overflow-hidden rounded-[10px]">
@@ -0,0 +1,6 @@
@font-face {
font-family: "Inter";
src: url("@opencode-ai/ui/fonts/Inter.ttf") format("truetype");
font-weight: 100 900;
font-style: normal;
}
@@ -1,6 +1,7 @@
import "@opencode-ai/ui/styles/tailwind" import "@opencode-ai/ui/styles/tailwind"
import "@opencode-ai/session-ui/styles" import "@opencode-ai/session-ui/styles"
import "@opencode-ai/ui/v2/styles/tailwind.css" import "@opencode-ai/ui/v2/styles/tailwind.css"
import "./preview.css"
import { createEffect, onCleanup, onMount } from "solid-js" import { createEffect, onCleanup, onMount } from "solid-js"
import addonA11y from "@storybook/addon-a11y" import addonA11y from "@storybook/addon-a11y"
+6
View File
@@ -96,3 +96,9 @@
transform: scale(0.96); transform: scale(0.96);
} }
} }
@media (prefers-reduced-motion: reduce) {
[data-component="popover-content"] {
animation: none;
}
}
+8
View File
@@ -33,6 +33,14 @@ const icons = {
viewBox: "0 0 20 20", viewBox: "0 0 20 20",
body: `<path d="M2 10V18H18V10M2 10V2H18V10M2 10H18M5 6H9M5 14H9" stroke="currentColor"/>`, body: `<path d="M2 10V18H18V10M2 10V2H18V10M2 10H18M5 6H9M5 14H9" stroke="currentColor"/>`,
}, },
tools: {
viewBox: "0 0 16 16",
body: `<path d="M8 6.74934V9.24934M10.668 6.74934V9.24934M5.33398 6.74934V9.24934M1.33398 2.44418V13.5542H14.6673V2.44418H1.33398Z" stroke="currentColor" stroke-linecap="square"/>`,
},
"tools-warning": {
viewBox: "0 0 16 16",
body: `<path d="M9.50073 2.44498H1.33398V13.555H14.6673V6.49997M8.00073 6.74934V9.24934M10.6687 6.74934V9.24934M5.33472 6.74934V9.24934" stroke="currentColor" stroke-linecap="square"/>`,
},
"status-active": { "status-active": {
viewBox: "0 0 20 20", viewBox: "0 0 20 20",
body: `<path d="M18 2H2V10H18V2Z" fill="currentColor" fill-opacity="0.1"/><path d="M2 18H18V10H2V18Z" fill="currentColor" fill-opacity="0.1"/><path d="M2 10V18H18V10M2 10V2H18V10M2 10H18M5 6H9M5 14H9" stroke="currentColor"/>`, body: `<path d="M18 2H2V10H18V2Z" fill="currentColor" fill-opacity="0.1"/><path d="M2 18H18V10H2V18Z" fill="currentColor" fill-opacity="0.1"/><path d="M2 10V18H18V10M2 10V2H18V10M2 10H18M5 6H9M5 14H9" stroke="currentColor"/>`,
@@ -37,7 +37,7 @@
font-style: normal; font-style: normal;
font-weight: 440; font-weight: 440;
font-size: 13px; font-size: 13px;
line-height: 100%; line-height: 16px;
letter-spacing: -0.04px; letter-spacing: -0.04px;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
font-variation-settings: "slnt" 0; font-variation-settings: "slnt" 0;