Apply PR #19545: feat: opencode remote control + opencode serve dependencies
This commit is contained in:
commit
ef7eeb6a82
118 changed files with 15109 additions and 466 deletions
|
|
@ -8,14 +8,20 @@ import { SettingsGeneral } from "./settings-general"
|
|||
import { SettingsKeybinds } from "./settings-keybinds"
|
||||
import { SettingsProviders } from "./settings-providers"
|
||||
import { SettingsModels } from "./settings-models"
|
||||
import { SettingsPair } from "./settings-pair"
|
||||
|
||||
export const DialogSettings: Component = () => {
|
||||
export const DialogSettings: Component<{ defaultTab?: string }> = (props) => {
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
|
||||
return (
|
||||
<Dialog size="x-large" transition>
|
||||
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
variant="settings"
|
||||
defaultValue={props.defaultTab ?? "general"}
|
||||
class="h-full settings-dialog"
|
||||
>
|
||||
<Tabs.List>
|
||||
<div class="flex flex-col justify-between h-full w-full">
|
||||
<div class="flex flex-col gap-3 w-full pt-3">
|
||||
|
|
@ -45,6 +51,10 @@ export const DialogSettings: Component = () => {
|
|||
<Icon name="models" />
|
||||
{language.t("settings.models.title")}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="pair">
|
||||
<Icon name="link" />
|
||||
{language.t("settings.pair.title")}
|
||||
</Tabs.Trigger>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -67,6 +77,9 @@ export const DialogSettings: Component = () => {
|
|||
<Tabs.Content value="models" class="no-scrollbar">
|
||||
<SettingsModels />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="pair" class="no-scrollbar">
|
||||
<SettingsPair />
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
</Dialog>
|
||||
)
|
||||
|
|
|
|||
166
packages/app/src/components/settings-pair.tsx
Normal file
166
packages/app/src/components/settings-pair.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import { type Component, createResource, Show } from "solid-js"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useServer } from "@/context/server"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { SettingsList } from "./settings-list"
|
||||
|
||||
type PairResult =
|
||||
| { enabled: false }
|
||||
| {
|
||||
enabled: true
|
||||
hosts: string[]
|
||||
relayURL?: string
|
||||
serverID?: string
|
||||
relaySecretHash?: string
|
||||
link: string
|
||||
qr: string
|
||||
}
|
||||
|
||||
export const SettingsPair: Component = () => {
|
||||
const language = useLanguage()
|
||||
const globalSDK = useGlobalSDK()
|
||||
const server = useServer()
|
||||
const platform = usePlatform()
|
||||
|
||||
const [data] = createResource(async () => {
|
||||
const url = `${globalSDK.url}/experimental/push/pair`
|
||||
console.debug("[settings-pair] fetching pair data", {
|
||||
serverUrl: globalSDK.url,
|
||||
serverName: server.name,
|
||||
serverKey: server.key,
|
||||
})
|
||||
const f = platform.fetch ?? fetch
|
||||
const res = await f(url)
|
||||
if (!res.ok) {
|
||||
console.debug("[settings-pair] pair endpoint returned non-ok", {
|
||||
status: res.status,
|
||||
serverUrl: globalSDK.url,
|
||||
})
|
||||
return { enabled: false as const }
|
||||
}
|
||||
const result = (await res.json()) as PairResult
|
||||
console.debug("[settings-pair] pair data received", {
|
||||
enabled: result.enabled,
|
||||
serverUrl: globalSDK.url,
|
||||
serverName: server.name,
|
||||
...(result.enabled
|
||||
? {
|
||||
relayURL: result.relayURL,
|
||||
serverID: result.serverID,
|
||||
relaySecretHash: result.relaySecretHash,
|
||||
hostCount: result.hosts.length,
|
||||
hosts: result.hosts,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-6 py-4 px-5">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="text-16-semibold text-text-strong">{language.t("settings.pair.title")}</h2>
|
||||
<p class="text-13-regular text-text-weak">{language.t("settings.pair.description")}</p>
|
||||
</div>
|
||||
|
||||
<Show when={data.loading}>
|
||||
<SettingsList>
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<span class="text-14-regular text-text-weak">{language.t("settings.pair.loading")}</span>
|
||||
</div>
|
||||
</SettingsList>
|
||||
</Show>
|
||||
|
||||
<Show when={data.error}>
|
||||
<SettingsList>
|
||||
<div class="flex flex-col items-center justify-center py-12 gap-3 text-center">
|
||||
<Icon name="warning" size="large" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-14-medium text-text-strong">{language.t("settings.pair.error.title")}</span>
|
||||
<span class="text-13-regular text-text-weak max-w-md">
|
||||
{language.t("settings.pair.error.description")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsList>
|
||||
</Show>
|
||||
|
||||
<Show when={!data.loading && !data.error && data()}>
|
||||
{(result) => (
|
||||
<Show
|
||||
when={result().enabled && result()}
|
||||
fallback={
|
||||
<SettingsList>
|
||||
<div class="flex flex-col items-center justify-center py-12 gap-3 text-center">
|
||||
<Icon name="link" size="large" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-14-medium text-text-strong">{language.t("settings.pair.disabled.title")}</span>
|
||||
<span class="text-13-regular text-text-weak max-w-md">
|
||||
{language.t("settings.pair.disabled.description")}
|
||||
</span>
|
||||
</div>
|
||||
<code class="text-12-regular text-text-weak bg-surface-inset px-3 py-1.5 rounded mt-1">
|
||||
opencode serve --relay-url <url> --relay-secret <secret>
|
||||
</code>
|
||||
</div>
|
||||
</SettingsList>
|
||||
}
|
||||
>
|
||||
{(pair) => {
|
||||
const p = pair() as PairResult & { enabled: true }
|
||||
return (
|
||||
<SettingsList>
|
||||
<div class="flex flex-col items-center py-8 gap-4">
|
||||
<Show when={server.list.length > 1 || p.relayURL}>
|
||||
<div class="flex flex-col gap-1.5 w-full max-w-sm text-left">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-12-medium text-text-weak shrink-0">
|
||||
{language.t("settings.pair.server.label")}
|
||||
</span>
|
||||
<code class="text-12-regular text-text-default bg-surface-inset px-2 py-0.5 rounded truncate">
|
||||
{server.name}
|
||||
</code>
|
||||
</div>
|
||||
<Show when={p.relayURL}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-12-medium text-text-weak shrink-0">
|
||||
{language.t("settings.pair.relay.label")}
|
||||
</span>
|
||||
<code class="text-12-regular text-text-default bg-surface-inset px-2 py-0.5 rounded truncate">
|
||||
{p.relayURL}
|
||||
</code>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={p.relaySecretHash}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-12-medium text-text-weak shrink-0">
|
||||
{language.t("settings.pair.secret.label")}
|
||||
</span>
|
||||
<code class="text-12-regular text-text-default bg-surface-inset px-2 py-0.5 rounded truncate">
|
||||
{p.relaySecretHash}
|
||||
</code>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<img src={p.qr} alt="Pairing QR code" class="w-64 h-64" />
|
||||
<div class="flex flex-col gap-1 text-center max-w-sm">
|
||||
<span class="text-14-medium text-text-strong">
|
||||
{language.t("settings.pair.instructions.title")}
|
||||
</span>
|
||||
<span class="text-13-regular text-text-weak">
|
||||
{language.t("settings.pair.instructions.description")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsList>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ export const dict = {
|
|||
"command.provider.connect": "Connect provider",
|
||||
"command.server.switch": "Switch server",
|
||||
"command.settings.open": "Open settings",
|
||||
"command.pair.show": "Pair mobile device",
|
||||
"command.session.previous": "Previous session",
|
||||
"command.session.next": "Next session",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
|
|
@ -878,6 +879,20 @@ export const dict = {
|
|||
"settings.providers.tag.config": "Config",
|
||||
"settings.providers.tag.custom": "Custom",
|
||||
"settings.providers.tag.other": "Other",
|
||||
"settings.pair.title": "Pair",
|
||||
"settings.pair.description": "Pair a mobile device for push notifications.",
|
||||
"settings.pair.loading": "Loading pairing info...",
|
||||
"settings.pair.error.title": "Could not load pairing info",
|
||||
"settings.pair.error.description": "Check that the server is reachable and try again.",
|
||||
"settings.pair.disabled.title": "Push relay is not enabled",
|
||||
"settings.pair.disabled.description": "Start the server with push relay options to enable mobile pairing.",
|
||||
"settings.pair.server.label": "Server",
|
||||
"settings.pair.relay.label": "Relay",
|
||||
"settings.pair.secret.label": "Secret",
|
||||
"settings.pair.instructions.title": "Scan with the OpenCode Control app",
|
||||
"settings.pair.instructions.description":
|
||||
"Open the OpenCode Control app and scan this QR code to pair your device for push notifications.",
|
||||
|
||||
"settings.models.title": "Models",
|
||||
"settings.models.description": "Model settings will be configurable here.",
|
||||
"settings.agents.title": "Agents",
|
||||
|
|
|
|||
|
|
@ -1060,6 +1060,13 @@ export default function Layout(props: ParentProps) {
|
|||
keybind: "mod+comma",
|
||||
onSelect: () => openSettings(),
|
||||
},
|
||||
{
|
||||
id: "pair.show",
|
||||
title: language.t("command.pair.show"),
|
||||
category: language.t("command.category.settings"),
|
||||
slash: "pair",
|
||||
onSelect: () => openSettings("pair"),
|
||||
},
|
||||
{
|
||||
id: "session.previous",
|
||||
title: language.t("command.session.previous"),
|
||||
|
|
@ -1212,11 +1219,11 @@ export default function Layout(props: ParentProps) {
|
|||
})
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
function openSettings(defaultTab?: string) {
|
||||
const run = ++dialogRun
|
||||
void import("@/components/dialog-settings").then((x) => {
|
||||
if (dialogDead || dialogRun !== run) return
|
||||
dialog.show(() => <x.DialogSettings />)
|
||||
dialog.show(() => <x.DialogSettings defaultTab={defaultTab} />)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue