fix(app): disambiguate workspace models and polish new model picker (#58419)

This commit is contained in:
Aarav Sareen
2026-10-23 16:08:40 +05:30
committed by GitHub
Unverified
parent 79696ee8e0
commit b2a10e5477
2 changed files with 55 additions and 28 deletions
@@ -44,7 +44,7 @@
background: transparent;
}
/* The 24px manage button sits 2px from every edge of the 28px row. */
/* The 24px buttons sit 2px from every edge of the 28px row. */
[data-component="search-list"] [data-slot="text-input-v2-trailing"] {
margin-inline-end: 0;
@@ -53,13 +53,14 @@
}
}
[data-component="search-list"] [data-slot="text-input-v2-leading-icon"] {
display: none;
/* Like a model row: 6px inset, a 16px icon slot and a 6px gap, so the query starts where the model names do. */
[data-component="search-list"] [data-slot="search-list-search"] [data-slot="text-input-v2-value"] {
gap: 6px;
}
/* Row inset, check slot and gap: the query starts where the model names do. */
[data-component="search-list"] [data-slot="text-input-v2-input"] {
padding-inline-start: 22px;
[data-component="search-list"] [data-slot="search-list-search"] [data-slot="text-input-v2-leading-icon"] {
width: 22px;
padding-inline-start: 6px;
}
/* The stuck header's gradient replaces it; the list measures sections for it, and here they have no box. */
@@ -69,7 +70,7 @@
/* Arrow keys keep the active row clear of the stuck header. */
[data-component="search-list"] [data-slot="search-list-scroll"] > .scroll-view__viewport {
scroll-padding-top: 32px;
scroll-padding-top: 36px;
}
[data-component="search-list"] [data-slot="search-list-results"] {
@@ -91,10 +92,10 @@
*/
[data-component="search-list"] [data-slot="search-list-header"] {
height: auto;
padding-block: 12px 2px;
padding-inline: 28px 6px;
padding-block: 16px 2px;
padding-inline: 34px 6px;
background: var(--v2-background-bg-base);
color: var(--v2-text-text-muted);
color: var(--v2-text-text-faint);
font-size: 12px;
line-height: 18px;
@@ -141,17 +142,11 @@
}
}
[data-component="search-list"]
[data-slot="search-list-section"]:first-child
> [data-slot="search-list-header"] {
padding-top: 16px;
}
[data-component="search-list"] [data-slot="search-list-row"] {
justify-content: flex-start;
gap: 3px;
gap: 6px;
height: 28px;
padding-inline: 3px;
padding-inline: 6px 3px;
border-radius: 6px;
font-size: 13px;
font-weight: 440;
@@ -670,6 +670,14 @@ export function DialogSelectModelNext(props: { provider?: string; model?: ModelS
void dialog.show(() => <DialogManageModels />)
}
const location = useWorkspaceLocation()
const connect = async () => {
const { DialogConnectProvider } = await import("@/providers/connect/dialog")
// The model chosen after connecting applies to this composer.
void dialog.show(() => <DialogConnectProvider directory={location().directory} selection={props.model} />)
}
const triangle = createSafeTriangle({
flyout: () => (flyoutItem() && flyoutRef?.isConnected ? flyoutRef : undefined),
})
@@ -913,9 +921,12 @@ export function DialogSelectModelNext(props: { provider?: string; model?: ModelS
<span class="min-w-0 truncate">
<SearchHighlight text={item().name} />
</span>
{/* Favorites and Recent mix providers, so their rows name one. */}
{/* Favorites and Recent mix providers, so their rows name one, with its workspace when its header has one. */}
<Show when={row.model.group.title}>
<span class="min-w-0 shrink-[999] truncate text-v2-text-text-faint">{labels.name(item().provider)}</span>
<Show when={labels.workspace(item().provider)}>
{(workspace) => <Badge class="shrink-0">{workspace()}</Badge>}
</Show>
</Show>
<Show when={isFreeModel(item())}>
<Badge class="shrink-0">{language.t("model.tag.free")}</Badge>
@@ -964,14 +975,29 @@ export function DialogSelectModelNext(props: { provider?: string; model?: ModelS
onQuery={setSearch}
placeholder={language.t("dialog.model.search.placeholder")}
trailing={
<Tooltip placement="top" gutter={4} value={language.t("dialog.model.manage")} ignoreSafeArea={false}>
<IconButton
variant="ghost-muted"
icon={<Icon name="outline-sliders" size="small" />}
aria-label={language.t("dialog.model.manage")}
onClick={manage}
/>
</Tooltip>
<>
<Tooltip
placement="top"
gutter={4}
value={language.t("command.provider.connect")}
ignoreSafeArea={false}
>
<IconButton
variant="ghost-muted"
icon={<Icon name="plus" size="small" />}
aria-label={language.t("command.provider.connect")}
onClick={connect}
/>
</Tooltip>
<Tooltip placement="top" gutter={4} value={language.t("dialog.model.manage")} ignoreSafeArea={false}>
<IconButton
variant="ghost-muted"
icon={<Icon name="outline-sliders" size="small" />}
aria-label={language.t("dialog.model.manage")}
onClick={manage}
/>
</Tooltip>
</>
}
items={rows()}
key={(row) => row.id}
@@ -1044,7 +1070,13 @@ export function DialogSelectModelNext(props: { provider?: string; model?: ModelS
<Keybind keys={formatKeybindParts("mod+f", language.t)} variant="neutral" class="shrink-0" />
</div>
<div data-slot="menu-v2-separator" role="separator" />
<ModelDetails model={item()} provider={labels.name(item().provider)} compact standalone />
{/* The full name, "Workspace / Provider", where a workspace badge tells the connections apart. */}
<ModelDetails
model={item()}
provider={labels.workspace(item().provider) ? item().provider.name : labels.name(item().provider)}
compact
standalone
/>
</div>
)}
</Show>