fix
This commit is contained in:
@@ -6,7 +6,7 @@ import type { LlmLog, SettingsOut } from "@/types";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Input } from "@/components/ui/Input";
|
||||
import { Card, CardBody, CardHeader } from "@/components/ui/Card";
|
||||
import { Save, ArrowLeft, Activity, Users, Zap, Ban, CheckCircle2 } from "lucide-react";
|
||||
import { Save, ArrowLeft, Activity, Users, Zap, Ban, CheckCircle2, Terminal, Wrench } from "lucide-react";
|
||||
|
||||
export function AdminPanelPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -22,6 +22,10 @@ export function AdminPanelPage() {
|
||||
const [error, setError] = useState("");
|
||||
const [embeddingTest, setEmbeddingTest] = useState<null | { ok: boolean; msg: string }>(null);
|
||||
const [testingEmbeddings, setTestingEmbeddings] = useState(false);
|
||||
const [llmTest, setLlmTest] = useState<null | { ok: boolean; msg: string }>(null);
|
||||
const [testingLlm, setTestingLlm] = useState(false);
|
||||
const [llmToolsTest, setLlmToolsTest] = useState<null | { ok: boolean; msg: string }>(null);
|
||||
const [testingLlmTools, setTestingLlmTools] = useState(false);
|
||||
const [userActionError, setUserActionError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -111,6 +115,92 @@ export function AdminPanelPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Build LLM overrides from current form values (excluding masked api_key).
|
||||
// Used by both testLlm and testLlmTools so the operator can tweak base_url
|
||||
// / model / api_key in the form and test before saving.
|
||||
const buildLlmOverrides = (): Record<string, any> => {
|
||||
const overrides: Record<string, any> = {};
|
||||
for (const k of ["llm.base_url", "llm.api_key", "llm.model", "llm.request_timeout"]) {
|
||||
const v = values[k];
|
||||
if (v !== undefined && v !== null && !(typeof v === "string" && v.includes("***"))) {
|
||||
overrides[k] = v;
|
||||
}
|
||||
}
|
||||
return overrides;
|
||||
};
|
||||
|
||||
const testLlm = async () => {
|
||||
setError("");
|
||||
setTestingLlm(true);
|
||||
setLlmTest(null);
|
||||
try {
|
||||
const r = await adminApi.testLlm(buildLlmOverrides());
|
||||
if (r.ok) {
|
||||
setLlmTest({
|
||||
ok: true,
|
||||
msg: t("admin.llm_test_ok", {
|
||||
latency: r.latency_ms ?? 0,
|
||||
preview: (r.response_preview || "").slice(0, 80),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
setLlmTest({
|
||||
ok: false,
|
||||
msg: t("admin.llm_test_fail", { error: r.error || `(${r.error_type || "unknown"})` }),
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
setLlmTest({
|
||||
ok: false,
|
||||
msg: t("admin.llm_test_fail", { error: err.response?.data?.detail || err.message }),
|
||||
});
|
||||
} finally {
|
||||
setTestingLlm(false);
|
||||
}
|
||||
};
|
||||
|
||||
const testLlmTools = async () => {
|
||||
setError("");
|
||||
setTestingLlmTools(true);
|
||||
setLlmToolsTest(null);
|
||||
try {
|
||||
const r = await adminApi.testLlmTools(buildLlmOverrides());
|
||||
if (r.ok && r.tool_calls_returned) {
|
||||
// Model returned a proper tool_call — function-calling works.
|
||||
setLlmToolsTest({
|
||||
ok: true,
|
||||
msg: t("admin.llm_test_tools_ok_with_call", {
|
||||
name: r.tool_call_name || "?",
|
||||
args: JSON.stringify(r.tool_call_args || {}),
|
||||
latency: r.latency_ms ?? 0,
|
||||
}),
|
||||
});
|
||||
} else if (r.ok && !r.tool_calls_returned) {
|
||||
// Model responded but did NOT use the tool — function-calling is NOT
|
||||
// supported. The fallback JSON parser will still work, but tool-based
|
||||
// flows (orchestrator, step-writer) will be unreliable.
|
||||
setLlmToolsTest({
|
||||
ok: false,
|
||||
msg: t("admin.llm_test_tools_ok_no_call", {
|
||||
text: (r.text || "").slice(0, 120),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
setLlmToolsTest({
|
||||
ok: false,
|
||||
msg: t("admin.llm_test_fail", { error: r.error || `(${r.error_type || "unknown"})` }),
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
setLlmToolsTest({
|
||||
ok: false,
|
||||
msg: t("admin.llm_test_fail", { error: err.response?.data?.detail || err.message }),
|
||||
});
|
||||
} finally {
|
||||
setTestingLlmTools(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleUserActive = async (userId: string, currentActive: boolean) => {
|
||||
setUserActionError("");
|
||||
try {
|
||||
@@ -217,6 +307,30 @@ export function AdminPanelPage() {
|
||||
{t("admin.streaming")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* LLM connectivity tests — runs against current form values
|
||||
(so the operator can tweak base_url / model / api_key and
|
||||
test BEFORE saving). */}
|
||||
<div className="flex flex-wrap items-center gap-2 pt-1">
|
||||
<Button variant="ghost" onClick={testLlm} disabled={testingLlm}>
|
||||
<Terminal size={14} className="mr-1" />
|
||||
{testingLlm ? t("admin.llm_test_testing") : t("admin.llm_test")}
|
||||
</Button>
|
||||
<Button variant="ghost" onClick={testLlmTools} disabled={testingLlmTools}>
|
||||
<Wrench size={14} className="mr-1" />
|
||||
{testingLlmTools ? t("admin.llm_test_tools_testing") : t("admin.llm_test_tools")}
|
||||
</Button>
|
||||
{llmTest && (
|
||||
<span className={`text-xs ${llmTest.ok ? "text-green-400" : "text-red-400"}`}>
|
||||
{llmTest.msg}
|
||||
</span>
|
||||
)}
|
||||
{llmToolsTest && (
|
||||
<span className={`text-xs ${llmToolsTest.ok ? "text-green-400" : "text-red-400"}`}>
|
||||
{llmToolsTest.msg}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user