wip(desktop): progress
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match, createSignal } from "solid-js"
|
||||
import {
|
||||
createEffect,
|
||||
on,
|
||||
Component,
|
||||
Show,
|
||||
For,
|
||||
onMount,
|
||||
onCleanup,
|
||||
Switch,
|
||||
Match,
|
||||
createSignal,
|
||||
createMemo,
|
||||
} from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createFocusSignal } from "@solid-primitives/active-element"
|
||||
import { useLocal } from "@/context/local"
|
||||
@@ -470,7 +482,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</Button>
|
||||
<Show when={layout.dialog.opened() === "model"}>
|
||||
<Switch>
|
||||
<Match when={providers().connected().length > 0}>
|
||||
<Match when={providers.paid().length > 0}>
|
||||
{iife(() => {
|
||||
const models = createMemo(() =>
|
||||
local.model
|
||||
.list()
|
||||
.filter((m) =>
|
||||
layout.connect.state() === "complete" ? m.provider.id === layout.connect.provider() : true,
|
||||
),
|
||||
)
|
||||
return (
|
||||
<SelectDialog
|
||||
defaultOpen
|
||||
onOpenChange={(open) => {
|
||||
@@ -484,7 +505,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
placeholder="Search models"
|
||||
emptyMessage="No model results"
|
||||
key={(x) => `${x.provider.id}:${x.id}`}
|
||||
items={local.model.list()}
|
||||
items={models}
|
||||
current={local.model.current()}
|
||||
filterKeys={["provider.name", "name", "id"]}
|
||||
// groupBy={(x) => (local.model.recent().includes(x) ? "Recent" : x.provider.name)}
|
||||
@@ -499,7 +520,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
|
||||
}}
|
||||
onSelect={(x) =>
|
||||
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { recent: true })
|
||||
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
||||
recent: true,
|
||||
})
|
||||
}
|
||||
actions={
|
||||
<Button
|
||||
@@ -524,6 +547,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</div>
|
||||
)}
|
||||
</SelectDialog>
|
||||
)
|
||||
})}
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
{iife(() => {
|
||||
@@ -554,7 +579,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<div class="text-14-medium text-text-base px-2.5">Free models provided by OpenCode</div>
|
||||
<List
|
||||
ref={(ref) => (listRef = ref)}
|
||||
items={local.model.list()}
|
||||
items={local.model.list}
|
||||
current={local.model.current()}
|
||||
key={(x) => `${x.provider.id}:${x.id}`}
|
||||
onSelect={(x) => {
|
||||
@@ -587,7 +612,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<List
|
||||
class="w-full"
|
||||
key={(x) => x?.id}
|
||||
items={providers().popular()}
|
||||
items={providers.popular}
|
||||
activeIcon="plus-small"
|
||||
sortBy={(a, b) => {
|
||||
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
|
||||
|
||||
@@ -45,15 +45,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
name: "default-layout.v7",
|
||||
},
|
||||
)
|
||||
const [ephemeral, setEphemeral] = createStore({
|
||||
const [ephemeral, setEphemeral] = createStore<{
|
||||
connect: {
|
||||
provider: undefined as undefined | string,
|
||||
state: undefined as undefined | "pending" | "complete" | "error",
|
||||
error: undefined as undefined | string,
|
||||
},
|
||||
provider?: string
|
||||
state?: "pending" | "complete" | "error"
|
||||
error?: string
|
||||
}
|
||||
dialog: {
|
||||
open: undefined as undefined | Dialog,
|
||||
},
|
||||
open?: Dialog
|
||||
}
|
||||
}>({
|
||||
connect: {},
|
||||
dialog: {},
|
||||
})
|
||||
const usedColors = new Set<string>()
|
||||
|
||||
@@ -177,22 +180,30 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
dialog: {
|
||||
opened: createMemo(() => ephemeral.dialog?.open),
|
||||
open(dialog: Dialog) {
|
||||
batch(() => {
|
||||
// if (dialog !== "connect") {
|
||||
// setEphemeral("connect", {})
|
||||
// }
|
||||
setEphemeral("dialog", "open", dialog)
|
||||
if (dialog !== "connect") {
|
||||
setEphemeral("connect", {})
|
||||
}
|
||||
})
|
||||
},
|
||||
close(dialog: Dialog) {
|
||||
if (ephemeral.dialog?.open === dialog) {
|
||||
setEphemeral("dialog", "open", undefined)
|
||||
setEphemeral("connect", {})
|
||||
if (ephemeral.dialog.open === dialog) {
|
||||
setEphemeral(
|
||||
produce((state) => {
|
||||
state.dialog.open = undefined
|
||||
state.connect = {}
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
connect(provider: string) {
|
||||
batch(() => {
|
||||
setEphemeral("dialog", "open", "connect")
|
||||
setEphemeral("connect", { provider, state: "pending" })
|
||||
})
|
||||
setEphemeral(
|
||||
produce((state) => {
|
||||
state.dialog.open = "connect"
|
||||
state.connect = { provider, state: "pending" }
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
connect: {
|
||||
|
||||
@@ -41,10 +41,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
const providers = useProviders()
|
||||
|
||||
function isModelValid(model: ModelKey) {
|
||||
const provider = providers().all.find((x) => x.id === model.providerID)
|
||||
const provider = providers.all().find((x) => x.id === model.providerID)
|
||||
return (
|
||||
!!provider?.models[model.modelID] &&
|
||||
providers()
|
||||
providers
|
||||
.connected()
|
||||
.map((p) => p.id)
|
||||
.includes(model.providerID)
|
||||
@@ -123,9 +123,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
})
|
||||
|
||||
const list = createMemo(() =>
|
||||
providers()
|
||||
.connected()
|
||||
.flatMap((p) =>
|
||||
providers.connected().flatMap((p) =>
|
||||
Object.values(p.models).map((m) => ({
|
||||
...m,
|
||||
name: m.name.replace("(latest)", "").trim(),
|
||||
@@ -153,11 +151,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
}
|
||||
|
||||
for (const p of providers().connected()) {
|
||||
if (p.id in providers().default) {
|
||||
for (const p of providers.connected()) {
|
||||
if (p.id in providers.default()) {
|
||||
return {
|
||||
providerID: p.id,
|
||||
modelID: providers().default[p.id],
|
||||
modelID: providers.default()[p.id],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ export function useProviders() {
|
||||
const connected = createMemo(() => providers().all.filter((p) => providers().connected.includes(p.id)))
|
||||
const paid = createMemo(() => connected().filter((p) => Object.values(p.models).find((m) => m.cost?.input)))
|
||||
const popular = createMemo(() => providers().all.filter((p) => popularProviders.includes(p.id)))
|
||||
return createMemo(() => ({
|
||||
all: providers().all,
|
||||
default: providers().default,
|
||||
return {
|
||||
all: createMemo(() => providers().all),
|
||||
default: createMemo(() => providers().default),
|
||||
popular,
|
||||
connected,
|
||||
paid,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ export default function Layout(props: ParentProps) {
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
|
||||
<Switch>
|
||||
<Match when={!providers().paid().length && layout.sidebar.opened()}>
|
||||
<Match when={!providers.paid().length && layout.sidebar.opened()}>
|
||||
<div class="rounded-md bg-background-stronger shadow-xs-border-base">
|
||||
<div class="p-3 flex flex-col gap-2">
|
||||
<div class="text-12-medium text-text-strong">Getting started</div>
|
||||
@@ -567,7 +567,7 @@ export default function Layout(props: ParentProps) {
|
||||
placeholder="Search providers"
|
||||
activeIcon="plus-small"
|
||||
key={(x) => x?.id}
|
||||
items={providers().all}
|
||||
items={providers.all}
|
||||
filterKeys={["id", "name"]}
|
||||
groupBy={(x) => (popularProviders.includes(x.id) ? "Popular" : "Other")}
|
||||
sortBy={(a, b) => {
|
||||
@@ -620,16 +620,19 @@ export default function Layout(props: ParentProps) {
|
||||
const [store, setStore] = createStore({
|
||||
method: undefined as undefined | ProviderAuthMethod,
|
||||
})
|
||||
const providerID = layout.connect.provider()!
|
||||
const provider = globalSync.data.provider.all.find((x) => x.id === providerID)!
|
||||
const methods = globalSync.data.provider_auth[providerID] ?? [
|
||||
const providerID = createMemo(() => layout.connect.provider()!)
|
||||
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === providerID())!)
|
||||
const methods = createMemo(
|
||||
() =>
|
||||
globalSync.data.provider_auth[providerID()] ?? [
|
||||
{
|
||||
type: "api",
|
||||
label: "API key",
|
||||
},
|
||||
]
|
||||
if (methods.length === 1) {
|
||||
setStore("method", methods[0])
|
||||
],
|
||||
)
|
||||
if (methods().length === 1) {
|
||||
setStore("method", methods()[0])
|
||||
}
|
||||
|
||||
let listRef: ListRef | undefined
|
||||
@@ -670,11 +673,14 @@ export default function Layout(props: ParentProps) {
|
||||
<Dialog.Body>
|
||||
<div class="flex flex-col gap-6 px-2.5 pb-3">
|
||||
<div class="px-2.5 flex gap-4 items-center">
|
||||
<ProviderIcon id={providerID as IconName} class="size-5 shrink-0 icon-strong-base" />
|
||||
<div class="text-16-medium text-text-strong">Connect {provider.name}</div>
|
||||
<ProviderIcon id={providerID() as IconName} class="size-5 shrink-0 icon-strong-base" />
|
||||
<div class="text-16-medium text-text-strong">Connect {provider().name}</div>
|
||||
</div>
|
||||
<Switch>
|
||||
<Match when={store.method === undefined}>
|
||||
<div class="px-2.5 text-14-regular text-text-base">
|
||||
Select login method for {provider().name}.
|
||||
</div>
|
||||
<Show when={store.method === undefined}>
|
||||
<div class="px-2.5 text-14-regular text-text-base">Select login method for {provider.name}.</div>
|
||||
<div class="">
|
||||
<Input hidden type="text" class="opacity-0 size-0" autofocus onKeyDown={handleKey} />
|
||||
<List
|
||||
@@ -724,8 +730,8 @@ export default function Layout(props: ParentProps) {
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={store.method?.type === "api"}>
|
||||
</Match>
|
||||
<Match when={store.method?.type === "api"}>
|
||||
{iife(() => {
|
||||
const [formStore, setFormStore] = createStore({
|
||||
value: "",
|
||||
@@ -746,20 +752,22 @@ export default function Layout(props: ParentProps) {
|
||||
|
||||
setFormStore("error", undefined)
|
||||
await globalSDK.client.auth.set({
|
||||
providerID,
|
||||
providerID: providerID(),
|
||||
auth: {
|
||||
type: "api",
|
||||
key: apiKey,
|
||||
},
|
||||
})
|
||||
await globalSDK.client.global.dispose()
|
||||
setTimeout(() => {
|
||||
layout.connect.complete()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="px-2.5 pb-10 flex flex-col gap-6">
|
||||
<Switch>
|
||||
<Match when={provider.id === "opencode"}>
|
||||
<Match when={provider().id === "opencode"}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-14-regular text-text-base">
|
||||
OpenCode Zen gives you access to a curated set of reliable optimized models for
|
||||
@@ -808,7 +816,8 @@ export default function Layout(props: ParentProps) {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Show>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</Dialog.Body>
|
||||
</Dialog>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createStore } from "solid-js/store"
|
||||
import { createList } from "solid-list"
|
||||
|
||||
export interface FilteredListProps<T> {
|
||||
items: T[] | ((filter: string) => Promise<T[]>)
|
||||
items: (filter: string) => T[] | Promise<T[]>
|
||||
key: (item: T) => string
|
||||
filterKeys?: string[]
|
||||
current?: T
|
||||
@@ -22,7 +22,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
||||
() => store.filter,
|
||||
async (filter) => {
|
||||
const needle = filter?.toLowerCase()
|
||||
const all = (typeof props.items === "function" ? await props.items(needle) : props.items) || []
|
||||
const all = (await props.items(needle)) || []
|
||||
const result = pipe(
|
||||
all,
|
||||
(x) => {
|
||||
|
||||
Reference in New Issue
Block a user