feat(dashboard): Stream Error Patterns editor on provider page
Per-provider textarea (one pattern per line: plain text or /regex/flags) saved via /api/settings as streamErrorPatterns. Mirrors the existing providerTimeouts load/save pattern.
This commit is contained in:
@@ -96,6 +96,7 @@ export default function ProviderDetailPage() {
|
||||
const [providerStickyLimit, setProviderStickyLimit] = useState("");
|
||||
const [thinkingMode, setThinkingMode] = useState("auto");
|
||||
const [providerTimeout, setProviderTimeout] = useState("");
|
||||
const [streamErrorPatternsText, setStreamErrorPatternsText] = useState("");
|
||||
const [autoPing, setAutoPing] = useState({ enabled: false, connections: {} });
|
||||
const [suggestedModels, setSuggestedModels] = useState([]);
|
||||
const [liveModels, setLiveModels] = useState([]);
|
||||
@@ -444,6 +445,12 @@ export default function ProviderDetailPage() {
|
||||
setProviderTimeout(
|
||||
timeoutCfg.timeoutMs != null ? String(timeoutCfg.timeoutMs) : "",
|
||||
);
|
||||
// Load per-provider stream error patterns (one per line)
|
||||
setStreamErrorPatternsText(
|
||||
((settingsData.streamErrorPatterns || {})[providerId] || []).join(
|
||||
"\n",
|
||||
),
|
||||
);
|
||||
const autoPingSettingsKey = AUTO_PING_SETTINGS_KEYS[providerId];
|
||||
const apCfg = autoPingSettingsKey
|
||||
? settingsData[autoPingSettingsKey] || {}
|
||||
@@ -595,6 +602,30 @@ export default function ProviderDetailPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const saveStreamErrorPatterns = async () => {
|
||||
try {
|
||||
const settingsRes = await fetch("/api/settings", {
|
||||
cache: "no-store",
|
||||
});
|
||||
const settingsData = settingsRes.ok ? await settingsRes.json() : {};
|
||||
const current = settingsData.streamErrorPatterns || {};
|
||||
const updated = { ...current };
|
||||
const lines = streamErrorPatternsText
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
if (lines.length > 0) updated[providerId] = lines;
|
||||
else delete updated[providerId];
|
||||
await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ streamErrorPatterns: updated }),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error saving stream error patterns:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTimeoutChange = (value) => {
|
||||
const cleaned = value.replace(/[^0-9]/g, "");
|
||||
setProviderTimeout(cleaned);
|
||||
@@ -2459,6 +2490,38 @@ export default function ProviderDetailPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Stream Error Patterns */}
|
||||
<Card>
|
||||
<div className="mb-3">
|
||||
<h2 className="text-lg font-semibold">Stream Error Patterns</h2>
|
||||
<p className="mt-1 text-sm text-text-muted">
|
||||
Treat HTTP-200 streams whose first bytes match one of these
|
||||
patterns as failed requests (enables fallback + FAILED logs). One
|
||||
pattern per line: plain text (case-insensitive substring) or{" "}
|
||||
<code className="rounded bg-background px-1 py-0.5 font-mono text-xs">
|
||||
/regex/flags
|
||||
</code>
|
||||
. Only the first few KB of the stream are scanned.
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
value={streamErrorPatternsText}
|
||||
onChange={(e) => setStreamErrorPatternsText(e.target.value)}
|
||||
rows={5}
|
||||
placeholder={"server_error\n/network connection lost/i"}
|
||||
className="w-full rounded-md border border-border bg-background p-2 font-mono text-xs focus:border-primary focus:outline-none"
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={saveStreamErrorPatterns}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Save Patterns
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Models */}
|
||||
<Card>
|
||||
<div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
||||
Reference in New Issue
Block a user