chore: commit workspace updates

This commit is contained in:
ddCat
2026-09-14 01:11:56 +08:00
parent 4eeef3cfd8
commit 4b0e9d8965
490 changed files with 12826 additions and 72249 deletions

View File

@@ -8,11 +8,9 @@ import { useSearchParams } from "react-router";
import { ChannelHeadersEditor, validateChannelHeaders } from "@/components/channel-headers-editor";
import { PaginationBar } from "@/components/layout/workspace-page";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { desktopLocalChannelFormState, desktopLocalChannelPayloadValue, DESKTOP_LOCAL_CHANNEL_EXAMPLE_BASE_URL } from "@/lib/desktop-local-channel";
import { refreshSystemChannels } from "@/lib/user-session";
import { createAdminChannel, deleteAdminChannel, listAdminChannels, updateAdminChannel } from "@/services/api/auth";
import { type ChannelHeader, type ModelChannel } from "@/stores/use-config-store";
import { useUserStore } from "@/stores/use-user-store";
import { useAdminContext } from "../admin-context";
import { AdminPageFrame } from "../components/admin-shell";
import { AdminDataTable, AdminRowActions, AdminStatusBadge, AdminTableEmpty, configuredSecretText } from "../components/admin-ui";
@@ -23,7 +21,6 @@ type ChannelFormValues = {
name: string;
publicAlias?: string;
baseUrl: string;
allowLocalChannel?: boolean;
apiKey?: string;
secretKey?: string;
headers?: ChannelHeader[];
@@ -32,37 +29,11 @@ type ChannelFormValues = {
enabled?: boolean;
};
export function adminLocalChannelFormOwner(desktopLocalChannelsEnabled: boolean, hostname: string, requestedAllowLocalChannel?: boolean) {
const state = desktopLocalChannelFormState(desktopLocalChannelsEnabled, hostname, requestedAllowLocalChannel);
return { ...state, payloadValue: desktopLocalChannelPayloadValue(desktopLocalChannelsEnabled, hostname, requestedAllowLocalChannel) };
}
export function AdminLocalChannelSwitch({ visible, checked, onChange }: { visible: boolean; checked: boolean; onChange: (checked: boolean) => void }) {
if (!visible) return null;
return (
<Form.Item name="allowLocalChannel" label="允许本机渠道" valuePropName="checked" extra={`仅放行精确 localhost 或 127.0.0.1;示例:${DESKTOP_LOCAL_CHANNEL_EXAMPLE_BASE_URL}`}>
<Switch checked={checked} onChange={onChange} />
</Form.Item>
);
}
export function AdminLocalChannelFields({ visible, checked, form }: { visible: boolean; checked: boolean; form: Pick<FormInstance<ChannelFormValues>, "setFieldValue"> }) {
return (
<>
<Form.Item name="baseUrl" label="Base URL" rules={[{ required: true, message: "请填写 Base URL" }]}>
<Input placeholder={checked ? DESKTOP_LOCAL_CHANNEL_EXAMPLE_BASE_URL : "填写渠道 Base URL"} />
</Form.Item>
<AdminLocalChannelSwitch visible={visible} checked={checked} onChange={(value) => form.setFieldValue("allowLocalChannel", value)} />
</>
);
}
export function adminChannelSavePayload(values: ChannelFormValues, desktopLocalChannelsEnabled: boolean, hostname: string) {
export function adminChannelSavePayload(values: ChannelFormValues) {
return {
name: values.name.trim(),
publicAlias: values.publicAlias?.trim() || "",
baseUrl: values.baseUrl.trim(),
allowLocalChannel: adminLocalChannelFormOwner(desktopLocalChannelsEnabled, hostname, values.allowLocalChannel).payloadValue,
apiKey: values.apiKey?.trim() || "",
secretKey: values.secretKey?.trim() || "",
headers: values.headers || [],
@@ -91,12 +62,6 @@ export default function ChannelsPage() {
const requestSequence = useRef(0);
const [form] = Form.useForm<ChannelFormValues>();
const useGlobalConcurrency = Form.useWatch("useGlobalConcurrency", form) !== false;
const requestedAllowLocalChannel = Form.useWatch("allowLocalChannel", form) === true;
const desktopLocalChannelsEnabled = useUserStore((state) => state.features.desktopLocalChannelsEnabled);
const desktopLocalChannelHostname = typeof window === "undefined" ? "" : window.location.hostname;
const desktopLocalChannelControl = adminLocalChannelFormOwner(desktopLocalChannelsEnabled, desktopLocalChannelHostname, requestedAllowLocalChannel);
const allowLocalChannel = desktopLocalChannelControl.checked;
const showDesktopLocalChannelControl = desktopLocalChannelControl.visible;
const hasFilters = Boolean(keyword || status !== "all");
const updateUrl = (patch: Record<string, string | number>, replace = false) => {
@@ -146,7 +111,6 @@ export default function ChannelsPage() {
? {
name: channel.name,
baseUrl: channel.baseUrl,
allowLocalChannel: adminLocalChannelFormOwner(desktopLocalChannelsEnabled, desktopLocalChannelHostname, channel.allowLocalChannel).checked,
apiKey: "",
secretKey: "",
headers: channel.headers || [],
@@ -154,7 +118,7 @@ export default function ChannelsPage() {
concurrencyLimit: channel.concurrencyLimit || undefined,
enabled: channel.enabled !== false,
}
: { name: "", baseUrl: "", allowLocalChannel: false, apiKey: "", secretKey: "", headers: [], useGlobalConcurrency: true, concurrencyLimit: undefined, enabled: true },
: { name: "", baseUrl: "", apiKey: "", secretKey: "", headers: [], useGlobalConcurrency: true, concurrencyLimit: undefined, enabled: true },
);
setDrawerOpen(true);
form.setFieldsValue({ publicAlias: channel?.publicAlias || "" });
@@ -182,7 +146,7 @@ export default function ChannelsPage() {
}
setSaving(true);
try {
const payload = adminChannelSavePayload(values, desktopLocalChannelsEnabled, desktopLocalChannelHostname);
const payload = adminChannelSavePayload(values);
await (editingChannel ? updateAdminChannel(editingChannel.id, payload) : createAdminChannel(payload));
await syncChannels();
setDrawerOpen(false);
@@ -382,7 +346,9 @@ export default function ChannelsPage() {
<Form.Item name="publicAlias" label="前台显示别名" extra="留空时显示渠道名称;填写后用户端只显示此别名,后台仍保留原渠道名称。" rules={[{ max: 80, message: "别名不能超过 80 个字符" }]}>
<Input maxLength={80} placeholder="可选,例如:精选图片" />
</Form.Item>
<AdminLocalChannelFields visible={showDesktopLocalChannelControl} checked={allowLocalChannel} form={form} />
<Form.Item name="baseUrl" label="Base URL" rules={[{ required: true, message: "请填写 Base URL" }]}>
<Input placeholder="填写云端渠道 Base URL" />
</Form.Item>
<Form.Item
name="apiKey"
label={editingChannel ? `API Key / Access Key(${configuredSecretText})` : "API Key / Access Key"}

View File

@@ -246,7 +246,7 @@ function taskStatusTone(value?: string): AdminStatusTone {
}
function quotaUsageItems(detail: AdminUserDetail) {
const structuredBytes = detail.storageUsage.assetBytes + detail.storageUsage.canvasBytes + detail.storageUsage.sessionBytes;
const structuredBytes = detail.storageUsage.assetBytes + detail.storageUsage.canvasBytes;
const bytes = (value: number) => value >= 1024 ** 3 ? `${(value / 1024 ** 3).toFixed(2)} GB` : `${(value / 1024 ** 2).toFixed(1)} MB`;
const number = (value: number) => new Intl.NumberFormat("zh-CN").format(value);
return [
@@ -256,7 +256,6 @@ function quotaUsageItems(detail: AdminUserDetail) {
{ label: "任务与请求日志数据", value: detail.storageUsage.taskBytes, limit: detail.quota.taskDataGB * 1024 ** 3, display: `${bytes(detail.storageUsage.taskBytes)} / ${detail.quota.taskDataGB} GB` },
{ label: "素材数量", value: detail.storageUsage.assetCount, limit: detail.quota.assetCount, display: `${number(detail.storageUsage.assetCount)} / ${number(detail.quota.assetCount)}` },
{ label: "画布数量", value: detail.storageUsage.canvasCount, limit: detail.quota.canvasCount, display: `${number(detail.storageUsage.canvasCount)} / ${number(detail.quota.canvasCount)}` },
{ label: "Agent 会话数量", value: detail.storageUsage.sessionCount, limit: detail.quota.sessionCount, display: `${number(detail.storageUsage.sessionCount)} / ${number(detail.quota.sessionCount)}` },
{ label: "任务历史数量", value: detail.storageUsage.taskCount, limit: detail.quota.taskCount, display: `${number(detail.storageUsage.taskCount)} / ${number(detail.quota.taskCount)}` },
{ label: "上游请求日志数量", value: detail.storageUsage.apiCallCount, limit: detail.quota.apiCallLogCount, display: `${number(detail.storageUsage.apiCallCount)} / ${number(detail.quota.apiCallLogCount)}` },
];

View File

@@ -323,7 +323,7 @@ export function ChannelModelEditor({
</div>
</section>
) : null}
{modelCapability === "image" || modelCapability === "video" ? (
{modelCapability === "text" || modelCapability === "image" || modelCapability === "video" ? (
<section className="admin-model-editor-section admin-model-editor-section-stacked admin-model-editor-parameters">
<SectionHeading title="协议参数" description="配置可发送参数、支持值与默认值;仅影响当前模型。" />
<div className="admin-model-editor-section-content">

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { App, Button, Skeleton } from "antd";
import { Switch } from "@/components/ui/base/switch";
import { AlertTriangle, Clapperboard, Coins, ListChecks, LockKeyhole, MonitorCog, PlugZap, RadioTower, RefreshCw, ShieldCheck, Sparkles } from "lucide-react";
import { AlertTriangle, Clapperboard, Coins, ListChecks, MonitorCog, PlugZap, RadioTower, RefreshCw, ShieldCheck, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
import { getAdminFeatureAvailability, updateAdminFeatureAvailability } from "@/services/api/auth";
@@ -269,7 +269,6 @@ export default function FeatureAvailabilityPanel() {
status={<AdminStatusBadge label={draftFeatures.frontendModelsEnabled ? "前台模型目录" : "系统渠道"} tone="info" />}
>
<FeatureSourceRow row={modelFeatureRows[0]} saved={savedFeatures} draft={draftFeatures} saving={saving} onChange={requestFeatureChange} />
<FeatureRuntimeRow enabled={draftFeatures.desktopLocalChannelsEnabled} />
</FeatureDomainPanel>
</div>
</div>
@@ -341,18 +340,6 @@ function FeatureSourceRow({ row, saved, draft, saving, onChange }: { row: Featur
);
}
function FeatureRuntimeRow({ enabled }: { enabled: boolean }) {
return (
<aside className="admin-feature-runtime-note" aria-label="桌面本地渠道部署状态">
<LockKeyhole className="size-4" aria-hidden="true" />
<div>
<strong>桌面本地渠道由部署环境控制</strong>
</div>
<AdminStatusBadge label={enabled ? "当前可用" : "当前不可用"} tone={enabled ? "success" : "neutral"} />
</aside>
);
}
function effectiveFeatureValue(features: FeatureAvailability, key: FeatureKey) {
if (key === "systemPluginsVisibleToUsers") return features.pluginCenterEnabled && features.systemPluginsVisibleToUsers;
return features[key];
@@ -388,7 +375,6 @@ function parseFeatureAvailability(value: unknown): FeatureAvailability {
frontendModelsEnabled: record.frontendModelsEnabled as boolean,
pluginCenterEnabled: record.pluginCenterEnabled as boolean,
systemPluginsVisibleToUsers: record.systemPluginsVisibleToUsers as boolean,
desktopLocalChannelsEnabled: record.desktopLocalChannelsEnabled === true,
configured: typeof record.configured === "boolean" ? record.configured : undefined,
updatedBy: typeof record.updatedBy === "string" ? record.updatedBy : undefined,
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,

View File

@@ -9,8 +9,7 @@ import { PaginationBar } from "@/components/layout/workspace-page";
import "@/lib/plugins/builtin";
import { EAGLE_PLUGIN_ID } from "@/lib/plugins/builtin/eagle";
import { PROMPT_OPTIMIZER_PLUGIN_ID } from "@/lib/plugins/builtin/prompt-optimizer";
import { COMFYUI_PLUGIN_ID, RUNNINGHUB_PLUGIN_ID } from "@/lib/plugins/builtin/workflows";
import { MEDIA_CONVERSION_PLUGIN_ID } from "@/lib/plugins/builtin/media-conversion";
import { RUNNINGHUB_PLUGIN_ID } from "@/lib/plugins/builtin/workflows";
import { listRegisteredPlugins } from "@/lib/plugins/plugin-registry";
import type { PluginManifest, PluginManifestV2 } from "@/lib/plugins/plugin-types";
import { fetchAdminPlugins, setPluginPlatformAvailability, uninstallPlugin, uploadPlugin, type AdminPluginState, type BackendPlugin, type PluginManagement } from "@/services/api/plugins";
@@ -27,7 +26,7 @@ type AdminPluginItem = {
error?: string;
};
const officialApplicationIds = new Set([RUNNINGHUB_PLUGIN_ID, COMFYUI_PLUGIN_ID, EAGLE_PLUGIN_ID, PROMPT_OPTIMIZER_PLUGIN_ID, "portrait-clearance", MEDIA_CONVERSION_PLUGIN_ID]);
const officialApplicationIds = new Set([RUNNINGHUB_PLUGIN_ID, EAGLE_PLUGIN_ID, PROMPT_OPTIMIZER_PLUGIN_ID]);
export default function AdminPluginsPage() {
const { message, modal } = App.useApp();
@@ -371,7 +370,7 @@ function mergePlugins(remote: BackendPlugin[]): AdminPluginItem[] {
origin: "official",
kind: application ? "application" : "protocol",
activationScope: application ? "user" : "system",
configurationScope: application ? (plugin.manifest.id === EAGLE_PLUGIN_ID || plugin.manifest.id === RUNNINGHUB_PLUGIN_ID || plugin.manifest.id === COMFYUI_PLUGIN_ID ? "user" : "none") : "system",
configurationScope: application ? (plugin.manifest.id === EAGLE_PLUGIN_ID || plugin.manifest.id === RUNNINGHUB_PLUGIN_ID ? "user" : "none") : "system",
},
});
}

View File

@@ -24,7 +24,6 @@ type PolicySectionDefinition = {
const resourceFields: PolicyField[] = [
{ group: "resource", name: "resourceUploadMB", label: "普通资源单文件", extra: "素材上传和远程导入的单文件业务上限。", unit: "MB", max: 999 },
{ group: "resource", name: "sessionUploadMB", label: "Agent 会话附件", extra: "单个会话附件的大小上限。", unit: "MB", max: 999 },
{ group: "resource", name: "generatedFileMB", label: "单个生成资源", extra: "上游生成响应和落库资源的单文件上限。", unit: "MB", max: 999 },
{ group: "resource", name: "dailyUploadMB", label: "每日上传总量", extra: "按 UTC 自然日累计资源与附件上传。", unit: "MB", max: 999_999 },
{ group: "resource", name: "storedFileGB", label: "账号文件总量", extra: "资源文件与 Agent 会话附件合计。", unit: "GB", max: 999 },
@@ -32,7 +31,6 @@ const resourceFields: PolicyField[] = [
{ group: "resource", name: "taskDataGB", label: "任务数据总量", extra: "任务历史、结果和上游请求日志合计。", unit: "GB", max: 999 },
{ group: "resource", name: "assetCount", label: "素材数量", extra: "单账号可保存的素材记录数。", unit: "条", max: 999_999_999 },
{ group: "resource", name: "canvasCount", label: "画布数量", extra: "单账号可保存的画布数量。", unit: "个", max: 999_999_999 },
{ group: "resource", name: "sessionCount", label: "Agent 会话数量", extra: "单账号可保存的 Agent 会话数量。", unit: "个", max: 999_999_999 },
{ group: "resource", name: "taskCount", label: "任务历史数量", extra: "单账号保留的任务历史记录数。", unit: "条", max: 999_999_999 },
{ group: "resource", name: "apiCallLogCount", label: "请求日志数量", extra: "单账号保留的上游请求日志数。", unit: "条", max: 999_999_999 },
{ group: "resource", name: "recycleBinRetentionDays", label: "回收站自动清理时间", extra: "素材移入回收站后自动彻底删除的天数,0 表示不自动清理。", unit: "天", min: 0, max: 365 },
@@ -55,10 +53,8 @@ const timeoutFields: PolicyField[] = [
const rateFields: PolicyField[] = [
{ group: "request", name: "taskCreatePerMinute", label: "任务创建", extra: "每账号每分钟允许创建的任务数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "sessionCreatePerMinute", label: "会话创建", extra: "每账号每分钟允许创建的会话数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "resourceUploadPerMinute", label: "资源上传", extra: "每账号每分钟上传资源的次数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "resourceImportPerMinute", label: "资源导入", extra: "每账号每分钟导入远程资源的次数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "sessionFilePerMinute", label: "会话附件", extra: "每账号每分钟上传会话附件的次数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "assetWritePerMinute", label: "素材写入", extra: "每账号每分钟写入素材的次数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "canvasWritePerMinute", label: "画布写入", extra: "每账号每分钟写入画布的次数。", unit: "次/分钟", max: 999_999 },
{ group: "request", name: "registerPerHour", label: "账号注册", extra: "每 IP 每小时允许的注册请求次数,包含失败尝试;多人共用网络时建议至少 30 次。", unit: "次/小时", max: 999_999 },