Skip to content

Commit 8bcbfd6

Browse files
committed
wip(app): settings
1 parent e521fee commit 8bcbfd6

16 files changed

Lines changed: 563 additions & 44 deletions

packages/app/src/app.tsx

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PermissionProvider } from "@/context/permission"
1414
import { LayoutProvider } from "@/context/layout"
1515
import { GlobalSDKProvider } from "@/context/global-sdk"
1616
import { ServerProvider, useServer } from "@/context/server"
17+
import { SettingsProvider } from "@/context/settings"
1718
import { TerminalProvider } from "@/context/terminal"
1819
import { PromptProvider } from "@/context/prompt"
1920
import { FileProvider } from "@/context/file"
@@ -82,15 +83,17 @@ export function AppInterface(props: { defaultUrl?: string }) {
8283
<GlobalSyncProvider>
8384
<Router
8485
root={(props) => (
85-
<PermissionProvider>
86-
<LayoutProvider>
87-
<NotificationProvider>
88-
<CommandProvider>
89-
<Layout>{props.children}</Layout>
90-
</CommandProvider>
91-
</NotificationProvider>
92-
</LayoutProvider>
93-
</PermissionProvider>
86+
<SettingsProvider>
87+
<PermissionProvider>
88+
<LayoutProvider>
89+
<NotificationProvider>
90+
<CommandProvider>
91+
<Layout>{props.children}</Layout>
92+
</CommandProvider>
93+
</NotificationProvider>
94+
</LayoutProvider>
95+
</PermissionProvider>
96+
</SettingsProvider>
9497
)}
9598
>
9699
<Route
@@ -105,16 +108,18 @@ export function AppInterface(props: { defaultUrl?: string }) {
105108
<Route path="/" component={() => <Navigate href="session" />} />
106109
<Route
107110
path="/session/:id?"
108-
component={() => (
109-
<TerminalProvider>
110-
<FileProvider>
111-
<PromptProvider>
112-
<Suspense fallback={<Loading />}>
113-
<Session />
114-
</Suspense>
115-
</PromptProvider>
116-
</FileProvider>
117-
</TerminalProvider>
111+
component={(p) => (
112+
<Show when={p.params.id ?? "new"} keyed>
113+
<TerminalProvider>
114+
<FileProvider>
115+
<PromptProvider>
116+
<Suspense fallback={<Loading />}>
117+
<Session />
118+
</Suspense>
119+
</PromptProvider>
120+
</FileProvider>
121+
</TerminalProvider>
122+
</Show>
118123
)}
119124
/>
120125
</Route>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Component, createSignal } from "solid-js"
2+
import { Dialog } from "@opencode-ai/ui/dialog"
3+
import { Tabs } from "@opencode-ai/ui/tabs"
4+
import { Icon } from "@opencode-ai/ui/icon"
5+
import { TextField } from "@opencode-ai/ui/text-field"
6+
import { SettingsGeneral } from "./settings-general"
7+
import { SettingsKeybinds } from "./settings-keybinds"
8+
import { SettingsPermissions } from "./settings-permissions"
9+
import { SettingsProviders } from "./settings-providers"
10+
import { SettingsModels } from "./settings-models"
11+
import { SettingsAgents } from "./settings-agents"
12+
import { SettingsCommands } from "./settings-commands"
13+
import { SettingsMcp } from "./settings-mcp"
14+
15+
export const DialogSettings: Component = () => {
16+
const [search, setSearch] = createSignal("")
17+
18+
return (
19+
<Dialog size="large">
20+
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
21+
<Tabs.List>
22+
<div class="settings-dialog__search px-3 pb-3">
23+
<TextField placeholder="Search" value={search()} onChange={setSearch} variant="normal" />
24+
</div>
25+
<Tabs.SectionTitle>Desktop</Tabs.SectionTitle>
26+
<Tabs.Trigger value="general">
27+
<Icon name="settings-gear" />
28+
General
29+
</Tabs.Trigger>
30+
<Tabs.Trigger value="shortcuts">
31+
<Icon name="console" />
32+
Shortcuts
33+
</Tabs.Trigger>
34+
<Tabs.SectionTitle>Server</Tabs.SectionTitle>
35+
<Tabs.Trigger value="permissions">
36+
<Icon name="checklist" />
37+
Permissions
38+
</Tabs.Trigger>
39+
<Tabs.Trigger value="providers">
40+
<Icon name="server" />
41+
Providers
42+
</Tabs.Trigger>
43+
<Tabs.Trigger value="models">
44+
<Icon name="brain" />
45+
Models
46+
</Tabs.Trigger>
47+
<Tabs.Trigger value="agents">
48+
<Icon name="task" />
49+
Agents
50+
</Tabs.Trigger>
51+
<Tabs.Trigger value="commands">
52+
<Icon name="console" />
53+
Commands
54+
</Tabs.Trigger>
55+
<Tabs.Trigger value="mcp">
56+
<Icon name="mcp" />
57+
MCP
58+
</Tabs.Trigger>
59+
</Tabs.List>
60+
<Tabs.Content value="general" class="no-scrollbar">
61+
<SettingsGeneral />
62+
</Tabs.Content>
63+
<Tabs.Content value="shortcuts" class="no-scrollbar">
64+
<SettingsKeybinds />
65+
</Tabs.Content>
66+
<Tabs.Content value="permissions" class="no-scrollbar">
67+
<SettingsPermissions />
68+
</Tabs.Content>
69+
<Tabs.Content value="providers" class="no-scrollbar">
70+
<SettingsProviders />
71+
</Tabs.Content>
72+
<Tabs.Content value="models" class="no-scrollbar">
73+
<SettingsModels />
74+
</Tabs.Content>
75+
<Tabs.Content value="agents" class="no-scrollbar">
76+
<SettingsAgents />
77+
</Tabs.Content>
78+
<Tabs.Content value="commands" class="no-scrollbar">
79+
<SettingsCommands />
80+
</Tabs.Content>
81+
<Tabs.Content value="mcp" class="no-scrollbar">
82+
<SettingsMcp />
83+
</Tabs.Content>
84+
</Tabs>
85+
</Dialog>
86+
)
87+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsAgents: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">Agents</h2>
8+
<p class="text-14-regular text-text-weak">Agent settings will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsCommands: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">Commands</h2>
8+
<p class="text-14-regular text-text-weak">Command settings will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Component, createMemo, type JSX } from "solid-js"
2+
import { Select } from "@opencode-ai/ui/select"
3+
import { Switch } from "@opencode-ai/ui/switch"
4+
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
5+
import { useSettings } from "@/context/settings"
6+
7+
export const SettingsGeneral: Component = () => {
8+
const theme = useTheme()
9+
const settings = useSettings()
10+
11+
const themeOptions = createMemo(() =>
12+
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
13+
)
14+
15+
const colorSchemeOptions: { value: ColorScheme; label: string }[] = [
16+
{ value: "system", label: "System setting" },
17+
{ value: "light", label: "Light" },
18+
{ value: "dark", label: "Dark" },
19+
]
20+
21+
const fontOptions = [
22+
{ value: "ibm-plex-mono", label: "IBM Plex Mono" },
23+
{ value: "fira-code", label: "Fira Code" },
24+
{ value: "jetbrains-mono", label: "JetBrains Mono" },
25+
{ value: "source-code-pro", label: "Source Code Pro" },
26+
]
27+
28+
return (
29+
<div class="flex flex-col h-full overflow-y-auto no-scrollbar">
30+
<div class="flex flex-col gap-8 p-8 max-w-[720px]">
31+
{/* Header */}
32+
<h2 class="text-16-medium text-text-strong">General</h2>
33+
34+
{/* Appearance Section */}
35+
<div class="flex flex-col gap-1">
36+
<h3 class="text-14-medium text-text-strong pb-2">Appearance</h3>
37+
38+
<SettingsRow title="Appearance" description="Customise how OpenCode looks on your device">
39+
<Select
40+
options={colorSchemeOptions}
41+
current={colorSchemeOptions.find((o) => o.value === theme.colorScheme())}
42+
value={(o) => o.value}
43+
label={(o) => o.label}
44+
onSelect={(option) => option && theme.setColorScheme(option.value)}
45+
variant="secondary"
46+
size="small"
47+
/>
48+
</SettingsRow>
49+
50+
<SettingsRow
51+
title="Theme"
52+
description={
53+
<>
54+
Customise how OpenCode is themed.{" "}
55+
<a href="#" class="text-text-interactive-base">
56+
Learn more
57+
</a>
58+
</>
59+
}
60+
>
61+
<Select
62+
options={themeOptions()}
63+
current={themeOptions().find((o) => o.id === theme.themeId())}
64+
value={(o) => o.id}
65+
label={(o) => o.name}
66+
onSelect={(option) => option && theme.setTheme(option.id)}
67+
variant="secondary"
68+
size="small"
69+
/>
70+
</SettingsRow>
71+
72+
<SettingsRow title="Font" description="Customise the mono font used in code blocks">
73+
<Select
74+
options={fontOptions}
75+
current={fontOptions.find((o) => o.value === settings.appearance.font())}
76+
value={(o) => o.value}
77+
label={(o) => o.label}
78+
onSelect={(option) => option && settings.appearance.setFont(option.value)}
79+
variant="secondary"
80+
size="small"
81+
/>
82+
</SettingsRow>
83+
</div>
84+
85+
{/* System notifications Section */}
86+
<div class="flex flex-col gap-1">
87+
<h3 class="text-14-medium text-text-strong pb-2">System notifications</h3>
88+
89+
<SettingsRow
90+
title="Agent"
91+
description="Show system notification when the agent is complete or needs attention"
92+
>
93+
<Switch
94+
checked={settings.notifications.agent()}
95+
onChange={(checked) => settings.notifications.setAgent(checked)}
96+
/>
97+
</SettingsRow>
98+
99+
<SettingsRow title="Permissions" description="Show system notification when a permission is required">
100+
<Switch
101+
checked={settings.notifications.permissions()}
102+
onChange={(checked) => settings.notifications.setPermissions(checked)}
103+
/>
104+
</SettingsRow>
105+
106+
<SettingsRow title="Errors" description="Show system notification when an error occurs">
107+
<Switch
108+
checked={settings.notifications.errors()}
109+
onChange={(checked) => settings.notifications.setErrors(checked)}
110+
/>
111+
</SettingsRow>
112+
</div>
113+
</div>
114+
</div>
115+
)
116+
}
117+
118+
interface SettingsRowProps {
119+
title: string
120+
description: string | JSX.Element
121+
children: JSX.Element
122+
}
123+
124+
const SettingsRow: Component<SettingsRowProps> = (props) => {
125+
return (
126+
<div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
127+
<div class="flex flex-col gap-0.5">
128+
<span class="text-14-medium text-text-strong">{props.title}</span>
129+
<span class="text-12-regular text-text-weak">{props.description}</span>
130+
</div>
131+
<div class="flex-shrink-0">{props.children}</div>
132+
</div>
133+
)
134+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsKeybinds: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">Shortcuts</h2>
8+
<p class="text-14-regular text-text-weak">Keyboard shortcuts will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsMcp: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">MCP</h2>
8+
<p class="text-14-regular text-text-weak">MCP settings will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsModels: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">Models</h2>
8+
<p class="text-14-regular text-text-weak">Model settings will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsPermissions: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">Permissions</h2>
8+
<p class="text-14-regular text-text-weak">Permission settings will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component } from "solid-js"
2+
3+
export const SettingsProviders: Component = () => {
4+
return (
5+
<div class="flex flex-col h-full overflow-y-auto">
6+
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
7+
<h2 class="text-16-medium text-text-strong">Providers</h2>
8+
<p class="text-14-regular text-text-weak">Provider settings will be configurable here.</p>
9+
</div>
10+
</div>
11+
)
12+
}

0 commit comments

Comments
 (0)