refactor: replace inline labels with reusable FieldLabel component in dialogs

This commit is contained in:
Shine1i 2026-02-22 02:30:04 +01:00
commit 63c1b95f20
25 changed files with 329 additions and 323 deletions

View file

@ -73,13 +73,13 @@ export const BLOCK_GROUPS: BlockGroup[] = [
{
kind: "sampler",
title: "Samplers",
description: "Numeric + categorical blocks.",
description: "Fast deterministic columns from distributions and categories.",
icon: DiceFaces03Icon,
},
{
kind: "seed",
title: "Seed",
description: "Columns from a seed dataset.",
description: "Bootstrap generation from an existing dataset.",
icon: Plant01Icon,
},
{
@ -91,7 +91,7 @@ export const BLOCK_GROUPS: BlockGroup[] = [
{
kind: "expression",
title: "Expression",
description: "Derived columns with Jinja.",
description: "Derive columns with Jinja templates.",
icon: FunctionIcon,
},
];
@ -101,7 +101,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "seed",
type: "seed",
title: "Seed (Hugging Face)",
description: "Configure a HF seed dataset.",
description: "Load real rows from HF and use them as generation context.",
icon: Plant01Icon,
dialogKey: "seed",
createConfig: (id, existing) => makeSeedConfig(id, existing),
@ -110,7 +110,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "category",
title: "Category",
description: "Pick from a list of values.",
description: "Define categorical values with optional weights and conditions.",
icon: Tag01Icon,
dialogKey: "category",
createConfig: (id, existing) => makeSamplerConfig(id, "category", existing),
@ -119,7 +119,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "subcategory",
title: "Subcategory",
description: "Map sub-values to a category.",
description: "Define hierarchical values mapped to a parent category.",
icon: TagsIcon,
dialogKey: "subcategory",
createConfig: (id, existing) => makeSamplerConfig(id, "subcategory", existing),
@ -128,7 +128,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "uniform",
title: "Uniform",
description: "Random number between low/high.",
description: "Sample evenly between low and high.",
icon: EqualSignIcon,
dialogKey: "uniform",
createConfig: (id, existing) => makeSamplerConfig(id, "uniform", existing),
@ -137,7 +137,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "gaussian",
title: "Gaussian",
description: "Normal distribution sampler.",
description: "Sample from a normal distribution (mean/stddev).",
icon: Parabola02Icon,
dialogKey: "gaussian",
createConfig: (id, existing) => makeSamplerConfig(id, "gaussian", existing),
@ -146,7 +146,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "bernoulli",
title: "Bernoulli",
description: "Binary sampler with probability.",
description: "Sample binary outcomes from probability p.",
icon: EqualSignIcon,
dialogKey: "bernoulli",
createConfig: (id, existing) => makeSamplerConfig(id, "bernoulli", existing),
@ -155,7 +155,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "datetime",
title: "Datetime",
description: "Date/time range sampler.",
description: "Sample timestamps within a start/end range.",
icon: Clock01Icon,
dialogKey: "datetime",
createConfig: (id, existing) => makeSamplerConfig(id, "datetime", existing),
@ -164,7 +164,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "timedelta",
title: "Timedelta",
description: "Offset from datetime column.",
description: "Sample time offsets from a reference datetime column.",
icon: Clock01Icon,
dialogKey: "timedelta",
createConfig: (id, existing) => makeSamplerConfig(id, "timedelta", existing),
@ -173,7 +173,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "uuid",
title: "UUID",
description: "UUID string sampler.",
description: "Generate unique identifiers with optional formatting.",
icon: FingerPrintIcon,
dialogKey: "uuid",
createConfig: (id, existing) => makeSamplerConfig(id, "uuid", existing),
@ -182,7 +182,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "person",
title: "Person",
description: "Faker person sampler.",
description: "Generate realistic synthetic people with faker attributes.",
icon: UserAccountIcon,
dialogKey: "person",
createConfig: (id, existing) => makeSamplerConfig(id, "person", existing),
@ -191,7 +191,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "text",
title: "LLM Text",
description: "Free-form prompt generation.",
description: "Generate natural language text from prompt templates.",
icon: PencilEdit02Icon,
dialogKey: "llm",
createConfig: (id, existing) => makeLlmConfig(id, "text", existing),
@ -200,7 +200,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "structured",
title: "LLM Structured",
description: "JSON output via schema.",
description: "Generate JSON constrained to a schema.",
icon: CodeIcon,
dialogKey: "llm",
createConfig: (id, existing) => makeLlmConfig(id, "structured", existing),
@ -209,7 +209,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "code",
title: "LLM Code",
description: "Generate code or SQL.",
description: "Generate code in a chosen language with clean extraction.",
icon: CodeSimpleIcon,
dialogKey: "llm",
createConfig: (id, existing) => makeLlmConfig(id, "code", existing),
@ -218,7 +218,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "judge",
title: "LLM Judge",
description: "Score outputs with criteria.",
description: "Score generated outputs with rubric-based criteria.",
icon: BalanceScaleIcon,
dialogKey: "llm",
createConfig: (id, existing) => makeLlmConfig(id, "judge", existing),
@ -227,7 +227,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "model_provider",
title: "Model Provider",
description: "Endpoint, auth, and provider settings.",
description: "Define endpoint and auth settings for model access.",
icon: Shield02Icon,
dialogKey: "model_provider",
createConfig: (id, existing) => makeModelProviderConfig(id, existing),
@ -236,7 +236,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "model_config",
title: "Model Config",
description: "Alias, model, provider, and inference params.",
description: "Bind alias to model, provider, and inference settings.",
icon: Plant01Icon,
dialogKey: "model_config",
createConfig: (id, existing) => makeModelConfig(id, existing),
@ -245,7 +245,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "expression",
type: "expression",
title: "Expression",
description: "Transform columns with Jinja.",
description: "Transform/combine columns using Jinja expressions.",
icon: FunctionIcon,
dialogKey: "expression",
createConfig: (id, existing) => makeExpressionConfig(id, existing),

View file

@ -253,6 +253,7 @@ export function BlockSheet({
description={item.description}
isActive={index === 0}
onClick={() => {
setSheetOpen(false);
if (item.kind === "sampler") {
onAddSampler(item.type as SamplerType);
} else if (item.kind === "seed") {

View file

@ -365,7 +365,7 @@ function RecipeGraphNodeBase({
openConfig(id);
}}
>
Details
Configure
</Button>
</BaseNodeHeader>

View file

@ -9,6 +9,7 @@ import { Textarea } from "@/components/ui/textarea";
import type { ReactElement } from "react";
import type { ExpressionConfig, ExpressionDtype } from "../../types";
import { AvailableVariables } from "../shared/available-variables";
import { FieldLabel } from "../shared/field-label";
import { NameField } from "../shared/name-field";
const DTYPE_OPTIONS: ExpressionDtype[] = ["str", "int", "float", "bool"];
@ -38,12 +39,11 @@ export function ExpressionDialog({
onChange={(value) => onUpdate({ name: value })}
/>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Output type"
htmlFor={dtypeId}
>
Output type
</label>
hint="Cast expression output type in final dataset."
/>
<Select
value={config.dtype}
onValueChange={(value) =>
@ -63,12 +63,11 @@ export function ExpressionDialog({
</Select>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Expression (Jinja2)"
htmlFor={exprId}
>
Expression (Jinja2)
</label>
hint="Use Jinja to combine or transform existing columns."
/>
<Textarea
id={exprId}
className="corner-squircle nodrag"

View file

@ -8,6 +8,7 @@ import {
} from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";
import { type ReactElement, useState } from "react";
import { FieldLabel } from "./shared/field-label";
type ImportDialogProps = {
open: boolean;
@ -55,12 +56,11 @@ export function ImportDialog({
<DialogTitle>Import recipe</DialogTitle>
</DialogHeader>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="JSON payload"
htmlFor={payloadId}
>
JSON payload
</label>
hint="Paste exported recipe payload JSON."
/>
<Textarea
id={payloadId}
className="corner-squircle nodrag min-h-[220px]"

View file

@ -17,6 +17,7 @@ import { Textarea } from "@/components/ui/textarea";
import { type ReactElement, type RefObject } from "react";
import type { LlmConfig } from "../../types";
import { AvailableVariables } from "../shared/available-variables";
import { FieldLabel } from "../shared/field-label";
import { NameField } from "../shared/name-field";
const CODE_LANG_OPTIONS = [
@ -75,12 +76,11 @@ export function LlmGeneralTab({
</div>
)}
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Model alias"
htmlFor={modelAliasId}
>
Model alias
</label>
hint="Alias must match a Model Config block."
/>
<div ref={modelAliasAnchorRef}>
<Combobox
items={modelConfigAliases}
@ -117,12 +117,11 @@ export function LlmGeneralTab({
</div>
{config.llm_type === "code" && (
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Code language"
htmlFor={codeLangId}
>
Code language
</label>
hint="Target language for LLM code generation."
/>
<Select
value={config.code_lang ?? "python"}
onValueChange={(value) => onUpdate({ code_lang: value })}
@ -141,12 +140,11 @@ export function LlmGeneralTab({
</div>
)}
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Prompt"
htmlFor={promptId}
>
Prompt
</label>
hint="Jinja template. references other columns via {{ variable }}."
/>
<Textarea
id={promptId}
className="corner-squircle nodrag"
@ -156,12 +154,11 @@ export function LlmGeneralTab({
</div>
{config.llm_type === "structured" && (
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Output format (JSON schema)"
htmlFor={outputFormatId}
>
Output format (JSON schema)
</label>
hint="Schema used to constrain structured JSON output."
/>
<Textarea
id={outputFormatId}
className="corner-squircle nodrag"
@ -173,12 +170,11 @@ export function LlmGeneralTab({
</div>
)}
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="System prompt (optional)"
htmlFor={systemPromptId}
>
System prompt (optional)
</label>
hint="Global behavior instructions prepended before prompt."
/>
<Textarea
id={systemPromptId}
className="corner-squircle nodrag"

View file

@ -22,6 +22,7 @@ import {
toApiProvider,
} from "./mcp-tools/helpers";
import { ToolConfigsSection } from "./mcp-tools/tool-configs-section";
import { FieldLabel } from "../shared/field-label";
type LlmMcpToolsTabProps = {
config: LlmConfig;
@ -236,9 +237,10 @@ export function LlmMcpToolsTab({
return (
<div className="space-y-5">
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Active tool alias
</label>
<FieldLabel
label="Active tool alias"
hint="Tool alias selected here is used by this LLM column."
/>
{toolAliasOptions.length > 0 ? (
<Select
value={activeToolAlias}

View file

@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import type { LlmMcpProviderConfig, McpEnvVar } from "../../../types";
import { FieldLabel } from "../../shared/field-label";
type McpProvidersSectionProps = {
providers: LlmMcpProviderConfig[];
@ -44,9 +45,10 @@ export function McpProvidersSection({
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold uppercase text-muted-foreground">
MCP servers
</p>
<FieldLabel
label="MCP servers"
hint="Server definitions used by tool configs for tool calls."
/>
<Button type="button" size="xs" variant="outline" onClick={onAddProvider}>
<HugeiconsIcon icon={PlusSignIcon} className="size-3.5" />
Add MCP server
@ -72,9 +74,10 @@ export function McpProvidersSection({
className="space-y-3 border-b border-border/40 pb-4 last:border-b-0"
>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Name
</label>
<FieldLabel
label="Name"
hint="Unique provider name referenced by tool configs."
/>
<Input
value={provider.name}
placeholder="MCP server name"
@ -102,9 +105,10 @@ export function McpProvidersSection({
{provider.provider_type === "stdio" ? (
<div className="space-y-3">
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Command to launch
</label>
<FieldLabel
label="Command to launch"
hint="Executable used to start stdio MCP server."
/>
<Input
value={provider.command ?? ""}
placeholder="npx"
@ -117,9 +121,10 @@ export function McpProvidersSection({
</div>
<div className="space-y-2">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Arguments
</p>
<FieldLabel
label="Arguments"
hint="CLI args passed to MCP command."
/>
{args.map((arg, argIndex) => (
<div key={`${provider.id}-arg-${argIndex}`} className="flex gap-2">
<Input
@ -150,9 +155,10 @@ export function McpProvidersSection({
</div>
<div className="space-y-2">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Environment variables
</p>
<FieldLabel
label="Environment variables"
hint="Key/value env vars for MCP process."
/>
{envVars.map((item, envIndex) => (
<div
key={`${provider.id}-env-${envIndex}`}
@ -199,9 +205,10 @@ export function McpProvidersSection({
) : (
<div className="space-y-2">
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Endpoint
</label>
<FieldLabel
label="Endpoint"
hint="Streamable HTTP MCP server URL."
/>
<Input
value={provider.endpoint ?? ""}
placeholder="https://example.com/mcp"
@ -213,9 +220,10 @@ export function McpProvidersSection({
/>
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
API key env (optional)
</label>
<FieldLabel
label="API key env (optional)"
hint="Env var name for endpoint auth token."
/>
<Input
value={provider.api_key_env ?? ""}
placeholder="MCP_API_KEY"
@ -228,9 +236,10 @@ export function McpProvidersSection({
/>
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
API key (optional)
</label>
<FieldLabel
label="API key (optional)"
hint="Inline token for endpoint auth."
/>
<Input
value={provider.api_key ?? ""}
placeholder="api key"

View file

@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input";
import { ChipInput } from "../../../components/chip-input";
import type { LlmToolConfig } from "../../../types";
import { addUnique, collectToolSuggestions } from "./helpers";
import { FieldLabel } from "../../shared/field-label";
type ToolConfigsSectionProps = {
toolConfigs: LlmToolConfig[];
@ -30,9 +31,10 @@ export function ToolConfigsSection({
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Tool configs
</p>
<FieldLabel
label="Tool configs"
hint="Map a tool alias to MCP providers and allowed tools."
/>
<div className="flex items-center gap-2">
<Button
type="button"
@ -63,9 +65,10 @@ export function ToolConfigsSection({
className="space-y-3 border-b border-border/40 pb-4 last:border-b-0"
>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Tool alias
</label>
<FieldLabel
label="Tool alias"
hint="Alias referenced by LLM column tool_alias."
/>
<Input
value={toolConfig.tool_alias}
placeholder="context7_tools"
@ -79,9 +82,10 @@ export function ToolConfigsSection({
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Providers
</label>
<FieldLabel
label="Providers"
hint="MCP provider names this alias can call."
/>
<ChipInput
values={toolConfig.providers}
suggestions={providerNameSuggestions}
@ -102,9 +106,10 @@ export function ToolConfigsSection({
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Allow tools (optional)
</label>
<FieldLabel
label="Allow tools (optional)"
hint="Optional allowlist of tool names."
/>
<ChipInput
// biome-ignore lint/style/useNamingConvention: api schema
values={toolConfig.allow_tools ?? []}
@ -129,9 +134,10 @@ export function ToolConfigsSection({
<div className="grid grid-cols-2 gap-2">
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Max turns
</label>
<FieldLabel
label="Max turns"
hint="Max tool-calling turns before forcing completion."
/>
<Input
value={toolConfig.max_tool_call_turns ?? ""}
onChange={(event) =>
@ -143,9 +149,10 @@ export function ToolConfigsSection({
/>
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Timeout sec
</label>
<FieldLabel
label="Timeout sec"
hint="Timeout per tool call."
/>
<Input
value={toolConfig.timeout_sec ?? ""}
onChange={(event) =>

View file

@ -1,6 +1,7 @@
import { Button } from "@/components/ui/button";
import { type ReactElement } from "react";
import type { LlmConfig, Score } from "../../types";
import { FieldLabel } from "../shared/field-label";
type LlmScoresTabProps = {
config: LlmConfig;
@ -38,9 +39,10 @@ export function LlmScoresTab({
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Scorers
</p>
<FieldLabel
label="Scorers"
hint="Rubrics used by LLM Judge to score each generated row."
/>
<Button type="button" size="xs" variant="outline" onClick={addScore}>
Add scorer block
</Button>

View file

@ -10,6 +10,7 @@ import {
import { Input } from "@/components/ui/input";
import { type ReactElement, useRef } from "react";
import type { ModelConfig } from "../../types";
import { FieldLabel } from "../shared/field-label";
import { NameField } from "../shared/name-field";
type ModelConfigDialogProps = {
@ -49,12 +50,11 @@ export function ModelConfigDialog({
onChange={(value) => onUpdate({ name: value })}
/>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Model"
htmlFor={modelId}
>
Model
</label>
hint="Exact model id string sent to provider."
/>
<Input
id={modelId}
className="nodrag"
@ -64,12 +64,11 @@ export function ModelConfigDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Provider name"
htmlFor={providerId}
>
Provider name
</label>
hint="Must match a Model Provider block name."
/>
<div ref={providerAnchorRef}>
<Combobox
items={providerOptions}
@ -111,9 +110,10 @@ export function ModelConfigDialog({
</p>
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Inference
</label>
<FieldLabel
label="Inference"
hint="Runtime generation params for this model alias."
/>
<div className="grid grid-cols-3 gap-2">
<Input
id={tempId}

View file

@ -2,6 +2,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import type { ReactElement } from "react";
import type { ModelProviderConfig } from "../../types";
import { FieldLabel } from "../shared/field-label";
import { NameField } from "../shared/name-field";
type ModelProviderDialogProps = {
@ -33,12 +34,11 @@ export function ModelProviderDialog({
onChange={(value) => onUpdate({ name: value })}
/>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Provider type"
htmlFor={providerTypeId}
>
Provider type
</label>
hint="Provider adapter type, e.g. openai or openrouter."
/>
<Input
id={providerTypeId}
className="nodrag"
@ -50,12 +50,11 @@ export function ModelProviderDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Endpoint"
htmlFor={endpointId}
>
Endpoint
</label>
hint="Base API URL used for model requests."
/>
<Input
id={endpointId}
className="nodrag"
@ -65,12 +64,11 @@ export function ModelProviderDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="API key env (optional)"
htmlFor={apiKeyEnvId}
>
API key env (optional)
</label>
hint="Env var name to read secret key from runtime."
/>
<Input
id={apiKeyEnvId}
className="nodrag"
@ -80,12 +78,11 @@ export function ModelProviderDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="API key (optional)"
htmlFor={apiKeyId}
>
API key (optional)
</label>
hint="Inline key. prefer env var for safer configs."
/>
<Input
id={apiKeyId}
className="nodrag"
@ -94,12 +91,11 @@ export function ModelProviderDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Extra headers (JSON)"
htmlFor={extraHeadersId}
>
Extra headers (JSON)
</label>
hint="Optional request headers merged into every call."
/>
<Textarea
id={extraHeadersId}
className="corner-squircle nodrag"
@ -111,12 +107,11 @@ export function ModelProviderDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Extra body (JSON)"
htmlFor={extraBodyId}
>
Extra body (JSON)
</label>
hint="Optional payload fields merged into requests."
/>
<Textarea
id={extraBodyId}
className="corner-squircle nodrag"

View file

@ -8,6 +8,7 @@ import {
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { type ReactElement } from "react";
import { FieldLabel } from "./shared/field-label";
type PreviewDialogProps = {
open: boolean;
@ -78,12 +79,11 @@ export function PreviewDialog({
</div>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Number of records"
htmlFor="preview-rows"
>
Number of records
</label>
hint="Target rows for preview run."
/>
<Input
id="preview-rows"
type="number"

View file

@ -8,6 +8,7 @@ import { type ReactElement, useMemo } from "react";
import type { RecipeProcessorConfig } from "../types";
import { buildDefaultSchemaTransform } from "../utils/processors";
import { AvailableVariables } from "./shared/available-variables";
import { FieldLabel } from "./shared/field-label";
type ProcessorsDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
@ -90,12 +91,11 @@ export function ProcessorsDialog({
<div className="space-y-3">
<AvailableVariables configId="" />
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Name"
htmlFor={nameId}
>
Name
</label>
hint="Processor name shown in graph and payload."
/>
<Input
id={nameId}
className="nodrag"
@ -104,12 +104,11 @@ export function ProcessorsDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Template (JSON)"
htmlFor={templateId}
>
Template (JSON)
</label>
hint="Target output schema template using Jinja references."
/>
<Textarea
id={templateId}
className="corner-squircle nodrag min-h-[220px]"

View file

@ -2,6 +2,7 @@ import { Input } from "@/components/ui/input";
import type { ReactElement } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
type BernoulliDialogProps = {
config: SamplerConfig;
@ -20,12 +21,11 @@ export function BernoulliDialog({
onChange={(value) => onUpdate({ name: value })}
/>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Probability (p)"
htmlFor={pId}
>
Probability (p)
</label>
hint="Success probability in [0, 1]."
/>
<Input
id={pId}
type="number"

View file

@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input";
import { type ReactElement, useState } from "react";
import type { SamplerConfig } from "../../types";
import { ChipInput } from "../../components/chip-input";
import { FieldLabel } from "../shared/field-label";
import { NameField } from "../shared/name-field";
type CategoryDialogProps = {
@ -85,9 +86,10 @@ export function CategoryDialog({
/>
<div className="space-y-3">
<div className="grid gap-2">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Values
</p>
<FieldLabel
label="Values"
hint="Define allowed categorical values for this column."
/>
<ChipInput
values={config.values ?? []}
onAdd={(value) => {
@ -132,9 +134,10 @@ export function CategoryDialog({
</CollapsibleTrigger>
<CollapsibleContent className="space-y-3 border-t border-border/60 p-3">
<div className="grid gap-2">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Weights (optional)
</p>
<FieldLabel
label="Weights (optional)"
hint="Set selection probability per value."
/>
{(config.values ?? []).length === 0 ? (
<p className="text-xs text-muted-foreground">
Add values first, then set optional weights.
@ -165,9 +168,10 @@ export function CategoryDialog({
)}
</div>
<div className="flex items-center justify-between gap-2">
<p className="text-xs font-semibold uppercase text-muted-foreground">
Conditional params (category)
</p>
<FieldLabel
label="Conditional params (category)"
hint="Override category values/weights when condition matches."
/>
<span className="text-xs text-muted-foreground">
{conditionalCount} rules
</span>

View file

@ -9,6 +9,7 @@ import {
import type { ReactElement } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
const DATETIME_UNITS = [
"second",
@ -47,12 +48,11 @@ export function DatetimeDialog({
<div className="grid gap-3">
<div className="grid gap-2 sm:grid-cols-2">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Start"
htmlFor={startId}
>
Start
</label>
hint="Earliest datetime allowed."
/>
<Input
id={startId}
type="datetime-local"
@ -64,12 +64,11 @@ export function DatetimeDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="End"
htmlFor={endId}
>
End
</label>
hint="Latest datetime allowed."
/>
<Input
id={endId}
type="datetime-local"
@ -82,12 +81,11 @@ export function DatetimeDialog({
</div>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Unit"
htmlFor={unitId}
>
Unit
</label>
hint="Sampling granularity for generated timestamps."
/>
<Select
value={config.datetime_unit ?? ""}
onValueChange={(value) => updateField("datetime_unit", value)}

View file

@ -9,6 +9,7 @@ import {
import type { ReactElement } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
type GaussianDialogProps = {
config: SamplerConfig;
@ -30,12 +31,11 @@ export function GaussianDialog({
/>
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Mean"
htmlFor={meanId}
>
Mean
</label>
hint="Center of the normal distribution."
/>
<Input
id={meanId}
type="number"
@ -45,12 +45,11 @@ export function GaussianDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Std"
htmlFor={stdId}
>
Std
</label>
hint="Standard deviation. must be > 0."
/>
<Input
id={stdId}
type="number"
@ -61,12 +60,11 @@ export function GaussianDialog({
</div>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Convert to"
htmlFor={convertId}
>
Convert to
</label>
hint="Optionally cast sampled values before output."
/>
<Select
value={config.convert_to ?? "none"}
onValueChange={(value) =>

View file

@ -9,6 +9,7 @@ import {
import { type ReactElement, useEffect } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
type PersonDialogProps = {
config: SamplerConfig;
@ -55,12 +56,11 @@ export function PersonDialog({
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Locale"
htmlFor={localeId}
>
Locale
</label>
hint="Faker locale e.g. en_US."
/>
<Input
id={localeId}
className="nodrag"
@ -71,12 +71,11 @@ export function PersonDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Sex"
htmlFor={sexId}
>
Sex
</label>
hint="Optional demographic filter."
/>
<Select
value={config.person_sex?.trim() ? config.person_sex : "any"}
onValueChange={(value) =>
@ -94,12 +93,11 @@ export function PersonDialog({
</Select>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Age range"
htmlFor={ageRangeId}
>
Age range
</label>
hint="Range format: min-max, e.g. 18-70."
/>
<Input
id={ageRangeId}
className="nodrag"
@ -111,12 +109,11 @@ export function PersonDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="City"
htmlFor={cityId}
>
City
</label>
hint="Optional city bias for faker generation."
/>
<Input
id={cityId}
className="nodrag"

View file

@ -9,6 +9,7 @@ import { type ReactElement, useCallback, useEffect, useMemo } from "react";
import type { SamplerConfig } from "../../types";
import { ChipInput } from "../../components/chip-input";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
type SubcategoryDialogProps = {
config: SamplerConfig;
@ -71,12 +72,11 @@ export function SubcategoryDialog({
/>
<div className="space-y-3">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Parent category column"
htmlFor={parentSelectId}
>
Parent category column
</label>
hint="Category column this block maps from."
/>
<Select
value={config.subcategory_parent ?? ""}
onValueChange={(value) => {

View file

@ -9,6 +9,7 @@ import {
import type { ReactElement } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
const TIMEDELTA_UNITS: Array<"D" | "h" | "m" | "s"> = ["D", "h", "m", "s"];
const NONE_VALUE = "__none";
@ -42,12 +43,11 @@ export function TimedeltaDialog({
/>
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="dt_min"
htmlFor={dtMinId}
>
dt_min
</label>
hint="Minimum offset from reference datetime."
/>
<Input
id={dtMinId}
type="number"
@ -57,12 +57,11 @@ export function TimedeltaDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="dt_max"
htmlFor={dtMaxId}
>
dt_max
</label>
hint="Maximum offset from reference datetime."
/>
<Input
id={dtMaxId}
type="number"
@ -73,12 +72,11 @@ export function TimedeltaDialog({
</div>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Unit"
htmlFor={unitId}
>
Unit
</label>
hint="Offset unit. D/h/m/s."
/>
<Select
value={config.timedelta_unit ?? "D"}
onValueChange={(value) =>
@ -98,12 +96,11 @@ export function TimedeltaDialog({
</Select>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Reference datetime column"
htmlFor={referenceId}
>
Reference datetime column
</label>
hint="Datetime column used as anchor before offset."
/>
<Select
value={config.reference_column_name?.trim() || NONE_VALUE}
onValueChange={(value) =>

View file

@ -9,6 +9,7 @@ import {
import type { ReactElement } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
type UniformDialogProps = {
config: SamplerConfig;
@ -30,12 +31,11 @@ export function UniformDialog({
/>
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Low"
htmlFor={lowId}
>
Low
</label>
hint="Minimum sampled value."
/>
<Input
id={lowId}
type="number"
@ -45,12 +45,11 @@ export function UniformDialog({
/>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="High"
htmlFor={highId}
>
High
</label>
hint="Maximum sampled value."
/>
<Input
id={highId}
type="number"
@ -61,12 +60,11 @@ export function UniformDialog({
</div>
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Convert to"
htmlFor={convertId}
>
Convert to
</label>
hint="Optionally cast sampled values before output."
/>
<Select
value={config.convert_to ?? "none"}
onValueChange={(value) =>

View file

@ -2,6 +2,7 @@ import { Input } from "@/components/ui/input";
import type { ReactElement } from "react";
import type { SamplerConfig } from "../../types";
import { NameField } from "../shared/name-field";
import { FieldLabel } from "../shared/field-label";
type UuidDialogProps = {
config: SamplerConfig;
@ -26,12 +27,11 @@ export function UuidDialog({
onChange={(value) => onUpdate({ name: value })}
/>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="UUID format (optional)"
htmlFor={uuidId}
>
UUID format (optional)
</label>
hint="Optional formatter e.g. prefix:, short, uppercase."
/>
<Input
id={uuidId}
className="nodrag"

View file

@ -34,6 +34,7 @@ import type {
SeedSamplingStrategy,
SeedSelectionType,
} from "../../types";
import { FieldLabel } from "../shared/field-label";
const SAMPLING_OPTIONS: Array<{ value: SeedSamplingStrategy; label: string }> = [
{ value: "ordered", label: "Ordered" },
@ -125,12 +126,11 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
<TabsContent value="config" className="pt-3">
<div className="space-y-4">
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="HF dataset URL"
htmlFor={urlId}
>
HF dataset URL
</label>
hint="Dataset URL or org/repo used to bootstrap seed columns."
/>
<div className="flex items-center gap-2">
<Input
id={urlId}
@ -159,12 +159,11 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
{(config.seed_splits?.length ?? 0) > 0 && (
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Split"
htmlFor={splitId}
>
Split
</label>
hint="Dataset split to sample from (train/validation/test)."
/>
<Select
value={config.hf_split ?? ""}
onValueChange={(value) => {
@ -187,12 +186,11 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
)}
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="HF path (auto)"
htmlFor={pathId}
>
HF path (auto)
</label>
hint="Resolved dataset file path/pattern."
/>
<Input
id={pathId}
className="nodrag"
@ -203,12 +201,11 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="HF token (optional)"
htmlFor={tokenId}
>
HF token (optional)
</label>
hint="Optional private dataset access token."
/>
<Input
id={tokenId}
className="nodrag"
@ -219,12 +216,11 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Sampling strategy"
htmlFor={samplingId}
>
Sampling strategy
</label>
hint="Ordered keeps row order. shuffle randomizes sampled rows."
/>
<Select
value={config.sampling_strategy}
onValueChange={(value) =>
@ -245,12 +241,11 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
</div>
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Selection strategy"
htmlFor={selectionId}
>
Selection strategy
</label>
hint="Select all, a row range, or partition block."
/>
<Select
value={config.selection_type}
onValueChange={(value) =>
@ -273,9 +268,10 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
{config.selection_type === "index_range" && (
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Start
</label>
<FieldLabel
label="Start"
hint="Inclusive start row index for index_range."
/>
<Input
className="nodrag"
inputMode="numeric"
@ -284,9 +280,10 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
/>
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
End
</label>
<FieldLabel
label="End"
hint="Inclusive end row index for index_range."
/>
<Input
className="nodrag"
inputMode="numeric"
@ -300,9 +297,10 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
{config.selection_type === "partition_block" && (
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Index
</label>
<FieldLabel
label="Index"
hint="Partition index to load."
/>
<Input
className="nodrag"
inputMode="numeric"
@ -311,9 +309,10 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
/>
</div>
<div className="grid gap-2">
<label className="text-xs font-semibold uppercase text-muted-foreground">
Partitions
</label>
<FieldLabel
label="Partitions"
hint="Total number of partitions."
/>
<Input
className="nodrag"
inputMode="numeric"

View file

@ -1,27 +1,32 @@
import { Input } from "@/components/ui/input";
import { type ReactElement, useId } from "react";
import { FieldLabel } from "./field-label";
type NameFieldProps = {
id?: string;
value: string;
onChange: (value: string) => void;
hint?: string;
};
export function NameField({
id,
value,
onChange,
hint,
}: NameFieldProps): ReactElement {
const fallbackId = useId();
const inputId = id ?? fallbackId;
return (
<div className="grid gap-2">
<label
className="text-xs font-semibold uppercase text-muted-foreground"
<FieldLabel
label="Column name"
htmlFor={inputId}
>
Column name
</label>
hint={
hint ??
"Unique field name used in templates and final dataset output."
}
/>
<Input
id={inputId}
className="nodrag"