Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 51 additions & 12 deletions src/modules/agent-network/AIProviderModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
Sparkles,
UploadIcon,
} from "lucide-react";
import React, { useMemo, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import AgentNetworkIcon from "@/assets/icons/AgentNetworkIcon";
import {
ReverseProxyDomain,
Expand Down Expand Up @@ -639,7 +639,7 @@ export default function AIProviderModal({
certificates on your proxy instances instead.{" "}
<InlineLink
href={
"https://docs.netbird.io/agent-network/providers/self-signed-certificates"
"https://docs.netbird.io/agent-network/providers#skip-tls-verification"
}
target={"_blank"}
>
Expand Down Expand Up @@ -1279,6 +1279,18 @@ type CatalogModelOption = {
output_per_1k: number;
};

// priceToInput renders a stored price as an editable string, always using "."
// as the decimal separator regardless of the browser locale.
function priceToInput(n: number): string {
return Number.isFinite(n) ? String(n) : "";
}

// priceFromInput parses an operator-typed price, accepting a "," as the decimal
// separator (some keyboards/locales) and normalising it to a plain number.
function priceFromInput(s: string): number {
return parseFloat(s.replace(/,/g, ".")) || 0;
}

Comment on lines +1282 to +1293

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

No validation against negative or malformed prices.

priceFromInput accepts any parseable float, including negative values (e.g. "-5"-5), and propagates it straight to onChangeInput/onChangeOutput on every keystroke without clamping. Since these values feed per-1k cost tracking, a stray negative sign could silently produce a negative billing rate.

🛡️ Proposed fix to clamp to non-negative
 function priceFromInput(s: string): number {
-  return parseFloat(s.replace(/,/g, ".")) || 0;
+  const n = parseFloat(s.replace(/,/g, "."));
+  return Number.isFinite(n) && n >= 0 ? n : 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// priceToInput renders a stored price as an editable string, always using "."
// as the decimal separator regardless of the browser locale.
function priceToInput(n: number): string {
return Number.isFinite(n) ? String(n) : "";
}
// priceFromInput parses an operator-typed price, accepting a "," as the decimal
// separator (some keyboards/locales) and normalising it to a plain number.
function priceFromInput(s: string): number {
return parseFloat(s.replace(/,/g, ".")) || 0;
}
// priceToInput renders a stored price as an editable string, always using "."
// as the decimal separator regardless of the browser locale.
function priceToInput(n: number): string {
return Number.isFinite(n) ? String(n) : "";
}
// priceFromInput parses an operator-typed price, accepting a "," as the decimal
// separator (some keyboards/locales) and normalising it to a plain number.
function priceFromInput(s: string): number {
const n = parseFloat(s.replace(/,/g, "."));
return Number.isFinite(n) && n >= 0 ? n : 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/agent-network/AIProviderModal.tsx` around lines 1282 - 1293, The
price parsing in priceFromInput allows negative or malformed values to flow
through into onChangeInput and onChangeOutput, which can produce invalid billing
rates. Update priceFromInput in AIProviderModal to validate the parsed result
and clamp it to a non-negative number, returning 0 for invalid input and never
allowing a negative price. Keep priceToInput unchanged, but ensure the handlers
that use priceFromInput continue to receive only safe, non-negative values.

function ModelRowEditor({
row,
catalogModels,
Expand All @@ -1296,6 +1308,29 @@ function ModelRowEditor({
onChangeOutput: (n: number) => void;
onRemove: () => void;
}) {
// Editable text for the price fields. We keep the raw string locally so the
// operator can type intermediate values ("0.", "0,00") without the number
// round-trip clobbering the cursor. The number is propagated to the parent
// on every change; a "." is always shown even in comma-decimal locales.
const [inputStr, setInputStr] = useState(() => priceToInput(row.inputPer1k));
const [outputStr, setOutputStr] = useState(() =>
priceToInput(row.outputPer1k),
);
// Re-sync when the price is set from outside (e.g. picking a catalog model
// fills its prices), but not while the operator is mid-typing the same value.
useEffect(() => {
if (priceFromInput(inputStr) !== row.inputPer1k) {
setInputStr(priceToInput(row.inputPer1k));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [row.inputPer1k]);
useEffect(() => {
if (priceFromInput(outputStr) !== row.outputPer1k) {
setOutputStr(priceToInput(row.outputPer1k));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [row.outputPer1k]);

// Whether this provider type ships a catalog of preset models.
// Stable across keystrokes — we mustn't let this flip mid-typing or
// React will unmount the input and steal focus.
Expand Down Expand Up @@ -1342,21 +1377,25 @@ function ModelRowEditor({
<div className={"w-[120px] shrink-0"}>
<Label>Input $/1k</Label>
<Input
type={"number"}
step={"0.0001"}
min={"0"}
value={row.inputPer1k}
onChange={(e) => onChangeInput(parseFloat(e.target.value) || 0)}
type={"text"}
inputMode={"decimal"}
value={inputStr}
onChange={(e) => {
setInputStr(e.target.value);
onChangeInput(priceFromInput(e.target.value));
}}
/>
</div>
<div className={"w-[120px] shrink-0"}>
<Label>Output $/1k</Label>
<Input
type={"number"}
step={"0.0001"}
min={"0"}
value={row.outputPer1k}
onChange={(e) => onChangeOutput(parseFloat(e.target.value) || 0)}
type={"text"}
inputMode={"decimal"}
value={outputStr}
onChange={(e) => {
setOutputStr(e.target.value);
onChangeOutput(priceFromInput(e.target.value));
}}
/>
</div>
<Button
Expand Down
Loading