Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: James Long <longster@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Jay <air@live.ca> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
147 lines
5.7 KiB
TypeScript
147 lines
5.7 KiB
TypeScript
import { Button } from "@opencode-ai/ui/button"
|
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
import { List, type ListRef } from "@opencode-ai/ui/list"
|
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
|
import { Tag } from "@opencode-ai/ui/tag"
|
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
import { type Component, Show } from "solid-js"
|
|
import { useLocal } from "@/context/local"
|
|
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
|
import { ModelTooltip } from "./model-tooltip"
|
|
import { useLanguage } from "@/context/language"
|
|
import { decode64 } from "@/utils/base64"
|
|
|
|
type ModelState = ReturnType<typeof useLocal>["model"]
|
|
|
|
export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props) => {
|
|
const local = useLocal()
|
|
const model = props.model ?? local.model
|
|
const dialog = useDialog()
|
|
const directory = () => decode64(local.slug())
|
|
const providers = useProviders(directory)
|
|
const language = useLanguage()
|
|
|
|
const openProviders = (provider?: string) => {
|
|
void import("./dialog-connect-provider").then((x) => {
|
|
const controller = x.useProviderConnectController()
|
|
controller.select(provider)
|
|
void dialog.show(() => <x.DialogConnectProvider controller={controller} directory={directory} />)
|
|
})
|
|
}
|
|
|
|
const connect = (provider: string) => openProviders(provider)
|
|
const all = () => openProviders()
|
|
|
|
let listRef: ListRef | undefined
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") return
|
|
listRef?.onKeyDown(e)
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
title={language.t("dialog.model.select.title")}
|
|
class="overflow-y-auto [&_[data-slot=dialog-body]]:overflow-visible [&_[data-slot=dialog-body]]:flex-none"
|
|
>
|
|
<div class="flex flex-col gap-3 px-2.5" onKeyDown={handleKeyDown}>
|
|
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
|
|
<List
|
|
class="px-3 [&_[data-slot=list-scroll]]:overflow-visible"
|
|
ref={(ref) => (listRef = ref)}
|
|
items={model.list}
|
|
current={model.current()}
|
|
key={(x) => `${x.provider.id}:${x.id}`}
|
|
itemWrapper={(item, node) => (
|
|
<Tooltip
|
|
class="w-full"
|
|
placement="right-start"
|
|
gutter={12}
|
|
value={
|
|
<ModelTooltip
|
|
model={item}
|
|
latest={item.latest}
|
|
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
|
|
/>
|
|
}
|
|
>
|
|
{node}
|
|
</Tooltip>
|
|
)}
|
|
onSelect={(x) => {
|
|
model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
|
recent: true,
|
|
})
|
|
dialog.close()
|
|
}}
|
|
>
|
|
{(i) => (
|
|
<div class="w-full flex items-center gap-x-2.5">
|
|
<span>{i.name}</span>
|
|
<Tag>{language.t("model.tag.free")}</Tag>
|
|
<Show when={i.latest}>
|
|
<Tag>{language.t("model.tag.latest")}</Tag>
|
|
</Show>
|
|
</div>
|
|
)}
|
|
</List>
|
|
</div>
|
|
<div class="px-1.5 pb-1.5">
|
|
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
|
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
|
|
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
|
|
<div class="w-full">
|
|
<List
|
|
class="w-full px-3"
|
|
key={(p) => p.id}
|
|
items={providers.popular}
|
|
activeIcon="plus-small"
|
|
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)
|
|
}}
|
|
onSelect={(x) => {
|
|
if (!x) return
|
|
connect(x.id)
|
|
}}
|
|
>
|
|
{(i) => (
|
|
<div class="w-full flex items-center gap-x-3">
|
|
<ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
|
|
<span>{i.name}</span>
|
|
<Show when={i.id === "opencode"}>
|
|
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
|
|
</Show>
|
|
<Show when={i.id === "opencode"}>
|
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
|
</Show>
|
|
<Show when={i.id === "opencode-go"}>
|
|
<>
|
|
<div class="text-14-regular text-text-weak">
|
|
{language.t("dialog.provider.opencodeGo.tagline")}
|
|
</div>
|
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
|
</>
|
|
</Show>
|
|
<Show when={i.id === "anthropic"}>
|
|
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
|
|
</Show>
|
|
</div>
|
|
)}
|
|
</List>
|
|
<Button
|
|
variant="ghost"
|
|
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
|
|
icon="dot-grid"
|
|
onClick={all}
|
|
>
|
|
{language.t("dialog.provider.viewAll")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
)
|
|
}
|