Merge branch 'master' of http://192.168.0.200/knowledge-base-bj/deerflow-server into dhk-1
This commit is contained in:
commit
45ce14e15a
13 files changed
+1011
-60
No files matched your search
@@ -0,0 +1,190 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
normalizeDomainInput,
|
||||
resolveIncomingIframeSelection,
|
||||
resolveConfiguredIframeAgentId,
|
||||
resolveDomainMappedAgentId,
|
||||
} from "./domain-agent.ts";
|
||||
|
||||
test("normalizes full urls and bare hosts to comparable domains", () => {
|
||||
assert.equal(
|
||||
normalizeDomainInput(" https://Foo.Example.com/path?q=1 "),
|
||||
"foo.example.com",
|
||||
);
|
||||
assert.equal(
|
||||
normalizeDomainInput("foo.example.com:8080/landing"),
|
||||
"foo.example.com:8080",
|
||||
);
|
||||
});
|
||||
|
||||
test("resolves the configured agent for a matching pageContext url", () => {
|
||||
assert.equal(
|
||||
resolveDomainMappedAgentId({
|
||||
pageUrl: "https://foo.example.com/article/123",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1", "agent-2"],
|
||||
}),
|
||||
"agent-1",
|
||||
);
|
||||
});
|
||||
|
||||
test("ignores disabled mappings and unavailable agents", () => {
|
||||
assert.equal(
|
||||
resolveDomainMappedAgentId({
|
||||
pageUrl: "https://foo.example.com/article/123",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: "m2",
|
||||
domain: "bar.example.com",
|
||||
agentId: "agent-2",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-3"],
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test("does not auto-select after manual selection or once a conversation has started", () => {
|
||||
assert.equal(
|
||||
resolveConfiguredIframeAgentId({
|
||||
pageUrl: "https://foo.example.com",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1"],
|
||||
isNewThread: true,
|
||||
hasStartedConversation: false,
|
||||
manualSelectionLocked: true,
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
resolveConfiguredIframeAgentId({
|
||||
pageUrl: "https://foo.example.com",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1"],
|
||||
isNewThread: true,
|
||||
hasStartedConversation: true,
|
||||
manualSelectionLocked: false,
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test("falls back to default assistant when url is unmapped and user has not manually selected an agent", () => {
|
||||
assert.deepEqual(
|
||||
resolveIncomingIframeSelection({
|
||||
pageUrl: "https://unknown.example.com",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1", "agent-2"],
|
||||
manualSelectedAgentId: null,
|
||||
}),
|
||||
{
|
||||
selectedAgentId: null,
|
||||
forceDefaultAssistant: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("falls back to the user's manual selection when url is unmapped", () => {
|
||||
assert.deepEqual(
|
||||
resolveIncomingIframeSelection({
|
||||
pageUrl: "https://unknown.example.com",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1", "agent-2"],
|
||||
manualSelectedAgentId: "agent-2",
|
||||
}),
|
||||
{
|
||||
selectedAgentId: "agent-2",
|
||||
forceDefaultAssistant: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("mapped domain overrides the user's manual selection for that incoming message", () => {
|
||||
assert.deepEqual(
|
||||
resolveIncomingIframeSelection({
|
||||
pageUrl: "https://foo.example.com/article",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1", "agent-2"],
|
||||
manualSelectedAgentId: "agent-2",
|
||||
}),
|
||||
{
|
||||
selectedAgentId: "agent-1",
|
||||
forceDefaultAssistant: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("unmapped incoming url resets auto-selected agent back to default when there is no manual selection", () => {
|
||||
assert.deepEqual(
|
||||
resolveIncomingIframeSelection({
|
||||
pageUrl: "https://another-unknown.example.com/article",
|
||||
mappings: [
|
||||
{
|
||||
id: "m1",
|
||||
domain: "foo.example.com",
|
||||
agentId: "agent-1",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
availableAgentIds: ["agent-1", "agent-2"],
|
||||
manualSelectedAgentId: null,
|
||||
}),
|
||||
{
|
||||
selectedAgentId: null,
|
||||
forceDefaultAssistant: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
export type DomainAgentMapping = {
|
||||
id: string;
|
||||
domain: string;
|
||||
agentId: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
function trimToUndefined(value: string | null | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeAgentId(agentId: string | null | undefined): string | undefined {
|
||||
const trimmed = agentId?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
export function normalizeDomainInput(
|
||||
value: string | null | undefined,
|
||||
): string | undefined {
|
||||
const trimmed = trimToUndefined(value);
|
||||
if (!trimmed) return undefined;
|
||||
|
||||
const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(trimmed)
|
||||
? trimmed
|
||||
: `https://${trimmed}`;
|
||||
|
||||
try {
|
||||
const parsed = new URL(candidate);
|
||||
const host = parsed.host.trim().toLowerCase();
|
||||
return host || undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDomainMappedAgentId({
|
||||
pageUrl,
|
||||
mappings,
|
||||
availableAgentIds,
|
||||
}: {
|
||||
pageUrl: string | null | undefined;
|
||||
mappings: DomainAgentMapping[];
|
||||
availableAgentIds?: readonly string[] | null;
|
||||
}): string | undefined {
|
||||
const host = normalizeDomainInput(pageUrl);
|
||||
if (!host) return undefined;
|
||||
|
||||
const allowedAgents =
|
||||
availableAgentIds && availableAgentIds.length > 0
|
||||
? new Set(availableAgentIds)
|
||||
: null;
|
||||
|
||||
for (const mapping of mappings) {
|
||||
if (!mapping.enabled) continue;
|
||||
if (normalizeDomainInput(mapping.domain) !== host) continue;
|
||||
if (allowedAgents && !allowedAgents.has(mapping.agentId)) continue;
|
||||
return mapping.agentId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveConfiguredIframeAgentId({
|
||||
pageUrl,
|
||||
mappings,
|
||||
availableAgentIds,
|
||||
isNewThread,
|
||||
hasStartedConversation,
|
||||
manualSelectionLocked,
|
||||
}: {
|
||||
pageUrl: string | null | undefined;
|
||||
mappings: DomainAgentMapping[];
|
||||
availableAgentIds?: readonly string[] | null;
|
||||
isNewThread: boolean;
|
||||
hasStartedConversation: boolean;
|
||||
manualSelectionLocked: boolean;
|
||||
}): string | undefined {
|
||||
if (!isNewThread || hasStartedConversation || manualSelectionLocked) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return resolveDomainMappedAgentId({
|
||||
pageUrl,
|
||||
mappings,
|
||||
availableAgentIds,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveIncomingIframeSelection({
|
||||
pageUrl,
|
||||
mappings,
|
||||
availableAgentIds,
|
||||
manualSelectedAgentId,
|
||||
}: {
|
||||
pageUrl: string | null | undefined;
|
||||
mappings: DomainAgentMapping[];
|
||||
availableAgentIds?: readonly string[] | null;
|
||||
manualSelectedAgentId: string | null;
|
||||
}): {
|
||||
selectedAgentId: string | null;
|
||||
forceDefaultAssistant: boolean;
|
||||
} {
|
||||
const mappedAgentId = resolveDomainMappedAgentId({
|
||||
pageUrl,
|
||||
mappings,
|
||||
availableAgentIds,
|
||||
});
|
||||
if (mappedAgentId) {
|
||||
return {
|
||||
selectedAgentId: mappedAgentId,
|
||||
forceDefaultAssistant: false,
|
||||
};
|
||||
}
|
||||
|
||||
const allowedAgents =
|
||||
availableAgentIds && availableAgentIds.length > 0
|
||||
? new Set(availableAgentIds)
|
||||
: null;
|
||||
const normalizedManualAgentId = normalizeAgentId(manualSelectedAgentId);
|
||||
if (
|
||||
normalizedManualAgentId &&
|
||||
(!allowedAgents || allowedAgents.has(normalizedManualAgentId))
|
||||
) {
|
||||
return {
|
||||
selectedAgentId: normalizedManualAgentId,
|
||||
forceDefaultAssistant: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
selectedAgentId: null,
|
||||
forceDefaultAssistant: true,
|
||||
};
|
||||
}
|
||||
@@ -8,12 +8,115 @@ export interface PromptPrefixSettings {
|
||||
prompt_prefix: string;
|
||||
}
|
||||
|
||||
export interface PluginDomainAgentMappingDto {
|
||||
id: string;
|
||||
domain: string;
|
||||
agent_id: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface PluginManagementSettings {
|
||||
domainAgentMappings: Array<{
|
||||
id: string;
|
||||
domain: string;
|
||||
agentId: string;
|
||||
enabled: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PluginManagementUpdate {
|
||||
domainAgentMappings?: Array<{
|
||||
id: string;
|
||||
domain: string;
|
||||
agentId: string;
|
||||
enabled: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PromptPrefixUpdate {
|
||||
enabled?: boolean;
|
||||
switch_label?: string;
|
||||
prompt_prefix?: string;
|
||||
}
|
||||
|
||||
function fromPluginManagementDto(data: {
|
||||
domain_agent_mappings: PluginDomainAgentMappingDto[];
|
||||
}): PluginManagementSettings {
|
||||
return {
|
||||
domainAgentMappings: (data.domain_agent_mappings ?? []).map((item) => ({
|
||||
id: item.id,
|
||||
domain: item.domain,
|
||||
agentId: item.agent_id,
|
||||
enabled: item.enabled,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function toPluginManagementDto(body: PluginManagementUpdate) {
|
||||
return {
|
||||
domain_agent_mappings: (body.domainAgentMappings ?? []).map((item) => ({
|
||||
id: item.id,
|
||||
domain: item.domain,
|
||||
agent_id: item.agentId,
|
||||
enabled: item.enabled,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadPluginManagementSettings(): Promise<PluginManagementSettings> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/system-settings/plugin-management`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to load plugin management settings: ${response.status}`,
|
||||
);
|
||||
}
|
||||
const data = (await response.json()) as {
|
||||
domain_agent_mappings: PluginDomainAgentMappingDto[];
|
||||
};
|
||||
const mapped = fromPluginManagementDto(data);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "CM助手-全量-系统设置-插件管理查看",
|
||||
url: "/api/system-settings/plugin-management",
|
||||
returnParam: mapped,
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
export async function updatePluginManagementSettings(
|
||||
body: PluginManagementUpdate,
|
||||
): Promise<PluginManagementSettings> {
|
||||
const payload = toPluginManagementDto(body);
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/system-settings/plugin-management`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
const detail = await response
|
||||
.json()
|
||||
.catch(() => ({ detail: "Failed to update plugin management settings" }));
|
||||
throw new Error(detail.detail ?? "Failed to update plugin management settings");
|
||||
}
|
||||
const data = (await response.json()) as {
|
||||
domain_agent_mappings: PluginDomainAgentMappingDto[];
|
||||
};
|
||||
const mapped = fromPluginManagementDto(data);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "CM助手-全量-系统设置-插件管理编辑",
|
||||
url: "/api/system-settings/plugin-management",
|
||||
reqParam: payload,
|
||||
returnParam: mapped,
|
||||
});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
export async function loadPromptPrefixSettings(): Promise<PromptPrefixSettings> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/system-settings/prompt-prefix`,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
loadCitationDisplaySettings,
|
||||
loadLeaderboardSettings,
|
||||
loadModelConcurrencySettings,
|
||||
loadPluginManagementSettings,
|
||||
loadPrivateSquareStatus,
|
||||
loadPromptPrefixSettings,
|
||||
loadPublishSquares,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
updateCitationDisplaySettings,
|
||||
updateLeaderboardSettings,
|
||||
updateModelConcurrencySettings,
|
||||
updatePluginManagementSettings,
|
||||
updatePrivateSquareSettings,
|
||||
updatePromptPrefixSettings,
|
||||
updatePublishSquares,
|
||||
@@ -25,6 +27,7 @@ import {
|
||||
type CitationDisplayUpdate,
|
||||
type LeaderboardSettingsUpdate,
|
||||
type ModelConcurrencyUpdate,
|
||||
type PluginManagementUpdate,
|
||||
type PrivateSquareUpdate,
|
||||
type PromptPrefixUpdate,
|
||||
type PublishSquaresUpdate,
|
||||
@@ -41,6 +44,7 @@ const CITATION_DISPLAY_KEY = ["systemSettings", "citationDisplay"] as const;
|
||||
const LEADERBOARD_KEY = ["systemSettings", "leaderboard"] as const;
|
||||
const SKILL_COMPRESSION_KEY = ["systemSettings", "skillCompression"] as const;
|
||||
const MODEL_CONCURRENCY_KEY = ["systemSettings", "modelConcurrency"] as const;
|
||||
const PLUGIN_MANAGEMENT_KEY = ["systemSettings", "pluginManagement"] as const;
|
||||
|
||||
export function usePromptPrefixSettings() {
|
||||
return useQuery({
|
||||
@@ -263,6 +267,27 @@ export function useUpdateModelConcurrencySettings() {
|
||||
});
|
||||
}
|
||||
|
||||
export function usePluginManagementSettings() {
|
||||
return useQuery({
|
||||
queryKey: PLUGIN_MANAGEMENT_KEY,
|
||||
queryFn: () => loadPluginManagementSettings(),
|
||||
staleTime: 60_000,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePluginManagementSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: PluginManagementUpdate) =>
|
||||
updatePluginManagementSettings(body),
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData(PLUGIN_MANAGEMENT_KEY, data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useLeaderboardSettings() {
|
||||
return useQuery({
|
||||
queryKey: LEADERBOARD_KEY,
|
||||
|
||||
Reference in new issue
Block a user