Surface extended sampler knobs in chat settings panel (PR #5711)
Adds an "Advanced Sampling" collapsible section under the existing Sampling block. Renders the 27 extended sampler fields plumbed earlier in this PR (typical_p, top_n_sigma, mirostat family, dynatemp, top_a, DRY chain, XTC, min_keep, ignore_eos, min_tokens, vLLM output knobs, truncate_prompt_tokens, n_keep, n_probs, cache_prompt, llama.cpp debug flags) using the same ParamSlider / Switch primitives as the core knobs. Each control is gated on the corresponding providerCapabilities flag so the section only appears for backends that accept the knob (local llama_cpp / custom: full set; vLLM: OAI subset + 4 output knobs; Ollama: dropped per the OAI translator; OpenRouter: top_a only; SaaS providers: section hidden entirely). The DRY chain's 3 child fields and the XTC threshold are additionally hidden until their master switch is non-zero, matching the upstream skip-when-disabled rule the chat-adapter already enforces. Nullable-number fields render at their upstream-disabled sentinel (1.0 for typ_p, -1 for top_n_sigma, 0 for repeat_last_n, etc.) and collapse back to null on the wire when the user moves them to that sentinel, so the adapter omits the field entirely. Nullable booleans render at the upstream default and only forward the non-default value (cachePrompt / skipSpecialTokens / spacesBetweenSpecialTokens default on and store null when on; ignoreEos / returnTokens / timingsPerToken / postSamplingProbs / includeStopStrInOutput default off and store null when off).
This commit is contained in:
parent
b60b0740c2
commit
319a95796c
1 changed files with 536 additions and 0 deletions
|
|
@ -444,6 +444,44 @@ export function ChatSettingsPanel({
|
|||
const showParallelToolCalls = isExternalModel
|
||||
? Boolean(providerCapabilities?.parallelToolCalls)
|
||||
: localSamplerSupportsExtras;
|
||||
// Extended llama.cpp / vLLM / OpenRouter samplers. Same gate as the
|
||||
// core knobs above: external → providerCapabilities flag; local →
|
||||
// GGUF-only (safetensors transformers ignores these).
|
||||
const capAdv = (k: keyof ProviderCapabilities): boolean =>
|
||||
isExternalModel
|
||||
? Boolean(providerCapabilities?.[k])
|
||||
: localSamplerSupportsExtras;
|
||||
const advCaps = {
|
||||
typicalP: capAdv("typicalP"),
|
||||
topNSigma: capAdv("topNSigma"),
|
||||
repeatLastN: capAdv("repeatLastN"),
|
||||
dynatempRange: capAdv("dynatempRange"),
|
||||
dynatempExponent: capAdv("dynatempExponent"),
|
||||
mirostat: capAdv("mirostat"),
|
||||
mirostatTau: capAdv("mirostatTau"),
|
||||
mirostatEta: capAdv("mirostatEta"),
|
||||
topA: capAdv("topA"),
|
||||
dryMultiplier: capAdv("dryMultiplier"),
|
||||
dryBase: capAdv("dryBase"),
|
||||
dryAllowedLength: capAdv("dryAllowedLength"),
|
||||
dryPenaltyLastN: capAdv("dryPenaltyLastN"),
|
||||
xtcProbability: capAdv("xtcProbability"),
|
||||
xtcThreshold: capAdv("xtcThreshold"),
|
||||
minKeep: capAdv("minKeep"),
|
||||
ignoreEos: capAdv("ignoreEos"),
|
||||
minTokens: capAdv("minTokens"),
|
||||
skipSpecialTokens: capAdv("skipSpecialTokens"),
|
||||
spacesBetweenSpecialTokens: capAdv("spacesBetweenSpecialTokens"),
|
||||
includeStopStrInOutput: capAdv("includeStopStrInOutput"),
|
||||
truncatePromptTokens: capAdv("truncatePromptTokens"),
|
||||
nKeep: capAdv("nKeep"),
|
||||
nProbs: capAdv("nProbs"),
|
||||
cachePrompt: capAdv("cachePrompt"),
|
||||
returnTokens: capAdv("returnTokens"),
|
||||
timingsPerToken: capAdv("timingsPerToken"),
|
||||
postSamplingProbs: capAdv("postSamplingProbs"),
|
||||
};
|
||||
const showAdvancedSamplingSection = Object.values(advCaps).some(Boolean);
|
||||
// Per-provider stop cap from provider-capabilities.ts; backend
|
||||
// re-trims on the wire if a stale UI sends more than the upstream
|
||||
// accepts.
|
||||
|
|
@ -1498,6 +1536,504 @@ export function ChatSettingsPanel({
|
|||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{showAdvancedSamplingSection ? (
|
||||
<CollapsibleSection label="Advanced Sampling" defaultOpen={false}>
|
||||
<div className="flex flex-col gap-5 pt-1">
|
||||
{advCaps.typicalP ? (
|
||||
<ParamSlider
|
||||
label="Typical P"
|
||||
value={params.typicalP ?? 1}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
onChange={(v) =>
|
||||
set("typicalP")(v >= 1 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.typicalP == null || params.typicalP >= 1
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `typ_p`. Locally typical sampling. 1.0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.topNSigma ? (
|
||||
<ParamSlider
|
||||
label="Top N Sigma"
|
||||
value={params.topNSigma ?? -1}
|
||||
min={-1}
|
||||
max={5}
|
||||
step={0.1}
|
||||
onChange={(v) =>
|
||||
set("topNSigma")(v <= -1 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.topNSigma == null || params.topNSigma <= -1
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `top_n_sigma`. Sigma-based truncation. -1 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.repeatLastN ? (
|
||||
<ParamSlider
|
||||
label="Repeat Last N"
|
||||
value={params.repeatLastN ?? 0}
|
||||
min={-1}
|
||||
max={2048}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("repeatLastN")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.repeatLastN == null
|
||||
? "Off"
|
||||
: params.repeatLastN === -1
|
||||
? "Ctx"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `repeat_last_n`. Token window the repetition penalty considers. 0 = off, -1 = full context."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.dynatempRange ? (
|
||||
<ParamSlider
|
||||
label="Dynatemp Range"
|
||||
value={params.dynatempRange ?? 0}
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.1}
|
||||
onChange={(v) =>
|
||||
set("dynatempRange")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.dynatempRange == null || params.dynatempRange === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `dynatemp_range`. Dynamic temperature swing around base temperature. 0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.dynatempExponent ? (
|
||||
<ParamSlider
|
||||
label="Dynatemp Exponent"
|
||||
value={params.dynatempExponent ?? 1}
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.1}
|
||||
onChange={(v) => set("dynatempExponent")(v)}
|
||||
info="llama.cpp `dynatemp_exponent`. Curve exponent for dynamic temperature. Paired with Dynatemp Range."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.mirostat ? (
|
||||
<ParamSlider
|
||||
label="Mirostat"
|
||||
value={params.mirostat ?? 0}
|
||||
min={0}
|
||||
max={2}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("mirostat")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.mirostat == null || params.mirostat === 0
|
||||
? "Off"
|
||||
: params.mirostat === 1
|
||||
? "v1"
|
||||
: "v2"
|
||||
}
|
||||
info="llama.cpp `mirostat`. Target-entropy sampler. 0 = off, 1 = Mirostat v1, 2 = Mirostat v2."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.mirostatTau ? (
|
||||
<ParamSlider
|
||||
label="Mirostat Tau"
|
||||
value={params.mirostatTau ?? 5}
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.1}
|
||||
onChange={(v) => set("mirostatTau")(v)}
|
||||
info="llama.cpp `mirostat_tau`. Target entropy. Higher = more diverse."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.mirostatEta ? (
|
||||
<ParamSlider
|
||||
label="Mirostat Eta"
|
||||
value={params.mirostatEta ?? 0.1}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
onChange={(v) => set("mirostatEta")(v)}
|
||||
info="llama.cpp `mirostat_eta`. Learning rate for the entropy controller."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.topA ? (
|
||||
<ParamSlider
|
||||
label="Top A"
|
||||
value={params.topA ?? 0}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
onChange={(v) =>
|
||||
set("topA")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.topA == null || params.topA === 0 ? "Off" : undefined
|
||||
}
|
||||
info="OpenRouter `top_a`. Tail-cut sampler scaled by the top token's probability. 0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.dryMultiplier ? (
|
||||
<ParamSlider
|
||||
label="DRY Multiplier"
|
||||
value={params.dryMultiplier ?? 0}
|
||||
min={0}
|
||||
max={3}
|
||||
step={0.1}
|
||||
onChange={(v) =>
|
||||
set("dryMultiplier")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.dryMultiplier == null || params.dryMultiplier === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp DRY sampler. Master switch for the 4-field DRY chain (base / allowed length / penalty last N). 0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.dryBase && (params.dryMultiplier ?? 0) > 0 ? (
|
||||
<ParamSlider
|
||||
label="DRY Base"
|
||||
value={params.dryBase ?? 1.75}
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.05}
|
||||
onChange={(v) => set("dryBase")(v)}
|
||||
info="llama.cpp `dry_base`. Exponential base for the DRY penalty. Default 1.75."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.dryAllowedLength && (params.dryMultiplier ?? 0) > 0 ? (
|
||||
<ParamSlider
|
||||
label="DRY Allowed Length"
|
||||
value={params.dryAllowedLength ?? 2}
|
||||
min={0}
|
||||
max={20}
|
||||
step={1}
|
||||
onChange={(v) => set("dryAllowedLength")(v)}
|
||||
info="llama.cpp `dry_allowed_length`. Repeats up to this length are not penalised. Default 2."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.dryPenaltyLastN && (params.dryMultiplier ?? 0) > 0 ? (
|
||||
<ParamSlider
|
||||
label="DRY Penalty Last N"
|
||||
value={params.dryPenaltyLastN ?? 0}
|
||||
min={-1}
|
||||
max={2048}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("dryPenaltyLastN")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.dryPenaltyLastN == null
|
||||
? "Off"
|
||||
: params.dryPenaltyLastN === -1
|
||||
? "Ctx"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `dry_penalty_last_n`. Token window the DRY penalty considers. 0 = off, -1 = full context."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.xtcProbability ? (
|
||||
<ParamSlider
|
||||
label="XTC Probability"
|
||||
value={params.xtcProbability ?? 0}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
onChange={(v) =>
|
||||
set("xtcProbability")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.xtcProbability == null || params.xtcProbability === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp XTC (eXclude Top Choices). Master switch. 0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.xtcThreshold && (params.xtcProbability ?? 0) > 0 ? (
|
||||
<ParamSlider
|
||||
label="XTC Threshold"
|
||||
value={params.xtcThreshold ?? 0.1}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
onChange={(v) => set("xtcThreshold")(v)}
|
||||
info="llama.cpp `xtc_threshold`. Minimum probability for a token to be removable by XTC. Default 0.1."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.minKeep ? (
|
||||
<ParamSlider
|
||||
label="Min Keep"
|
||||
value={params.minKeep ?? 0}
|
||||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("minKeep")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.minKeep == null || params.minKeep === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `min_keep`. Minimum tokens retained past all sampler filters."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.minTokens ? (
|
||||
<ParamSlider
|
||||
label="Min Tokens"
|
||||
value={params.minTokens ?? 0}
|
||||
min={0}
|
||||
max={512}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("minTokens")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.minTokens == null || params.minTokens === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp + vLLM. Minimum tokens before stop / EOS can fire."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.truncatePromptTokens ? (
|
||||
<ParamSlider
|
||||
label="Truncate Prompt"
|
||||
value={params.truncatePromptTokens ?? 0}
|
||||
min={0}
|
||||
max={32768}
|
||||
step={64}
|
||||
onChange={(v) =>
|
||||
set("truncatePromptTokens")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.truncatePromptTokens == null ||
|
||||
params.truncatePromptTokens === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="vLLM `truncate_prompt_tokens`. Left-truncate the prompt to this many tokens. 0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.nKeep ? (
|
||||
<ParamSlider
|
||||
label="N Keep"
|
||||
value={params.nKeep ?? 0}
|
||||
min={-1}
|
||||
max={1024}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("nKeep")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.nKeep == null
|
||||
? "Off"
|
||||
: params.nKeep === -1
|
||||
? "All"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `n_keep`. Tokens to retain when the context is shifted. 0 = off, -1 = keep all."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.nProbs ? (
|
||||
<ParamSlider
|
||||
label="N Probs"
|
||||
value={params.nProbs ?? 0}
|
||||
min={0}
|
||||
max={20}
|
||||
step={1}
|
||||
onChange={(v) =>
|
||||
set("nProbs")(v === 0 ? null : v)
|
||||
}
|
||||
displayValue={
|
||||
params.nProbs == null || params.nProbs === 0
|
||||
? "Off"
|
||||
: undefined
|
||||
}
|
||||
info="llama.cpp `n_probs`. Return the top-N token probabilities per token (diagnostic)."
|
||||
/>
|
||||
) : null}
|
||||
{advCaps.ignoreEos ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Ignore EOS
|
||||
</span>
|
||||
<InfoHint>
|
||||
llama.cpp + vLLM. Keep generating past the model's
|
||||
end-of-sequence token. Useful for forcing long replies.
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={Boolean(params.ignoreEos)}
|
||||
onCheckedChange={(v) => set("ignoreEos")(v ? true : null)}
|
||||
aria-label="Ignore EOS"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.skipSpecialTokens ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Skip Special Tokens
|
||||
</span>
|
||||
<InfoHint>
|
||||
vLLM `skip_special_tokens`. Default on. Turn off to keep
|
||||
chat-template markers (e.g. `<|im_end|>`) in the
|
||||
decoded output.
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={params.skipSpecialTokens ?? true}
|
||||
onCheckedChange={(v) =>
|
||||
set("skipSpecialTokens")(v ? null : false)
|
||||
}
|
||||
aria-label="Skip special tokens"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.spacesBetweenSpecialTokens ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Spaces Between Special Tokens
|
||||
</span>
|
||||
<InfoHint>
|
||||
vLLM `spaces_between_special_tokens`. Default on.
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={params.spacesBetweenSpecialTokens ?? true}
|
||||
onCheckedChange={(v) =>
|
||||
set("spacesBetweenSpecialTokens")(v ? null : false)
|
||||
}
|
||||
aria-label="Spaces between special tokens"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.includeStopStrInOutput ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Include Stop String
|
||||
</span>
|
||||
<InfoHint>
|
||||
vLLM `include_stop_str_in_output`. Echo the matched stop
|
||||
string back in the response (useful for agentic tools).
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={Boolean(params.includeStopStrInOutput)}
|
||||
onCheckedChange={(v) =>
|
||||
set("includeStopStrInOutput")(v ? true : null)
|
||||
}
|
||||
aria-label="Include stop string in output"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.cachePrompt ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Cache Prompt
|
||||
</span>
|
||||
<InfoHint>
|
||||
llama.cpp `cache_prompt`. Default on. Reuses the KV cache
|
||||
across requests with shared prefixes.
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={params.cachePrompt ?? true}
|
||||
onCheckedChange={(v) =>
|
||||
set("cachePrompt")(v ? null : false)
|
||||
}
|
||||
aria-label="Cache prompt"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.returnTokens ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Return Tokens
|
||||
</span>
|
||||
<InfoHint>
|
||||
llama.cpp `return_tokens`. Include the raw token ids in
|
||||
the response (debug).
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={Boolean(params.returnTokens)}
|
||||
onCheckedChange={(v) =>
|
||||
set("returnTokens")(v ? true : null)
|
||||
}
|
||||
aria-label="Return tokens"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.timingsPerToken ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Timings Per Token
|
||||
</span>
|
||||
<InfoHint>
|
||||
llama.cpp `timings_per_token`. Per-token wall-clock
|
||||
timings in the response (perf debug).
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={Boolean(params.timingsPerToken)}
|
||||
onCheckedChange={(v) =>
|
||||
set("timingsPerToken")(v ? true : null)
|
||||
}
|
||||
aria-label="Timings per token"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{advCaps.postSamplingProbs ? (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Post-Sampling Probs
|
||||
</span>
|
||||
<InfoHint>
|
||||
llama.cpp `post_sampling_probs`. Report the
|
||||
post-sampling distribution (sampler debug).
|
||||
</InfoHint>
|
||||
</div>
|
||||
<Switch
|
||||
className="panel-switch shrink-0"
|
||||
checked={Boolean(params.postSamplingProbs)}
|
||||
onCheckedChange={(v) =>
|
||||
set("postSamplingProbs")(v ? true : null)
|
||||
}
|
||||
aria-label="Post-sampling probs"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
) : null}
|
||||
|
||||
{!isExternalModel ? (
|
||||
<CollapsibleSection label="Tools">
|
||||
<div className="flex flex-col gap-5 pt-1">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue