refactor(config): 将AI配置逻辑迁移至统一的状态管理store
- 移除独立的ai-config.ts文件,将其功能整合到use-config-store - 更新所有组件导入路径从 "@/lib/ai-config" 到 "@/stores/use-config-store" - 实现云端渠道和本地直连两种配置模式的支持 - 添加模型渠道配置管理和API请求代理转发功能 - 统一配置验证逻辑和有效配置获取方法 - 更新组件中使用的配置状态钩子和API调用方式
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
import { CONFIG_STORE_KEY, defaultConfig, type AiConfig } from "@/lib/ai-config";
|
||||
|
||||
type AiConfigStore = {
|
||||
config: AiConfig;
|
||||
updateConfig: <K extends keyof AiConfig>(key: K, value: AiConfig[K]) => void;
|
||||
};
|
||||
|
||||
export const useAiConfigStore = create<AiConfigStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
config: defaultConfig,
|
||||
updateConfig: (key, value) =>
|
||||
set((state) => ({
|
||||
config: {
|
||||
...state.config,
|
||||
[key]: value,
|
||||
},
|
||||
})),
|
||||
}),
|
||||
{
|
||||
name: CONFIG_STORE_KEY,
|
||||
merge: (persisted, current) => {
|
||||
const config = { ...defaultConfig, ...((persisted as Partial<AiConfigStore>).config || {}) };
|
||||
return { ...current, config: { ...config, imageModel: config.imageModel || config.model, textModel: config.textModel || config.model } };
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -1,21 +0,0 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type ConfigDialogStore = {
|
||||
isOpen: boolean;
|
||||
shouldPromptContinue: boolean;
|
||||
openConfigDialog: (shouldPromptContinue?: boolean) => void;
|
||||
setConfigDialogOpen: (isOpen: boolean) => void;
|
||||
clearPromptContinue: () => void;
|
||||
};
|
||||
|
||||
export const useConfigDialogStore = create<ConfigDialogStore>()((set) => ({
|
||||
isOpen: false,
|
||||
shouldPromptContinue: false,
|
||||
openConfigDialog: (shouldPromptContinue = false) =>
|
||||
set({
|
||||
isOpen: true,
|
||||
shouldPromptContinue,
|
||||
}),
|
||||
setConfigDialogOpen: (isOpen) => set({ isOpen }),
|
||||
clearPromptContinue: () => set({ shouldPromptContinue: false }),
|
||||
}));
|
||||
@@ -0,0 +1,123 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
import { apiGet } from "@/services/api/request";
|
||||
import type { AdminPublicSettings } from "@/services/api/admin";
|
||||
|
||||
export type AiConfig = {
|
||||
channelMode: "remote" | "local";
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
model: string;
|
||||
imageModel: string;
|
||||
textModel: string;
|
||||
systemPrompt: string;
|
||||
models: string[];
|
||||
quality: string;
|
||||
size: string;
|
||||
count: string;
|
||||
};
|
||||
|
||||
export const CONFIG_STORE_KEY = "infinite-canvas:ai_config_store";
|
||||
|
||||
export const defaultConfig: AiConfig = {
|
||||
channelMode: "remote",
|
||||
baseUrl: "https://api.openai.com",
|
||||
apiKey: "",
|
||||
model: "gpt-image-2",
|
||||
imageModel: "gpt-image-2",
|
||||
textModel: "gpt-5.5",
|
||||
systemPrompt: "",
|
||||
models: [],
|
||||
quality: "auto",
|
||||
size: "1:1",
|
||||
count: "1",
|
||||
};
|
||||
|
||||
type ConfigStore = {
|
||||
config: AiConfig;
|
||||
publicSettings: AdminPublicSettings | null;
|
||||
isPublicSettingsLoading: boolean;
|
||||
isConfigOpen: boolean;
|
||||
shouldPromptContinue: boolean;
|
||||
updateConfig: <K extends keyof AiConfig>(key: K, value: AiConfig[K]) => void;
|
||||
loadPublicSettings: () => Promise<void>;
|
||||
openConfigDialog: (shouldPromptContinue?: boolean) => void;
|
||||
setConfigDialogOpen: (isOpen: boolean) => void;
|
||||
clearPromptContinue: () => void;
|
||||
};
|
||||
|
||||
export const useConfigStore = create<ConfigStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
config: defaultConfig,
|
||||
publicSettings: null,
|
||||
isPublicSettingsLoading: false,
|
||||
isConfigOpen: false,
|
||||
shouldPromptContinue: false,
|
||||
updateConfig: (key, value) =>
|
||||
set((state) => ({
|
||||
config: {
|
||||
...state.config,
|
||||
[key]: value,
|
||||
},
|
||||
})),
|
||||
loadPublicSettings: async () => {
|
||||
if (get().isPublicSettingsLoading) return;
|
||||
set({ isPublicSettingsLoading: true });
|
||||
try {
|
||||
set({ publicSettings: await apiGet<AdminPublicSettings>("/api/settings") });
|
||||
} finally {
|
||||
set({ isPublicSettingsLoading: false });
|
||||
}
|
||||
},
|
||||
openConfigDialog: (shouldPromptContinue = false) => set({ isConfigOpen: true, shouldPromptContinue }),
|
||||
setConfigDialogOpen: (isConfigOpen) => set({ isConfigOpen }),
|
||||
clearPromptContinue: () => set({ shouldPromptContinue: false }),
|
||||
}),
|
||||
{
|
||||
name: CONFIG_STORE_KEY,
|
||||
partialize: (state) => ({ config: state.config }),
|
||||
merge: (persisted, current) => {
|
||||
const config = { ...defaultConfig, ...((persisted as Partial<ConfigStore>).config || {}) };
|
||||
return { ...current, config: { ...config, channelMode: config.channelMode || "remote", imageModel: config.imageModel || config.model, textModel: config.textModel || config.model } };
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export function useEffectiveAiConfig(config: AiConfig) {
|
||||
const modelChannel = useConfigStore((state) => state.publicSettings?.modelChannel);
|
||||
|
||||
return useMemo(() => {
|
||||
const channelMode = modelChannel?.allowCustomChannel ? config.channelMode : "remote";
|
||||
if (channelMode === "local" || !modelChannel) return { ...config, channelMode };
|
||||
const models = modelChannel.availableModels;
|
||||
return {
|
||||
...config,
|
||||
channelMode,
|
||||
models,
|
||||
model: models.includes(config.model) ? config.model : modelChannel.defaultModel,
|
||||
imageModel: models.includes(config.imageModel) ? config.imageModel : modelChannel.defaultImageModel || modelChannel.defaultModel,
|
||||
textModel: models.includes(config.textModel) ? config.textModel : modelChannel.defaultTextModel || modelChannel.defaultModel,
|
||||
systemPrompt: modelChannel.systemPrompt,
|
||||
};
|
||||
}, [config, modelChannel]);
|
||||
}
|
||||
|
||||
export function normalizeBaseUrl(value: string) {
|
||||
return value.trim().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
export function buildApiUrl(baseUrl: string, path: string) {
|
||||
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
||||
const apiBaseUrl = normalizedBaseUrl.endsWith("/v1") ? normalizedBaseUrl : `${normalizedBaseUrl}/v1`;
|
||||
return `${apiBaseUrl}${path}`;
|
||||
}
|
||||
|
||||
export function isAiConfigReady(config: AiConfig, model: string) {
|
||||
return Boolean(model.trim()) && (config.channelMode === "remote" || Boolean(config.baseUrl.trim() && config.apiKey.trim()));
|
||||
}
|
||||
Reference in New Issue
Block a user