Fix MITM on window

This commit is contained in:
decolua committed 2026-02-28 10:04:57 +07:00
1 parent 49a56612bf
commit 833069caac
22 files changed
+650 -199

No files matched your search

@@ -240,7 +240,23 @@ export default function AntigravityToolCard({
{isExpanded && (
<div className="mt-4 pt-4 border-t border-border flex flex-col gap-4">
{/* Start/Stop Button - always on top */}
{/* Status indicators */}
<div className="flex items-center gap-3">
{[
{ label: "DNS", ok: status?.dnsConfigured },
{ label: "Cert", ok: status?.certExists },
{ label: "Server", ok: status?.running },
].map(({ label, ok }) => (
<div key={label} className="flex items-center gap-1">
<span className={`material-symbols-outlined text-[14px] ${ok ? "text-green-500" : "text-text-muted"}`}>
{ok ? "check_circle" : "radio_button_unchecked"}
</span>
<span className={`text-xs ${ok ? "text-green-500" : "text-text-muted"}`}>{label}</span>
</div>
))}
</div>
{/* Start/Stop Button */}
<div className="flex items-center gap-2">
{isRunning ? (
<button
@@ -26,6 +26,7 @@ export default function ProviderDetailPage() {
const [headerImgError, setHeaderImgError] = useState(false);
const [modelTestResults, setModelTestResults] = useState({});
const [testingModels, setTestingModels] = useState(false);
const [showAddCustomModel, setShowAddCustomModel] = useState(false);
const { copied, copy } = useCopyToClipboard();
const providerInfo = providerNode
@@ -307,9 +308,21 @@ export default function ProviderDetailPage() {
/>
);
}
if (models.length === 0) {
return <p className="text-sm text-text-muted">No models configured</p>;
}
// Custom models added by user (stored as aliases: modelId → providerAlias/modelId)
const customModels = Object.entries(modelAliases)
.filter(([alias, fullModel]) => {
const prefix = `${providerStorageAlias}/`;
if (!fullModel.startsWith(prefix)) return false;
const modelId = fullModel.slice(prefix.length);
// Only show if not already in hardcoded list
return !models.some((m) => m.id === modelId) && alias === modelId;
})
.map(([alias, fullModel]) => ({
id: fullModel.slice(`${providerStorageAlias}/`.length),
alias,
fullModel,
}));
return (
<div className="flex flex-wrap gap-3">
{models.map((model) => {
@@ -332,6 +345,30 @@ export default function ProviderDetailPage() {
/>
);
})}
{/* Custom models inline */}
{customModels.map((model) => (
<ModelRow
key={model.id}
model={{ id: model.id }}
fullModel={`${providerDisplayAlias}/${model.id}`}
alias={model.alias}
copied={copied}
onCopy={copy}
onSetAlias={() => {}}
onDeleteAlias={() => handleDeleteAlias(model.alias)}
isCustom
/>
))}
{/* Add model button — inline, same style as model chips */}
<button
onClick={() => setShowAddCustomModel(true)}
className="flex items-center gap-1.5 px-3 py-2 rounded-lg border border-dashed border-black/15 dark:border-white/15 text-xs text-text-muted hover:text-primary hover:border-primary/40 transition-colors"
>
<span className="material-symbols-outlined text-sm">add</span>
Add Model
</button>
</div>
);
};
@@ -526,7 +563,8 @@ export default function ProviderDetailPage() {
<Button
size="sm"
variant="secondary"
icon={testingModels ? "progress_activity" : "science"}
icon="science"
loading={testingModels}
onClick={handleTestModels}
disabled={testingModels}
>
@@ -584,11 +622,23 @@ export default function ProviderDetailPage() {
isAnthropic={isAnthropicCompatible}
/>
)}
{!isCompatible && !providerInfo?.passthroughModels && (
<AddCustomModelModal
isOpen={showAddCustomModel}
providerAlias={providerStorageAlias}
providerDisplayAlias={providerDisplayAlias}
onSave={async (modelId) => {
await handleSetAlias(modelId, modelId, providerStorageAlias);
setShowAddCustomModel(false);
}}
onClose={() => setShowAddCustomModel(false)}
/>
)}
</div>
);
}
function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus }) {
function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus, isCustom, onDeleteAlias }) {
const borderColor = testStatus === "ok"
? "border-green-500/40"
: testStatus === "error"
@@ -602,7 +652,7 @@ function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus }) {
: undefined;
return (
<div className={`flex items-center gap-2 px-3 py-2 rounded-lg border ${borderColor} hover:bg-sidebar/50`}>
<div className={`group flex items-center gap-2 px-3 py-2 rounded-lg border ${borderColor} hover:bg-sidebar/50`}>
<span
className="material-symbols-outlined text-base"
style={iconColor ? { color: iconColor } : undefined}
@@ -619,6 +669,15 @@ function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus }) {
{copied === `model-${model.id}` ? "check" : "content_copy"}
</span>
</button>
{isCustom && (
<button
onClick={onDeleteAlias}
className="p-0.5 hover:bg-red-500/10 rounded text-text-muted hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity ml-auto"
title="Remove custom model"
>
<span className="material-symbols-outlined text-sm">close</span>
</button>
)}
</div>
);
}
@@ -632,6 +691,8 @@ ModelRow.propTypes = {
copied: PropTypes.string,
onCopy: PropTypes.func.isRequired,
testStatus: PropTypes.oneOf(["ok", "error"]),
isCustom: PropTypes.bool,
onDeleteAlias: PropTypes.func,
};
function PassthroughModelsSection({ providerAlias, modelAliases, copied, onCopy, onSetAlias, onDeleteAlias }) {
@@ -1553,3 +1614,115 @@ EditCompatibleNodeModal.propTypes = {
isAnthropic: PropTypes.bool,
};
function AddCustomModelModal({ isOpen, providerAlias, providerDisplayAlias, onSave, onClose }) {
const [modelId, setModelId] = useState("");
const [testStatus, setTestStatus] = useState(null); // null | "testing" | "ok" | "error"
const [testError, setTestError] = useState("");
const [saving, setSaving] = useState(false);
// Reset state when modal opens
useEffect(() => {
if (isOpen) { setModelId(""); setTestStatus(null); setTestError(""); }
}, [isOpen]);
const handleTest = async () => {
if (!modelId.trim()) return;
setTestStatus("testing");
setTestError("");
try {
const res = await fetch("/api/models/test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: `${providerAlias}/${modelId.trim()}` }),
});
const data = await res.json();
setTestStatus(data.ok ? "ok" : "error");
setTestError(data.error || "");
} catch (err) {
setTestStatus("error");
setTestError(err.message);
}
};
const handleSave = async () => {
if (!modelId.trim() || saving) return;
setSaving(true);
try {
await onSave(modelId.trim());
} finally {
setSaving(false);
}
};
const handleKeyDown = (e) => {
if (e.key === "Enter") handleTest();
};
return (
<Modal isOpen={isOpen} onClose={onClose} title="Add Custom Model">
<div className="flex flex-col gap-4">
<div>
<label className="text-sm font-medium mb-1.5 block">Model ID</label>
<div className="flex gap-2">
<input
type="text"
value={modelId}
onChange={(e) => { setModelId(e.target.value); setTestStatus(null); setTestError(""); }}
onKeyDown={handleKeyDown}
placeholder="e.g. claude-opus-4-5"
className="flex-1 px-3 py-2 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
autoFocus
/>
<Button
variant="secondary"
icon="science"
loading={testStatus === "testing"}
onClick={handleTest}
disabled={!modelId.trim() || testStatus === "testing"}
>
{testStatus === "testing" ? "Testing..." : "Test"}
</Button>
</div>
<p className="text-xs text-text-muted mt-1">
Sent to provider as: <code className="font-mono bg-sidebar px-1 rounded">{modelId.trim() || "model-id"}</code>
</p>
</div>
{/* Test result */}
{testStatus === "ok" && (
<div className="flex items-center gap-2 text-sm text-green-600">
<span className="material-symbols-outlined text-base">check_circle</span>
Model is reachable
</div>
)}
{testStatus === "error" && (
<div className="flex items-start gap-2 text-sm text-red-500">
<span className="material-symbols-outlined text-base shrink-0">cancel</span>
<span>{testError || "Model not reachable"}</span>
</div>
)}
<div className="flex gap-2 pt-1">
<Button onClick={onClose} variant="ghost" fullWidth size="sm">Cancel</Button>
<Button
onClick={handleSave}
fullWidth
size="sm"
disabled={!modelId.trim() || saving}
>
{saving ? "Adding..." : "Add Model"}
</Button>
</div>
</div>
</Modal>
);
}
AddCustomModelModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
providerAlias: PropTypes.string.isRequired,
providerDisplayAlias: PropTypes.string.isRequired,
onSave: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
@@ -222,7 +222,7 @@ export default function ProvidersPage() {
title="Test all OAuth connections"
aria-label="Test all OAuth connections"
>
<span className="material-symbols-outlined text-[14px]">
<span className={`material-symbols-outlined text-[14px]${testingMode === "oauth" ? " animate-spin" : ""}`}>
{testingMode === "oauth" ? "sync" : "play_arrow"}
</span>
{testingMode === "oauth" ? "Testing..." : "Test All"}
@@ -260,7 +260,7 @@ export default function ProvidersPage() {
title="Test all Free connections"
aria-label="Test all Free provider connections"
>
<span className="material-symbols-outlined text-[14px]">
<span className={`material-symbols-outlined text-[14px]${testingMode === "free" ? " animate-spin" : ""}`}>
{testingMode === "free" ? "sync" : "play_arrow"}
</span>
{testingMode === "free" ? "Testing..." : "Test All"}
@@ -297,7 +297,7 @@ export default function ProvidersPage() {
title="Test all API Key connections"
aria-label="Test all API Key connections"
>
<span className="material-symbols-outlined text-[14px]">
<span className={`material-symbols-outlined text-[14px]${testingMode === "apikey" ? " animate-spin" : ""}`}>
{testingMode === "apikey" ? "sync" : "play_arrow"}
</span>
{testingMode === "apikey" ? "Testing..." : "Test All"}
@@ -335,7 +335,7 @@ export default function ProvidersPage() {
}`}
title="Test all Compatible connections"
>
<span className="material-symbols-outlined text-[14px]">
<span className={`material-symbols-outlined text-[14px]${testingMode === "compatible" ? " animate-spin" : ""}`}>
{testingMode === "compatible" ? "sync" : "play_arrow"}
</span>
{testingMode === "compatible" ? "Testing..." : "Test All"}
@@ -144,7 +144,7 @@ function buildLayout(providers, activeSet, lastSet, errorSet) {
const error = !active && errorSet.has(p.provider?.toLowerCase());
const nodeId = `provider-${p.provider}`;
const data = {
label: config.name || p.name || p.provider,
label: (config.name !== p.provider ? config.name : null) || p.name || p.provider,
color: config.color || "#6b7280",
imageUrl: getProviderImageUrl(p.provider),
textIcon: config.textIcon || (p.provider || "?").slice(0, 2).toUpperCase(),