@allbluecn/admin-app 0.2.6 → 0.3.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 (204) hide show
  1. package/package.json +16 -12
  2. package/src/components/admin/admin-user-menu.tsx +2 -4
  3. package/src/components/admin/developer-log.tsx +3 -5
  4. package/src/components/ai-memory/memory-facts-panel.tsx +383 -0
  5. package/src/components/ai-prompts/preview-panel.tsx +165 -0
  6. package/src/components/ai-prompts/prompt-editor.tsx +328 -0
  7. package/src/components/ai-prompts/prompt-table.tsx +141 -0
  8. package/src/components/ai-prompts/prompt-utils.ts +124 -0
  9. package/src/components/ai-prompts/section-editor.tsx +135 -0
  10. package/src/components/ai-prompts/variable-panel.tsx +128 -0
  11. package/src/components/ai-prompts/version-history.tsx +157 -0
  12. package/src/components/ai-provider/provider-form.tsx +6 -10
  13. package/src/components/ai-provider/provider-table.tsx +33 -13
  14. package/src/components/auth/admin-login-form.tsx +1 -1
  15. package/src/components/component/component-form-dialog.tsx +4 -4
  16. package/src/components/component/component-import-dialog.tsx +2 -2
  17. package/src/components/dependency/changelog-dialog.tsx +2 -4
  18. package/src/components/dependency/columns.tsx +4 -8
  19. package/src/components/dependency/dependency-table.tsx +2 -2
  20. package/src/components/dependency/edit-dialog.tsx +7 -11
  21. package/src/components/invitations/invitations-page.tsx +4 -8
  22. package/src/components/knowledge-public/bookmark-form-dialog.tsx +1 -1
  23. package/src/components/knowledge-public/compliance-form-dialog.tsx +1 -1
  24. package/src/components/knowledge-public/legal-form-dialog.tsx +2 -2
  25. package/src/components/layout/sidebar-02/app-sidebar.tsx +17 -9
  26. package/src/components/layout/sidebar-02/nav-main.tsx +6 -14
  27. package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -7
  28. package/src/components/magicui/rainbow-button.tsx +2 -2
  29. package/src/components/market/columns.tsx +2 -4
  30. package/src/components/market/market-app-form.tsx +2 -2
  31. package/src/components/market/market-app-list.tsx +1 -1
  32. package/src/components/oauth-providers/provider-page.tsx +1 -1
  33. package/src/components/ui/alert-dialog.tsx +28 -57
  34. package/src/components/ui/alert.tsx +3 -4
  35. package/src/components/ui/avatar.tsx +5 -28
  36. package/src/components/ui/badge.tsx +19 -34
  37. package/src/components/ui/button.tsx +14 -41
  38. package/src/components/ui/card.tsx +6 -24
  39. package/src/components/ui/checkbox.tsx +4 -26
  40. package/src/components/ui/collapsible.tsx +6 -37
  41. package/src/components/ui/dialog.tsx +30 -60
  42. package/src/components/ui/dropdown-menu.tsx +118 -137
  43. package/src/components/ui/field.tsx +8 -27
  44. package/src/components/ui/input-group.tsx +13 -26
  45. package/src/components/ui/input.tsx +4 -21
  46. package/src/components/ui/label.tsx +3 -27
  47. package/src/components/ui/pagination.tsx +17 -31
  48. package/src/components/ui/select.tsx +101 -76
  49. package/src/components/ui/separator.tsx +4 -27
  50. package/src/components/ui/sheet.tsx +29 -58
  51. package/src/components/ui/sidebar.tsx +119 -117
  52. package/src/components/ui/skeleton.tsx +1 -18
  53. package/src/components/ui/switch.tsx +5 -24
  54. package/src/components/ui/table.tsx +1 -19
  55. package/src/components/ui/textarea.tsx +2 -20
  56. package/src/components/ui/toast.tsx +282 -0
  57. package/src/components/ui/tooltip.tsx +34 -45
  58. package/src/components/user/settings-activity-log.tsx +1 -1
  59. package/src/components/user/user-create-dialog.tsx +2 -2
  60. package/src/components/user/user-filters.tsx +2 -2
  61. package/src/hooks/use-mobile.ts +0 -17
  62. package/src/lib/pacer.ts +21 -0
  63. package/src/lib/utils.ts +1 -23
  64. package/src/paraglide/messages/_index.js +95 -0
  65. package/src/paraglide/messages/admin_ai_memory_action_clearuser1.js +30 -0
  66. package/src/paraglide/messages/admin_ai_memory_action_query.js +30 -0
  67. package/src/paraglide/messages/admin_ai_memory_clear_cancel.js +30 -0
  68. package/src/paraglide/messages/admin_ai_memory_clear_confirm_action.js +30 -0
  69. package/src/paraglide/messages/admin_ai_memory_clear_confirm_description.js +30 -0
  70. package/src/paraglide/messages/admin_ai_memory_clear_confirm_title.js +30 -0
  71. package/src/paraglide/messages/admin_ai_memory_columns_actions.js +30 -0
  72. package/src/paraglide/messages/admin_ai_memory_columns_content.js +30 -0
  73. package/src/paraglide/messages/admin_ai_memory_columns_createdat1.js +30 -0
  74. package/src/paraglide/messages/admin_ai_memory_columns_threadid1.js +30 -0
  75. package/src/paraglide/messages/admin_ai_memory_delete_cancel.js +30 -0
  76. package/src/paraglide/messages/admin_ai_memory_delete_confirm_action.js +30 -0
  77. package/src/paraglide/messages/admin_ai_memory_delete_confirm_description.js +30 -0
  78. package/src/paraglide/messages/admin_ai_memory_delete_confirm_title.js +30 -0
  79. package/src/paraglide/messages/admin_ai_memory_empty.js +30 -0
  80. package/src/paraglide/messages/admin_ai_memory_emptyhint1.js +30 -0
  81. package/src/paraglide/messages/admin_ai_memory_error_nouserid2.js +30 -0
  82. package/src/paraglide/messages/admin_ai_memory_error_query.js +30 -0
  83. package/src/paraglide/messages/admin_ai_memory_error_title.js +30 -0
  84. package/src/paraglide/messages/admin_ai_memory_form_user.js +30 -0
  85. package/src/paraglide/messages/admin_ai_memory_page_description.js +30 -0
  86. package/src/paraglide/messages/admin_ai_memory_page_title.js +30 -0
  87. package/src/paraglide/messages/admin_ai_memory_placeholder_user.js +30 -0
  88. package/src/paraglide/messages/admin_ai_memory_stat_count.js +30 -0
  89. package/src/paraglide/messages/admin_ai_memory_stat_lastwrite1.js +30 -0
  90. package/src/paraglide/messages/admin_ai_memory_toast_cleared.js +30 -0
  91. package/src/paraglide/messages/admin_ai_memory_toast_deleted.js +30 -0
  92. package/src/paraglide/messages/admin_ai_prompts_active.js +30 -0
  93. package/src/paraglide/messages/admin_ai_prompts_back.js +30 -0
  94. package/src/paraglide/messages/admin_ai_prompts_badge_protected.js +30 -0
  95. package/src/paraglide/messages/admin_ai_prompts_cache_hint.js +30 -0
  96. package/src/paraglide/messages/admin_ai_prompts_columns_active.js +30 -0
  97. package/src/paraglide/messages/admin_ai_prompts_columns_key.js +30 -0
  98. package/src/paraglide/messages/admin_ai_prompts_columns_name.js +30 -0
  99. package/src/paraglide/messages/admin_ai_prompts_columns_protected.js +30 -0
  100. package/src/paraglide/messages/admin_ai_prompts_columns_updatedat1.js +30 -0
  101. package/src/paraglide/messages/admin_ai_prompts_columns_version.js +30 -0
  102. package/src/paraglide/messages/admin_ai_prompts_description.js +30 -0
  103. package/src/paraglide/messages/admin_ai_prompts_description_placeholder.js +30 -0
  104. package/src/paraglide/messages/admin_ai_prompts_edit.js +30 -0
  105. package/src/paraglide/messages/admin_ai_prompts_name.js +30 -0
  106. package/src/paraglide/messages/admin_ai_prompts_name_placeholder.js +30 -0
  107. package/src/paraglide/messages/admin_ai_prompts_note_placeholder.js +30 -0
  108. package/src/paraglide/messages/admin_ai_prompts_page_description.js +30 -0
  109. package/src/paraglide/messages/admin_ai_prompts_page_title.js +30 -0
  110. package/src/paraglide/messages/admin_ai_prompts_preview_empty.js +30 -0
  111. package/src/paraglide/messages/admin_ai_prompts_preview_generating.js +30 -0
  112. package/src/paraglide/messages/admin_ai_prompts_preview_hint.js +30 -0
  113. package/src/paraglide/messages/admin_ai_prompts_preview_nocontent1.js +30 -0
  114. package/src/paraglide/messages/admin_ai_prompts_preview_placeholder.js +30 -0
  115. package/src/paraglide/messages/admin_ai_prompts_preview_section.js +30 -0
  116. package/src/paraglide/messages/admin_ai_prompts_preview_title.js +30 -0
  117. package/src/paraglide/messages/admin_ai_prompts_protected_hint.js +30 -0
  118. package/src/paraglide/messages/admin_ai_prompts_rollback.js +30 -0
  119. package/src/paraglide/messages/admin_ai_prompts_rollback_confirm_action.js +30 -0
  120. package/src/paraglide/messages/admin_ai_prompts_rollback_confirm_description.js +30 -0
  121. package/src/paraglide/messages/admin_ai_prompts_rollback_confirm_title.js +30 -0
  122. package/src/paraglide/messages/admin_ai_prompts_save.js +30 -0
  123. package/src/paraglide/messages/admin_ai_prompts_section_add.js +30 -0
  124. package/src/paraglide/messages/admin_ai_prompts_section_id.js +30 -0
  125. package/src/paraglide/messages/admin_ai_prompts_section_movedown1.js +30 -0
  126. package/src/paraglide/messages/admin_ai_prompts_section_moveup1.js +30 -0
  127. package/src/paraglide/messages/admin_ai_prompts_section_order.js +30 -0
  128. package/src/paraglide/messages/admin_ai_prompts_section_remove.js +30 -0
  129. package/src/paraglide/messages/admin_ai_prompts_section_template_placeholder.js +30 -0
  130. package/src/paraglide/messages/admin_ai_prompts_sections_empty.js +30 -0
  131. package/src/paraglide/messages/admin_ai_prompts_sections_hint.js +30 -0
  132. package/src/paraglide/messages/admin_ai_prompts_sections_title.js +30 -0
  133. package/src/paraglide/messages/admin_ai_prompts_toast_activated.js +30 -0
  134. package/src/paraglide/messages/admin_ai_prompts_toast_failed.js +30 -0
  135. package/src/paraglide/messages/admin_ai_prompts_toast_protected_locked.js +30 -0
  136. package/src/paraglide/messages/admin_ai_prompts_toast_rolled_back.js +30 -0
  137. package/src/paraglide/messages/admin_ai_prompts_toast_save_failed.js +30 -0
  138. package/src/paraglide/messages/admin_ai_prompts_toast_saved.js +30 -0
  139. package/src/paraglide/messages/admin_ai_prompts_variable_add.js +30 -0
  140. package/src/paraglide/messages/admin_ai_prompts_variable_declarehint1.js +30 -0
  141. package/src/paraglide/messages/admin_ai_prompts_variable_description_placeholder.js +30 -0
  142. package/src/paraglide/messages/admin_ai_prompts_variable_name_placeholder.js +30 -0
  143. package/src/paraglide/messages/admin_ai_prompts_variable_remove.js +30 -0
  144. package/src/paraglide/messages/admin_ai_prompts_variable_undeclared.js +30 -0
  145. package/src/paraglide/messages/admin_ai_prompts_variables_empty.js +30 -0
  146. package/src/paraglide/messages/admin_ai_prompts_variables_title.js +30 -0
  147. package/src/paraglide/messages/admin_ai_prompts_versions_current.js +30 -0
  148. package/src/paraglide/messages/admin_ai_prompts_versions_empty.js +30 -0
  149. package/src/paraglide/messages/admin_ai_prompts_versions_hint.js +30 -0
  150. package/src/paraglide/messages/admin_ai_prompts_versions_nonote1.js +30 -0
  151. package/src/paraglide/messages/admin_ai_prompts_versions_title.js +30 -0
  152. package/src/paraglide/messages/admin_ai_prompts_versions_unknowncreator1.js +30 -0
  153. package/src/paraglide/messages/admin_ai_table_seedfromseed2.js +30 -0
  154. package/src/paraglide/messages/admin_ai_table_toast_seedcreated1.js +30 -0
  155. package/src/paraglide/messages/admin_ai_table_toast_seedfailed1.js +30 -0
  156. package/src/paraglide/messages/admin_ai_table_toast_seednonew2.js +30 -0
  157. package/src/paraglide/messages/common_monitoring_aiusage_sandbox1.js +30 -0
  158. package/src/paraglide/messages/common_monitoring_aiusage_sandboxvalue2.js +30 -0
  159. package/src/paraglide/messages/common_nav_aiprompts1.js +30 -0
  160. package/src/routeTree.gen.ts +101 -0
  161. package/src/routes/__root.tsx +2 -2
  162. package/src/{components/ui/sonner.tsx → routes/_nav/ai-memory/route.lazy.tsx} +24 -16
  163. package/src/routes/_nav/ai-memory/route.tsx +31 -0
  164. package/src/routes/_nav/ai-prompts/$key/route.lazy.tsx +32 -0
  165. package/src/routes/_nav/ai-prompts/$key/route.tsx +37 -0
  166. package/src/routes/_nav/ai-prompts/index/route.lazy.tsx +47 -0
  167. package/src/routes/_nav/ai-prompts/route.lazy.tsx +26 -0
  168. package/src/routes/_nav/ai-prompts/route.tsx +31 -0
  169. package/src/routes/_nav/compliance/route.lazy.tsx +2 -4
  170. package/src/routes/_nav/components/route.lazy.tsx +1 -1
  171. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +1 -1
  172. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +1 -1
  173. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +1 -1
  174. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +1 -1
  175. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +10 -1
  176. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +1 -1
  177. package/src/routes/_nav/monitoring/health/route.lazy.tsx +1 -1
  178. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +1 -1
  179. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +1 -1
  180. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +1 -1
  181. package/src/routes/_nav/permissions/route.lazy.tsx +2 -2
  182. package/src/routes/_nav/sso/route.lazy.tsx +1 -1
  183. package/src/routes/_nav/users/$userId/route.lazy.tsx +3 -3
  184. package/src/routes/setup/route.lazy.tsx +1 -1
  185. package/src/server/__tests__/prompt-defaults.test.ts +139 -0
  186. package/src/server/admin-ai-usage.ts +22 -4
  187. package/src/server/ai-memory/store.ts +103 -0
  188. package/src/server/serverFns/ai-memory/clear.ts +31 -0
  189. package/src/server/serverFns/ai-memory/delete.ts +31 -0
  190. package/src/server/serverFns/ai-memory/list-users.ts +40 -0
  191. package/src/server/serverFns/ai-memory/list.ts +54 -0
  192. package/src/server/serverFns/ai-prompts/activate.ts +53 -0
  193. package/src/server/serverFns/ai-prompts/get.ts +74 -0
  194. package/src/server/serverFns/ai-prompts/list.ts +55 -0
  195. package/src/server/serverFns/ai-prompts/preview.ts +66 -0
  196. package/src/server/serverFns/ai-prompts/prompt-defaults.ts +63 -0
  197. package/src/server/serverFns/ai-prompts/prompt-schemas.ts +47 -0
  198. package/src/server/serverFns/ai-prompts/rollback.ts +86 -0
  199. package/src/server/serverFns/ai-prompts/save.ts +129 -0
  200. package/src/server/serverFns/ai-provider/seed-from-seed.ts +64 -0
  201. package/src/styles/globals.css +92 -77
  202. package/src/components/layout/sidebar-02/logo.tsx +0 -40
  203. package/src/components/layout/sidebar-02/team-switcher.tsx +0 -106
  204. package/src/components/providers/theme-toggle.tsx +0 -59
@@ -0,0 +1,53 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/activate.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { prisma } from "@allbluecn/database"
22
+ import { requireAdmin } from "../../auth-helpers"
23
+
24
+ const activateSchema = z.object({
25
+ key: z.string(),
26
+ isActive: z.boolean(),
27
+ })
28
+
29
+ /** 切换启用状态;受保护模板不可停用 */
30
+ export const activatePromptTemplateFn = createServerFn({ method: "POST" })
31
+ .validator(activateSchema)
32
+ .handler(async ({ data }) => {
33
+ const session = await requireAdmin()
34
+ const updatedBy = session.user?.id ?? null
35
+ const existing = await prisma.systemPromptTemplate.findUnique({
36
+ where: { key: data.key },
37
+ })
38
+ if (!existing) throw new Error("模板不存在")
39
+ if (!data.isActive && existing.isProtected) {
40
+ throw new Error("受保护的模板不可停用(停用会回退到内置默认,可能降低安全策略)")
41
+ }
42
+ const updated = await prisma.systemPromptTemplate.update({
43
+ where: { id: existing.id },
44
+ data: { isActive: data.isActive, updatedBy },
45
+ })
46
+ return {
47
+ id: updated.id,
48
+ key: updated.key,
49
+ isActive: updated.isActive,
50
+ isProtected: updated.isProtected,
51
+ updatedAt: updated.updatedAt.toISOString(),
52
+ }
53
+ })
@@ -0,0 +1,74 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/get.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { prisma } from "@allbluecn/database"
22
+ import type {
23
+ SystemPromptSectionDTO,
24
+ SystemPromptTemplateDTO,
25
+ } from "@allbluecn/kernel"
26
+ import { requireAdmin } from "../../auth-helpers"
27
+ import { backfillMissingPromptTemplates } from "./prompt-defaults"
28
+
29
+ const getInput = z.object({ key: z.string() })
30
+
31
+ export interface PromptVersionSummary {
32
+ id: string
33
+ version: number
34
+ note: string | null
35
+ createdBy: string | null
36
+ createdAt: string
37
+ }
38
+
39
+ /** 按 key 取单个模板 + 版本历史(历史只带元数据,不含 sections 快照正文) */
40
+ export const getPromptTemplateFn = createServerFn({ method: "GET" })
41
+ .validator(getInput)
42
+ .handler(async ({ data }) => {
43
+ await requireAdmin()
44
+ await backfillMissingPromptTemplates()
45
+ const template = await prisma.systemPromptTemplate.findUnique({
46
+ where: { key: data.key },
47
+ })
48
+ if (!template) throw new Error("模板不存在")
49
+ const versions = await prisma.systemPromptVersion.findMany({
50
+ where: { templateId: template.id },
51
+ orderBy: { version: "desc" },
52
+ take: 30,
53
+ })
54
+ const dto: SystemPromptTemplateDTO = {
55
+ id: template.id,
56
+ key: template.key,
57
+ name: template.name,
58
+ description: template.description,
59
+ sections: template.sections as unknown as SystemPromptSectionDTO[],
60
+ variables: template.variables as unknown as SystemPromptTemplateDTO["variables"],
61
+ version: template.version,
62
+ isActive: template.isActive,
63
+ isProtected: template.isProtected,
64
+ updatedAt: template.updatedAt.toISOString(),
65
+ }
66
+ const versionSummaries: PromptVersionSummary[] = versions.map((v) => ({
67
+ id: v.id,
68
+ version: v.version,
69
+ note: v.note,
70
+ createdBy: v.createdBy,
71
+ createdAt: v.createdAt.toISOString(),
72
+ }))
73
+ return { template: dto, versions: versionSummaries }
74
+ })
@@ -0,0 +1,55 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/list.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { prisma } from "@allbluecn/database"
22
+ import { requireAdmin } from "../../auth-helpers"
23
+ import { backfillMissingPromptTemplates } from "./prompt-defaults"
24
+
25
+ /** 列出提示词模板(列表页只取轻量字段,详情经 get 加载) */
26
+ export const listPromptTemplatesFn = createServerFn({ method: "GET" })
27
+ .validator(z.object({}))
28
+ .handler(
29
+ async () => {
30
+ await requireAdmin()
31
+ await backfillMissingPromptTemplates()
32
+ const templates = await prisma.systemPromptTemplate.findMany({
33
+ orderBy: [{ key: "asc" }],
34
+ take: 50,
35
+ select: {
36
+ id: true,
37
+ key: true,
38
+ name: true,
39
+ version: true,
40
+ isActive: true,
41
+ isProtected: true,
42
+ updatedAt: true,
43
+ },
44
+ })
45
+ return templates.map((t) => ({
46
+ id: t.id,
47
+ key: t.key,
48
+ name: t.name,
49
+ version: t.version,
50
+ isActive: t.isActive,
51
+ isProtected: t.isProtected,
52
+ updatedAt: t.updatedAt.toISOString(),
53
+ }))
54
+ },
55
+ )
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/preview.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { interpolateTemplate, UnknownPromptVariableError } from "@allbluecn/kernel"
22
+ import { requireAdmin } from "../../auth-helpers"
23
+ import { previewSectionSchema } from "./prompt-schemas"
24
+
25
+ const previewSchema = z.object({
26
+ key: z.string(),
27
+ // 用宽容 schema:编辑中临时空分区不应让整个预览失败(见 prompt-schemas.ts)
28
+ sections: z.array(previewSectionSchema),
29
+ vars: z.record(z.string(), z.string()),
30
+ })
31
+
32
+ // admin-only 宽容行为,与运行时严格性故意不同:
33
+ // 运行时(apps/web ai-gateway)对未知变量直接抛 UnknownPromptVariableError 使请求失败;
34
+ // 预览场景下内置变量由服务端填充占位串(下方 PREVIEW_VAR_PLACEHOLDERS),
35
+ // 用户新增的未知变量仍返回 { ok:false, error } 而非静默留空。
36
+ const PREVIEW_VAR_PLACEHOLDERS: Record<string, string> = {
37
+ now: "[当前时间]",
38
+ locale: "[语言]",
39
+ userName: "[用户名]",
40
+ workspaceName: "[工作区名]",
41
+ }
42
+
43
+ /** 编辑器实时预览:按 order 拼合分区,未知变量返回 error 而非中断整个调用 */
44
+ export const previewPromptFn = createServerFn({ method: "POST" })
45
+ .validator(previewSchema)
46
+ .handler(async ({ data }) => {
47
+ await requireAdmin()
48
+ const vars = { ...PREVIEW_VAR_PLACEHOLDERS, ...data.vars }
49
+ try {
50
+ const sections = data.sections
51
+ .filter((s) => s.template.trim().length > 0)
52
+ .slice()
53
+ .sort((a, b) => a.order - b.order)
54
+ .map((s) => ({
55
+ id: s.id,
56
+ order: s.order,
57
+ text: interpolateTemplate(s.template, vars),
58
+ }))
59
+ return { ok: true as const, sections }
60
+ } catch (e) {
61
+ if (e instanceof UnknownPromptVariableError) {
62
+ return { ok: false as const, error: `未知变量: ${e.variable}` }
63
+ }
64
+ throw e
65
+ }
66
+ })
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/prompt-defaults.ts
19
+ import { prisma, type Prisma } from "@allbluecn/database"
20
+ import { listPromptDefinitions } from "@allbluecn/web-app/ai-gateway/prompts/definitions"
21
+
22
+ // 提示词的唯一来源是代码内置定义(apps/web ai-gateway/prompts/builtin.ts);
23
+ // admin 后台只是查看与二次编辑的入口,DB 永远不该是「唯一事实」——
24
+ // 只要 admin 单独部署或 web 进程尚未执行过 ensurePromptSeeded,DB 里就
25
+ // 可能一行都没有。这里把缺失的 key 按内置默认值补进 DB,保证后台任何
26
+ // 时刻都能看到默认提示词,且默认值绝不可能是空串。
27
+
28
+ export const BACKFILL_SNAPSHOT_NOTE =
29
+ "初始化自代码内置默认提示词(可在后台编辑)"
30
+
31
+ /** 只补缺失的 key:已存在的行(含被停用或留空的)一律不动 */
32
+ function toJson(value: unknown): Prisma.InputJsonValue {
33
+ return value as unknown as Prisma.InputJsonValue
34
+ }
35
+
36
+ export async function backfillMissingPromptTemplates() {
37
+ const defaults = listPromptDefinitions()
38
+ const existing = await prisma.systemPromptTemplate.findMany({
39
+ select: { key: true },
40
+ })
41
+ const known = new Set(existing.map((row) => row.key))
42
+ const missing = defaults.filter((definition) => !known.has(definition.key))
43
+ if (missing.length === 0) return 0
44
+ try {
45
+ await prisma.systemPromptTemplate.createMany({
46
+ data: missing.map((definition) => ({
47
+ key: definition.key,
48
+ name: definition.name,
49
+ description: definition.description ?? null,
50
+ sections: toJson(definition.sections),
51
+ variables: toJson(definition.variables),
52
+ version: 1,
53
+ isProtected: definition.isProtected,
54
+ })),
55
+ })
56
+ return missing.length
57
+ } catch {
58
+ // createMany 是原子的:并发补种时先到的那次已经写完整批,
59
+ // 后到的整批失败——此时行已存在,返回 0 由调用方按现状读取。
60
+ return 0
61
+ }
62
+ }
63
+
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/prompt-schemas.ts
19
+ import { z } from "zod"
20
+
21
+ const PROMPT_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/
22
+
23
+ const promptSectionShape = {
24
+ id: z.string().min(1),
25
+ order: z.number().int(),
26
+ }
27
+
28
+ export const promptSectionSchema = z.object({
29
+ ...promptSectionShape,
30
+ // trim().min(1) 同时拦截空串与纯空白:否则落库后 web 侧
31
+ // parseSections 会丢弃该行并静默回落内置默认,后台编辑等于无效。
32
+ template: z.string().trim().min(1, "分区正文不能为空"),
33
+ })
34
+
35
+ // 预览故意比保存宽容:清空某个分区的瞬间不能让整个预览面板变空白。
36
+ // 空分区在 preview.ts 的 handler 内过滤,非空约束只在写入路径强制。
37
+ export const previewSectionSchema = z.object({
38
+ ...promptSectionShape,
39
+ template: z.string(),
40
+ })
41
+
42
+ export const promptVariableSchema = z.object({
43
+ name: z
44
+ .string()
45
+ .regex(PROMPT_VAR_NAME_RE, "变量名须以字母或下划线开头,仅含字母数字下划线"),
46
+ description: z.string(),
47
+ })
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/rollback.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { prisma, Prisma } from "@allbluecn/database"
22
+ import type {
23
+ PromptVariableDefDTO,
24
+ SystemPromptSectionDTO,
25
+ } from "@allbluecn/kernel"
26
+ import { requireAdmin } from "../../auth-helpers"
27
+
28
+ const rollbackSchema = z.object({
29
+ key: z.string(),
30
+ version: z.number().int(),
31
+ expectedVersion: z.number().int(),
32
+ })
33
+
34
+ function toJson(value: unknown): Prisma.InputJsonValue {
35
+ return value as unknown as Prisma.InputJsonValue
36
+ }
37
+
38
+ /** 回滚到指定历史版本:先快照当前内容,再把快照 sections 写回模板并 bump version */
39
+ export const rollbackPromptTemplateFn = createServerFn({ method: "POST" })
40
+ .validator(rollbackSchema)
41
+ .handler(async ({ data }) => {
42
+ const session = await requireAdmin()
43
+ const updatedBy = session.user?.id ?? null
44
+ return prisma.$transaction(async (tx) => {
45
+ const existing = await tx.systemPromptTemplate.findUnique({
46
+ where: { key: data.key },
47
+ })
48
+ if (!existing) throw new Error("模板不存在")
49
+ if (existing.version !== data.expectedVersion) {
50
+ throw new Error("版本冲突,请刷新后重试")
51
+ }
52
+ const snapshot = await tx.systemPromptVersion.findUnique({
53
+ where: { templateId_version: { templateId: existing.id, version: data.version } },
54
+ })
55
+ if (!snapshot) throw new Error("未找到该版本快照")
56
+ await tx.systemPromptVersion.create({
57
+ data: {
58
+ templateId: existing.id,
59
+ version: existing.version,
60
+ sections: toJson(existing.sections),
61
+ note: `回滚自 v${existing.version}`,
62
+ createdBy: updatedBy,
63
+ },
64
+ })
65
+ const updated = await tx.systemPromptTemplate.update({
66
+ where: { id: existing.id },
67
+ data: {
68
+ sections: toJson(snapshot.sections),
69
+ version: existing.version + 1,
70
+ updatedBy,
71
+ },
72
+ })
73
+ return {
74
+ id: updated.id,
75
+ key: updated.key,
76
+ name: updated.name,
77
+ description: updated.description,
78
+ sections: updated.sections as unknown as SystemPromptSectionDTO[],
79
+ variables: updated.variables as unknown as PromptVariableDefDTO[],
80
+ version: updated.version,
81
+ isActive: updated.isActive,
82
+ isProtected: updated.isProtected,
83
+ updatedAt: updated.updatedAt.toISOString(),
84
+ }
85
+ })
86
+ })
@@ -0,0 +1,129 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-prompts/save.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { prisma, Prisma } from "@allbluecn/database"
22
+ import { requireAdmin } from "../../auth-helpers"
23
+ import { promptSectionSchema, promptVariableSchema } from "./prompt-schemas"
24
+ import { getPromptDefinition } from "@allbluecn/web-app/ai-gateway/prompts/definitions"
25
+ import { BACKFILL_SNAPSHOT_NOTE } from "./prompt-defaults"
26
+
27
+ const saveSchema = z.object({
28
+ key: z.string(),
29
+ expectedVersion: z.number().int(),
30
+ name: z.string().min(1).optional(),
31
+ description: z.string().nullish(),
32
+ sections: z.array(promptSectionSchema).min(1),
33
+ note: z.string().max(200).optional(),
34
+ variables: z.array(promptVariableSchema).default([]),
35
+ })
36
+
37
+ function toJson(value: unknown): Prisma.InputJsonValue {
38
+ return value as unknown as Prisma.InputJsonValue
39
+ }
40
+
41
+ /** 保存分区 + 变量,带乐观锁;成功后先落历史快照再 bump version */
42
+ export const savePromptTemplateFn = createServerFn({ method: "POST" })
43
+ .validator(saveSchema)
44
+ .handler(async ({ data }) => {
45
+ const session = await requireAdmin()
46
+ const updatedBy = session.user?.id ?? null
47
+ return prisma.$transaction(async (tx) => {
48
+ const existing = await tx.systemPromptTemplate.findUnique({
49
+ where: { key: data.key },
50
+ })
51
+ if (!existing) {
52
+ // 后台首屏看到的是补种进来的「代码内置默认值」,此时还没有历史快照。
53
+ // 首次保存以默认正文补一份 v1 快照再升到 v2,与下方更新路径保持一致
54
+ // 的不变量:当前版本无快照,快照只保存历史。
55
+ const definition = getPromptDefinition(data.key)
56
+ if (!definition) throw new Error("提示词不在代码内置定义中")
57
+ const created = await tx.systemPromptTemplate.create({
58
+ data: {
59
+ key: data.key,
60
+ name: data.name ?? definition.name,
61
+ description: data.description ?? definition.description ?? null,
62
+ sections: toJson(data.sections),
63
+ variables: toJson(data.variables),
64
+ version: 2,
65
+ isProtected: definition.isProtected,
66
+ updatedBy,
67
+ },
68
+ })
69
+ await tx.systemPromptVersion.create({
70
+ data: {
71
+ templateId: created.id,
72
+ version: 1,
73
+ sections: toJson(definition.sections),
74
+ note: BACKFILL_SNAPSHOT_NOTE,
75
+ createdBy: updatedBy,
76
+ },
77
+ })
78
+ return {
79
+ id: created.id,
80
+ key: created.key,
81
+ name: created.name,
82
+ description: created.description,
83
+ sections: created.sections as unknown as typeof data.sections,
84
+ variables: created.variables as unknown as typeof data.variables,
85
+ version: created.version,
86
+ isActive: created.isActive,
87
+ isProtected: created.isProtected,
88
+ updatedAt: created.updatedAt.toISOString(),
89
+ }
90
+ }
91
+ if (existing.version !== data.expectedVersion) {
92
+ throw new Error("版本冲突,请刷新后重试")
93
+ }
94
+ await tx.systemPromptVersion.create({
95
+ data: {
96
+ templateId: existing.id,
97
+ version: existing.version,
98
+ sections: toJson(existing.sections),
99
+ note: data.note ?? `保存前 v${existing.version}`,
100
+ createdBy: updatedBy,
101
+ },
102
+ })
103
+ const updated = await tx.systemPromptTemplate.update({
104
+ where: { id: existing.id },
105
+ data: {
106
+ ...(data.name !== undefined ? { name: data.name } : {}),
107
+ ...(data.description !== undefined
108
+ ? { description: data.description }
109
+ : {}),
110
+ sections: toJson(data.sections),
111
+ variables: toJson(data.variables),
112
+ version: existing.version + 1,
113
+ updatedBy,
114
+ },
115
+ })
116
+ return {
117
+ id: updated.id,
118
+ key: updated.key,
119
+ name: updated.name,
120
+ description: updated.description,
121
+ sections: updated.sections as unknown as typeof data.sections,
122
+ variables: updated.variables as unknown as typeof data.variables,
123
+ version: updated.version,
124
+ isActive: updated.isActive,
125
+ isProtected: updated.isProtected,
126
+ updatedAt: updated.updatedAt.toISOString(),
127
+ }
128
+ })
129
+ })
@@ -0,0 +1,64 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // apps/admin/src/server/serverFns/ai-provider/seed-from-seed.ts
19
+ import { createServerFn } from "@tanstack/react-start"
20
+ import { z } from "zod"
21
+ import { prisma } from "@allbluecn/database"
22
+ import { SEED_AI_PROVIDERS } from "@allbluecn/kernel"
23
+ import { requireAdmin } from "../../auth-helpers"
24
+
25
+ export const seedFromSeedFn = createServerFn({ method: "POST" })
26
+ .validator(z.object({}))
27
+ .handler(async () => {
28
+ await requireAdmin()
29
+ const created: string[] = []
30
+ const skipped: string[] = []
31
+ for (let i = 0; i < SEED_AI_PROVIDERS.length; i++) {
32
+ const p = SEED_AI_PROVIDERS[i]!
33
+ const existing = await prisma.aiProvider.findUnique({
34
+ where: { providerId: p.providerId },
35
+ })
36
+ if (existing) {
37
+ skipped.push(p.providerId)
38
+ continue
39
+ }
40
+ await prisma.aiProvider.create({
41
+ data: {
42
+ providerId: p.providerId,
43
+ displayName: p.displayName,
44
+ brandName: p.brandName,
45
+ description: p.description,
46
+ category: p.category,
47
+ adapterKind: p.adapterKind,
48
+ defaultBaseUrl: p.defaultBaseUrl ?? null,
49
+ keyUrl: p.keyUrl ?? null,
50
+ docsUrl: p.docsUrl ?? null,
51
+ keyPlaceholder: p.keyPlaceholder ?? null,
52
+ iconSlug: p.iconSlug ?? null,
53
+ brandColor: p.brandColor,
54
+ badge: p.badge ?? null,
55
+ isLocal: p.isLocal ?? false,
56
+ models: p.models as any,
57
+ sortOrder: i,
58
+ enabled: true,
59
+ },
60
+ })
61
+ created.push(p.providerId)
62
+ }
63
+ return { created, skipped }
64
+ })