From 4246cdb069502c96ab11e260eb36a07a0370b710 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Sun, 14 Dec 2025 19:33:40 -0600
Subject: [PATCH] wip(desktop): progress
---
.../desktop/src/components/dialog-connect.tsx | 2 +-
.../src/components/dialog-file-select.tsx | 52 ++++
.../src/components/dialog-manage-models.tsx | 65 +++++
.../src/components/dialog-model-unpaid.tsx | 133 +++++++++
.../desktop/src/components/dialog-model.tsx | 275 ++++++------------
.../src/components/dialog-select-provider.tsx | 101 ++++---
.../desktop/src/components/prompt-input.tsx | 9 +-
packages/desktop/src/context/local.tsx | 7 +-
packages/desktop/src/pages/session.tsx | 38 +--
packages/ui/src/components/dialog.css | 27 +-
packages/ui/src/components/list.css | 38 +++
packages/ui/src/components/list.tsx | 141 +++++----
packages/ui/src/components/select-dialog.css | 44 ---
packages/ui/src/components/select-dialog.tsx | 93 ------
packages/ui/src/components/session-turn.css | 2 -
packages/ui/src/components/session-turn.tsx | 4 +-
packages/ui/src/components/switch.css | 131 +++++++++
packages/ui/src/components/switch.tsx | 30 ++
packages/ui/src/hooks/use-filtered-list.tsx | 11 +-
packages/ui/src/styles/index.css | 2 +-
20 files changed, 726 insertions(+), 479 deletions(-)
create mode 100644 packages/desktop/src/components/dialog-file-select.tsx
create mode 100644 packages/desktop/src/components/dialog-manage-models.tsx
create mode 100644 packages/desktop/src/components/dialog-model-unpaid.tsx
delete mode 100644 packages/ui/src/components/select-dialog.css
delete mode 100644 packages/ui/src/components/select-dialog.tsx
create mode 100644 packages/ui/src/components/switch.css
create mode 100644 packages/ui/src/components/switch.tsx
diff --git a/packages/desktop/src/components/dialog-connect.tsx b/packages/desktop/src/components/dialog-connect.tsx
index d482b3f50..3a1e05f27 100644
--- a/packages/desktop/src/components/dialog-connect.tsx
+++ b/packages/desktop/src/components/dialog-connect.tsx
@@ -117,7 +117,7 @@ export const DialogConnect: Component<{ provider: string }> = (props) => {
title: `${provider().name} connected`,
description: `${provider().name} models are now available to use.`,
})
- dialog.replace(() => )
+ dialog.replace(() => )
}, 500)
}
diff --git a/packages/desktop/src/components/dialog-file-select.tsx b/packages/desktop/src/components/dialog-file-select.tsx
new file mode 100644
index 000000000..3afe06062
--- /dev/null
+++ b/packages/desktop/src/components/dialog-file-select.tsx
@@ -0,0 +1,52 @@
+import { Component } from "solid-js"
+import { useLocal } from "@/context/local"
+import { Dialog } from "@opencode-ai/ui/dialog"
+import { List } from "@opencode-ai/ui/list"
+import { FileIcon } from "@opencode-ai/ui/file-icon"
+import { getDirectory, getFilename } from "@opencode-ai/util/path"
+
+export const DialogFileSelect: Component<{
+ onOpenChange?: (open: boolean) => void
+ onSelect?: (path: string) => void
+}> = (props) => {
+ const local = useLocal()
+ let closeButton!: HTMLButtonElement
+
+ return (
+
+ )
+}
diff --git a/packages/desktop/src/components/dialog-manage-models.tsx b/packages/desktop/src/components/dialog-manage-models.tsx
new file mode 100644
index 000000000..2904f9a5b
--- /dev/null
+++ b/packages/desktop/src/components/dialog-manage-models.tsx
@@ -0,0 +1,65 @@
+import { Component } from "solid-js"
+import { useLocal } from "@/context/local"
+import { useDialog } from "@/context/dialog"
+import { popularProviders } from "@/hooks/use-providers"
+import { Dialog } from "@opencode-ai/ui/dialog"
+import { List } from "@opencode-ai/ui/list"
+import { Switch } from "@opencode-ai/ui/switch"
+
+export const DialogManageModels: Component = () => {
+ const local = useLocal()
+ const dialog = useDialog()
+
+ return (
+
+ )
+}
diff --git a/packages/desktop/src/components/dialog-model-unpaid.tsx b/packages/desktop/src/components/dialog-model-unpaid.tsx
new file mode 100644
index 000000000..d218770d9
--- /dev/null
+++ b/packages/desktop/src/components/dialog-model-unpaid.tsx
@@ -0,0 +1,133 @@
+import { Component, onCleanup, onMount, Show } from "solid-js"
+import { useLocal } from "@/context/local"
+import { useDialog } from "@/context/dialog"
+import { popularProviders, useProviders } from "@/hooks/use-providers"
+import { Button } from "@opencode-ai/ui/button"
+import { Tag } from "@opencode-ai/ui/tag"
+import { Dialog } from "@opencode-ai/ui/dialog"
+import { List, ListRef } from "@opencode-ai/ui/list"
+import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
+import { IconName } from "@opencode-ai/ui/icons/provider"
+import { DialogSelectProvider } from "./dialog-select-provider"
+import { DialogConnect } from "./dialog-connect"
+
+export const DialogModelUnpaid: Component = () => {
+ const local = useLocal()
+ const dialog = useDialog()
+ const providers = useProviders()
+
+ let listRef: ListRef | undefined
+ const handleKey = (e: KeyboardEvent) => {
+ if (e.key === "Escape") return
+ listRef?.onKeyDown(e)
+ }
+
+ onMount(() => {
+ document.addEventListener("keydown", handleKey)
+ onCleanup(() => {
+ document.removeEventListener("keydown", handleKey)
+ })
+ })
+
+ return (
+
+ )
+}
diff --git a/packages/desktop/src/components/dialog-model.tsx b/packages/desktop/src/components/dialog-model.tsx
index 7f90e1a78..e8f9df055 100644
--- a/packages/desktop/src/components/dialog-model.tsx
+++ b/packages/desktop/src/components/dialog-model.tsx
@@ -1,208 +1,95 @@
-import { Component, createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js"
+import { Component, createMemo, Show } from "solid-js"
import { useLocal } from "@/context/local"
import { useDialog } from "@/context/dialog"
-import { popularProviders, useProviders } from "@/hooks/use-providers"
-import { SelectDialog } from "@opencode-ai/ui/select-dialog"
+import { popularProviders } from "@/hooks/use-providers"
import { Button } from "@opencode-ai/ui/button"
import { Tag } from "@opencode-ai/ui/tag"
import { Dialog } from "@opencode-ai/ui/dialog"
-import { List, ListRef } from "@opencode-ai/ui/list"
-import { iife } from "@opencode-ai/util/iife"
-import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
-import { IconName } from "@opencode-ai/ui/icons/provider"
+import { List } from "@opencode-ai/ui/list"
import { DialogSelectProvider } from "./dialog-select-provider"
-import { DialogConnect } from "./dialog-connect"
+import { DialogManageModels } from "./dialog-manage-models"
-export const DialogModel: Component<{ connectedProvider?: string }> = (props) => {
+export const DialogModel: Component<{ provider?: string }> = (props) => {
const local = useLocal()
const dialog = useDialog()
- const providers = useProviders()
+
+ let closeButton!: HTMLButtonElement
+ const models = createMemo(() =>
+ local.model
+ .list()
+ .filter((m) => m.visible)
+ .filter((m) => (props.provider ? m.provider.id === props.provider : true)),
+ )
return (
-
- 0}>
- {iife(() => {
- const models = createMemo(() =>
- local.model
- .list()
- .filter((m) => m.visible)
- .filter((m) => (props.connectedProvider ? m.provider.id === props.connectedProvider : true)),
- )
- return (
- {
- if (!open) {
- dialog.clear()
- }
- }}
- title="Select model"
- placeholder="Search models"
- emptyMessage="No model results"
- key={(x) => `${x.provider.id}:${x.id}`}
- items={models}
- current={local.model.current()}
- filterKeys={["provider.name", "name", "id"]}
- sortBy={(a, b) => a.name.localeCompare(b.name)}
- groupBy={(x) => x.provider.name}
- sortGroupsBy={(a, b) => {
- if (a.category === "Recent" && b.category !== "Recent") return -1
- if (b.category === "Recent" && a.category !== "Recent") return 1
- const aProvider = a.items[0].provider.id
- const bProvider = b.items[0].provider.id
- if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
- if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
- return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
- }}
- onSelect={(x) =>
- local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
- recent: true,
- })
- }
- actions={
-
- }
- >
- {(i) => (
-
- {i.name}
-
- Free
-
-
- Latest
-
-
- )}
-
- )
- })}
-
-
- {iife(() => {
- let listRef: ListRef | undefined
- const handleKey = (e: KeyboardEvent) => {
- if (e.key === "Escape") return
- listRef?.onKeyDown(e)
- }
-
- onMount(() => {
- document.addEventListener("keydown", handleKey)
- onCleanup(() => {
- document.removeEventListener("keydown", handleKey)
+
-
+ closeButton.click()
+ }}
+ >
+ {(i) => (
+
+ {i.name}
+
+ Free
+
+
+ Latest
+
+
+ )}
+
+
+
+
)
}
diff --git a/packages/desktop/src/components/dialog-select-provider.tsx b/packages/desktop/src/components/dialog-select-provider.tsx
index 6dabdb8b4..1c54184bd 100644
--- a/packages/desktop/src/components/dialog-select-provider.tsx
+++ b/packages/desktop/src/components/dialog-select-provider.tsx
@@ -1,7 +1,8 @@
import { Component, Show } from "solid-js"
import { useDialog } from "@/context/dialog"
import { popularProviders, useProviders } from "@/hooks/use-providers"
-import { SelectDialog } from "@opencode-ai/ui/select-dialog"
+import { Dialog } from "@opencode-ai/ui/dialog"
+import { List } from "@opencode-ai/ui/list"
import { Tag } from "@opencode-ai/ui/tag"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { IconName } from "@opencode-ai/ui/icons/provider"
@@ -12,56 +13,66 @@ export const DialogSelectProvider: Component = () => {
const providers = useProviders()
return (
- x?.id}
- items={providers.all}
- filterKeys={["id", "name"]}
- groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")}
- sortBy={(a, b) => {
- if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
- return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
- return a.name.localeCompare(b.name)
- }}
- sortGroupsBy={(a, b) => {
- if (a.category === "Popular" && b.category !== "Popular") return -1
- if (b.category === "Popular" && a.category !== "Popular") return 1
- return 0
- }}
- onSelect={(x) => {
- if (!x) return
- dialog.replace(() => )
- }}
onOpenChange={(open) => {
if (!open) {
dialog.clear()
}
}}
>
- {(i) => (
-
-
-
{i.name}
-
- Recommended
-
-
- Connect with Claude Pro/Max or API key
-
-
- )}
-
+
+ Connect provider
+
+
+
+ x?.id}
+ items={providers.all}
+ filterKeys={["id", "name"]}
+ groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")}
+ sortBy={(a, b) => {
+ if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
+ return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
+ return a.name.localeCompare(b.name)
+ }}
+ sortGroupsBy={(a, b) => {
+ if (a.category === "Popular" && b.category !== "Popular") return -1
+ if (b.category === "Popular" && a.category !== "Popular") return 1
+ return 0
+ }}
+ onSelect={(x) => {
+ if (!x) return
+ dialog.replace(() => )
+ }}
+ >
+ {(i) => (
+
+
+
{i.name}
+
+ Recommended
+
+
+ Connect with Claude Pro/Max or API key
+
+
+ )}
+
+
+
)
}
diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx
index ca0ccf96a..faecd9520 100644
--- a/packages/desktop/src/components/prompt-input.tsx
+++ b/packages/desktop/src/components/prompt-input.tsx
@@ -17,6 +17,8 @@ import { Select } from "@opencode-ai/ui/select"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { useDialog } from "@/context/dialog"
import { DialogModel } from "@/components/dialog-model"
+import { DialogModelUnpaid } from "@/components/dialog-model-unpaid"
+import { useProviders } from "@/hooks/use-providers"
interface PromptInputProps {
class?: string
@@ -58,6 +60,7 @@ export const PromptInput: Component = (props) => {
const local = useLocal()
const session = useSession()
const dialog = useDialog()
+ const providers = useProviders()
let editorRef!: HTMLDivElement
const [store, setStore] = createStore<{
@@ -610,7 +613,11 @@ export const PromptInput: Component = (props) => {
class="capitalize"
variant="ghost"
/>
-