fix: recipe studio dialog combobox click-select + simplify model provider form
This commit is contained in:
parent
cbbccefcdf
commit
1de4b73244
5 changed files with 102 additions and 91 deletions
|
|
@ -162,20 +162,20 @@ function ComboboxContent({
|
|||
<ComboboxPrimitive.Positioner
|
||||
side={side}
|
||||
sideOffset={sideOffset}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
anchor={anchor}
|
||||
className="isolate z-50"
|
||||
>
|
||||
<ComboboxPrimitive.Popup
|
||||
data-slot="combobox-content"
|
||||
data-chips={!!anchor}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 shadow-border ring-1 ring-border *:data-[slot=input-group]:bg-input/30 max-h-72 min-w-36 overflow-hidden rounded-xl corner-squircle duration-100 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-9 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:shadow-none group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) data-[chips=true]:min-w-(--anchor-width)",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
anchor={anchor}
|
||||
className="isolate z-[120] pointer-events-auto"
|
||||
>
|
||||
<ComboboxPrimitive.Popup
|
||||
data-slot="combobox-content"
|
||||
data-chips={!!anchor}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 shadow-border ring-1 ring-border *:data-[slot=input-group]:bg-input/30 max-h-72 min-w-36 overflow-hidden rounded-xl corner-squircle duration-100 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-9 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:shadow-none group/combobox-content relative pointer-events-auto max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) data-[chips=true]:min-w-(--anchor-width)",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ComboboxPrimitive.Positioner>
|
||||
</ComboboxPrimitive.Portal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,19 +14,6 @@ export function InlineModel(props: InlineModelProps): ReactElement {
|
|||
if (props.config.kind === "model_provider") {
|
||||
return (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<InlineField label="Provider type">
|
||||
<Input
|
||||
className="nodrag h-8 w-full text-xs"
|
||||
placeholder="openai-compatible"
|
||||
value={props.config.provider_type}
|
||||
onChange={(event) =>
|
||||
props.onUpdate({
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
provider_type: event.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Endpoint">
|
||||
<Input
|
||||
className="nodrag h-8 w-full text-xs"
|
||||
|
|
@ -35,6 +22,19 @@ export function InlineModel(props: InlineModelProps): ReactElement {
|
|||
onChange={(event) => props.onUpdate({ endpoint: event.target.value })}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="API key">
|
||||
<Input
|
||||
className="nodrag h-8 w-full text-xs"
|
||||
placeholder="Optional"
|
||||
value={props.config.api_key ?? ""}
|
||||
onChange={(event) =>
|
||||
props.onUpdate({
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
api_key: event.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export function LlmGeneralTab({
|
|||
/>
|
||||
<Textarea
|
||||
id={promptId}
|
||||
className="corner-squircle nodrag"
|
||||
className="corner-squircle nodrag max-h-[600px] overflow-auto"
|
||||
value={config.prompt}
|
||||
onChange={(event) => onUpdate({ prompt: event.target.value })}
|
||||
/>
|
||||
|
|
@ -177,7 +177,7 @@ export function LlmGeneralTab({
|
|||
/>
|
||||
<Textarea
|
||||
id={systemPromptId}
|
||||
className="corner-squircle nodrag"
|
||||
className="corner-squircle nodrag max-h-[600px] overflow-auto"
|
||||
value={config.system_prompt}
|
||||
onChange={(event) => onUpdate({ system_prompt: event.target.value })}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import type { ReactElement } from "react";
|
||||
import { ArrowDown01Icon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import type { ModelProviderConfig } from "../../types";
|
||||
import { FieldLabel } from "../shared/field-label";
|
||||
import { NameField } from "../shared/name-field";
|
||||
|
|
@ -14,8 +22,8 @@ export function ModelProviderDialog({
|
|||
config,
|
||||
onUpdate,
|
||||
}: ModelProviderDialogProps): ReactElement {
|
||||
const [optionalOpen, setOptionalOpen] = useState(false);
|
||||
const endpointId = `${config.id}-endpoint`;
|
||||
const providerTypeId = `${config.id}-provider-type`;
|
||||
const apiKeyEnvId = `${config.id}-api-key-env`;
|
||||
const apiKeyId = `${config.id}-api-key`;
|
||||
const extraHeadersId = `${config.id}-extra-headers`;
|
||||
|
|
@ -33,22 +41,6 @@ export function ModelProviderDialog({
|
|||
value={config.name}
|
||||
onChange={(value) => onUpdate({ name: value })}
|
||||
/>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="Provider type"
|
||||
htmlFor={providerTypeId}
|
||||
hint="Provider adapter type, e.g. openai or openrouter."
|
||||
/>
|
||||
<Input
|
||||
id={providerTypeId}
|
||||
className="nodrag"
|
||||
placeholder="openai"
|
||||
value={config.provider_type}
|
||||
onChange={(event) =>
|
||||
updateField("provider_type", event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="Endpoint"
|
||||
|
|
@ -63,20 +55,6 @@ export function ModelProviderDialog({
|
|||
onChange={(event) => updateField("endpoint", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="API key env (optional)"
|
||||
htmlFor={apiKeyEnvId}
|
||||
hint="Env var name to read secret key from runtime."
|
||||
/>
|
||||
<Input
|
||||
id={apiKeyEnvId}
|
||||
className="nodrag"
|
||||
placeholder="OPENAI_API_KEY"
|
||||
value={config.api_key_env ?? ""}
|
||||
onChange={(event) => updateField("api_key_env", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="API key (optional)"
|
||||
|
|
@ -90,36 +68,69 @@ export function ModelProviderDialog({
|
|||
onChange={(event) => updateField("api_key", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="Extra headers (JSON)"
|
||||
htmlFor={extraHeadersId}
|
||||
hint="Optional request headers merged into every call."
|
||||
/>
|
||||
<Textarea
|
||||
id={extraHeadersId}
|
||||
className="corner-squircle nodrag"
|
||||
placeholder='{"X-Header": "value"}'
|
||||
value={config.extra_headers ?? ""}
|
||||
onChange={(event) =>
|
||||
updateField("extra_headers", event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="Extra body (JSON)"
|
||||
htmlFor={extraBodyId}
|
||||
hint="Optional payload fields merged into requests."
|
||||
/>
|
||||
<Textarea
|
||||
id={extraBodyId}
|
||||
className="corner-squircle nodrag"
|
||||
placeholder='{"key": "value"}'
|
||||
value={config.extra_body ?? ""}
|
||||
onChange={(event) => updateField("extra_body", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Collapsible open={optionalOpen} onOpenChange={setOptionalOpen}>
|
||||
<CollapsibleTrigger asChild={true}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full justify-between"
|
||||
>
|
||||
Optional
|
||||
<HugeiconsIcon
|
||||
icon={ArrowDown01Icon}
|
||||
strokeWidth={2}
|
||||
className={
|
||||
optionalOpen ? "rotate-180 transition-transform" : "transition-transform"
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="mt-3 space-y-4">
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="API key env (optional)"
|
||||
htmlFor={apiKeyEnvId}
|
||||
hint="Env var name to read secret key from runtime."
|
||||
/>
|
||||
<Input
|
||||
id={apiKeyEnvId}
|
||||
className="nodrag"
|
||||
placeholder="OPENAI_API_KEY"
|
||||
value={config.api_key_env ?? ""}
|
||||
onChange={(event) => updateField("api_key_env", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="Extra headers (JSON)"
|
||||
htmlFor={extraHeadersId}
|
||||
hint="Optional request headers merged into every call."
|
||||
/>
|
||||
<Textarea
|
||||
id={extraHeadersId}
|
||||
className="corner-squircle nodrag"
|
||||
placeholder='{"X-Header": "value"}'
|
||||
value={config.extra_headers ?? ""}
|
||||
onChange={(event) => updateField("extra_headers", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<FieldLabel
|
||||
label="Extra body (JSON)"
|
||||
htmlFor={extraBodyId}
|
||||
hint="Optional payload fields merged into requests."
|
||||
/>
|
||||
<Textarea
|
||||
id={extraBodyId}
|
||||
className="corner-squircle nodrag"
|
||||
placeholder='{"key": "value"}'
|
||||
value={config.extra_body ?? ""}
|
||||
onChange={(event) => updateField("extra_body", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function buildModelProvider(
|
|||
name: config.name,
|
||||
endpoint: config.endpoint,
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
provider_type: config.provider_type,
|
||||
provider_type: "openai",
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
api_key_env: config.api_key_env?.trim() || undefined,
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue