Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions packages/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import DirectoryLayout from "@/pages/directory-layout"
import { ErrorPage } from "./pages/error"
import { iife } from "@opencode-ai/util/iife"
import { Suspense } from "solid-js"
import { CredentialsProvider } from "./context/credentials"

const Home = lazy(() => import("@/pages/home"))
const Session = lazy(() => import("@/pages/session"))
Expand Down Expand Up @@ -132,11 +133,37 @@ export function AppInterface(props: { defaultUrl?: string }) {
</TerminalProvider>
</Show>
)}
/>
</Route>
</Router>
</GlobalSyncProvider>
</GlobalSDKProvider>
>
<Route
path="/"
component={() => (
<Suspense fallback={<Loading />}>
<Home />
</Suspense>
)}
/>
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={() => (
<TerminalProvider>
<FileProvider>
<PromptProvider>
<Suspense fallback={<Loading />}>
<Session />
</Suspense>
</PromptProvider>
</FileProvider>
</TerminalProvider>
)}
/>
</Route>
</Router>
</GlobalSyncProvider>
</GlobalSDKProvider>
</CredentialsProvider>
</Suspense>
</ServerKey>
</ServerProvider>
)
Expand Down
24 changes: 4 additions & 20 deletions packages/app/src/components/dialog-select-mcp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Component, createMemo, createSignal, Show } from "solid-js"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { Component, createMemo } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
Expand All @@ -12,28 +10,14 @@ export const DialogSelectMcp: Component = () => {
const language = useLanguage()
const [loading, setLoading] = createSignal<string | null>(null)

const items = createMemo(() =>
const mcpItems = createMemo(() =>
Object.entries(sync.data.mcp ?? {})
.map(([name, status]) => ({ name, status: status.status }))
.sort((a, b) => a.name.localeCompare(b.name)),
)

const toggle = async (name: string) => {
if (loading()) return
setLoading(name)
const status = sync.data.mcp[name]
if (status?.status === "connected") {
await sdk.client.mcp.disconnect({ name })
} else {
await sdk.client.mcp.connect({ name })
}
const result = await sdk.client.mcp.status()
if (result.data) sync.set("mcp", result.data)
setLoading(null)
}

const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
const totalCount = createMemo(() => items().length)
const enabledCount = createMemo(() => mcpItems().filter((i) => i.status === "connected").length)
const totalCount = createMemo(() => mcpItems().length)

return (
<Dialog
Expand Down
3 changes: 0 additions & 3 deletions packages/app/src/components/dialog-select-server.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { createResource, createEffect, createMemo, onCleanup, Show } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { TextField } from "@opencode-ai/ui/text-field"
Expand Down
78 changes: 78 additions & 0 deletions packages/app/src/components/dialog-server-config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { createMemo } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Tabs } from "@opencode-ai/ui/tabs"
import { useServer } from "@/context/server"
import { useSync } from "@/context/sync"
import { ServerTab } from "./server-tab"
import { McpTab } from "./mcp-tab"
import { LspTab } from "./lsp-tab"
import { PluginsTab } from "./plugins-tab"

export function DialogServerConfig(
props: { defaultTab?: "servers" | "mcp" | "lsp" | "plugins" } = { defaultTab: "servers" },
) {
const server = useServer()
const sync = useSync()

const mcpItems = createMemo(() =>
Object.entries(sync.data.mcp ?? {})
.map(([name]) => name)
.sort((a, b) => a.localeCompare(b)),
)

return (
<Dialog class="min-h-[150px]">
<Tabs
aria-label="Server Configurations"
class="tabs"
data-component="tabs"
data-active="servers"
defaultValue={props.defaultTab}
variant="alt"
style={{
"background-color": "var(--background-base)",
"border-radius": "12px",
overflow: "hidden",
}}
>
<Tabs.List
data-slot="tablist"
style={{
"background-color": "var(--background-stronger)",
"border-bottom": "none",
padding: "8px 24px 0",
}}
>
<Tabs.Trigger value="servers" data-slot="tab">
{server.list.length} {server.list.length === 1 ? "Server" : "Servers"}
</Tabs.Trigger>
<Tabs.Trigger value="mcp" data-slot="tab">
{mcpItems().length} {mcpItems().length === 1 ? "MCP" : "MCPs"}
</Tabs.Trigger>
<Tabs.Trigger value="lsp" data-slot="tab">
LSP
</Tabs.Trigger>
<Tabs.Trigger value="plugins" data-slot="tab">
Plugins
</Tabs.Trigger>
</Tabs.List>

<Tabs.Content data-slot="panel" value="servers">
<ServerTab />
</Tabs.Content>

<Tabs.Content as="div" value="mcp">
<McpTab />
</Tabs.Content>

<Tabs.Content as="div" value="lsp">
<LspTab />
</Tabs.Content>

<Tabs.Content as="div" value="plugins">
<PluginsTab />
</Tabs.Content>
</Tabs>
</Dialog>
)
}
9 changes: 9 additions & 0 deletions packages/app/src/components/lsp-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Component } from "solid-js"

export const LspTab: Component = () => {
return (
<div class="flex items-center justify-center p-6 min-h-[150px]">
<span class="text-text-weak">LSPs auto-detected from file types</span>
</div>
)
}
70 changes: 70 additions & 0 deletions packages/app/src/components/mcp-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createSignal, createMemo } from "solid-js"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import type { Component } from "solid-js"

export const McpTab: Component = () => {
const sync = useSync()
const sdk = useSDK()
const [mcpLoading, setMcpLoading] = createSignal<string | null>(null)

const mcpItems = createMemo(() =>
Object.entries(sync.data.mcp ?? {})
.map(([name, status]) => ({ name, status: status.status }))
.sort((a, b) => a.name.localeCompare(b.name)),
)

const toggleMcp = async (name: string) => {
if (mcpLoading()) return
setMcpLoading(name)
const status = sync.data.mcp[name]
if (status?.status === "connected") {
await sdk.client.mcp.disconnect({ name })
} else {
await sdk.client.mcp.connect({ name })
}
const result = await sdk.client.mcp.status()
if (result.data) sync.set("mcp", result.data)
setMcpLoading(null)
}

return (
<div class="flex flex-col gap-4 pb-6 pt-4 min-h-[150px]">
<List
search={{ placeholder: "Search MCPs", autofocus: true }}
emptyMessage="No MCPs configured"
items={mcpItems}
key={(x) => x.name}
onSelect={(x) => {
if (x) toggleMcp(x.name)
}}
>
{(i) => {
const mcpStatus = () => sync.data.mcp[i.name]
const status = () => mcpStatus()?.status
const enabled = () => status() === "connected"
return (
<div class="flex items-center gap-2 min-w-0 flex-1 group/item">
<div class="flex items-center gap-2 min-w-0 flex-1">
<div
classList={{
"size-1.5 rounded-full shrink-0": true,
"bg-icon-success-base": status() === "connected",
"bg-icon-critical-base": status() === "failed",
"bg-border-weak-base": status() !== "connected" && status() !== "failed",
}}
/>
<span class="truncate">{i.name}</span>
</div>
<div onClick={(e) => e.stopPropagation()}>
<Switch checked={enabled()} disabled={mcpLoading() === i.name} onChange={() => toggleMcp(i.name)} />
</div>
</div>
)
}}
</List>
</div>
)
}
11 changes: 11 additions & 0 deletions packages/app/src/components/plugins-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Component } from "solid-js"

export const PluginsTab: Component = () => {
return (
<div class="flex items-center justify-center p-6 min-h-[150px]">
<span class="text-text-weak">
Plugins configured in <code class="bg-surface-raised-base-hover px-1 py-0.5 rounded-sm">opencode.json</code>
</span>
</div>
)
}
Loading
Loading