feat(plugins): 插件管理 - 区分系统与官方插件权限 (#329)
This commit is contained in:
@@ -262,11 +262,12 @@ export function CanvasNodeContextMenu({
|
||||
function AddNodeContextMenu({ parentPosition, workspaceMode, isProjectLinked, reducedMotion, onAddNode, onAddFolder, onChooseStyle, onOpenDirector, onUpload, onOpenAssets, onOpenProjectCharacters }: { parentPosition: { left: number; top: number }; workspaceMode: CanvasWorkspaceMode; isProjectLinked: boolean; reducedMotion: boolean; onAddNode: (type: CanvasNodeTypeId) => void; onAddFolder: () => void; onChooseStyle: () => void; onOpenDirector: () => void; onUpload: () => void; onOpenAssets: () => void; onOpenProjectCharacters: () => void }) {
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const installations = usePluginStore((state) => state.installations);
|
||||
const pluginStates = usePluginStore((state) => state.pluginStates);
|
||||
const left = getSubmenuLeft(parentPosition.left);
|
||||
const createContext: AddNodeMenuContext = {
|
||||
workspaceMode,
|
||||
isProjectLinked,
|
||||
enabledPluginIds: new Set(installations.filter((item) => item.enabled).map((item) => item.manifest.id)),
|
||||
enabledPluginIds: new Set(installations.filter((item) => pluginStates[item.manifest.id]?.effectiveEnabled ?? item.enabled).map((item) => item.manifest.id)),
|
||||
handlers: {
|
||||
onAddText: () => onAddNode(CanvasNodeType.Text),
|
||||
onAddImage: () => onAddNode(CanvasNodeType.Image),
|
||||
|
||||
@@ -59,6 +59,7 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi
|
||||
const theme = canvasThemes[themeName];
|
||||
const creditsEnabled = useUserStore((state) => state.features.creditsEnabled);
|
||||
const promptOptimizerInstallation = usePluginStore((state) => state.installations.find((item) => item.manifest.id === PROMPT_OPTIMIZER_PLUGIN_ID));
|
||||
const promptOptimizerEnabled = usePluginStore((state) => state.pluginStates[PROMPT_OPTIMIZER_PLUGIN_ID]?.effectiveEnabled ?? Boolean(state.installations.find((item) => item.manifest.id === PROMPT_OPTIMIZER_PLUGIN_ID)?.enabled));
|
||||
const simpleMode = workspaceMode === "simple";
|
||||
const mode = defaultMode(node.type);
|
||||
const hasTextContent = node.type === CanvasNodeType.Text && Boolean(node.metadata?.content?.trim());
|
||||
@@ -102,9 +103,9 @@ export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfi
|
||||
};
|
||||
const config = buildNodeConfig(globalConfig, node, mode, requirements);
|
||||
const promptOptimizerProvider = useMemo(() => {
|
||||
if (!promptOptimizerInstallation?.enabled || !promptOptimizerPlugin.createPromptOptimizer) return null;
|
||||
if (!promptOptimizerEnabled || !promptOptimizerInstallation || !promptOptimizerPlugin.createPromptOptimizer) return null;
|
||||
return promptOptimizerPlugin.createPromptOptimizer(createPluginHostContext(promptOptimizerPlugin, promptOptimizerInstallation, globalConfig));
|
||||
}, [globalConfig, promptOptimizerInstallation]);
|
||||
}, [globalConfig, promptOptimizerEnabled, promptOptimizerInstallation]);
|
||||
const generationCount = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1)));
|
||||
const priceChannel = resolveModelChannel(config, config.model);
|
||||
const credits = requestCreditCost({
|
||||
|
||||
@@ -85,6 +85,7 @@ export function CanvasToolbar({
|
||||
const dockRef = useRef<HTMLDivElement>(null);
|
||||
const colorTheme = useThemeStore((state) => state.theme);
|
||||
const installations = usePluginStore((state) => state.installations);
|
||||
const pluginStates = usePluginStore((state) => state.pluginStates);
|
||||
const setTheme = useThemeStore((state) => state.setTheme);
|
||||
const theme = canvasThemes[colorTheme];
|
||||
const [addOpen, setAddOpen] = useState(false);
|
||||
@@ -173,7 +174,7 @@ export function CanvasToolbar({
|
||||
handlers,
|
||||
};
|
||||
|
||||
const enabledPluginIds = new Set(installations.filter((item) => item.enabled).map((item) => item.manifest.id));
|
||||
const enabledPluginIds = new Set(installations.filter((item) => pluginStates[item.manifest.id]?.effectiveEnabled ?? item.enabled).map((item) => item.manifest.id));
|
||||
|
||||
const items = resolveToolbarEntries("main", ctx, prefs ?? defaultToolbarPrefs("main"));
|
||||
|
||||
|
||||
@@ -23,11 +23,10 @@ const roleLabels: Record<PortraitClearanceInputRole, string> = {
|
||||
|
||||
export function PortraitClearanceNodeContent({ node }: PortraitClearanceNodeProps) {
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const installations = usePluginStore((state) => state.installations);
|
||||
const enabled = usePluginStore((state) => state.pluginStates[PORTRAIT_CLEARANCE_PLUGIN_ID]?.effectiveEnabled ?? Boolean(state.installations.find((item) => item.manifest.id === PORTRAIT_CLEARANCE_PLUGIN_ID)?.enabled));
|
||||
const { openPortraitClearance } = useCanvasNodeActions();
|
||||
const upstream = useUpstreamNodes(node.id).filter((item) => isPortraitImageInput(item));
|
||||
const state = node.metadata?.portraitClearance;
|
||||
const enabled = Boolean(installations.find((item) => item.manifest.id === PORTRAIT_CLEARANCE_PLUGIN_ID)?.enabled);
|
||||
const bindings = state?.inputBindings || [];
|
||||
const roleByNodeId = new Map(bindings.map((binding) => [binding.nodeId, binding.role]));
|
||||
const query = upstream.find((item) => roleByNodeId.get(item.id) === "query") || upstream[0];
|
||||
|
||||
@@ -22,9 +22,8 @@ type PortraitClearanceModalProps = {
|
||||
};
|
||||
|
||||
export function PortraitClearanceModal({ projectId, node, upstreamNodes, open, onClose, onUpdateState, onAddCandidate }: PortraitClearanceModalProps) {
|
||||
const installations = usePluginStore((state) => state.installations);
|
||||
const effectiveConfig = useEffectiveConfig();
|
||||
const enabled = Boolean(installations.find((item) => item.manifest.id === PORTRAIT_CLEARANCE_PLUGIN_ID)?.enabled);
|
||||
const enabled = usePluginStore((state) => state.pluginStates[PORTRAIT_CLEARANCE_PLUGIN_ID]?.effectiveEnabled ?? Boolean(state.installations.find((item) => item.manifest.id === PORTRAIT_CLEARANCE_PLUGIN_ID)?.enabled));
|
||||
const state = node?.metadata?.portraitClearance || createDefaultPortraitClearanceState();
|
||||
const [starting, setStarting] = useState(false);
|
||||
const [installingModels, setInstallingModels] = useState(false);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { appQueryClient } from "@/lib/query-client";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { listRegisteredPlugins } from "@/lib/plugins/plugin-registry";
|
||||
import { usePluginStore } from "@/stores/use-plugin-store";
|
||||
import { fetchWorkflowPluginStatuses } from "@/services/api/plugins";
|
||||
import { fetchPluginRuntimeState, setUserPluginEnabled } from "@/services/api/plugins";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
|
||||
export function AppProviders({ children }: { children: ReactNode }) {
|
||||
@@ -19,29 +19,52 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
const dark = theme === "dark";
|
||||
const ensurePlugin = usePluginStore((state) => state.ensurePlugin);
|
||||
const setRuntimeStatuses = usePluginStore((state) => state.setRuntimeStatuses);
|
||||
const setPluginStates = usePluginStore((state) => state.setPluginStates);
|
||||
const pluginStoreHydrated = usePluginStore((state) => state.hydrated);
|
||||
const userId = useUserStore((state) => state.user?.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pluginStoreHydrated) return;
|
||||
for (const plugin of listRegisteredPlugins()) ensurePlugin(plugin.manifest);
|
||||
}, [ensurePlugin]);
|
||||
}, [ensurePlugin, pluginStoreHydrated, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId) {
|
||||
setRuntimeStatuses({});
|
||||
setPluginStates({});
|
||||
return;
|
||||
}
|
||||
if (!pluginStoreHydrated) return;
|
||||
let cancelled = false;
|
||||
void fetchWorkflowPluginStatuses()
|
||||
.then((statuses) => {
|
||||
if (!cancelled) setRuntimeStatuses(statuses);
|
||||
void fetchPluginRuntimeState()
|
||||
.then(async ({ statuses, states }) => {
|
||||
const legacyEnabledIds = usePluginStore.getState().installations
|
||||
.filter((installation) => installation.enabled && states[installation.manifest.id]?.canToggle && !states[installation.manifest.id]?.userConfigured)
|
||||
.map((installation) => installation.manifest.id);
|
||||
if (legacyEnabledIds.length) {
|
||||
try {
|
||||
const migrated = await Promise.all(legacyEnabledIds.map((pluginId) => setUserPluginEnabled(pluginId, true)));
|
||||
for (const state of migrated) states[state.pluginId] = state;
|
||||
for (const pluginId of legacyEnabledIds) statuses[pluginId] = states[pluginId]?.effectiveEnabled ? "enabled" : "disabled";
|
||||
} catch (error) {
|
||||
console.warn("迁移用户插件启用状态失败,已保留服务端状态", error);
|
||||
}
|
||||
}
|
||||
if (!cancelled) {
|
||||
setRuntimeStatuses(statuses);
|
||||
setPluginStates(states);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setRuntimeStatuses({});
|
||||
if (!cancelled) {
|
||||
setRuntimeStatuses({});
|
||||
setPluginStates({});
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [setRuntimeStatuses, userId]);
|
||||
}, [pluginStoreHydrated, setPluginStates, setRuntimeStatuses, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle("dark", dark);
|
||||
|
||||
Reference in New Issue
Block a user