diff --git a/.gitignore b/.gitignore index 6e006a6..bf8f964 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ src/paraglide/ target/ .reference +.magi \ No newline at end of file diff --git a/src/features/config/form.test.ts b/src/features/config/form.test.ts index ccded66..47e309f 100644 --- a/src/features/config/form.test.ts +++ b/src/features/config/form.test.ts @@ -38,6 +38,14 @@ describe("config/form", () => { expect(validate({ ...EMPTY_FORM, upstreams: [upstream] }).valid).toBe(true); }); + it("creates new upstream as disabled draft", () => { + const upstream = createEmptyUpstream(); + upstream.id = "openai-1"; + + expect(upstream.enabled).toBe(false); + expect(validate({ ...EMPTY_FORM, upstreams: [upstream] }).valid).toBe(true); + }); + it("extracts and merges unknown config keys as extras", () => { const payload = toPayload(EMPTY_FORM); const configWithExtras = { ...payload, foo: 1, bar: { nested: true } }; @@ -82,4 +90,3 @@ describe("config/form", () => { expect(payload.upstreams[0]?.convert_from_map).toBeUndefined(); }); }); - diff --git a/src/features/config/form.ts b/src/features/config/form.ts index a109f3d..a30bcab 100644 --- a/src/features/config/form.ts +++ b/src/features/config/form.ts @@ -93,7 +93,8 @@ export function createEmptyUpstream(): UpstreamForm { preferredEndpoint: "", proxyUrl: "", priority: "", - enabled: true, + // 新增上游默认作为草稿,避免用户尚未填完必填项时被“无法保存”阻塞。 + enabled: false, modelMappings: [], convertFromMap: {}, overrides: { header: [] }, diff --git a/src/features/config/sections.test.ts b/src/features/config/sections.test.ts new file mode 100644 index 0000000..506e3a2 --- /dev/null +++ b/src/features/config/sections.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; + +import { + DEFAULT_CONFIG_SECTION, + getSectionIdFromPathname, +} from "@/features/config/sections"; + +describe("config/sections", () => { + it("parses section id from config pathname", () => { + expect(getSectionIdFromPathname("/config/upstreams")).toBe("upstreams"); + expect(getSectionIdFromPathname("/config/providers")).toBe("providers"); + expect(getSectionIdFromPathname("/config/settings/")).toBe("settings"); + }); + + it("falls back to default section for invalid pathname", () => { + expect(getSectionIdFromPathname("/config")).toBe(DEFAULT_CONFIG_SECTION); + expect(getSectionIdFromPathname("/config/unknown")).toBe(DEFAULT_CONFIG_SECTION); + expect(getSectionIdFromPathname("/other")).toBe(DEFAULT_CONFIG_SECTION); + }); +}); + diff --git a/src/features/config/sections.ts b/src/features/config/sections.ts index 3a8bb2e..1d9dd47 100644 --- a/src/features/config/sections.ts +++ b/src/features/config/sections.ts @@ -115,3 +115,15 @@ export function findSection(sectionId: ConfigSectionId) { export function getSectionRoute(sectionId: ConfigSectionId) { return getSection(sectionId).route; } + +export function getSectionIdFromPathname(pathname: string) { + const normalizedPathname = pathname.replace(/\/+$/, "") || "/"; + if (normalizedPathname === "/config") { + return DEFAULT_CONFIG_SECTION; + } + if (!normalizedPathname.startsWith("/config/")) { + return DEFAULT_CONFIG_SECTION; + } + const section = normalizedPathname.slice("/config/".length); + return toConfigSectionId(section) ?? DEFAULT_CONFIG_SECTION; +} diff --git a/src/routes/config/route.tsx b/src/routes/config/route.tsx index 079d525..528f880 100644 --- a/src/routes/config/route.tsx +++ b/src/routes/config/route.tsx @@ -1,5 +1,14 @@ -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, useLocation } from "@tanstack/react-router"; + +import { ConfigScreen } from "@/features/config/ConfigScreen"; +import { getSectionIdFromPathname } from "@/features/config/sections"; + +function ConfigLayoutRoute() { + const location = useLocation(); + const sectionId = getSectionIdFromPathname(location.pathname); + return ; +} export const Route = createFileRoute("/config")({ - component: Outlet, + component: ConfigLayoutRoute, });