mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-08 06:53:19 +00:00
fix(app): scope redesigned settings by server
This commit is contained in:
parent
3b645690ea
commit
d3aee774d9
29 changed files with 326 additions and 588 deletions
|
|
@ -389,7 +389,7 @@ function ProviderConnection(props: {
|
|||
const settings = useSettings()
|
||||
const newLayout = settings.general.newLayoutDesigns
|
||||
const providers = useProviders(() => props.directory?.())
|
||||
const directory = () => props.directory?.() ?? decode64(params.dir)
|
||||
const directory = () => (props.directory ? props.directory() : decode64(params.dir))
|
||||
const location = () => {
|
||||
const value = directory()
|
||||
return value ? { directory: value } : undefined
|
||||
|
|
|
|||
|
|
@ -80,10 +80,11 @@ export const ServerRowMenuView: Component<{
|
|||
>
|
||||
{props.labels.edit}
|
||||
</MenuV2.Item>
|
||||
<Show when={props.canDefault}>
|
||||
<MenuV2.Item disabled={props.isDefault} onSelect={props.onSetDefault}>
|
||||
{props.labels.default}
|
||||
</MenuV2.Item>
|
||||
<Show when={props.canDefault && !props.isDefault}>
|
||||
<MenuV2.Item onSelect={props.onSetDefault}>{props.labels.default}</MenuV2.Item>
|
||||
</Show>
|
||||
<Show when={props.canDefault && props.isDefault}>
|
||||
<MenuV2.Item onSelect={props.onRemoveDefault}>{props.labels.defaultRemove}</MenuV2.Item>
|
||||
</Show>
|
||||
<Show when={props.canRemove}>
|
||||
<MenuV2.Separator />
|
||||
|
|
|
|||
106
packages/app/src/components/settings-server-picker.tsx
Normal file
106
packages/app/src/components/settings-server-picker.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { QueryClientProvider } from "@tanstack/solid-query"
|
||||
import { createMemo, For, type ParentProps, Show } from "solid-js"
|
||||
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
|
||||
import { ModelsProvider } from "@/context/models"
|
||||
import { ServerConnection } from "@/context/server"
|
||||
import { ServerSDKProvider } from "@/context/server-sdk"
|
||||
import { ServerSyncProvider } from "@/context/server-sync"
|
||||
import { useGlobal } from "@/context/global"
|
||||
import { useSettings } from "@/context/settings"
|
||||
|
||||
export function SettingsServerScope(props: ParentProps) {
|
||||
const global = useGlobal()
|
||||
const settings = useSettings()
|
||||
|
||||
return (
|
||||
<Show when={settings.general.newLayoutDesigns()} fallback={props.children}>
|
||||
<Show when={global.settings.server.selected()} keyed>
|
||||
{(server) => <SettingsServerDataProviders server={server}>{props.children}</SettingsServerDataProviders>}
|
||||
</Show>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsServerDataProviders(props: ParentProps<{ server: ServerConnection.Any }>) {
|
||||
const global = useGlobal()
|
||||
const serverCtx = () => global.ensureServerCtx(props.server)
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={serverCtx().queryClient}>
|
||||
<ServerSDKProvider server={() => props.server}>
|
||||
<ServerSyncProvider server={() => props.server}>
|
||||
<ModelsProvider>{props.children}</ModelsProvider>
|
||||
</ServerSyncProvider>
|
||||
</ServerSDKProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsServerPicker() {
|
||||
const global = useGlobal()
|
||||
const settings = useSettings()
|
||||
const selected = createMemo(() =>
|
||||
settings.general.newLayoutDesigns() ? global.settings.server.selected() : undefined,
|
||||
)
|
||||
|
||||
return (
|
||||
<Show when={selected()}>
|
||||
{(conn) => (
|
||||
<DropdownMenu gutter={4} placement="bottom-end">
|
||||
<DropdownMenu.Trigger
|
||||
as={Button}
|
||||
variant="secondary"
|
||||
size="large"
|
||||
class="h-8 max-w-[260px] gap-2 px-2 py-1.5 data-[expanded]:bg-surface-base-active"
|
||||
>
|
||||
<ServerHealthIndicator health={global.servers.health[ServerConnection.key(conn())]} />
|
||||
<ServerRow
|
||||
conn={conn()}
|
||||
status={global.servers.health[ServerConnection.key(conn())]}
|
||||
class="flex items-center gap-2 min-w-0 flex-1"
|
||||
nameClass="text-14-regular text-text-base truncate"
|
||||
versionClass="hidden"
|
||||
/>
|
||||
<Icon name="chevron-down" size="small" class="text-icon-weak shrink-0" />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content class="w-[320px] mt-1 [&_[data-slot=dropdown-menu-radio-item]]:pl-2 [&_[data-slot=dropdown-menu-radio-item]]:pr-2">
|
||||
<DropdownMenu.RadioGroup
|
||||
value={global.settings.server.key}
|
||||
onChange={(key) => {
|
||||
if (typeof key === "string") global.settings.server.set(ServerConnection.Key.make(key))
|
||||
}}
|
||||
>
|
||||
<For each={global.servers.list()}>
|
||||
{(item) => {
|
||||
const key = ServerConnection.key(item)
|
||||
const blocked = () => global.servers.health[key]?.healthy === false
|
||||
return (
|
||||
<DropdownMenu.RadioItem value={key} disabled={blocked()}>
|
||||
<ServerHealthIndicator health={global.servers.health[key]} />
|
||||
<ServerRow
|
||||
conn={item}
|
||||
dimmed={blocked()}
|
||||
status={global.servers.health[key]}
|
||||
class="flex items-center gap-2 min-w-0 flex-1"
|
||||
nameClass="text-14-regular text-text-base truncate"
|
||||
versionClass="text-12-regular text-text-weak truncate"
|
||||
/>
|
||||
<DropdownMenu.ItemIndicator>
|
||||
<Icon name="check-small" size="small" class="text-icon-weak" />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
</DropdownMenu.RadioItem>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</DropdownMenu.RadioGroup>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { Component, Show, createMemo, createResource } from "solid-js"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||
|
|
@ -8,10 +7,7 @@ import { usePlatform } from "@/context/platform"
|
|||
import { ExternalLink } from "../external-link"
|
||||
import { SettingsListV2 } from "./parts/list"
|
||||
import { SettingsRowV2 } from "./parts/row"
|
||||
import {
|
||||
createAppearanceSettingsController,
|
||||
type AppearanceSettingsController,
|
||||
} from "./general-controllers"
|
||||
import { createAppearanceSettingsController, type AppearanceSettingsController } from "./general-controllers"
|
||||
import "./settings-v2.css"
|
||||
|
||||
const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"]
|
||||
|
|
@ -86,21 +82,11 @@ export const SettingsAppearanceV2: Component = () => {
|
|||
void update.catch(() => setPinchZoom(!checked))
|
||||
}
|
||||
|
||||
const restoreDefaults = () => {
|
||||
appearance.scheme.select("system")
|
||||
appearance.fonts.setUI("")
|
||||
appearance.fonts.setCode("")
|
||||
appearance.fonts.setTerminal("")
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="settings-v2-tab-header">
|
||||
<div class="settings-v2-tab-header-row">
|
||||
<h2 class="settings-v2-tab-title">{language.t("settings.general.section.appearance")}</h2>
|
||||
<ButtonV2 size="small" variant="ghost-muted" onClick={restoreDefaults}>
|
||||
{language.t("common.reset")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ import { SettingsModelsV2 } from "./models"
|
|||
import { SettingsServersV2 } from "./servers"
|
||||
import { SettingsProjectsV2 } from "./projects"
|
||||
import { SettingsExtensionsV2 } from "./extensions"
|
||||
import { SettingsServerScope } from "../settings-server-picker"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useTabs } from "@/context/tabs"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
import { useGlobal } from "@/context/global"
|
||||
import "./settings-v2.css"
|
||||
|
||||
export const DialogSettings: Component<{
|
||||
|
|
@ -29,8 +31,11 @@ export const DialogSettings: Component<{
|
|||
const layout = useLayout()
|
||||
const tabs = useTabs()
|
||||
const serverSync = useServerSync()
|
||||
const global = useGlobal()
|
||||
const [tab, setTab] = createSignal(props.defaultValue ?? "general")
|
||||
const directory = createMemo(() => {
|
||||
const server = global.settings.server.selected()
|
||||
if (!server || serverSync() !== global.ensureServerCtx(server).sync) return
|
||||
const route = layout.route()
|
||||
if (route.type === "dir-new-sesssion") return route.dir
|
||||
if (route.type === "draft") {
|
||||
|
|
@ -131,15 +136,17 @@ export const DialogSettings: Component<{
|
|||
<TabsV2.Content value="projects" class="settings-v2-panel">
|
||||
<SettingsProjectsV2 />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="providers" class="settings-v2-panel">
|
||||
<SettingsProvidersV2 directory={directory} onBack={showProviders} />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="models" class="settings-v2-panel">
|
||||
<SettingsModelsV2 />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="extensions" class="settings-v2-panel">
|
||||
<SettingsExtensionsV2 />
|
||||
</TabsV2.Content>
|
||||
<SettingsServerScope>
|
||||
<TabsV2.Content value="providers" class="settings-v2-panel">
|
||||
<SettingsProvidersV2 directory={directory} onBack={showProviders} />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="models" class="settings-v2-panel">
|
||||
<SettingsModelsV2 />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="extensions" class="settings-v2-panel">
|
||||
<SettingsExtensionsV2 />
|
||||
</TabsV2.Content>
|
||||
</SettingsServerScope>
|
||||
</TabsV2>
|
||||
</Dialog>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,71 +1,44 @@
|
|||
import { Component, For, Show, createMemo, createSignal } from "solid-js"
|
||||
import { Component, For, createMemo, createResource } from "solid-js"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { ServerConnection, useServer } from "@/context/server"
|
||||
import { useServerSDK } from "@/context/server-sdk"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
import { ExternalLink } from "../external-link"
|
||||
import { InlineServerSelect } from "./parts/server-select"
|
||||
import "./settings-v2.css"
|
||||
|
||||
type ExtensionSubTab = "mcps" | "plugins" | "skills"
|
||||
|
||||
interface McpRowItem {
|
||||
name: string
|
||||
status?: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
interface PluginRowItem {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface SkillRowItem {
|
||||
name: string
|
||||
}
|
||||
|
||||
export const SettingsExtensionsV2: Component = () => {
|
||||
const language = useLanguage()
|
||||
const server = useServer()
|
||||
const serverSdk = useServerSDK()
|
||||
const serverSync = useServerSync()
|
||||
const [selectedServer, setSelectedServer] = createSignal<ServerConnection.Key | "all">(server.key)
|
||||
const [activeSubTab, setActiveSubTab] = createSignal<ExtensionSubTab>("mcps")
|
||||
|
||||
const [mcpOverrides, setMcpOverrides] = createSignal<Record<string, boolean>>({})
|
||||
const mcps = createMemo<McpRowItem[]>(() => {
|
||||
const configMcp = (serverSync().data.config as { mcp?: Record<string, { disabled?: boolean }> })?.mcp ?? {}
|
||||
const configEntries = Object.entries(configMcp).map(([name, conf]) => ({
|
||||
const configMcp = serverSync().data.config.mcp ?? {}
|
||||
return Object.entries(configMcp).map(([name, config]) => ({
|
||||
name,
|
||||
status: conf.disabled ? "disabled" : "connected",
|
||||
enabled: config.enabled !== false,
|
||||
}))
|
||||
|
||||
const firstProject = server.projects.list()[0]?.worktree
|
||||
const childMcp = firstProject ? serverSync().child(firstProject, { mcp: true })[0].mcp : {}
|
||||
const childEntries = Object.entries(childMcp ?? {}).map(([name, stat]) => ({
|
||||
name,
|
||||
status: stat?.status,
|
||||
}))
|
||||
|
||||
const map = new Map<string, McpRowItem>()
|
||||
for (const item of configEntries) map.set(item.name, item)
|
||||
for (const item of childEntries) {
|
||||
const existing = map.get(item.name)
|
||||
map.set(item.name, { ...existing, ...item })
|
||||
}
|
||||
|
||||
return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name))
|
||||
})
|
||||
|
||||
const isMcpEnabled = (item: McpRowItem) => {
|
||||
if (mcpOverrides()[item.name] !== undefined) return mcpOverrides()[item.name]
|
||||
return item.status === "connected"
|
||||
}
|
||||
|
||||
const handleMcpToggle = (item: McpRowItem, checked: boolean) => {
|
||||
setMcpOverrides((prev) => ({ ...prev, [item.name]: checked }))
|
||||
const firstProject = server.projects.list()[0]?.worktree
|
||||
if (firstProject) {
|
||||
void serverSync().mcp.toggle(firstProject, item.name)
|
||||
}
|
||||
const before = serverSync().data.config.mcp ?? {}
|
||||
const config = before[item.name]
|
||||
if (!config) return
|
||||
const next = { ...before, [item.name]: { ...config, enabled: checked } }
|
||||
serverSync().set("config", "mcp", next)
|
||||
void serverSync()
|
||||
.updateConfig({ mcp: next })
|
||||
.catch(() => serverSync().set("config", "mcp", before))
|
||||
}
|
||||
|
||||
const plugins = createMemo<PluginRowItem[]>(() => {
|
||||
|
|
@ -76,10 +49,14 @@ export const SettingsExtensionsV2: Component = () => {
|
|||
})
|
||||
})
|
||||
|
||||
const skills = createMemo<SkillRowItem[]>(() => {
|
||||
const configSkills = (serverSync().data.config as { skills?: string[] })?.skills ?? []
|
||||
return configSkills.map((name) => ({ name }))
|
||||
})
|
||||
const [skills] = createResource(
|
||||
() => serverSdk(),
|
||||
async (sdk) => {
|
||||
const result = await sdk.client.v2.skill.list(undefined, { throwOnError: true })
|
||||
return result.data.data
|
||||
},
|
||||
{ initialValue: [] },
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -89,125 +66,95 @@ export const SettingsExtensionsV2: Component = () => {
|
|||
<h2 class="settings-v2-tab-title">{language.t("settings.tab.extensions")}</h2>
|
||||
<span class="text-11-regular text-v2-text-text-muted">{language.t("settings.mcps.description")}</span>
|
||||
</div>
|
||||
<InlineServerSelect value={selectedServer()} onChange={setSelectedServer} includeAll />
|
||||
<InlineServerSelect />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-v2-tab-body">
|
||||
{/* Sub-Tabs Pill Strip */}
|
||||
<div class="settings-v2-extensions-tabs" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeSubTab() === "mcps"}
|
||||
data-selected={activeSubTab() === "mcps" ? "" : undefined}
|
||||
onClick={() => setActiveSubTab("mcps")}
|
||||
>
|
||||
{language.t("status.popover.tab.mcp")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeSubTab() === "plugins"}
|
||||
data-selected={activeSubTab() === "plugins" ? "" : undefined}
|
||||
onClick={() => setActiveSubTab("plugins")}
|
||||
>
|
||||
{language.t("status.popover.tab.plugins")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeSubTab() === "skills"}
|
||||
data-selected={activeSubTab() === "skills" ? "" : undefined}
|
||||
onClick={() => setActiveSubTab("skills")}
|
||||
>
|
||||
{language.t("settings.permissions.tool.skill.title")}
|
||||
</button>
|
||||
</div>
|
||||
<TabsV2 variant="pill" defaultValue="mcps" class="settings-v2-extensions-tabs">
|
||||
<TabsV2.List>
|
||||
<TabsV2.Trigger value="mcps">{language.t("status.popover.tab.mcp")}</TabsV2.Trigger>
|
||||
<TabsV2.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</TabsV2.Trigger>
|
||||
<TabsV2.Trigger value="skills">{language.t("settings.permissions.tool.skill.title")}</TabsV2.Trigger>
|
||||
</TabsV2.List>
|
||||
|
||||
{/* MCPs Sub-Tab */}
|
||||
<Show when={activeSubTab() === "mcps"}>
|
||||
<div class="settings-v2-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-13-medium text-v2-text-text-base">
|
||||
{language.t("settings.extensions.availableAll")}
|
||||
</span>
|
||||
<span class="text-13-regular text-v2-text-faint">{language.t("settings.extensions.manageConfig")}</span>
|
||||
</div>
|
||||
<div class="bg-[var(--v2-background-bg-base)] border-[0.5px] border-[var(--v2-border-border-base)] rounded-[8px] pl-4 pr-3 overflow-hidden">
|
||||
<For each={mcps()}>
|
||||
{(item) => (
|
||||
<div class="py-4 flex items-center justify-between border-b-[0.5px] border-[var(--v2-border-border-base)] last:border-b-0">
|
||||
<div class="flex items-center gap-2.5 min-w-0">
|
||||
<Icon name="mcp" class="text-v2-icon-icon-muted shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate">{item.name}</span>
|
||||
<TabsV2.Content value="mcps">
|
||||
<div class="settings-v2-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-13-medium text-v2-text-text-base">
|
||||
{language.t("settings.extensions.availableAll")}
|
||||
</span>
|
||||
<span class="text-13-regular text-v2-text-faint">{language.t("settings.extensions.manageConfig")}</span>
|
||||
</div>
|
||||
<div class="bg-[var(--v2-background-bg-base)] border-[0.5px] border-[var(--v2-border-border-base)] rounded-[8px] pl-4 pr-3 overflow-hidden">
|
||||
<For each={mcps()}>
|
||||
{(item) => (
|
||||
<div class="py-4 flex items-center justify-between border-b-[0.5px] border-[var(--v2-border-border-base)] last:border-b-0">
|
||||
<div class="flex items-center gap-2.5 min-w-0">
|
||||
<Icon name="mcp" class="text-v2-icon-icon-muted shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate">{item.name}</span>
|
||||
</div>
|
||||
<Switch checked={item.enabled} onChange={(checked) => handleMcpToggle(item, checked)} hideLabel>
|
||||
{item.name}
|
||||
</Switch>
|
||||
</div>
|
||||
<Switch
|
||||
checked={isMcpEnabled(item)}
|
||||
onChange={(checked) => handleMcpToggle(item, checked)}
|
||||
hideLabel
|
||||
>
|
||||
{item.name}
|
||||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</TabsV2.Content>
|
||||
|
||||
{/* Plugins Sub-Tab */}
|
||||
<Show when={activeSubTab() === "plugins"}>
|
||||
<div class="settings-v2-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-13-medium text-v2-text-text-base">
|
||||
{language.t("settings.extensions.availableAll")}
|
||||
</span>
|
||||
<span class="text-13-regular text-v2-text-faint">{language.t("settings.extensions.manageConfig")}</span>
|
||||
</div>
|
||||
<div class="bg-[var(--v2-background-bg-base)] border-[0.5px] border-[var(--v2-border-border-base)] rounded-[8px] pl-4 pr-3 overflow-hidden">
|
||||
<For each={plugins()}>
|
||||
{(plugin) => (
|
||||
<div class="py-4 flex items-center justify-between border-b-[0.5px] border-[var(--v2-border-border-base)] last:border-b-0">
|
||||
<div class="flex items-center gap-2.5 min-w-0">
|
||||
<Icon name="cube" class="text-v2-icon-icon-muted shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate font-mono">{plugin.name}</span>
|
||||
<TabsV2.Content value="plugins">
|
||||
<div class="settings-v2-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-13-medium text-v2-text-text-base">
|
||||
{language.t("settings.extensions.availableAll")}
|
||||
</span>
|
||||
<span class="text-13-regular text-v2-text-faint">{language.t("settings.extensions.manageConfig")}</span>
|
||||
</div>
|
||||
<div class="bg-[var(--v2-background-bg-base)] border-[0.5px] border-[var(--v2-border-border-base)] rounded-[8px] pl-4 pr-3 overflow-hidden">
|
||||
<For each={plugins()}>
|
||||
{(plugin) => (
|
||||
<div class="py-4 flex items-center justify-between border-b-[0.5px] border-[var(--v2-border-border-base)] last:border-b-0">
|
||||
<div class="flex items-center gap-2.5 min-w-0">
|
||||
<Icon name="cube" class="text-v2-icon-icon-muted shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate font-mono">{plugin.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</TabsV2.Content>
|
||||
|
||||
{/* Skills Sub-Tab */}
|
||||
<Show when={activeSubTab() === "skills"}>
|
||||
<div class="settings-v2-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-13-medium text-v2-text-text-base">
|
||||
{language.t("settings.extensions.availableAll")}
|
||||
</span>
|
||||
<ExternalLink
|
||||
class="text-13-regular text-v2-text-accent hover:underline"
|
||||
href="https://opencode.ai/docs/skills/"
|
||||
>
|
||||
{language.t("settings.extensions.addSkills")}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div class="bg-[var(--v2-background-bg-base)] border-[0.5px] border-[var(--v2-border-border-base)] rounded-[8px] pl-4 pr-3 overflow-hidden">
|
||||
<For each={skills()}>
|
||||
{(skill) => (
|
||||
<div class="py-4 flex items-center justify-between border-b-[0.5px] border-[var(--v2-border-border-base)] last:border-b-0">
|
||||
<div class="flex items-center gap-2.5 min-w-0">
|
||||
<Icon name="post-skill" class="text-v2-icon-icon-muted shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate">{skill.name}</span>
|
||||
<TabsV2.Content value="skills">
|
||||
<div class="settings-v2-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-13-medium text-v2-text-text-base">
|
||||
{language.t("settings.extensions.availableAll")}
|
||||
</span>
|
||||
<ExternalLink
|
||||
class="text-13-regular text-v2-text-accent hover:underline"
|
||||
href="https://opencode.ai/docs/skills/"
|
||||
>
|
||||
{language.t("settings.extensions.addSkills")}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div class="bg-[var(--v2-background-bg-base)] border-[0.5px] border-[var(--v2-border-border-base)] rounded-[8px] pl-4 pr-3 overflow-hidden">
|
||||
<For each={skills()}>
|
||||
{(skill) => (
|
||||
<div class="py-4 flex items-center justify-between border-b-[0.5px] border-[var(--v2-border-border-base)] last:border-b-0">
|
||||
<div class="flex items-center gap-2.5 min-w-0">
|
||||
<Icon name="post-skill" class="text-v2-icon-icon-muted shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate">{skill.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</TabsV2.Content>
|
||||
</TabsV2>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1 @@
|
|||
export { DialogSettings } from "./dialog-settings-v2"
|
||||
export { SettingsGeneralV2 } from "./general"
|
||||
export { SettingsAppearanceV2 } from "./appearance"
|
||||
export { SettingsNotificationsV2 } from "./notifications"
|
||||
export { SettingsProvidersV2 } from "./providers"
|
||||
export { SettingsModelsV2 } from "./models"
|
||||
export { SettingsServersV2 } from "./servers"
|
||||
export { SettingsProjectsV2 } from "./projects"
|
||||
export { SettingsExtensionsV2 } from "./extensions"
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
|||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||
import { type Component, For, Show, createSignal } from "solid-js"
|
||||
import { type Component, For, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useModels } from "@/context/models"
|
||||
import { useServerSDK } from "@/context/server-sdk"
|
||||
import { ServerConnection, useServer } from "@/context/server"
|
||||
import { popularProviders } from "@/hooks/use-providers"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { InlineServerSelect } from "./parts/server-select"
|
||||
|
|
@ -24,9 +23,7 @@ const PROVIDER_ICON_SIZE = 16
|
|||
export const SettingsModelsV2: Component = () => {
|
||||
const language = useLanguage()
|
||||
const models = useModels()
|
||||
const server = useServer()
|
||||
const serverSdk = useServerSDK()
|
||||
const [selectedServer, setSelectedServer] = createSignal<ServerConnection.Key | "all">(server.key)
|
||||
const [store, setStore] = persisted(
|
||||
Persist.serverGlobal(serverSdk().scope, "settings-v2.models.providers"),
|
||||
createStore({ collapsed: {} as Record<string, boolean> }),
|
||||
|
|
@ -59,11 +56,7 @@ export const SettingsModelsV2: Component = () => {
|
|||
<div class="settings-v2-tab-header settings-v2-tab-header--stacked">
|
||||
<div class="settings-v2-tab-header-row">
|
||||
<h2 class="settings-v2-tab-title">{language.t("settings.models.title")}</h2>
|
||||
<InlineServerSelect
|
||||
value={selectedServer()}
|
||||
onChange={setSelectedServer}
|
||||
includeAll
|
||||
/>
|
||||
<InlineServerSelect />
|
||||
</div>
|
||||
<div class="settings-v2-tab-search">
|
||||
<TextInputV2
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Component } from "solid-js"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
||||
import { useLanguage } from "@/context/language"
|
||||
|
|
@ -56,23 +55,11 @@ export const SettingsNotificationsV2: Component = () => {
|
|||
const settings = useSettings()
|
||||
const sounds = createSoundSettingsController()
|
||||
|
||||
const restoreDefaults = () => {
|
||||
settings.notifications.setAgent(true)
|
||||
settings.notifications.setPermissions(true)
|
||||
settings.notifications.setErrors(true)
|
||||
settings.sounds.setAgent("bip-bop-01")
|
||||
settings.sounds.setPermissions("alert-01")
|
||||
settings.sounds.setErrors("nope-01")
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="settings-v2-tab-header">
|
||||
<div class="settings-v2-tab-header-row">
|
||||
<h2 class="settings-v2-tab-title">{language.t("settings.tab.notifications")}</h2>
|
||||
<ButtonV2 size="small" variant="ghost-muted" onClick={restoreDefaults}>
|
||||
{language.t("common.reset")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +1,25 @@
|
|||
import { Show, createMemo, type Component } from "solid-js"
|
||||
import { Show, type Component } from "solid-js"
|
||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
||||
import { useGlobal } from "@/context/global"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { ServerConnection, serverName, useServer } from "@/context/server"
|
||||
import { ServerConnection, serverName } from "@/context/server"
|
||||
|
||||
export interface ServerSelectOption {
|
||||
key: ServerConnection.Key | "all"
|
||||
label: string
|
||||
isDefault?: boolean
|
||||
isAll?: boolean
|
||||
}
|
||||
|
||||
export const InlineServerSelect: Component<{
|
||||
value: ServerConnection.Key | "all"
|
||||
onChange: (key: ServerConnection.Key | "all") => void
|
||||
includeAll?: boolean
|
||||
disabled?: boolean
|
||||
}> = (props) => {
|
||||
const language = useLanguage()
|
||||
export const InlineServerSelect: Component = () => {
|
||||
const global = useGlobal()
|
||||
const server = useServer()
|
||||
|
||||
const hasMultipleServers = createMemo(() => global.servers.list().length > 1)
|
||||
|
||||
const options = createMemo<ServerSelectOption[]>(() => {
|
||||
const list: ServerSelectOption[] = []
|
||||
if (props.includeAll) {
|
||||
list.push({
|
||||
key: "all",
|
||||
label: language.t("settings.server.all"),
|
||||
isAll: true,
|
||||
})
|
||||
}
|
||||
|
||||
const servers = global.servers.list()
|
||||
for (const item of servers) {
|
||||
const key = ServerConnection.key(item)
|
||||
const isDefault = key === server.key
|
||||
list.push({
|
||||
key,
|
||||
label: serverName(item) || key,
|
||||
isDefault,
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
})
|
||||
|
||||
const currentOption = createMemo(() => {
|
||||
return options().find((opt) => opt.key === props.value) ?? options()[0]
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={hasMultipleServers()}>
|
||||
<Show when={global.servers.list().length > 1}>
|
||||
<SelectV2
|
||||
appearance="inline"
|
||||
data-action="settings-server-select"
|
||||
options={options()}
|
||||
current={currentOption()}
|
||||
value={(opt) => opt.key}
|
||||
label={(opt) => opt.label}
|
||||
disabled={props.disabled}
|
||||
options={global.servers.list()}
|
||||
current={global.settings.server.selected()}
|
||||
value={ServerConnection.key}
|
||||
label={(server) => serverName(server) || ServerConnection.key(server)}
|
||||
optionDisabled={(server) => global.servers.health[ServerConnection.key(server)]?.healthy === false}
|
||||
placement="bottom-end"
|
||||
gutter={6}
|
||||
onSelect={(opt) => {
|
||||
if (opt) props.onChange(opt.key)
|
||||
onSelect={(server) => {
|
||||
if (server) global.settings.server.set(ServerConnection.key(server))
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Component, For, Show, createMemo, createSignal } from "solid-js"
|
||||
import { Component, For, Show, createMemo } from "solid-js"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { ProjectAvatar } from "@opencode-ai/ui/v2/project-avatar-v2"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { ServerConnection, useServer } from "@/context/server"
|
||||
import { useGlobal } from "@/context/global"
|
||||
import { useLayout, getProjectAvatarVariant } from "@/context/layout"
|
||||
import { getProjectAvatarVariant } from "@/context/layout"
|
||||
import { displayName } from "@/pages/layout/helpers"
|
||||
import { InlineServerSelect } from "./parts/server-select"
|
||||
import { DialogEditProjectV2 } from "../dialog-edit-project-v2"
|
||||
import "./settings-v2.css"
|
||||
|
|
@ -14,19 +14,18 @@ import "./settings-v2.css"
|
|||
export const SettingsProjectsV2: Component = () => {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
const server = useServer()
|
||||
const global = useGlobal()
|
||||
const layout = useLayout()
|
||||
const [selectedServer, setSelectedServer] = createSignal<ServerConnection.Key | "all">(server.key)
|
||||
|
||||
const selected = global.settings.server.selected
|
||||
const projects = createMemo(() => {
|
||||
return layout.projects.list()
|
||||
const server = selected()
|
||||
if (!server) return []
|
||||
return global.ensureServerCtx(server).projects.list()
|
||||
})
|
||||
|
||||
const openProjectSettings = (project: ReturnType<typeof layout.projects.list>[number]) => {
|
||||
const currentServer = server.current ?? global.servers.list()[0]
|
||||
if (!currentServer) return
|
||||
dialog.push(() => <DialogEditProjectV2 project={project} server={currentServer} />)
|
||||
const openProjectSettings = (project: ReturnType<typeof projects>[number]) => {
|
||||
const server = selected()
|
||||
if (!server) return
|
||||
dialog.push(() => <DialogEditProjectV2 project={project} server={server} />)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -37,11 +36,7 @@ export const SettingsProjectsV2: Component = () => {
|
|||
<h2 class="settings-v2-tab-title">{language.t("settings.projects.title")}</h2>
|
||||
<span class="text-11-regular text-v2-text-text-muted">{language.t("settings.projects.description")}</span>
|
||||
</div>
|
||||
<InlineServerSelect
|
||||
value={selectedServer()}
|
||||
onChange={setSelectedServer}
|
||||
includeAll
|
||||
/>
|
||||
<InlineServerSelect />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -57,7 +52,7 @@ export const SettingsProjectsV2: Component = () => {
|
|||
>
|
||||
<For each={projects()}>
|
||||
{(project) => {
|
||||
const name = () => project.name || project.worktree.split(/[/\\]/).pop() || project.worktree
|
||||
const name = () => displayName(project)
|
||||
const color = () => getProjectAvatarVariant(project.icon?.color)
|
||||
|
||||
return (
|
||||
|
|
@ -66,11 +61,7 @@ export const SettingsProjectsV2: Component = () => {
|
|||
onClick={() => openProjectSettings(project)}
|
||||
>
|
||||
<div class="flex items-center gap-2.5 min-w-0 flex-1">
|
||||
<ProjectAvatar
|
||||
fallback={name()}
|
||||
variant={color()}
|
||||
class="shrink-0"
|
||||
/>
|
||||
<ProjectAvatar fallback={name()} variant={color()} class="shrink-0" />
|
||||
<span class="text-13-medium text-v2-text-text-base truncate">{name()}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
||||
import { createMemo, createSignal, type Accessor, type Component, For, Show } from "solid-js"
|
||||
import { createMemo, type Accessor, type Component, For, Show } from "solid-js"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useServerSDK } from "@/context/server-sdk"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
import { ServerConnection, useServer } from "@/context/server"
|
||||
import { DialogConnectProvider, useProviderConnectController } from "../dialog-connect-provider"
|
||||
import { DialogCustomProvider } from "../dialog-custom-provider"
|
||||
import { SettingsServerScope } from "../settings-server-picker"
|
||||
import { InlineServerSelect } from "./parts/server-select"
|
||||
import { SettingsListV2 } from "./parts/list"
|
||||
import "./settings-v2.css"
|
||||
|
|
@ -42,12 +42,13 @@ export const SettingsProvidersV2: Component<{
|
|||
const providers = useProviders(props.directory)
|
||||
const providerConnect = useProviderConnectController({ onBack: props.onBack })
|
||||
|
||||
const server = useServer()
|
||||
const [selectedServer, setSelectedServer] = createSignal<ServerConnection.Key | "all">(server.key)
|
||||
|
||||
const connect = (provider?: string) => {
|
||||
providerConnect.select(provider)
|
||||
void dialog.show(() => <DialogConnectProvider directory={props.directory} controller={providerConnect} />)
|
||||
void dialog.show(() => (
|
||||
<SettingsServerScope>
|
||||
<DialogConnectProvider directory={props.directory} controller={providerConnect} />
|
||||
</SettingsServerScope>
|
||||
))
|
||||
}
|
||||
|
||||
const connected = createMemo(() => {
|
||||
|
|
@ -101,9 +102,9 @@ export const SettingsProvidersV2: Component<{
|
|||
return
|
||||
const before = serverSync().data.config.disabled_providers ?? []
|
||||
const next = before.includes(providerID) ? before : [...before, providerID]
|
||||
serverSync().set("config", "disabled_providers", next)
|
||||
sync.set("config", "disabled_providers", next)
|
||||
|
||||
await serverSync()
|
||||
await sync
|
||||
.updateConfig({ disabled_providers: next })
|
||||
.then(() => {
|
||||
showToast({
|
||||
|
|
@ -114,7 +115,7 @@ export const SettingsProvidersV2: Component<{
|
|||
})
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
serverSync().set("config", "disabled_providers", before)
|
||||
sync.set("config", "disabled_providers", before)
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||
})
|
||||
|
|
@ -150,11 +151,7 @@ export const SettingsProvidersV2: Component<{
|
|||
<div class="settings-v2-tab-header">
|
||||
<div class="settings-v2-tab-header-row">
|
||||
<h2 class="settings-v2-tab-title">{language.t("settings.providers.title")}</h2>
|
||||
<InlineServerSelect
|
||||
value={selectedServer()}
|
||||
onChange={setSelectedServer}
|
||||
includeAll
|
||||
/>
|
||||
<InlineServerSelect />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -258,7 +255,11 @@ export const SettingsProvidersV2: Component<{
|
|||
variant="neutral"
|
||||
icon="plus"
|
||||
onClick={() => {
|
||||
dialog.show(() => <DialogCustomProvider onBack={dialog.close} />)
|
||||
dialog.show(() => (
|
||||
<SettingsServerScope>
|
||||
<DialogCustomProvider onBack={dialog.close} />
|
||||
</SettingsServerScope>
|
||||
))
|
||||
}}
|
||||
>
|
||||
{language.t("common.connect")}
|
||||
|
|
|
|||
|
|
@ -182,13 +182,13 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-list"] {
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] > [data-slot="tabs-v2-list"] {
|
||||
background-color: var(--v2-background-bg-layer-01);
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.settings-v2[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"]
|
||||
[data-slot="tabs-v2-list"] {
|
||||
> [data-slot="tabs-v2-list"] {
|
||||
width: 144px;
|
||||
min-width: 144px;
|
||||
padding-inline: 8px;
|
||||
|
|
@ -736,39 +736,19 @@
|
|||
color: var(--v2-state-fg-danger);
|
||||
}
|
||||
|
||||
.settings-v2-extensions-tabs {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
.settings-v2-extensions-tabs [data-slot="tabs-v2-list"] {
|
||||
width: 280px;
|
||||
height: 32px;
|
||||
overflow: visible;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.settings-v2-extensions-tabs > button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: none;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border: 0.5px solid transparent;
|
||||
.settings-v2-extensions-tabs [data-slot="tabs-v2-list"]::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-v2-extensions-tabs [data-slot="tabs-v2-trigger-wrapper"] {
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--v2-text-text-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04px;
|
||||
}
|
||||
|
||||
.settings-v2-extensions-tabs > button[data-selected] {
|
||||
border-color: var(--v2-border-border-muted);
|
||||
background: var(--v2-background-bg-layer-03);
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
.settings-v2-extensions-tabs > button:focus-visible {
|
||||
outline: 2px solid var(--v2-border-border-focus);
|
||||
outline-offset: 2px;
|
||||
.settings-v2-extensions-tabs [data-slot="tabs-v2-trigger"] {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1005,24 +1005,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "فشل إنشاء أيقونة المشروع الدائمة",
|
||||
"error.childStore.storeCreateFailed": "فشل إنشاء المخزن",
|
||||
"terminal.connectionLost.abnormalClose": "تم إغلاق WebSocket بشكل غير طبيعي: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1021,24 +1021,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Falha ao criar ícone de projeto persistente",
|
||||
"error.childStore.storeCreateFailed": "Falha ao criar armazenamento",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket fechado anormalmente: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1097,24 +1097,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Nije uspjelo kreiranje trajne ikone projekta",
|
||||
"error.childStore.storeCreateFailed": "Nije uspjelo kreiranje skladišta",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket zatvoren nenormalno: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -849,7 +849,6 @@ export const dict = {
|
|||
"settings.tab.notifications": "Notifications",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
|
|
|
|||
|
|
@ -1105,24 +1105,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Error al crear icono de proyecto persistente",
|
||||
"error.childStore.storeCreateFailed": "Error al crear almacén",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket cerrado anormalmente: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1108,24 +1108,4 @@ export const dict = {
|
|||
"settings.general.row.pinchZoom.description": "Tillat knipebevegelser på styreflaten og Ctrl-rulling for å zoome",
|
||||
"settings.updates.action.downloading": "Laster ned...",
|
||||
"settings.updates.action.installing": "Installerer...",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
} satisfies Partial<Record<Keys, string>>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,27 @@ const appLocales = [
|
|||
"zht",
|
||||
] as const
|
||||
const desktopLocales = appLocales.filter((locale) => locale !== "th" && locale !== "tr")
|
||||
const appFallbackKeys = new Set([
|
||||
"command.session.export",
|
||||
"command.session.export.description",
|
||||
"context.export.session",
|
||||
"toast.session.export.success.title",
|
||||
"toast.session.export.success.description",
|
||||
"toast.session.export.failed.title",
|
||||
"toast.session.export.failed.description",
|
||||
"common.export",
|
||||
"settings.tab.preferences",
|
||||
"settings.tab.notifications",
|
||||
"settings.tab.projects",
|
||||
"settings.tab.extensions",
|
||||
"settings.projects.title",
|
||||
"settings.projects.description",
|
||||
"settings.projects.empty",
|
||||
"settings.mcps.description",
|
||||
"settings.extensions.availableAll",
|
||||
"settings.extensions.manageConfig",
|
||||
"settings.extensions.addSkills",
|
||||
])
|
||||
|
||||
const domains = [
|
||||
{
|
||||
|
|
@ -48,7 +69,9 @@ describe.skipIf(!!process.env.CI)("i18n parity", () => {
|
|||
const source = await dictionary(domain.source)
|
||||
for (const locale of domain.locales) {
|
||||
const target = await dictionary(domain.target(locale))
|
||||
const missing = Object.keys(source).filter((key) => !Object.hasOwn(target, key))
|
||||
const missing = Object.keys(source).filter(
|
||||
(key) => !Object.hasOwn(target, key) && (domain.name !== "app" || !appFallbackKeys.has(key)),
|
||||
)
|
||||
const extra = Object.keys(target).filter((key) => !Object.hasOwn(source, key))
|
||||
expect({ domain: domain.name, locale, missing, extra }).toEqual({
|
||||
domain: domain.name,
|
||||
|
|
|
|||
|
|
@ -1021,24 +1021,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Nie udało się utworzyć trwałej ikony projektu",
|
||||
"error.childStore.storeCreateFailed": "Nie udało się utworzyć magazynu",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket zamknięty nieprawidłowo: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1100,24 +1100,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Не удалось создать постоянный значок проекта",
|
||||
"error.childStore.storeCreateFailed": "Не удалось создать хранилище",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket закрыт аварийно: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1084,24 +1084,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "ไม่สามารถสร้างไอคอนโปรเจกต์ถาวร",
|
||||
"error.childStore.storeCreateFailed": "ไม่สามารถสร้างที่เก็บ",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket ปิดอย่างผิดปกติ: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1103,24 +1103,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "Kalıcı proje simgesi oluşturulamadı",
|
||||
"error.childStore.storeCreateFailed": "Depo oluşturulamadı",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket anormal şekilde kapandı: {{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
} satisfies Partial<Record<Keys, string>>
|
||||
|
|
|
|||
|
|
@ -1102,24 +1102,4 @@ export const dict = {
|
|||
"workspace.reset.archived.one": "1 сесію буде заархівовано.",
|
||||
"workspace.reset.archived.many": "{{count}} сесій буде заархівовано.",
|
||||
"workspace.reset.note": "Це скине робочу область, щоб вона відповідала гілці за замовчуванням.",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1075,24 +1075,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "创建持久化项目图标失败",
|
||||
"error.childStore.storeCreateFailed": "创建存储失败",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket 异常关闭:{{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
} satisfies Partial<Record<Keys, string>>
|
||||
|
|
|
|||
|
|
@ -1071,24 +1071,4 @@ export const dict = {
|
|||
"error.childStore.persistedProjectIconCreateFailed": "建立持續性專案圖示失敗",
|
||||
"error.childStore.storeCreateFailed": "建立儲存區失敗",
|
||||
"terminal.connectionLost.abnormalClose": "WebSocket 異常關閉:{{code}}",
|
||||
"command.session.export": "Export session",
|
||||
"command.session.export.description": "Export the full session transcript as JSON",
|
||||
"context.export.session": "Export session",
|
||||
"toast.session.export.success.title": "Session exported",
|
||||
"toast.session.export.success.description": "Saved session to {{filename}}",
|
||||
"toast.session.export.failed.title": "Failed to export session",
|
||||
"toast.session.export.failed.description": "An error occurred while exporting the session",
|
||||
"common.export": "Export",
|
||||
"settings.tab.preferences": "Preferences",
|
||||
"settings.tab.projects": "Projects",
|
||||
"settings.tab.extensions": "Extensions",
|
||||
"settings.server.all": "All servers",
|
||||
"settings.projects.title": "Projects",
|
||||
"settings.projects.description": "View and configure projects on this server",
|
||||
"settings.projects.empty": "No projects found",
|
||||
"settings.mcps.description": "Manage Model Context Protocol (MCP) servers and tools",
|
||||
"settings.extensions.availableAll": "Available to all projects",
|
||||
"settings.extensions.manageConfig": "Manage in opencode.json",
|
||||
"settings.extensions.addSkills": "How to add skills",
|
||||
"settings.tab.notifications": "Notifications",
|
||||
} satisfies Partial<Record<Keys, string>>
|
||||
|
|
|
|||
|
|
@ -145,14 +145,16 @@ export function WslServerSettings(props: {
|
|||
{language.t("wsl.server.retryStart")}
|
||||
</MenuV2.Item>
|
||||
</Show>
|
||||
<Show when={props.controller.canDefault()}>
|
||||
<MenuV2.Item
|
||||
disabled={props.controller.defaultKey() === key}
|
||||
onSelect={() => props.controller.setDefault(key)}
|
||||
>
|
||||
<Show when={props.controller.canDefault() && props.controller.defaultKey() !== key}>
|
||||
<MenuV2.Item onSelect={() => props.controller.setDefault(key)}>
|
||||
{language.t("dialog.server.menu.default")}
|
||||
</MenuV2.Item>
|
||||
</Show>
|
||||
<Show when={props.controller.canDefault() && props.controller.defaultKey() === key}>
|
||||
<MenuV2.Item onSelect={() => props.controller.setDefault(null)}>
|
||||
{language.t("dialog.server.menu.defaultRemove")}
|
||||
</MenuV2.Item>
|
||||
</Show>
|
||||
<MenuV2.Separator />
|
||||
<MenuV2.Item onSelect={() => remove(key)}>
|
||||
{language.t("dialog.server.menu.delete")}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@
|
|||
border: 0.5px solid var(--v2-border-border-muted);
|
||||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-list"] {
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] > [data-slot="tabs-v2-list"] {
|
||||
flex-direction: column;
|
||||
width: 240px;
|
||||
min-width: 200px;
|
||||
|
|
@ -186,7 +186,9 @@
|
|||
border-right: 1px solid var(--v2-border-border-base);
|
||||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-section-title"] {
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"]
|
||||
> [data-slot="tabs-v2-list"]
|
||||
[data-slot="tabs-v2-section-title"] {
|
||||
width: 100%;
|
||||
padding-left: 4px;
|
||||
color: var(--v2-text-text-muted);
|
||||
|
|
@ -195,7 +197,9 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-trigger-wrapper"] {
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"]
|
||||
> [data-slot="tabs-v2-list"]
|
||||
[data-slot="tabs-v2-trigger-wrapper"] {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
|
|
@ -204,7 +208,9 @@
|
|||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"] [data-slot="tabs-v2-trigger"] {
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"]
|
||||
> [data-slot="tabs-v2-list"]
|
||||
[data-slot="tabs-v2-trigger"] {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
|
|
@ -213,12 +219,14 @@
|
|||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"]
|
||||
> [data-slot="tabs-v2-list"]
|
||||
[data-slot="tabs-v2-trigger-wrapper"]:hover:not(:disabled):not(:has([data-selected])) {
|
||||
background-color: var(--v2-background-bg-layer-03);
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
[data-component="tabs-v2"][data-variant="settings"][data-orientation="vertical"]
|
||||
> [data-slot="tabs-v2-list"]
|
||||
[data-slot="tabs-v2-trigger-wrapper"]:has([data-selected]) {
|
||||
background-color: var(--v2-background-bg-layer-03);
|
||||
color: var(--v2-text-text-base);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue