@allbluecn/admin-app 0.1.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.
- package/package.json +86 -0
- package/src/client.tsx +31 -0
- package/src/components/admin/admin-user-menu.tsx +90 -0
- package/src/components/admin/developer-log.tsx +356 -0
- package/src/components/ai-provider/model-list-editor.tsx +147 -0
- package/src/components/ai-provider/provider-form.tsx +434 -0
- package/src/components/ai-provider/provider-icon.tsx +43 -0
- package/src/components/ai-provider/provider-logo.tsx +87 -0
- package/src/components/ai-provider/provider-table.tsx +258 -0
- package/src/components/auth/admin-login-form.tsx +214 -0
- package/src/components/auth/auth-brand-panel.tsx +60 -0
- package/src/components/auth/auth-header.tsx +38 -0
- package/src/components/auth/auth-layout.tsx +66 -0
- package/src/components/auth/auth-providers.tsx +34 -0
- package/src/components/auth/icon-input.tsx +46 -0
- package/src/components/auth/oauth-section.tsx +51 -0
- package/src/components/auth/password-input.tsx +67 -0
- package/src/components/common/page-header.tsx +42 -0
- package/src/components/common/placeholder-page.tsx +42 -0
- package/src/components/component/component-form-dialog.tsx +348 -0
- package/src/components/component/component-import-dialog.tsx +235 -0
- package/src/components/component/component-table.tsx +153 -0
- package/src/components/data-table/data-table-pagination.tsx +114 -0
- package/src/components/data-table/data-table.tsx +218 -0
- package/src/components/data-table/table-detail-layout.tsx +99 -0
- package/src/components/dependency/changelog-dialog.tsx +108 -0
- package/src/components/dependency/columns.tsx +232 -0
- package/src/components/dependency/dependency-table.tsx +368 -0
- package/src/components/dependency/edit-dialog.tsx +200 -0
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +215 -0
- package/src/components/knowledge-public/compliance-form-dialog.tsx +308 -0
- package/src/components/knowledge-public/legal-form-dialog.tsx +315 -0
- package/src/components/layout/sidebar-02/app-sidebar.tsx +160 -0
- package/src/components/layout/sidebar-02/index.tsx +33 -0
- package/src/components/layout/sidebar-02/logo.tsx +40 -0
- package/src/components/layout/sidebar-02/nav-main.tsx +153 -0
- package/src/components/layout/sidebar-02/nav-notifications.tsx +78 -0
- package/src/components/layout/sidebar-02/team-switcher.tsx +106 -0
- package/src/components/magicui/animated-theme-toggler.tsx +272 -0
- package/src/components/magicui/rainbow-button.tsx +78 -0
- package/src/components/market/columns.tsx +151 -0
- package/src/components/market/market-app-form.tsx +237 -0
- package/src/components/market/market-app-list.tsx +156 -0
- package/src/components/monitoring/error-list.tsx +143 -0
- package/src/components/monitoring/health-cards.tsx +152 -0
- package/src/components/monitoring/metrics-stats.tsx +184 -0
- package/src/components/monitoring/request-log-table.tsx +179 -0
- package/src/components/providers/theme-toggle.tsx +59 -0
- package/src/components/settings/avatar/avatar-config.ts +46 -0
- package/src/components/ui/alert-dialog.tsx +216 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/avatar.tsx +129 -0
- package/src/components/ui/badge.tsx +66 -0
- package/src/components/ui/button.tsx +84 -0
- package/src/components/ui/card.tsx +120 -0
- package/src/components/ui/checkbox.tsx +50 -0
- package/src/components/ui/collapsible.tsx +50 -0
- package/src/components/ui/dialog.tsx +185 -0
- package/src/components/ui/dropdown-menu.tsx +286 -0
- package/src/components/ui/field.tsx +255 -0
- package/src/components/ui/input-group.tsx +171 -0
- package/src/components/ui/input.tsx +36 -0
- package/src/components/ui/label.tsx +41 -0
- package/src/components/ui/pagination.tsx +146 -0
- package/src/components/ui/select.tsx +209 -0
- package/src/components/ui/separator.tsx +45 -0
- package/src/components/ui/sheet.tsx +164 -0
- package/src/components/ui/sidebar.tsx +719 -0
- package/src/components/ui/skeleton.tsx +30 -0
- package/src/components/ui/sonner.tsx +37 -0
- package/src/components/ui/switch.tsx +48 -0
- package/src/components/ui/table.tsx +133 -0
- package/src/components/ui/textarea.tsx +35 -0
- package/src/components/ui/tooltip.tsx +74 -0
- package/src/components/user/columns.tsx +153 -0
- package/src/components/user/settings-activity-log.tsx +358 -0
- package/src/components/user/settings-profile/constants.ts +42 -0
- package/src/components/user/settings-profile/types.ts +47 -0
- package/src/components/user/settings-profile.tsx +664 -0
- package/src/components/user/user-create-dialog.tsx +137 -0
- package/src/components/user/user-filters.tsx +117 -0
- package/src/hooks/use-mobile.ts +36 -0
- package/src/instrument.client.ts +34 -0
- package/src/lib/anyhub/capabilities.ts +59 -0
- package/src/lib/anyhub/types.ts +43 -0
- package/src/lib/auth-client.ts +83 -0
- package/src/lib/auth-utils.ts +24 -0
- package/src/lib/auth.config.ts +264 -0
- package/src/lib/avatar.ts +41 -0
- package/src/lib/i18n/client.ts +32 -0
- package/src/lib/i18n/config.ts +40 -0
- package/src/lib/i18n/detect.ts +43 -0
- package/src/lib/i18n/locales/en/admin-ai.json +128 -0
- package/src/lib/i18n/locales/en/admin-billing.json +56 -0
- package/src/lib/i18n/locales/en/admin-users.json +88 -0
- package/src/lib/i18n/locales/en/common.json +877 -0
- package/src/lib/i18n/locales/en/enterprise.json +208 -0
- package/src/lib/i18n/locales/zh/admin-ai.json +128 -0
- package/src/lib/i18n/locales/zh/admin-billing.json +56 -0
- package/src/lib/i18n/locales/zh/admin-users.json +88 -0
- package/src/lib/i18n/locales/zh/common.json +877 -0
- package/src/lib/i18n/locales/zh/enterprise.json +208 -0
- package/src/lib/i18n/server.ts +18 -0
- package/src/lib/i18n/types.ts +5 -0
- package/src/lib/monitoring-benchmarks.ts +85 -0
- package/src/lib/monitoring-descriptions.ts +78 -0
- package/src/lib/prisma.ts +18 -0
- package/src/lib/router-context.ts +38 -0
- package/src/lib/session.ts +38 -0
- package/src/lib/utils.ts +23 -0
- package/src/providers/index.tsx +45 -0
- package/src/routeTree.gen.ts +1199 -0
- package/src/router.tsx +51 -0
- package/src/routes/__root.tsx +170 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.lazy.tsx +74 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.tsx +35 -0
- package/src/routes/_nav/ai-providers/index/route.lazy.tsx +33 -0
- package/src/routes/_nav/ai-providers/new/route.lazy.tsx +31 -0
- package/src/routes/_nav/ai-providers/new/route.tsx +20 -0
- package/src/routes/_nav/ai-providers/route.lazy.tsx +26 -0
- package/src/routes/_nav/ai-providers/route.tsx +31 -0
- package/src/routes/_nav/apps/route.lazy.tsx +30 -0
- package/src/routes/_nav/apps/route.tsx +20 -0
- package/src/routes/_nav/audit/route.lazy.tsx +100 -0
- package/src/routes/_nav/audit/route.tsx +25 -0
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +86 -0
- package/src/routes/_nav/billing/invoices/route.tsx +25 -0
- package/src/routes/_nav/billing/route.lazy.tsx +48 -0
- package/src/routes/_nav/billing/route.tsx +27 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +160 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.tsx +25 -0
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +113 -0
- package/src/routes/_nav/billing/subscriptions/route.tsx +28 -0
- package/src/routes/_nav/compliance/route.lazy.tsx +184 -0
- package/src/routes/_nav/compliance/route.tsx +25 -0
- package/src/routes/_nav/components/route.lazy.tsx +184 -0
- package/src/routes/_nav/components/route.tsx +40 -0
- package/src/routes/_nav/dashboard/route.lazy.tsx +33 -0
- package/src/routes/_nav/dashboard/route.tsx +20 -0
- package/src/routes/_nav/dependencies/route.lazy.tsx +66 -0
- package/src/routes/_nav/dependencies/route.tsx +32 -0
- package/src/routes/_nav/developer-logs/route.lazy.tsx +6 -0
- package/src/routes/_nav/developer-logs/route.tsx +14 -0
- package/src/routes/_nav/docs/route.lazy.tsx +30 -0
- package/src/routes/_nav/docs/route.tsx +20 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +355 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +245 -0
- package/src/routes/_nav/knowledge-public/compliance/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +242 -0
- package/src/routes/_nav/knowledge-public/legal/route.tsx +40 -0
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +235 -0
- package/src/routes/_nav/licenses/$licenseId/route.tsx +25 -0
- package/src/routes/_nav/licenses/new/route.lazy.tsx +237 -0
- package/src/routes/_nav/licenses/route.lazy.tsx +143 -0
- package/src/routes/_nav/licenses/route.tsx +25 -0
- package/src/routes/_nav/market/route.lazy.tsx +31 -0
- package/src/routes/_nav/market/route.tsx +20 -0
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +305 -0
- package/src/routes/_nav/monitoring/ai-usage/route.tsx +3 -0
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +111 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +260 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +165 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +240 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +138 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +68 -0
- package/src/routes/_nav/monitoring/route.tsx +20 -0
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +399 -0
- package/src/routes/_nav/monitoring/sentry/route.tsx +20 -0
- package/src/routes/_nav/permissions/route.lazy.tsx +504 -0
- package/src/routes/_nav/permissions/route.tsx +26 -0
- package/src/routes/_nav/route.lazy.tsx +31 -0
- package/src/routes/_nav/route.tsx +34 -0
- package/src/routes/_nav/settings/route.lazy.tsx +30 -0
- package/src/routes/_nav/settings/route.tsx +20 -0
- package/src/routes/_nav/sso/route.lazy.tsx +145 -0
- package/src/routes/_nav/sso/route.tsx +25 -0
- package/src/routes/_nav/users/$userId/route.lazy.tsx +432 -0
- package/src/routes/_nav/users/$userId/route.tsx +27 -0
- package/src/routes/_nav/users/-components/users-table.tsx +38 -0
- package/src/routes/_nav/users/route.lazy.tsx +74 -0
- package/src/routes/_nav/users/route.tsx +43 -0
- package/src/routes/api/compliance-download/$jobId.ts +35 -0
- package/src/routes/api/observability/errors.ts +41 -0
- package/src/routes/api/observability/health.ts +30 -0
- package/src/routes/api.auth.$.ts +31 -0
- package/src/routes/api.sentry-test.ts +33 -0
- package/src/routes/index/route.lazy.tsx +22 -0
- package/src/routes/index/route.tsx +24 -0
- package/src/routes/login/route.lazy.tsx +46 -0
- package/src/routes/login/route.tsx +28 -0
- package/src/routes/sentry-tunnel.ts +61 -0
- package/src/routes/setup/route.lazy.tsx +94 -0
- package/src/routes/setup/route.tsx +28 -0
- package/src/server/__tests__/admin-ai-alerts.test.ts +113 -0
- package/src/server/__tests__/licenses-issue.test.ts +98 -0
- package/src/server/admin-ai-alerts.ts +177 -0
- package/src/server/admin-ai-usage.ts +134 -0
- package/src/server/auth-helpers.ts +51 -0
- package/src/server/entitlements.ts +24 -0
- package/src/server/knowledge-public/canonical-id.ts +55 -0
- package/src/server/knowledge-public/github-sync.ts +86 -0
- package/src/server/license-issue.ts +78 -0
- package/src/server/middlewares/request.ts +26 -0
- package/src/server/observability/debug-middleware.ts +39 -0
- package/src/server/observability/error-reporter.ts +99 -0
- package/src/server/observability/health-check.ts +140 -0
- package/src/server/observability/index.ts +33 -0
- package/src/server/observability/logger.ts +49 -0
- package/src/server/observability/metrics.ts +105 -0
- package/src/server/observability/request-middleware.ts +125 -0
- package/src/server/observability/timing.ts +42 -0
- package/src/server/pro-services.ts +32 -0
- package/src/server/serverFns/admin-billing.ts +94 -0
- package/src/server/serverFns/ai-provider/create.ts +92 -0
- package/src/server/serverFns/ai-provider/delete.ts +42 -0
- package/src/server/serverFns/ai-provider/get.ts +35 -0
- package/src/server/serverFns/ai-provider/list.ts +92 -0
- package/src/server/serverFns/ai-provider/update.ts +73 -0
- package/src/server/serverFns/anyhub/market.ts +64 -0
- package/src/server/serverFns/audit.ts +57 -0
- package/src/server/serverFns/auth/session.ts +26 -0
- package/src/server/serverFns/auth/setup.ts +73 -0
- package/src/server/serverFns/compliance.ts +85 -0
- package/src/server/serverFns/components/import.ts +197 -0
- package/src/server/serverFns/components/list.ts +51 -0
- package/src/server/serverFns/components/mutations.ts +84 -0
- package/src/server/serverFns/dependency/helpers.ts +245 -0
- package/src/server/serverFns/dependency/list.ts +127 -0
- package/src/server/serverFns/dependency/sync.ts +123 -0
- package/src/server/serverFns/dependency/update.ts +51 -0
- package/src/server/serverFns/developer-log-auto-generate.ts +176 -0
- package/src/server/serverFns/developer-logs.ts +132 -0
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +53 -0
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +66 -0
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +56 -0
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +50 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +58 -0
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/compliance/list.ts +59 -0
- package/src/server/serverFns/knowledge-public/compliance/update.ts +53 -0
- package/src/server/serverFns/knowledge-public/legal/create.ts +62 -0
- package/src/server/serverFns/knowledge-public/legal/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/legal/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/legal/list.ts +60 -0
- package/src/server/serverFns/knowledge-public/legal/update.ts +55 -0
- package/src/server/serverFns/licenses.ts +92 -0
- package/src/server/serverFns/locale.ts +10 -0
- package/src/server/serverFns/monitoring/check-ai-alerts.ts +13 -0
- package/src/server/serverFns/monitoring/get-ai-usage-overview.ts +25 -0
- package/src/server/serverFns/monitoring/get-errors.ts +43 -0
- package/src/server/serverFns/monitoring/get-health-settings.ts +35 -0
- package/src/server/serverFns/monitoring/get-health.ts +55 -0
- package/src/server/serverFns/monitoring/get-metrics.ts +46 -0
- package/src/server/serverFns/monitoring/get-request-logs.ts +66 -0
- package/src/server/serverFns/monitoring/report-error.ts +55 -0
- package/src/server/serverFns/monitoring/reset.ts +70 -0
- package/src/server/serverFns/permissions.ts +214 -0
- package/src/server/serverFns/sentry/config.ts +88 -0
- package/src/server/serverFns/sso-config.ts +83 -0
- package/src/server/serverFns/user/create.ts +40 -0
- package/src/server/serverFns/user/delete.ts +29 -0
- package/src/server/serverFns/user/detail.ts +45 -0
- package/src/server/serverFns/user/list.ts +56 -0
- package/src/server/serverFns/user/reset-password.ts +28 -0
- package/src/server/serverFns/user/update-role.ts +26 -0
- package/src/server/serverFns/user/update-status.ts +26 -0
- package/src/server.ts +27 -0
- package/src/start.ts +85 -0
- package/src/styles/_keyframe-animations.scss +91 -0
- package/src/styles/_variables.scss +296 -0
- package/src/styles/globals.css +200 -0
- package/src/types/start-authjs.d.ts +27 -0
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
19
|
+
import { prisma } from '@allbluecn/database'
|
|
20
|
+
import { metrics } from './metrics'
|
|
21
|
+
import { logger } from './logger'
|
|
22
|
+
import { reportError } from './error-reporter'
|
|
23
|
+
|
|
24
|
+
interface RequestLogEntry {
|
|
25
|
+
timestamp: string
|
|
26
|
+
method: string
|
|
27
|
+
path: string
|
|
28
|
+
status: number
|
|
29
|
+
duration: number
|
|
30
|
+
ip?: string
|
|
31
|
+
userAgent?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class RequestLogStore {
|
|
35
|
+
private store: RequestLogEntry[] = []
|
|
36
|
+
private readonly MAX_SIZE = 10000
|
|
37
|
+
|
|
38
|
+
add(entry: RequestLogEntry): void {
|
|
39
|
+
this.store.push(entry)
|
|
40
|
+
if (this.store.length > this.MAX_SIZE) {
|
|
41
|
+
this.store.shift()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getLogs(limit = 100, offset = 0): RequestLogEntry[] {
|
|
46
|
+
return this.store.slice(offset, offset + limit)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getTotalCount(): number {
|
|
50
|
+
return this.store.length
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const requestLogStore = new RequestLogStore()
|
|
55
|
+
|
|
56
|
+
export const observabilityMiddleware = createMiddleware().server(async ({ request, next }: any) => {
|
|
57
|
+
const startTime = Date.now()
|
|
58
|
+
const url = new URL(request.url)
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await next() as any
|
|
62
|
+
const duration = Date.now() - startTime
|
|
63
|
+
const response = result.response || result
|
|
64
|
+
const status = response.status || 500
|
|
65
|
+
const path = url.pathname
|
|
66
|
+
|
|
67
|
+
const entry: RequestLogEntry = {
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
method: request.method,
|
|
70
|
+
path,
|
|
71
|
+
status,
|
|
72
|
+
duration,
|
|
73
|
+
ip: request.headers?.get('x-forwarded-for') || request.headers?.get('x-real-ip'),
|
|
74
|
+
userAgent: request.headers?.get('user-agent'),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
requestLogStore.add(entry)
|
|
78
|
+
|
|
79
|
+
saveRequestLogAsync(entry).catch((e) => console.error('[RequestLogSave]', e))
|
|
80
|
+
metrics.recordTiming(`request.${path}`, duration)
|
|
81
|
+
|
|
82
|
+
logger('info', 'Request completed', {
|
|
83
|
+
method: request.method,
|
|
84
|
+
path,
|
|
85
|
+
status,
|
|
86
|
+
duration,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return response
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const duration = Date.now() - startTime
|
|
92
|
+
const path = url.pathname
|
|
93
|
+
|
|
94
|
+
metrics.recordTiming(`request.${path}`, duration)
|
|
95
|
+
reportError(error instanceof Error ? error : new Error(String(error)), {
|
|
96
|
+
source: 'request',
|
|
97
|
+
method: request.method,
|
|
98
|
+
path,
|
|
99
|
+
duration,
|
|
100
|
+
ip: request.headers?.get('x-forwarded-for') || request.headers?.get('x-real-ip'),
|
|
101
|
+
})
|
|
102
|
+
logger('error', `Request failed: ${path}`, {
|
|
103
|
+
method: request.method,
|
|
104
|
+
path,
|
|
105
|
+
duration,
|
|
106
|
+
error: (error as Error).message,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
throw error
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
async function saveRequestLogAsync(entry: RequestLogEntry): Promise<void> {
|
|
114
|
+
await prisma.monitoringRequestLog.create({
|
|
115
|
+
data: {
|
|
116
|
+
timestamp: new Date(entry.timestamp),
|
|
117
|
+
method: entry.method,
|
|
118
|
+
path: entry.path,
|
|
119
|
+
status: entry.status,
|
|
120
|
+
duration: entry.duration,
|
|
121
|
+
ip: entry.ip || undefined,
|
|
122
|
+
userAgent: entry.userAgent || undefined,
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
import { logger } from './logger'
|
|
19
|
+
import { metrics } from './metrics'
|
|
20
|
+
|
|
21
|
+
export interface TimingResult {
|
|
22
|
+
duration: number
|
|
23
|
+
label: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function timed(label: string, fn: () => Promise<unknown> | unknown): Promise<TimingResult & { data: unknown }> {
|
|
27
|
+
return new Promise(async (resolve, reject) => {
|
|
28
|
+
const startTime = Date.now()
|
|
29
|
+
try {
|
|
30
|
+
const data = await fn()
|
|
31
|
+
const duration = Date.now() - startTime
|
|
32
|
+
metrics.recordTiming(label, duration)
|
|
33
|
+
logger('info', `Timed operation`, { label, duration })
|
|
34
|
+
resolve({ label, duration, data })
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const duration = Date.now() - startTime
|
|
37
|
+
metrics.recordTiming(label, duration)
|
|
38
|
+
logger('error', `Timed operation failed`, { label, duration, error: (error as Error).message })
|
|
39
|
+
reject(error)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
// P1 起:服务端商业能力 seam(与 web 端 apps/web/src/server/pro-services.ts 同构)。
|
|
11
|
+
// admin 尚未接 @allbluecn/kernel 装配器(P2 统一接入),OSS admin 下无商业插件,
|
|
12
|
+
// 恒返回 null —— 与旧 pro-stubs 全 null 行为等价。
|
|
13
|
+
// P2 接装配时仅需将下方 PLUGINS 数据源换成 plugins.gen.ts 装配产物。
|
|
14
|
+
import type { ProFeature, ProModule } from '@allbluecn/kernel'
|
|
15
|
+
|
|
16
|
+
const PLUGINS: { contributes: { services?: Record<string, unknown> } }[] = []
|
|
17
|
+
|
|
18
|
+
export function getProService<K extends ProFeature>(key: K): ProModule[K] | null {
|
|
19
|
+
for (const plugin of PLUGINS) {
|
|
20
|
+
const svc = plugin.contributes.services
|
|
21
|
+
if (svc && typeof svc === 'object' && key in svc) {
|
|
22
|
+
return svc[key] as ProModule[K]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isProEdition(): boolean {
|
|
29
|
+
return PLUGINS.some(
|
|
30
|
+
(p) => p.contributes.services && Object.keys(p.contributes.services).length > 0,
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
|
|
23
|
+
const listSubscriptionsInput = z.object({
|
|
24
|
+
search: z.string().optional(),
|
|
25
|
+
plan: z.string().optional(),
|
|
26
|
+
status: z.string().optional(),
|
|
27
|
+
page: z.number().default(1),
|
|
28
|
+
pageSize: z.number().default(20),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const listAllSubscriptionsFn = createServerFn({ method: 'GET' })
|
|
32
|
+
.validator(listSubscriptionsInput)
|
|
33
|
+
.handler(async ({ data }) => {
|
|
34
|
+
await requireAdmin()
|
|
35
|
+
|
|
36
|
+
const where: any = {}
|
|
37
|
+
if (data.search) {
|
|
38
|
+
where.OR = [{ workspace: { name: { contains: data.search } } }]
|
|
39
|
+
}
|
|
40
|
+
if (data.plan) {
|
|
41
|
+
where.planId = data.plan
|
|
42
|
+
}
|
|
43
|
+
if (data.status) {
|
|
44
|
+
where.status = data.status
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [items, total] = await Promise.all([
|
|
48
|
+
prisma.subscription.findMany({
|
|
49
|
+
where,
|
|
50
|
+
include: { plan: true, workspace: true },
|
|
51
|
+
orderBy: { createdAt: 'desc' },
|
|
52
|
+
skip: (data.page - 1) * data.pageSize,
|
|
53
|
+
take: data.pageSize,
|
|
54
|
+
}),
|
|
55
|
+
prisma.subscription.count({ where }),
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
return { items, total, page: data.page, pageSize: data.pageSize }
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
export const getSubscriptionDetailFn = createServerFn({ method: 'GET' })
|
|
62
|
+
.validator(z.object({ subscriptionId: z.string() }))
|
|
63
|
+
.handler(async ({ data }) => {
|
|
64
|
+
await requireAdmin()
|
|
65
|
+
return prisma.subscription.findUnique({
|
|
66
|
+
where: { id: data.subscriptionId },
|
|
67
|
+
include: {
|
|
68
|
+
plan: true,
|
|
69
|
+
workspace: true,
|
|
70
|
+
invoices: { orderBy: { createdAt: 'desc' } },
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export const listAllInvoicesFn = createServerFn({ method: 'GET' })
|
|
76
|
+
.validator(
|
|
77
|
+
z.object({
|
|
78
|
+
page: z.number().default(1),
|
|
79
|
+
pageSize: z.number().default(20),
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
.handler(async ({ data }) => {
|
|
83
|
+
await requireAdmin()
|
|
84
|
+
const [items, total] = await Promise.all([
|
|
85
|
+
prisma.invoice.findMany({
|
|
86
|
+
orderBy: { createdAt: 'desc' },
|
|
87
|
+
include: { subscription: { include: { workspace: true, plan: true } } },
|
|
88
|
+
skip: (data.page - 1) * data.pageSize,
|
|
89
|
+
take: data.pageSize,
|
|
90
|
+
}),
|
|
91
|
+
prisma.invoice.count(),
|
|
92
|
+
])
|
|
93
|
+
return { items, total, page: data.page, pageSize: data.pageSize }
|
|
94
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
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/create.ts
|
|
19
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
20
|
+
import { z } from "zod"
|
|
21
|
+
import { prisma } from "@allbluecn/database"
|
|
22
|
+
import {
|
|
23
|
+
ADAPTER_KIND_VALUES,
|
|
24
|
+
PROVIDER_CATEGORY_VALUES,
|
|
25
|
+
} from "@allbluecn/kernel"
|
|
26
|
+
|
|
27
|
+
const modelSchema = z.object({
|
|
28
|
+
id: z.string().min(1),
|
|
29
|
+
displayName: z.string().min(1),
|
|
30
|
+
contextWindow: z.number().optional(),
|
|
31
|
+
capabilities: z.array(z.string()).optional(),
|
|
32
|
+
defaultEnabled: z.boolean().optional(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const createInput = z.object({
|
|
36
|
+
providerId: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.max(64)
|
|
40
|
+
.regex(/^[a-z0-9-]+$/, "只能含小写字母、数字、短横线"),
|
|
41
|
+
displayName: z.string().min(1).max(64),
|
|
42
|
+
brandName: z.string().min(1).max(64),
|
|
43
|
+
description: z.string().min(1).max(500),
|
|
44
|
+
category: z.enum(PROVIDER_CATEGORY_VALUES as [string, ...string[]]),
|
|
45
|
+
adapterKind: z.enum(ADAPTER_KIND_VALUES as [string, ...string[]]),
|
|
46
|
+
defaultBaseUrl: z.string().url().or(z.literal("")).optional(),
|
|
47
|
+
keyUrl: z.string().url().or(z.literal("")).optional(),
|
|
48
|
+
docsUrl: z.string().url().or(z.literal("")).optional(),
|
|
49
|
+
keyPlaceholder: z.string().max(100).optional(),
|
|
50
|
+
iconSlug: z.string().max(64).optional(),
|
|
51
|
+
badge: z.string().max(64).optional(),
|
|
52
|
+
brandColor: z
|
|
53
|
+
.string()
|
|
54
|
+
.regex(/^#[0-9a-fA-F]{6}$/, "格式 #RRGGBB")
|
|
55
|
+
.default("#6467F2"),
|
|
56
|
+
isLocal: z.boolean().default(false),
|
|
57
|
+
enabled: z.boolean().default(true),
|
|
58
|
+
sortOrder: z.number().int().default(0),
|
|
59
|
+
models: z.array(modelSchema),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export const createAiProviderAdminFn = createServerFn({ method: "POST" })
|
|
63
|
+
.validator(createInput)
|
|
64
|
+
.handler(async ({ data }) => {
|
|
65
|
+
const exists = await prisma.aiProvider.findUnique({
|
|
66
|
+
where: { providerId: data.providerId },
|
|
67
|
+
})
|
|
68
|
+
if (exists) throw new Error(`providerId 已存在:${data.providerId}`)
|
|
69
|
+
|
|
70
|
+
const created = await prisma.aiProvider.create({
|
|
71
|
+
data: {
|
|
72
|
+
providerId: data.providerId,
|
|
73
|
+
displayName: data.displayName,
|
|
74
|
+
brandName: data.brandName,
|
|
75
|
+
description: data.description,
|
|
76
|
+
category: data.category,
|
|
77
|
+
adapterKind: data.adapterKind,
|
|
78
|
+
defaultBaseUrl: data.defaultBaseUrl || null,
|
|
79
|
+
keyUrl: data.keyUrl || null,
|
|
80
|
+
docsUrl: data.docsUrl || null,
|
|
81
|
+
keyPlaceholder: data.keyPlaceholder || null,
|
|
82
|
+
iconSlug: data.iconSlug || null,
|
|
83
|
+
badge: data.badge || null,
|
|
84
|
+
brandColor: data.brandColor,
|
|
85
|
+
isLocal: data.isLocal,
|
|
86
|
+
enabled: data.enabled,
|
|
87
|
+
sortOrder: data.sortOrder,
|
|
88
|
+
models: data.models,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
return created
|
|
92
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
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/delete.ts
|
|
19
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
20
|
+
import { z } from "zod"
|
|
21
|
+
import { prisma } from "@allbluecn/database"
|
|
22
|
+
|
|
23
|
+
export const deleteAiProviderAdminFn = createServerFn({ method: "POST" })
|
|
24
|
+
.validator(z.object({ id: z.string() }))
|
|
25
|
+
.handler(async ({ data }) => {
|
|
26
|
+
const provider = await prisma.aiProvider.findUnique({
|
|
27
|
+
where: { id: data.id },
|
|
28
|
+
select: { providerId: true },
|
|
29
|
+
})
|
|
30
|
+
if (!provider) throw new Error("未找到服务商")
|
|
31
|
+
|
|
32
|
+
// 业务规则:无人使用时可硬删除;已被用户配置的服务商禁止删除(请改为停用)
|
|
33
|
+
const count = await prisma.aiProviderConfig.count({
|
|
34
|
+
where: { providerId: provider.providerId },
|
|
35
|
+
})
|
|
36
|
+
if (count > 0) {
|
|
37
|
+
throw new Error(`已有 ${count} 个用户配置此服务商,无法删除,请改为停用`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await prisma.aiProvider.delete({ where: { id: data.id } })
|
|
41
|
+
return { success: true }
|
|
42
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
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/get.ts
|
|
19
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
20
|
+
import { z } from "zod"
|
|
21
|
+
import { prisma } from "@allbluecn/database"
|
|
22
|
+
import type { AiProviderMeta } from "@allbluecn/kernel"
|
|
23
|
+
|
|
24
|
+
export const getAiProviderAdminFn = createServerFn({ method: "GET" })
|
|
25
|
+
.validator(z.object({ id: z.string() }))
|
|
26
|
+
.handler(async ({ data }) => {
|
|
27
|
+
const p = await prisma.aiProvider.findUnique({ where: { id: data.id } })
|
|
28
|
+
if (!p) throw new Error("未找到服务商")
|
|
29
|
+
return {
|
|
30
|
+
...p,
|
|
31
|
+
models: p.models as unknown as AiProviderMeta["models"],
|
|
32
|
+
category: p.category as AiProviderMeta["category"],
|
|
33
|
+
adapterKind: p.adapterKind as AiProviderMeta["adapterKind"],
|
|
34
|
+
}
|
|
35
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
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/list.ts
|
|
19
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
20
|
+
import { z } from "zod"
|
|
21
|
+
import { prisma } from "@allbluecn/database"
|
|
22
|
+
import type { AiProviderRow } from "@allbluecn/kernel"
|
|
23
|
+
|
|
24
|
+
const listInput = z.object({
|
|
25
|
+
search: z.string().optional(),
|
|
26
|
+
enabled: z.boolean().optional(),
|
|
27
|
+
category: z.string().optional(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/** 列出所有服务商(含每个服务商的用户配置数) */
|
|
31
|
+
export const listAiProvidersAdminFn = createServerFn({ method: "GET" })
|
|
32
|
+
.validator(listInput)
|
|
33
|
+
.handler(async ({ data }) => {
|
|
34
|
+
const where: {
|
|
35
|
+
OR?: Array<Record<string, unknown>>
|
|
36
|
+
enabled?: boolean
|
|
37
|
+
category?: string
|
|
38
|
+
} = {}
|
|
39
|
+
if (data.search) {
|
|
40
|
+
where.OR = [
|
|
41
|
+
{ displayName: { contains: data.search, mode: "insensitive" } },
|
|
42
|
+
{ brandName: { contains: data.search, mode: "insensitive" } },
|
|
43
|
+
{ providerId: { contains: data.search, mode: "insensitive" } },
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
if (data.enabled !== undefined) where.enabled = data.enabled
|
|
47
|
+
if (data.category) where.category = data.category
|
|
48
|
+
|
|
49
|
+
const providers = await prisma.aiProvider.findMany({
|
|
50
|
+
where,
|
|
51
|
+
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const providerIds = providers.map((p) => p.providerId)
|
|
55
|
+
const configCounts =
|
|
56
|
+
providerIds.length > 0
|
|
57
|
+
? await prisma.aiProviderConfig.groupBy({
|
|
58
|
+
by: ["providerId"],
|
|
59
|
+
where: { providerId: { in: providerIds } },
|
|
60
|
+
_count: { _all: true },
|
|
61
|
+
})
|
|
62
|
+
: []
|
|
63
|
+
const countMap = new Map(
|
|
64
|
+
configCounts.map((c) => [c.providerId, c._count._all]),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return providers.map(
|
|
68
|
+
(p): AiProviderRow => ({
|
|
69
|
+
id: p.id,
|
|
70
|
+
providerId: p.providerId,
|
|
71
|
+
displayName: p.displayName,
|
|
72
|
+
brandName: p.brandName,
|
|
73
|
+
description: p.description,
|
|
74
|
+
category: p.category as AiProviderRow["category"],
|
|
75
|
+
iconSlug: p.iconSlug ?? "",
|
|
76
|
+
badge: p.badge ?? "",
|
|
77
|
+
brandColor: p.brandColor,
|
|
78
|
+
adapterKind: p.adapterKind as AiProviderRow["adapterKind"],
|
|
79
|
+
defaultBaseUrl: p.defaultBaseUrl ?? undefined,
|
|
80
|
+
keyUrl: p.keyUrl ?? undefined,
|
|
81
|
+
docsUrl: p.docsUrl ?? undefined,
|
|
82
|
+
keyPlaceholder: p.keyPlaceholder ?? undefined,
|
|
83
|
+
isLocal: p.isLocal,
|
|
84
|
+
models: p.models as unknown as AiProviderRow["models"],
|
|
85
|
+
enabled: p.enabled,
|
|
86
|
+
sortOrder: p.sortOrder,
|
|
87
|
+
configCount: countMap.get(p.providerId) ?? 0,
|
|
88
|
+
createdAt: p.createdAt.toISOString(),
|
|
89
|
+
updatedAt: p.updatedAt.toISOString(),
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
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/update.ts
|
|
19
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
20
|
+
import { z } from "zod"
|
|
21
|
+
import { prisma } from "@allbluecn/database"
|
|
22
|
+
import {
|
|
23
|
+
ADAPTER_KIND_VALUES,
|
|
24
|
+
PROVIDER_CATEGORY_VALUES,
|
|
25
|
+
} from "@allbluecn/kernel"
|
|
26
|
+
|
|
27
|
+
const modelSchema = z.object({
|
|
28
|
+
id: z.string().min(1),
|
|
29
|
+
displayName: z.string().min(1),
|
|
30
|
+
contextWindow: z.number().optional(),
|
|
31
|
+
capabilities: z.array(z.string()).optional(),
|
|
32
|
+
defaultEnabled: z.boolean().optional(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const updateInput = z.object({
|
|
36
|
+
id: z.string(),
|
|
37
|
+
displayName: z.string().min(1).max(64).optional(),
|
|
38
|
+
brandName: z.string().min(1).max(64).optional(),
|
|
39
|
+
description: z.string().min(1).max(500).optional(),
|
|
40
|
+
category: z.enum(PROVIDER_CATEGORY_VALUES as [string, ...string[]]).optional(),
|
|
41
|
+
adapterKind: z.enum(ADAPTER_KIND_VALUES as [string, ...string[]]).optional(),
|
|
42
|
+
defaultBaseUrl: z.string().url().or(z.literal("")).optional(),
|
|
43
|
+
keyUrl: z.string().url().or(z.literal("")).optional(),
|
|
44
|
+
docsUrl: z.string().url().or(z.literal("")).optional(),
|
|
45
|
+
keyPlaceholder: z.string().max(100).optional(),
|
|
46
|
+
iconSlug: z.string().max(64).optional(),
|
|
47
|
+
badge: z.string().max(64).optional(),
|
|
48
|
+
brandColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
|
49
|
+
isLocal: z.boolean().optional(),
|
|
50
|
+
enabled: z.boolean().optional(),
|
|
51
|
+
sortOrder: z.number().int().optional(),
|
|
52
|
+
models: z.array(modelSchema).optional(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const updateAiProviderAdminFn = createServerFn({ method: "POST" })
|
|
56
|
+
.validator(updateInput)
|
|
57
|
+
.handler(async ({ data }) => {
|
|
58
|
+
const { id, ...rest } = data
|
|
59
|
+
const update: Record<string, unknown> = {}
|
|
60
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
61
|
+
if (v !== undefined) {
|
|
62
|
+
if (
|
|
63
|
+
["defaultBaseUrl", "keyUrl", "docsUrl", "keyPlaceholder", "iconSlug", "badge"].includes(k)
|
|
64
|
+
) {
|
|
65
|
+
update[k] = v || null
|
|
66
|
+
} else {
|
|
67
|
+
update[k] = v
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return prisma.aiProvider.update({ where: { id }, data: update })
|
|
73
|
+
})
|