@pactor-app/skills 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/package.json +26 -0
  4. package/pactor/SKILL.md +373 -0
  5. package/pactor/references/dsl-authoring.md +448 -0
  6. package/prd-to-app/SKILL.md +75 -0
  7. package/prd-to-app/references/acceptance-checklist.md +47 -0
  8. package/prd-to-app/references/component-mapping.md +41 -0
  9. package/prd-to-app/references/mock-data-routes.md +66 -0
  10. package/prd-to-app/references/page-templates.md +274 -0
  11. package/prd-to-app/references/project-structure.md +89 -0
  12. package/prd-to-app/references/theme-and-style.md +41 -0
  13. package/prd-to-app/template/README.md +26 -0
  14. package/prd-to-app/template/app/[[...slug]]/page.tsx +7 -0
  15. package/prd-to-app/template/app/api/auth/login/route.ts +32 -0
  16. package/prd-to-app/template/app/api/auth/logout/route.ts +22 -0
  17. package/prd-to-app/template/app/api/data/[...parts]/route.ts +118 -0
  18. package/prd-to-app/template/app/api/public/pages/[id]/route.ts +40 -0
  19. package/prd-to-app/template/app/api/sitemap/route.ts +21 -0
  20. package/prd-to-app/template/app/api/users/route.ts +57 -0
  21. package/prd-to-app/template/app/favicon.ico +0 -0
  22. package/prd-to-app/template/app/globals.css +248 -0
  23. package/prd-to-app/template/app/layout.tsx +49 -0
  24. package/prd-to-app/template/app/login.css +122 -0
  25. package/prd-to-app/template/eslint.config.mjs +18 -0
  26. package/prd-to-app/template/next-env.d.ts +7 -0
  27. package/prd-to-app/template/next.config.js +32 -0
  28. package/prd-to-app/template/package.json +34 -0
  29. package/prd-to-app/template/pnpm-lock.yaml +6854 -0
  30. package/prd-to-app/template/pnpm-workspace.yaml +7 -0
  31. package/prd-to-app/template/postcss.config.mjs +7 -0
  32. package/prd-to-app/template/public/file.svg +1 -0
  33. package/prd-to-app/template/public/globe.svg +1 -0
  34. package/prd-to-app/template/public/next.svg +1 -0
  35. package/prd-to-app/template/public/skins/chenwunaifu.css +111 -0
  36. package/prd-to-app/template/public/skins/taijingshenlin.css +109 -0
  37. package/prd-to-app/template/public/skins/xuanzhimoyun.css +110 -0
  38. package/prd-to-app/template/public/vercel.svg +1 -0
  39. package/prd-to-app/template/public/window.svg +1 -0
  40. package/prd-to-app/template/scripts/build.mjs +35 -0
  41. package/prd-to-app/template/src/pactor/App.tsx +55 -0
  42. package/prd-to-app/template/src/pactor/actions.ts +438 -0
  43. package/prd-to-app/template/src/pactor/components/card-links.tsx +86 -0
  44. package/prd-to-app/template/src/pactor/components/chip-filter.tsx +89 -0
  45. package/prd-to-app/template/src/pactor/components/chip-node.tsx +46 -0
  46. package/prd-to-app/template/src/pactor/components/debug-chat.tsx +189 -0
  47. package/prd-to-app/template/src/pactor/components/index.ts +28 -0
  48. package/prd-to-app/template/src/pactor/components/log-status.tsx +64 -0
  49. package/prd-to-app/template/src/pactor/components/status-badge.tsx +46 -0
  50. package/prd-to-app/template/src/pactor/components/theme-switcher.tsx +136 -0
  51. package/prd-to-app/template/src/pactor/config.ts +28 -0
  52. package/prd-to-app/template/src/pactor/mocks/audit-mock.ts +403 -0
  53. package/prd-to-app/template/src/pactor/mocks/call-logs-mock.ts +875 -0
  54. package/prd-to-app/template/src/pactor/mocks/client-debug-mock.ts +188 -0
  55. package/prd-to-app/template/src/pactor/mocks/client-templates-mock.ts +497 -0
  56. package/prd-to-app/template/src/pactor/mocks/clients-mock.ts +483 -0
  57. package/prd-to-app/template/src/pactor/mocks/costs-mock.ts +406 -0
  58. package/prd-to-app/template/src/pactor/mocks/dashboard-mock.ts +192 -0
  59. package/prd-to-app/template/src/pactor/mocks/model-debug-mock.ts +144 -0
  60. package/prd-to-app/template/src/pactor/mocks/models-mock.ts +799 -0
  61. package/prd-to-app/template/src/pactor/mocks/ops-usage-mock.ts +806 -0
  62. package/prd-to-app/template/src/pactor/mocks/providers-mock.ts +424 -0
  63. package/prd-to-app/template/src/pactor/mocks/routes-mock.ts +604 -0
  64. package/prd-to-app/template/src/pactor/services.ts +185 -0
  65. package/prd-to-app/template/src/pactor/skin.ts +52 -0
  66. package/prd-to-app/template/src/server/accounts.ts +63 -0
  67. package/prd-to-app/template/src/server/cors.ts +34 -0
  68. package/prd-to-app/template/src/server/sitemap.ts +176 -0
  69. package/prd-to-app/template/src/server/yaml/common/dashboard.yaml +498 -0
  70. package/prd-to-app/template/src/server/yaml/common/login.yaml +84 -0
  71. package/prd-to-app/template/src/server/yaml/provider/provider-create.yaml +340 -0
  72. package/prd-to-app/template/src/server/yaml/provider/provider-detail.yaml +780 -0
  73. package/prd-to-app/template/src/server/yaml/provider/provider-edit.yaml +366 -0
  74. package/prd-to-app/template/src/server/yaml/provider/provider-list.yaml +1989 -0
  75. package/prd-to-app/template/src/server/yaml/route/route-create.yaml +511 -0
  76. package/prd-to-app/template/src/server/yaml/route/route-detail.yaml +610 -0
  77. package/prd-to-app/template/src/server/yaml/route/route-edit.yaml +809 -0
  78. package/prd-to-app/template/src/server/yaml/route/route-list.yaml +1793 -0
  79. package/prd-to-app/template/src/server/yaml/sitemap.yaml +250 -0
  80. package/prd-to-app/template/tsconfig.json +34 -0
@@ -0,0 +1,189 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { Button, ChatInput } from "@pactor-app/ui";
5
+
6
+ /**
7
+ * 调试台共享对话面板(客户端调试 / 模型调试 左侧对话区),DSL 自定义组件。
8
+ * PRD《调试台/00-总览》:Chat 对话式左右分栏,左栏 = 对话记录 + 输入框 + 清空对话。
9
+ * 纯展示组件:messages / sendDisabled / streaming 等经 DSL props 表达式下发,
10
+ * 交互经 events 上行(onSend(text) / onStop / onClear / onSelectRound(round)),
11
+ * 对话状态与流式模拟由页面注册的自定义 action 持有(见各页 *-actions.ts)。
12
+ */
13
+
14
+ export interface DebugChatMessage {
15
+ id: string;
16
+ role: "user" | "assistant" | "error";
17
+ content: string;
18
+ /** 关联的调用轮次对象(客户端调试:点击消息经 onSelectRound 整体上行,DSL 写入 selectedRound) */
19
+ round?: { id?: string };
20
+ /** assistant 消息下方的紧凑摘要(Token 用量 / TTFT / 延迟 / 上游状态码) */
21
+ meta?: string;
22
+ /** 流式输出中(内容边收边长,尾部带光标) */
23
+ streaming?: boolean;
24
+ }
25
+
26
+ /** 模拟流式输出:按固定节奏把 full 逐段吐出;onChunk 收到的是累计文本。
27
+ * 返回取消函数(停止生成 / 清空对话时调用)。 */
28
+ export function streamText(
29
+ full: string,
30
+ onChunk: (text: string) => void,
31
+ onDone: () => void,
32
+ opts?: { interval?: number; chunkSize?: number },
33
+ ): () => void {
34
+ const interval = opts?.interval ?? 36;
35
+ const chunkSize = opts?.chunkSize ?? 3;
36
+ let pos = 0;
37
+ const timer = setInterval(() => {
38
+ pos = Math.min(full.length, pos + chunkSize);
39
+ onChunk(full.slice(0, pos));
40
+ if (pos >= full.length) {
41
+ clearInterval(timer);
42
+ onDone();
43
+ }
44
+ }, interval);
45
+ return () => clearInterval(timer);
46
+ }
47
+
48
+ const ROLE_CONFIG: Record<
49
+ DebugChatMessage["role"],
50
+ { label: string; dot: string }
51
+ > = {
52
+ user: { label: "你", dot: "bg-primary" },
53
+ assistant: { label: "助手", dot: "bg-emerald-500" },
54
+ error: { label: "错误", dot: "bg-red-500" },
55
+ };
56
+
57
+ export interface DebugChatProps {
58
+ /** 消息列表(`${state.messages}` 整体表达式下发,数组类型保留) */
59
+ messages?: DebugChatMessage[];
60
+ /** 禁用发送(未选齐调用身份 / 供应商模型等) */
61
+ sendDisabled?: boolean;
62
+ /** 输入框占位文案(禁用时建议传入原因提示) */
63
+ placeholder?: string;
64
+ /** 流式生成中:输入区上方展示「停止生成」 */
65
+ streaming?: boolean;
66
+ /** 当前选中轮次 ID(高亮对应消息;与消息 round.id 比对) */
67
+ selectedRoundId?: string;
68
+ /** 空会话提示文案 */
69
+ emptyHint?: string;
70
+ // ── DSL events 注入(Renderer 包装为同名函数 prop,单参 event)──
71
+ onSend?: (event?: unknown) => void;
72
+ onStop?: (event?: unknown) => void;
73
+ onClear?: (event?: unknown) => void;
74
+ onSelectRound?: (event?: unknown) => void;
75
+ }
76
+
77
+ export function DebugChat({
78
+ messages,
79
+ sendDisabled,
80
+ placeholder,
81
+ streaming,
82
+ selectedRoundId,
83
+ emptyHint,
84
+ onSend,
85
+ onStop,
86
+ onClear,
87
+ onSelectRound,
88
+ }: DebugChatProps) {
89
+ const [draft, setDraft] = useState("");
90
+ const listRef = useRef<HTMLDivElement>(null);
91
+
92
+ // 新消息 / 流式追加时滚到底部
93
+ useEffect(() => {
94
+ const el = listRef.current;
95
+ if (el) el.scrollTop = el.scrollHeight;
96
+ }, [messages]);
97
+
98
+ const handleSubmit = (text: string) => {
99
+ onSend?.(text);
100
+ setDraft("");
101
+ };
102
+
103
+ return (
104
+ <div className="debug-chat-root flex min-h-[420px] flex-col gap-3">
105
+ {/* ── 对话记录 ── */}
106
+ <div
107
+ ref={listRef}
108
+ className="debug-chat-list h-[52vh] min-h-[320px] overflow-y-auto rounded-lg border bg-background p-4"
109
+ >
110
+ {!messages || messages.length === 0 ? (
111
+ <div className="flex h-full items-center justify-center text-sm text-muted-foreground">
112
+ {emptyHint ?? "暂无对话,输入消息开始调试"}
113
+ </div>
114
+ ) : (
115
+ <div className="flex flex-col gap-4">
116
+ {messages.map((msg) => {
117
+ const cfg = ROLE_CONFIG[msg.role];
118
+ const clickable = !!msg.round && !!onSelectRound;
119
+ const selected =
120
+ !!msg.round?.id && msg.round.id === selectedRoundId;
121
+ return (
122
+ <div
123
+ key={msg.id}
124
+ className={`rounded-lg p-1 ${clickable ? "cursor-pointer" : ""} ${
125
+ selected ? "ring-1 ring-primary" : ""
126
+ }`}
127
+ onClick={clickable ? () => onSelectRound(msg.round) : undefined}
128
+ >
129
+ <div className="mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
130
+ <span className={`size-2 rounded-full ${cfg.dot}`} />
131
+ {cfg.label}
132
+ {msg.streaming && (
133
+ <span className="text-muted-foreground/70">(生成中…)</span>
134
+ )}
135
+ </div>
136
+ <div
137
+ className={`whitespace-pre-wrap rounded-md px-3 py-2 text-sm leading-6 ${
138
+ msg.role === "user"
139
+ ? "bg-muted"
140
+ : msg.role === "error"
141
+ ? "text-red-600 [data-pactor-theme=dark]:text-red-400"
142
+ : ""
143
+ }`}
144
+ >
145
+ {msg.content}
146
+ {msg.streaming && (
147
+ <span className="ml-0.5 inline-block animate-pulse">▍</span>
148
+ )}
149
+ </div>
150
+ {msg.meta && (
151
+ <div className="mt-1 px-1 text-xs text-muted-foreground">
152
+ {msg.meta}
153
+ </div>
154
+ )}
155
+ </div>
156
+ );
157
+ })}
158
+ </div>
159
+ )}
160
+ </div>
161
+
162
+ {/* ── 停止生成(流式中) ── */}
163
+ {streaming && onStop && (
164
+ <div className="flex justify-center">
165
+ <Button text="停止生成" size="small" onClick={() => onStop()} />
166
+ </div>
167
+ )}
168
+
169
+ {/* ── 输入框(Enter 发送 / Shift+Enter 换行,ChatInput 内建) ── */}
170
+ <ChatInput
171
+ value={draft}
172
+ placeholder={placeholder ?? "输入消息..."}
173
+ disabled={sendDisabled || streaming}
174
+ onChange={setDraft}
175
+ onSubmit={handleSubmit}
176
+ />
177
+
178
+ {/* ── 清空对话 ── */}
179
+ <div className="flex justify-end">
180
+ <Button
181
+ text="清空对话"
182
+ size="small"
183
+ disabled={!messages || messages.length === 0}
184
+ onClick={() => onClear?.()}
185
+ />
186
+ </div>
187
+ </div>
188
+ );
189
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ /**
4
+ * 页面 DSL 引用的自定义组件注册表(页面级自定义组件
5
+ * `components={{...}}`),经 SitemapApp 的 `components` prop 全局注册。
6
+ */
7
+ import { ChipNode } from "./chip-node";
8
+ import { ChipFilter } from "./chip-filter";
9
+ import { StatusBadge } from "./status-badge";
10
+ import { LogStatus } from "./log-status";
11
+ import { DebugChat } from "./debug-chat";
12
+ import { CardLink, StatusToggleIcon, TextLink } from "./card-links";
13
+ import { ThemeSwitcher } from "./theme-switcher";
14
+
15
+ export const pactorComponents: Record<string, unknown> = {
16
+ ChipNode,
17
+ ChipFilter,
18
+ StatusBadge,
19
+ LogStatus,
20
+ DebugChat,
21
+ ProviderCardLink: CardLink,
22
+ ModelCardLink: CardLink,
23
+ RouteCardLink: CardLink,
24
+ TemplateCardLink: CardLink,
25
+ TextLink,
26
+ StatusToggleIcon,
27
+ ThemeSwitcher,
28
+ };
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ /**
4
+ * 调用日志状态色点徽标(运行管理 / 调用日志 共享的自定义组件)。
5
+ * 映射规则(PRD《运行管理/01-调用日志列表与详情》状态图例):
6
+ * success / 成功 → 绿点 成功(HTTP 2xx 且上游调用完成)
7
+ * error / 错误 → 红点 错误(4xx/5xx 或上游失败)
8
+ * unfinished / 未完成 → 橙点 未完成(请求中断、超时未完成)
9
+ * cancelled / 已取消 → 紫点 已取消(客户端或网关主动取消)
10
+ * 其他 → 灰点(未归类状态 / 未知值原样展示)
11
+ * status 由 DSL props 表达式求值后传入,
12
+ * 如 props: { status: "${row.status}" } / { status: "${data.detail.status}" }。
13
+ */
14
+ const STATUS_CONFIG: Record<string, { label: string; className: string }> = {
15
+ success: {
16
+ label: "成功",
17
+ className: "text-emerald-600 [data-pactor-theme=dark]:text-emerald-400",
18
+ },
19
+ error: {
20
+ label: "错误",
21
+ className: "text-red-600 [data-pactor-theme=dark]:text-red-400",
22
+ },
23
+ unfinished: {
24
+ label: "未完成",
25
+ className: "text-amber-600 [data-pactor-theme=dark]:text-amber-400",
26
+ },
27
+ cancelled: {
28
+ label: "已取消",
29
+ className: "text-violet-600 [data-pactor-theme=dark]:text-violet-400",
30
+ },
31
+ other: { label: "其他", className: "text-muted-foreground" },
32
+ 成功: {
33
+ label: "成功",
34
+ className: "text-emerald-600 [data-pactor-theme=dark]:text-emerald-400",
35
+ },
36
+ 错误: {
37
+ label: "错误",
38
+ className: "text-red-600 [data-pactor-theme=dark]:text-red-400",
39
+ },
40
+ 未完成: {
41
+ label: "未完成",
42
+ className: "text-amber-600 [data-pactor-theme=dark]:text-amber-400",
43
+ },
44
+ 已取消: {
45
+ label: "已取消",
46
+ className: "text-violet-600 [data-pactor-theme=dark]:text-violet-400",
47
+ },
48
+ 其他: { label: "其他", className: "text-muted-foreground" },
49
+ };
50
+
51
+ export function LogStatus({ status }: { status?: string }) {
52
+ const cfg = STATUS_CONFIG[status ?? ""] ?? {
53
+ label: status ?? "—",
54
+ className: "text-muted-foreground",
55
+ };
56
+ return (
57
+ <span
58
+ className={`inline-flex w-fit shrink-0 items-center gap-1.5 whitespace-nowrap text-xs font-medium ${cfg.className}`}
59
+ >
60
+ <span className="size-2 rounded-full bg-current" />
61
+ {cfg.label}
62
+ </span>
63
+ );
64
+ }
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ /**
4
+ * 启停状态徽标(Provider / Model / Route 页面共享的自定义组件)。
5
+ * 映射规则:
6
+ * enabled / 启用 → ● 启用(绿色底纹,globals.css .badge-enabled)
7
+ * disabled / 停用 → ● 停用(secondary 灰底)
8
+ * expired / 已过期 → ● 已过期(warning 琥珀底纹)
9
+ * draft / 草稿 → 草稿(secondary 灰底,无圆点)
10
+ * 其他 → ● <原值>
11
+ * status 由 DSL props 表达式求值后传入,
12
+ * 如 props: { status: "${data.detail.status}" } / { status: "${row.status}" }。
13
+ */
14
+ const STATUS_LABELS: Record<string, string> = {
15
+ enabled: "● 启用",
16
+ disabled: "● 停用",
17
+ expired: "● 已过期",
18
+ draft: "草稿",
19
+ 启用: "● 启用",
20
+ 停用: "● 停用",
21
+ 已过期: "● 已过期",
22
+ 草稿: "草稿",
23
+ };
24
+
25
+ const BASE =
26
+ "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap";
27
+
28
+ export function StatusBadge({ status }: { status?: string }) {
29
+ const enabled = status === "enabled" || status === "启用";
30
+ const expired = status === "expired" || status === "已过期";
31
+ const text = STATUS_LABELS[status ?? ""] ?? `● ${status ?? "—"}`;
32
+ return (
33
+ <span
34
+ data-slot="badge"
35
+ className={`${BASE} ${
36
+ enabled
37
+ ? "badge-enabled border-transparent bg-primary text-primary-foreground"
38
+ : expired
39
+ ? "border-amber-500/45 bg-amber-500/15 text-amber-600 [data-pactor-theme=dark]:text-amber-400"
40
+ : "border-transparent bg-secondary text-secondary-foreground"
41
+ }`}
42
+ >
43
+ {text}
44
+ </span>
45
+ );
46
+ }
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { Icon } from "@pactor-app/ui";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger,
10
+ } from "@pactor-app/ui/ui";
11
+ import {
12
+ applySkinStylesheet,
13
+ DEFAULT_SKIN,
14
+ SKINS,
15
+ SKIN_STORAGE_KEY,
16
+ } from "../skin";
17
+
18
+ /**
19
+ * 外壳 Header 右侧的皮肤切换器:
20
+ * - 皮肤下拉:三套 Theme Studio 皮肤(色点 + 名称,当前项打勾),
21
+ * 切换即换 <link data-pactor-skin> 皮肤样式表(app/skin.ts)并持久化
22
+ * localStorage(mc-skin);
23
+ * - 明暗切换:太阳/月亮按钮,写 `<html data-pactor-theme="dark">` 并
24
+ * 持久化 localStorage(mc-mode);缺省(system)跟随系统深浅色。
25
+ * 首屏皮肤 link 与明暗属性由 layout.tsx 内联脚本预置,组件挂载后接管切换。
26
+ */
27
+
28
+ type SkinValue = (typeof SKINS)[number]["value"];
29
+ type Mode = "light" | "dark" | "system";
30
+
31
+ function isDarkMode(mode: Mode) {
32
+ return (
33
+ mode === "dark" ||
34
+ (mode === "system" &&
35
+ window.matchMedia("(prefers-color-scheme: dark)").matches)
36
+ );
37
+ }
38
+
39
+ function applyMode(mode: Mode) {
40
+ const root = document.documentElement;
41
+ const dark = isDarkMode(mode);
42
+ if (dark) {
43
+ root.setAttribute("data-pactor-theme", "dark");
44
+ } else {
45
+ root.removeAttribute("data-pactor-theme");
46
+ }
47
+ return dark;
48
+ }
49
+
50
+ const triggerClass =
51
+ "flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground outline-hidden cursor-pointer";
52
+
53
+ export function ThemeSwitcher() {
54
+ const [skin, setSkin] = useState<SkinValue>(DEFAULT_SKIN);
55
+ const [mode, setMode] = useState<Mode>("system");
56
+ const [dark, setDark] = useState(false);
57
+
58
+ // 挂载后读取持久化偏好并接管系统深浅色监听(mode 为 system 时)
59
+ useEffect(() => {
60
+ const savedSkin = (localStorage.getItem(SKIN_STORAGE_KEY) ??
61
+ DEFAULT_SKIN) as SkinValue;
62
+ const savedMode = (localStorage.getItem("mc-mode") ?? "system") as Mode;
63
+ setSkin(savedSkin);
64
+ setMode(savedMode);
65
+ setDark(applyMode(savedMode));
66
+
67
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
68
+ const onSystemChange = () => {
69
+ if (savedMode === "system") setDark(applyMode("system"));
70
+ };
71
+ media.addEventListener("change", onSystemChange);
72
+ return () => media.removeEventListener("change", onSystemChange);
73
+ }, []);
74
+
75
+ const selectSkin = (value: SkinValue) => {
76
+ setSkin(value);
77
+ localStorage.setItem(SKIN_STORAGE_KEY, value);
78
+ applySkinStylesheet(value);
79
+ };
80
+
81
+ const toggleMode = () => {
82
+ const next: Mode = dark ? "light" : "dark";
83
+ setMode(next);
84
+ localStorage.setItem("mc-mode", next);
85
+ setDark(applyMode(next));
86
+ };
87
+
88
+ const current = SKINS.find((s) => s.value === skin) ?? SKINS[0];
89
+
90
+ return (
91
+ <div data-slot="theme-switcher" className="ml-auto flex items-center gap-1">
92
+ <DropdownMenu>
93
+ <DropdownMenuTrigger
94
+ data-slot="theme-switcher-trigger"
95
+ className={triggerClass}
96
+ >
97
+ <span
98
+ aria-hidden="true"
99
+ className="size-3 rounded-full border border-black/10"
100
+ style={{ backgroundColor: current.color }}
101
+ />
102
+ {current.label}
103
+ <Icon name="chevron-down" size="sm" />
104
+ </DropdownMenuTrigger>
105
+ <DropdownMenuContent align="end">
106
+ {SKINS.map((s) => (
107
+ <DropdownMenuItem
108
+ key={s.value}
109
+ className="cursor-pointer"
110
+ onClick={() => selectSkin(s.value)}
111
+ >
112
+ <span
113
+ aria-hidden="true"
114
+ className="size-3 rounded-full border border-black/10"
115
+ style={{ backgroundColor: s.color }}
116
+ />
117
+ {s.label}
118
+ {s.value === skin && (
119
+ <Icon name="check" size="sm" className="ml-auto" />
120
+ )}
121
+ </DropdownMenuItem>
122
+ ))}
123
+ </DropdownMenuContent>
124
+ </DropdownMenu>
125
+ <button
126
+ type="button"
127
+ data-slot="theme-mode-toggle"
128
+ aria-label={dark ? "切换为浅色" : "切换为深色"}
129
+ className={triggerClass}
130
+ onClick={toggleMode}
131
+ >
132
+ <Icon name={dark ? "sun" : "moon"} size="sm" />
133
+ </button>
134
+ </div>
135
+ );
136
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 入口配置(design/dynamic-page.md):开发者在此配置一个或多个
3
+ * 返回 sitemap 对象的接口 URL。多源时 pages / menus / layouts 按键合并
4
+ * (id / path 冲突后源覆盖前源并告警),app / user / permissions / auth
5
+ * 取第一个源。
6
+ *
7
+ * 外部接口:sources 只写相对路径,由 apiBaseUrl 补全为外部绝对地址;
8
+ * 服务端已开通 CORS(cookie 会话见 src/server/cors.ts),浏览器直连,
9
+ * 无需项目内代理。
10
+ */
11
+ export const sitemapSources: string[] = ["/api/sitemap"];
12
+
13
+ /**
14
+ * 懒加载页面端点(SitemapApp pageEndpoint)。
15
+ * 框架缺省为 /api/pages/{id};本仓库服务端路由在 app/api/public/pages/[id],
16
+ * 需显式覆盖,否则客户端仍会请求框架缺省路径。
17
+ */
18
+ export const pageEndpoint = (pageId: string) =>
19
+ `/api/public/pages/${pageId}`;
20
+
21
+ /**
22
+ * 外部接口基准地址(SitemapApp baseUrl)。
23
+ * 由 next.config.js 的 API_BASE_URLS 按构建环境(APP_ENV:
24
+ * build / build:dev / build:test → production / development / test)
25
+ * 解析,经 env 配置内联进客户端包;缺省回退本地开发地址。
26
+ */
27
+ export const apiBaseUrl =
28
+ process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:9011";