| import { AlertTriangle } from "lucide-react" |
| import { useDashboard } from "@/lib/dashboard" |
| import { Label } from "@/components/ui/label" |
| import { Input } from "@/components/ui/input" |
| import { Button } from "@/components/ui/button" |
| import { Switch } from "@/components/ui/switch" |
| import { Separator } from "@/components/ui/separator" |
| import { cn } from "@/lib/utils" |
| import type { CompilerProvider } from "@/lib/types" |
|
|
| const PROVIDERS: { id: CompilerProvider; label: string; placeholder: string }[] = [ |
| { id: "replicate", label: "Replicate", placeholder: "r8_..." }, |
| { id: "openai", label: "OpenAI", placeholder: "sk-..." }, |
| { id: "anthropic", label: "Claude", placeholder: "sk-ant-..." }, |
| ] |
|
|
| const MODELS = { |
| openai: [ |
| { value: "gpt-5.5", label: "GPT-5.5" }, |
| { value: "gpt-5.4", label: "GPT-5.4" }, |
| { value: "gpt-5.4-mini", label: "GPT-5.4 mini" }, |
| ], |
| anthropic: [ |
| { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" }, |
| { value: "claude-opus-4-8", label: "Claude Opus 4.8" }, |
| { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5" }, |
| ], |
| } |
|
|
| export function SettingsPanel() { |
| const { |
| params, |
| setParam, |
| compiler, |
| setCompilerProvider, |
| setCompilerApiKey, |
| setCompilerModel, |
| } = useDashboard() |
| const activeProvider = PROVIDERS.find((provider) => provider.id === compiler.provider) ?? PROVIDERS[0] |
| const apiKey = |
| compiler.provider === "replicate" |
| ? compiler.replicateApiKey |
| : compiler.provider === "openai" |
| ? compiler.openaiApiKey |
| : compiler.anthropicApiKey |
| const model = |
| compiler.provider === "openai" |
| ? compiler.openaiModel |
| : compiler.provider === "anthropic" |
| ? compiler.anthropicModel |
| : "" |
| const modelProvider = |
| compiler.provider === "openai" || compiler.provider === "anthropic" |
| ? compiler.provider |
| : null |
|
|
| return ( |
| <div className="space-y-5"> |
| {/* webhook dispatch */} |
| <div className="space-y-3"> |
| <div className="flex items-center justify-between"> |
| <div> |
| <Label className="text-foreground">Webhook dispatch</Label> |
| <p className="mt-0.5 text-[0.6875rem] text-muted-foreground"> |
| Off by default — actions only simulate. |
| </p> |
| </div> |
| <Switch |
| checked={params.enableWebhooks} |
| onCheckedChange={(v) => setParam("enableWebhooks", v)} |
| /> |
| </div> |
| |
| <div |
| className={cn( |
| "transition-opacity", |
| params.enableWebhooks ? "opacity-100" : "pointer-events-none opacity-40", |
| )} |
| > |
| <Label htmlFor="webhook">Webhook URL</Label> |
| <Input |
| id="webhook" |
| value={params.webhookUrl} |
| onChange={(e) => setParam("webhookUrl", e.target.value)} |
| className="mt-1.5 font-mono text-xs" |
| placeholder="http://127.0.0.1:8123/api/webhook/example" |
| /> |
| </div> |
| |
| {params.enableWebhooks && ( |
| <div className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/5 px-3 py-2 text-[0.6875rem] text-warning reveal"> |
| <AlertTriangle className="mt-0.5 size-3.5 shrink-0" /> |
| Live POSTs are enabled. Matching rules will hit the URL above for real. |
| </div> |
| )} |
| </div> |
| |
| <Separator /> |
| |
| <div className="space-y-1.5"> |
| <div className="font-mono text-[0.625rem] uppercase tracking-[0.16em] text-muted-foreground"> |
| rule compiler |
| </div> |
| <div className="grid grid-cols-3 gap-1 rounded-lg border border-border bg-black/20 p-1"> |
| {PROVIDERS.map((provider) => ( |
| <Button |
| key={provider.id} |
| type="button" |
| size="sm" |
| variant={compiler.provider === provider.id ? "default" : "ghost"} |
| onClick={() => setCompilerProvider(provider.id)} |
| > |
| {provider.label} |
| </Button> |
| ))} |
| </div> |
| <div className="space-y-1.5"> |
| <Label htmlFor="compiler-api-key">{activeProvider.label} API key</Label> |
| <Input |
| id="compiler-api-key" |
| type="password" |
| value={apiKey} |
| onChange={(e) => setCompilerApiKey(compiler.provider, e.target.value)} |
| placeholder={activeProvider.placeholder} |
| className="font-mono text-xs" |
| /> |
| </div> |
| {modelProvider && ( |
| <div className="space-y-1.5"> |
| <Label htmlFor="compiler-model">{activeProvider.label} model</Label> |
| <select |
| id="compiler-model" |
| value={model} |
| onChange={(e) => setCompilerModel(modelProvider, e.target.value)} |
| className="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" |
| > |
| {MODELS[modelProvider].map((option) => ( |
| <option key={option.value} value={option.value}> |
| {option.label} |
| </option> |
| ))} |
| </select> |
| </div> |
| )} |
| <p className="text-[0.6875rem] text-muted-foreground"> |
| Keys pasted here are sent only when compiling. Space owners can also set provider |
| secrets as environment variables. |
| </p> |
| </div> |
| </div> |
| ) |
| } |
|
|