Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
import { useState , useRef , useEffect , useCallback } from "react" ;
import {
Tooltip ,
TooltipTrigger ,
TooltipContent ,
} from "@/components/ui/tooltip" ;
2026-03-02 16:44:03 -06:00
import {
Dialog ,
DialogContent ,
DialogHeader ,
DialogTitle ,
DialogDescription ,
DialogFooter ,
} from "@/components/ui/dialog" ;
import { Button } from "@/components/ui/button" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
import { HelpCircle , ChevronDown , ChevronRight } from "lucide-react" ;
import { cn } from "../lib/utils" ;
2026-03-07 17:07:14 -08:00
import { AGENT_ROLE_LABELS } from "@paperclipai/shared" ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Help text for (?) tooltips ---- */
export const help : Record < string , string > = {
name : "Display name for this agent." ,
title : "Job title shown in the org chart." ,
role : "Organizational role. Determines position and capabilities." ,
reportsTo : "The agent this one reports to in the org hierarchy." ,
capabilities : "Describes what this agent can do. Shown in the org chart and used for task routing." ,
2026-03-07 18:50:25 -06:00
adapterType : "How this agent runs: local CLI (Claude/Codex/OpenCode), OpenClaw Gateway, spawned process, or generic HTTP webhook." ,
2026-03-20 07:04:41 -05:00
cwd : "Deprecated legacy working directory fallback for local adapters. Existing agents may still carry this value, but new configurations should use project workspaces instead." ,
2026-03-13 08:49:11 -05:00
promptTemplate : "Sent on every heartbeat. Keep this small and dynamic. Use it for current-task framing, not large static instructions. Supports {{ agent.id }}, {{ agent.name }}, {{ agent.role }} and other template variables." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
model : "Override the default model used by the adapter." ,
2026-02-20 10:32:32 -06:00
thinkingEffort : "Control model reasoning depth. Supported values vary by adapter/model." ,
2026-02-26 16:32:59 -06:00
chrome : "Enable Claude's Chrome integration by passing --chrome." ,
2026-03-26 08:43:27 -05:00
dangerouslySkipPermissions : "Run unattended by auto-approving adapter permission prompts when supported." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
dangerouslyBypassSandbox : "Run Codex without sandbox restrictions. Required for filesystem/network access." ,
search : "Enable Codex web search capability during runs." ,
2026-03-10 10:58:38 -05:00
workspaceStrategy : "How Paperclip should realize an execution workspace for this agent. Keep project_primary for normal cwd execution, or use git_worktree for issue-scoped isolated checkouts." ,
workspaceBaseRef : "Base git ref used when creating a worktree branch. Leave blank to use the resolved workspace ref or HEAD." ,
workspaceBranchTemplate : "Template for naming derived branches. Supports {{issue.identifier}}, {{issue.title}}, {{agent.name}}, {{project.id}}, {{workspace.repoRef}}, and {{slug}}." ,
worktreeParentDir : "Directory where derived worktrees should be created. Absolute, ~-prefixed, and repo-relative paths are supported." ,
runtimeServicesJson : "Optional workspace runtime service definitions. Use this for shared app servers, workers, or other long-lived companion processes attached to the workspace." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
maxTurnsPerRun : "Maximum number of agentic turns (tool calls) per heartbeat run." ,
command : "The command to execute (e.g. node, python)." ,
2026-03-05 15:24:20 +01:00
localCommand : "Override the path to the CLI command you want the adapter to call (e.g. /usr/local/bin/claude, codex, opencode)." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
args : "Command-line arguments, comma-separated." ,
2026-02-18 13:02:23 -06:00
extraArgs : "Extra CLI arguments for local adapters, comma-separated." ,
2026-02-19 15:44:05 -06:00
envVars : "Environment variables injected into the adapter process. Use plain values or secret references." ,
2026-03-13 08:49:11 -05:00
bootstrapPrompt : "Only sent when Paperclip starts a fresh session. Use this for stable setup guidance that should not be repeated on every heartbeat." ,
2026-03-10 10:58:38 -05:00
payloadTemplateJson : "Optional JSON merged into remote adapter request payloads before Paperclip adds its standard wake and workspace fields." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
webhookUrl : "The URL that receives POST requests when the agent is invoked." ,
heartbeatInterval : "Run this agent automatically on a timer. Useful for periodic tasks like checking for new work." ,
intervalSec : "Seconds between automatic heartbeat invocations." ,
timeoutSec : "Maximum seconds a run can take before being terminated. 0 means no timeout." ,
graceSec : "Seconds to wait after sending interrupt before force-killing the process." ,
2026-02-18 16:46:29 -06:00
wakeOnDemand : "Allow this agent to be woken by assignments, API calls, UI actions, or automated systems." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
cooldownSec : "Minimum seconds between consecutive heartbeat runs." ,
2026-02-20 12:50:34 -06:00
maxConcurrentRuns : "Maximum number of heartbeat runs that can execute simultaneously for this agent." ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
budgetMonthlyCents : "Monthly spending limit in cents. 0 means no limit." ,
} ;
export const adapterLabels : Record < string , string > = {
claude_local : "Claude (local)" ,
codex_local : "Codex (local)" ,
2026-03-08 16:43:34 +05:30
gemini_local : "Gemini CLI (local)" ,
2026-03-04 16:48:54 -06:00
opencode_local : "OpenCode (local)" ,
2026-03-07 08:59:29 -06:00
openclaw_gateway : "OpenClaw Gateway" ,
2026-03-05 06:31:22 -06:00
cursor : "Cursor (local)" ,
2026-03-28 01:34:48 +01:00
hermes_local : "Hermes Agent" ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
process : "Process" ,
http : "HTTP" ,
} ;
2026-03-07 17:07:14 -08:00
export const roleLabels = AGENT_ROLE_LABELS as Record < string , string > ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
/* ---- Primitive components ---- */
export function HintIcon ( { text } : { text : string } ) {
return (
< Tooltip >
< TooltipTrigger asChild >
< button type = "button" className = "inline-flex text-muted-foreground/50 hover:text-muted-foreground transition-colors" >
< HelpCircle className = "h-3 w-3" / >
< / button >
< / TooltipTrigger >
< TooltipContent side = "top" className = "max-w-xs" >
{ text }
< / TooltipContent >
< / Tooltip >
) ;
}
export function Field ( { label , hint , children } : { label : string ; hint? : string ; children : React.ReactNode } ) {
return (
< div >
< div className = "flex items-center gap-1.5 mb-1" >
< label className = "text-xs text-muted-foreground" > { label } < / label >
{ hint && < HintIcon text = { hint } / > }
< / div >
{ children }
< / div >
) ;
}
export function ToggleField ( {
label ,
hint ,
checked ,
onChange ,
2026-03-30 13:17:51 -05:00
toggleTestId ,
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} : {
label : string ;
hint? : string ;
checked : boolean ;
onChange : ( v : boolean ) = > void ;
2026-03-30 13:17:51 -05:00
toggleTestId? : string ;
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
} ) {
return (
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-1.5" >
< span className = "text-xs text-muted-foreground" > { label } < / span >
{ hint && < HintIcon text = { hint } / > }
< / div >
< button
2026-03-22 06:35:32 -05:00
data - slot = "toggle"
2026-03-30 13:17:51 -05:00
data - testid = { toggleTestId }
type = "button"
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = { cn (
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors" ,
checked ? "bg-green-600" : "bg-muted"
) }
onClick = { ( ) = > onChange ( ! checked ) }
>
< span
className = { cn (
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform" ,
checked ? "translate-x-4.5" : "translate-x-0.5"
) }
/ >
< / button >
< / div >
) ;
}
export function ToggleWithNumber ( {
label ,
hint ,
checked ,
onCheckedChange ,
number ,
onNumberChange ,
numberLabel ,
numberHint ,
numberPrefix ,
showNumber ,
} : {
label : string ;
hint? : string ;
checked : boolean ;
onCheckedChange : ( v : boolean ) = > void ;
number : number ;
onNumberChange : ( v : number ) = > void ;
numberLabel : string ;
numberHint? : string ;
numberPrefix? : string ;
showNumber : boolean ;
} ) {
return (
< div className = "space-y-2" >
< div className = "flex items-center justify-between gap-3" >
< div className = "flex items-center gap-1.5" >
< span className = "text-xs text-muted-foreground" > { label } < / span >
{ hint && < HintIcon text = { hint } / > }
< / div >
< button
2026-03-22 06:35:32 -05:00
data - slot = "toggle"
Extract AgentConfigForm and agent-config-primitives components
Shared primitives (Field, ToggleField, ToggleWithNumber, CollapsibleSection,
DraftInput, DraftTextarea, DraftNumberInput, HintIcon, help text, adapterLabels,
roleLabels) extracted into agent-config-primitives.tsx.
AgentConfigForm is a dual-mode form supporting both create (controlled values)
and edit (save-on-blur per field) modes, used by NewAgentDialog and AgentDetail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 20:07:26 -06:00
className = { cn (
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors shrink-0" ,
checked ? "bg-green-600" : "bg-muted"
) }
onClick = { ( ) = > onCheckedChange ( ! checked ) }
>
< span
className = { cn (
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform" ,
checked ? "translate-x-4.5" : "translate-x-0.5"
) }
/ >
< / button >
< / div >
{ showNumber && (
< div className = "flex items-center gap-1.5 text-xs text-muted-foreground" >
{ numberPrefix && < span > { numberPrefix } < / span > }
< input
type = "number"
className = "w-16 rounded-md border border-border px-2 py-0.5 bg-transparent outline-none text-xs font-mono text-center"
value = { number }
onChange = { ( e ) = > onNumberChange ( Number ( e . target . value ) ) }
/ >
< span > { numberLabel } < / span >
{ numberHint && < HintIcon text = { numberHint } / > }
< / div >
) }
< / div >
) ;
}
export function CollapsibleSection ( {
title ,
icon ,
open ,
onToggle ,
bordered ,
children ,
} : {
title : string ;
icon? : React.ReactNode ;
open : boolean ;
onToggle : ( ) = > void ;
bordered? : boolean ;
children : React.ReactNode ;
} ) {
return (
< div className = { cn ( bordered && "border-t border-border" ) } >
< button
className = "flex items-center gap-2 w-full px-4 py-2 text-xs font-medium text-muted-foreground hover:bg-accent/30 transition-colors"
onClick = { onToggle }
>
{ open ? < ChevronDown className = "h-3 w-3" / > : < ChevronRight className = "h-3 w-3" / > }
{ icon }
{ title }
< / button >
{ open && < div className = "px-4 pb-3" > { children } < / div > }
< / div >
) ;
}
export function AutoExpandTextarea ( {
value ,
onChange ,
onBlur ,
placeholder ,
minRows ,
} : {
value : string ;
onChange : ( v : string ) = > void ;
onBlur ? : ( ) = > void ;
placeholder? : string ;
minRows? : number ;
} ) {
const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
const rows = minRows ? ? 3 ;
const lineHeight = 20 ;
const minHeight = rows * lineHeight ;
const adjustHeight = useCallback ( ( ) = > {
const el = textareaRef . current ;
if ( ! el ) return ;
el . style . height = "auto" ;
el . style . height = ` ${ Math . max ( minHeight , el . scrollHeight ) } px ` ;
} , [ minHeight ] ) ;
useEffect ( ( ) = > { adjustHeight ( ) ; } , [ value , adjustHeight ] ) ;
return (
< textarea
ref = { textareaRef }
className = "w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40 resize-none overflow-hidden"
placeholder = { placeholder }
value = { value }
onChange = { ( e ) = > onChange ( e . target . value ) }
onBlur = { onBlur }
style = { { minHeight } }
/ >
) ;
}
/ * *
* Text input that manages internal draft state .
* Calls ` onCommit ` on blur ( and optionally on every change if ` immediate ` is set ) .
* /
export function DraftInput ( {
value ,
onCommit ,
immediate ,
className ,
. . . props
} : {
value : string ;
onCommit : ( v : string ) = > void ;
immediate? : boolean ;
className? : string ;
} & Omit < React.InputHTMLAttributes < HTMLInputElement > , "value" | "onChange" | "className" > ) {
const [ draft , setDraft ] = useState ( value ) ;
useEffect ( ( ) = > setDraft ( value ) , [ value ] ) ;
return (
< input
className = { className }
value = { draft }
onChange = { ( e ) = > {
setDraft ( e . target . value ) ;
if ( immediate ) onCommit ( e . target . value ) ;
} }
onBlur = { ( ) = > {
if ( draft !== value ) onCommit ( draft ) ;
} }
{ . . . props }
/ >
) ;
}
/ * *
* Auto - expanding textarea with draft state and blur - commit .
* /
export function DraftTextarea ( {
value ,
onCommit ,
immediate ,
placeholder ,
minRows ,
} : {
value : string ;
onCommit : ( v : string ) = > void ;
immediate? : boolean ;
placeholder? : string ;
minRows? : number ;
} ) {
const [ draft , setDraft ] = useState ( value ) ;
useEffect ( ( ) = > setDraft ( value ) , [ value ] ) ;
const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
const rows = minRows ? ? 3 ;
const lineHeight = 20 ;
const minHeight = rows * lineHeight ;
const adjustHeight = useCallback ( ( ) = > {
const el = textareaRef . current ;
if ( ! el ) return ;
el . style . height = "auto" ;
el . style . height = ` ${ Math . max ( minHeight , el . scrollHeight ) } px ` ;
} , [ minHeight ] ) ;
useEffect ( ( ) = > { adjustHeight ( ) ; } , [ draft , adjustHeight ] ) ;
return (
< textarea
ref = { textareaRef }
className = "w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40 resize-none overflow-hidden"
placeholder = { placeholder }
value = { draft }
onChange = { ( e ) = > {
setDraft ( e . target . value ) ;
if ( immediate ) onCommit ( e . target . value ) ;
} }
onBlur = { ( ) = > {
if ( draft !== value ) onCommit ( draft ) ;
} }
style = { { minHeight } }
/ >
) ;
}
/ * *
* Number input with draft state and blur - commit .
* /
export function DraftNumberInput ( {
value ,
onCommit ,
immediate ,
className ,
. . . props
} : {
value : number ;
onCommit : ( v : number ) = > void ;
immediate? : boolean ;
className? : string ;
} & Omit < React.InputHTMLAttributes < HTMLInputElement > , "value" | "onChange" | "className" | "type" > ) {
const [ draft , setDraft ] = useState ( String ( value ) ) ;
useEffect ( ( ) = > setDraft ( String ( value ) ) , [ value ] ) ;
return (
< input
type = "number"
className = { className }
value = { draft }
onChange = { ( e ) = > {
setDraft ( e . target . value ) ;
if ( immediate ) onCommit ( Number ( e . target . value ) || 0 ) ;
} }
onBlur = { ( ) = > {
const num = Number ( draft ) || 0 ;
if ( num !== value ) onCommit ( num ) ;
} }
{ . . . props }
/ >
) ;
}
2026-03-02 16:44:03 -06:00
/ * *
* "Choose" button that opens a dialog explaining the user must manually
* type the path due to browser security limitations .
* /
export function ChoosePathButton() {
const [ open , setOpen ] = useState ( false ) ;
return (
< >
< button
type = "button"
className = "inline-flex items-center rounded-md border border-border px-2 py-0.5 text-xs text-muted-foreground hover:bg-accent/50 transition-colors shrink-0"
onClick = { ( ) = > setOpen ( true ) }
>
Choose
< / button >
< Dialog open = { open } onOpenChange = { setOpen } >
< DialogContent >
< DialogHeader >
< DialogTitle > Specify path manually < / DialogTitle >
< DialogDescription >
Browser security blocks apps from reading full local paths via a file picker .
Copy the absolute path and paste it into the input .
< / DialogDescription >
< / DialogHeader >
< div className = "space-y-4 text-sm" >
< section className = "space-y-1.5" >
< p className = "font-medium" > macOS ( Finder ) < / p >
< ol className = "list-decimal space-y-1 pl-5 text-muted-foreground" >
< li > Find the folder in Finder . < / li >
< li > Hold < kbd > Option < / kbd > and right - click the folder . < / li >
< li > Click "Copy <folder name> as Pathname" . < / li >
< li > Paste the result into the path input . < / li >
< / ol >
< p className = "rounded-md bg-muted px-2 py-1 font-mono text-xs" >
/ U s e r s / y o u r n a m e / D o c u m e n t s / p r o j e c t
< / p >
< / section >
< section className = "space-y-1.5" >
< p className = "font-medium" > Windows ( File Explorer ) < / p >
< ol className = "list-decimal space-y-1 pl-5 text-muted-foreground" >
< li > Find the folder in File Explorer . < / li >
< li > Hold < kbd > Shift < / kbd > and right - click the folder . < / li >
< li > Click "Copy as path" . < / li >
< li > Paste the result into the path input . < / li >
< / ol >
< p className = "rounded-md bg-muted px-2 py-1 font-mono text-xs" >
C : \ Users \ yourname \ Documents \ project
< / p >
< / section >
< section className = "space-y-1.5" >
< p className = "font-medium" > Terminal fallback ( macOS / Linux ) < / p >
< ol className = "list-decimal space-y-1 pl-5 text-muted-foreground" >
< li > Run < code > cd / path / to / folder < / code > . < / li >
< li > Run < code > pwd < / code > . < / li >
< li > Copy the output and paste it into the path input . < / li >
< / ol >
< / section >
< / div >
< DialogFooter >
< Button variant = "outline" onClick = { ( ) = > setOpen ( false ) } >
OK
< / Button >
< / DialogFooter >
< / DialogContent >
< / Dialog >
< / >
) ;
}
/ * *
* Label + input rendered on the same line ( inline layout for compact fields ) .
* /
export function InlineField ( { label , hint , children } : { label : string ; hint? : string ; children : React.ReactNode } ) {
return (
< div className = "flex items-center gap-3" >
< div className = "flex items-center gap-1.5 shrink-0" >
< label className = "text-xs text-muted-foreground" > { label } < / label >
{ hint && < HintIcon text = { hint } / > }
< / div >
< div className = "w-24 ml-auto" > { children } < / div >
< / div >
) ;
}