mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 12:10:45 +03:00
fix(app): show free models alongside their paid twins by default (#55712)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
543b42ec2c
commit
a70eaa759b
@@ -17,6 +17,9 @@ function modelKey(model: ModelKey) {
|
||||
return `${model.providerID}:${model.modelID}`
|
||||
}
|
||||
|
||||
export const isFreeModel = (model: { provider: { id: string }; cost?: { input: number } }) =>
|
||||
model.provider.id === "opencode" && (!model.cost || model.cost.input === 0)
|
||||
|
||||
const createModelsController = (directory: Accessor<string | undefined>) => {
|
||||
const providers = useProviders(() => directory())
|
||||
const models = useGlobal().models
|
||||
@@ -54,7 +57,8 @@ const createModelsController = (directory: Accessor<string | undefined>) => {
|
||||
mapValues((models) =>
|
||||
pipe(
|
||||
models,
|
||||
groupBy((x) => x.family),
|
||||
// A free model shares its family and release date with its paid twin, so group them apart.
|
||||
groupBy((x) => `${x.family}:${isFreeModel(x)}`),
|
||||
values(),
|
||||
(groups) =>
|
||||
groups.flatMap((g) => {
|
||||
@@ -161,7 +165,7 @@ const createModelsController = (directory: Accessor<string | undefined>) => {
|
||||
export const { use: useModels, provider: ModelsProvider } = createSimpleContext({
|
||||
name: "Models",
|
||||
gate: false,
|
||||
init: (props: { directory?: string | Accessor<string | undefined> } = {}) => {
|
||||
return createModelsController(() => (typeof props.directory === "function" ? props.directory() : props.directory))
|
||||
init: (props: { directory?: string } = {}) => {
|
||||
return createModelsController(() => props.directory)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Component, ComponentProps, createEffect, createMemo, For, JSX, Show, Su
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { useLocal, type ModelSelection } from "@/providers/models/selection"
|
||||
import { isFreeModel } from "@/providers/models/models"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { popularProviders } from "@/providers/catalog/providers"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
@@ -40,9 +41,6 @@ const MobilePanelDrawer = lazy(async () => {
|
||||
return { default: MobilePanelDrawer }
|
||||
})
|
||||
|
||||
const isFree = (provider: string, cost: { input: number } | undefined) =>
|
||||
provider === "opencode" && (!cost || cost.input === 0)
|
||||
|
||||
type ModelState = ModelSelection
|
||||
|
||||
type ModelItem = ReturnType<ModelState["list"]>[number]
|
||||
@@ -158,12 +156,12 @@ const ModelList: Component<{
|
||||
gutter={12}
|
||||
openDelay={0}
|
||||
value={
|
||||
<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} v2 />
|
||||
<ModelTooltip model={item} latest={item.latest} free={isFreeModel(item)} v2 />
|
||||
}
|
||||
>
|
||||
<span class="min-w-0 truncate">{item.name}</span>
|
||||
</Tooltip>
|
||||
<Show when={isFree(item.provider.id, item.cost)}>
|
||||
<Show when={isFreeModel(item)}>
|
||||
<Badge class="shrink-0">{language.t("model.tag.free")}</Badge>
|
||||
</Show>
|
||||
<Show when={item.latest}>
|
||||
@@ -728,7 +726,7 @@ export function ModelSelectorPopoverView(props: {
|
||||
<ModelTooltip
|
||||
model={item}
|
||||
latest={item.latest}
|
||||
free={isFree(item.provider.id, item.cost)}
|
||||
free={isFreeModel(item)}
|
||||
v2
|
||||
/>
|
||||
}
|
||||
@@ -746,7 +744,7 @@ export function ModelSelectorPopoverView(props: {
|
||||
onSelect={() => selectModel(item)}
|
||||
>
|
||||
<span class="min-w-0 truncate leading-5">{item.name}</span>
|
||||
<Show when={isFree(item.provider.id, item.cost)}>
|
||||
<Show when={isFreeModel(item)}>
|
||||
<Badge class="shrink-0">{language.t("model.tag.free")}</Badge>
|
||||
</Show>
|
||||
<Show when={item.latest}>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { useTheme } from "@opencode/ui/theme"
|
||||
import { createMemo, onCleanup, onMount, type Component, For, Show } from "solid-js"
|
||||
import { useLocal, type ModelSelection } from "@/providers/models/selection"
|
||||
import { isFreeModel } from "@/providers/models/models"
|
||||
import { useIntegrations } from "@/providers/catalog/integrations"
|
||||
import { decode64 } from "@/runtime/persistence/base64"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
@@ -34,13 +35,10 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
||||
return c ? `${c.provider.id}:${c.id}` : undefined
|
||||
})
|
||||
|
||||
const isFree = (item: ReturnType<ModelState["list"]>[number]) =>
|
||||
item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)
|
||||
|
||||
const providerName = (provider: { id: string; name: string }) =>
|
||||
provider.id === "opencode" ? language.t("provider.connect.opencode.name") : provider.name
|
||||
|
||||
const freeModels = createMemo(() => model.list().filter(isFree))
|
||||
const freeModels = createMemo(() => model.list().filter(isFreeModel))
|
||||
|
||||
const openProviders = (provider?: string) => {
|
||||
void import("@/providers/connect/dialog").then((x) => {
|
||||
@@ -66,7 +64,7 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
||||
const buttons = Array.from(listEl.querySelectorAll<HTMLButtonElement>("button"))
|
||||
|
||||
if (buttons.length === 0) return
|
||||
const index = buttons.indexOf(document.activeElement as HTMLButtonElement)
|
||||
const index = buttons.findIndex((button) => button === document.activeElement)
|
||||
|
||||
const next =
|
||||
index < 0 ? (e.key === "ArrowDown" ? 0 : buttons.length - 1) : index + (e.key === "ArrowDown" ? 1 : -1)
|
||||
@@ -108,7 +106,7 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
||||
<ModelTooltip
|
||||
model={{ ...item, name: displayModelName(item.name) }}
|
||||
latest={item.latest}
|
||||
free={isFree(item)}
|
||||
free={isFreeModel(item)}
|
||||
v2
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function TargetSessionRouteContent() {
|
||||
return (
|
||||
<>
|
||||
<MarkSessionViewed sessionID={() => params.id} />
|
||||
<ModelsProvider directory={directory}>
|
||||
<ModelsProvider directory={directory()}>
|
||||
<TargetSessionSettingsCommand />
|
||||
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)}>
|
||||
<Show when={tabs.pendingSession(server.key, params.id)} fallback={<ResolvedTargetSessionRoute />}>
|
||||
|
||||
Reference in New Issue
Block a user