shin / frontend /src /components /FieldControl.tsx
shinmentakezo07
feat: add usage tracking with live throughput rates and analytics dashboard
e8f2acc
Raw
History Blame Contribute Delete
5.89 kB
import { useState } from "react"
import { Info } from "lucide-react"
import type { AdminField } from "@/lib/types"
import { cn } from "@/lib/utils"
import { Badge } from "./ui/badge"
import { Checkbox } from "./ui/checkbox"
import { Input } from "./ui/input"
import { Label } from "./ui/label"
import { Select } from "./ui/select"
import { Textarea } from "./ui/textarea"
import { ModelCombobox } from "./ModelCombobox"
import { PoolEditor } from "./PoolEditor"
interface FieldControlProps {
field: AdminField
value: string
modelOptions: string[]
onValueChange: (key: string, value: string) => void
onMessage: (text: string, kind?: string) => void
}
export function FieldControl({
field,
value,
modelOptions,
onValueChange,
onMessage,
}: FieldControlProps) {
if (field.pool_supported) {
return (
<PoolEditor
value={value}
locked={field.locked}
keyCount={field.key_count ?? 0}
fieldKey={field.key}
onChange={(next) => onValueChange(field.key, next)}
onMessage={onMessage}
/>
)
}
switch (field.type) {
case "boolean":
return (
<Checkbox
id={`field-${field.key}`}
checked={value === "true"}
disabled={field.locked}
onCheckedChange={(checked) =>
onValueChange(field.key, checked === true ? "true" : "false")
}
/>
)
case "select":
return (
<Select
id={`field-${field.key}`}
value={value}
disabled={field.locked}
onChange={(event) => onValueChange(field.key, event.target.value)}
>
{field.options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
)
case "textarea":
return (
<Textarea
id={`field-${field.key}`}
value={value}
disabled={field.locked}
onChange={(event) => onValueChange(field.key, event.target.value)}
/>
)
case "model":
case "optional_model":
return (
<ModelCombobox
id={`field-${field.key}`}
value={value}
fieldType={field.type}
models={modelOptions}
disabled={field.locked}
onChange={(next) => onValueChange(field.key, next)}
/>
)
case "secret":
return (
<SecretInput
field={field}
value={value}
onValueChange={onValueChange}
/>
)
default:
return (
<Input
id={`field-${field.key}`}
type={field.type === "number" ? "number" : "text"}
value={value}
disabled={field.locked}
onChange={(event) => onValueChange(field.key, event.target.value)}
/>
)
}
}
function SecretInput({
field,
value,
onValueChange,
}: {
field: AdminField
value: string
onValueChange: (key: string, value: string) => void
}) {
const [visible, setVisible] = useState(false)
return (
<div className="relative">
<Input
id={`field-${field.key}`}
type={visible ? "text" : "password"}
value={value}
disabled={field.locked}
placeholder={
field.configured
? "Configured - enter a new value to replace"
: "Not configured"
}
autoComplete="off"
onChange={(event) => onValueChange(field.key, event.target.value)}
/>
<button
type="button"
className="absolute inset-y-0 right-2 flex items-center text-xs text-muted-foreground"
aria-label={visible ? "Hide API key" : "Show API key"}
onMouseDown={(event) => {
event.preventDefault()
setVisible((prev) => !prev)
}}
>
{visible ? "Hide" : "Show"}
</button>
</div>
)
}
interface FieldRowProps {
field: AdminField
value: string
modelOptions: string[]
onValueChange: (key: string, value: string) => void
onMessage: (text: string, kind?: string) => void
className?: string
}
export function FieldRow({
field,
value,
modelOptions,
onValueChange,
onMessage,
className,
}: FieldRowProps) {
const source = sourceText(field)
return (
<div
className={cn(
"flex flex-col gap-2.5 rounded-lg border bg-card/40 p-4 transition-colors hover:border-border/80",
className,
)}
data-key={field.key}
>
<div className="flex items-center justify-between gap-2">
<Label htmlFor={field.type === "boolean" ? undefined : `field-${field.key}`}>
<span className="text-sm font-medium">{field.label}</span>
</Label>
{source ? (
<Badge
variant="outline"
className="gap-1 px-1.5 py-0 text-[10px] font-medium text-muted-foreground"
>
{source}
</Badge>
) : null}
</div>
<FieldControl
field={field}
value={value}
modelOptions={modelOptions}
onValueChange={onValueChange}
onMessage={onMessage}
/>
{field.description ? (
<p className="flex items-start gap-1.5 text-xs text-muted-foreground">
<Info className="mt-0.5 size-3.5 shrink-0 opacity-70" />
<span>{field.description}</span>
</p>
) : null}
</div>
)
}
function sourceText(field: AdminField): string {
const labels: Record<string, string> = {
default: "default",
template: "template",
repo_env: "repo .env",
explicit_env_file: "FCC_ENV_FILE",
process: "process env",
}
const parts: string[] = []
const label = Object.prototype.hasOwnProperty.call(labels, field.source)
? labels[field.source]
: field.source
if (label) parts.push(label)
if (field.locked) parts.push("locked")
return parts.join(" ")
}