main: 初始化 Face to API 项目
- 添加前端抽卡台与模型配置界面 - 添加 Express API 服务与多厂商模型调用适配 - 配置本地环境示例、构建脚本和忽略规则
This commit is contained in:
121
server/modelConfigStore.ts
Normal file
121
server/modelConfigStore.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export type SavedModelConfig = {
|
||||
baseUrl?: string;
|
||||
model?: string;
|
||||
apiKey?: string;
|
||||
enabled?: boolean;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
type ConfigFile = {
|
||||
version: 1;
|
||||
models: Record<string, SavedModelConfig>;
|
||||
};
|
||||
|
||||
export type SaveModelConfigInput = {
|
||||
baseUrl?: string;
|
||||
model?: string;
|
||||
apiKey?: string;
|
||||
enabled?: boolean;
|
||||
clearApiKey?: boolean;
|
||||
};
|
||||
|
||||
const defaultConfigPath = () =>
|
||||
path.resolve(process.cwd(), process.env.MODEL_CONFIG_PATH?.trim() || ".face-to-api/model-configs.json");
|
||||
|
||||
let cache: ConfigFile | undefined;
|
||||
|
||||
const emptyStore = (): ConfigFile => ({ version: 1, models: {} });
|
||||
|
||||
const readStore = (): ConfigFile => {
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const filePath = defaultConfigPath();
|
||||
if (!fs.existsSync(filePath)) {
|
||||
cache = emptyStore();
|
||||
return cache;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as ConfigFile;
|
||||
cache = {
|
||||
version: 1,
|
||||
models: parsed.models ?? {}
|
||||
};
|
||||
return cache;
|
||||
} catch {
|
||||
cache = emptyStore();
|
||||
return cache;
|
||||
}
|
||||
};
|
||||
|
||||
const writeStore = (store: ConfigFile) => {
|
||||
const filePath = defaultConfigPath();
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}\n`);
|
||||
cache = store;
|
||||
};
|
||||
|
||||
const trimOrUndefined = (value?: string) => {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
};
|
||||
|
||||
export const getSavedModelConfig = (modelId: string): SavedModelConfig | undefined => {
|
||||
const saved = readStore().models[modelId];
|
||||
return saved ? { ...saved } : undefined;
|
||||
};
|
||||
|
||||
export const saveModelConfig = (modelId: string, input: SaveModelConfigInput) => {
|
||||
const store = readStore();
|
||||
const existing = store.models[modelId] ?? {};
|
||||
const next: SavedModelConfig = { ...existing };
|
||||
const baseUrl = trimOrUndefined(input.baseUrl);
|
||||
const model = trimOrUndefined(input.model);
|
||||
const apiKey = trimOrUndefined(input.apiKey);
|
||||
|
||||
if (input.baseUrl !== undefined) {
|
||||
if (baseUrl) {
|
||||
next.baseUrl = baseUrl;
|
||||
} else {
|
||||
delete next.baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.model !== undefined) {
|
||||
if (model) {
|
||||
next.model = model;
|
||||
} else {
|
||||
delete next.model;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.clearApiKey) {
|
||||
delete next.apiKey;
|
||||
} else if (apiKey) {
|
||||
next.apiKey = apiKey;
|
||||
}
|
||||
|
||||
if (input.enabled !== undefined) {
|
||||
next.enabled = input.enabled;
|
||||
}
|
||||
|
||||
next.updatedAt = new Date().toISOString();
|
||||
store.models[modelId] = next;
|
||||
writeStore(store);
|
||||
|
||||
return { ...next };
|
||||
};
|
||||
|
||||
export const maskApiKey = (apiKey?: string) => {
|
||||
if (!apiKey) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const tail = apiKey.slice(-4);
|
||||
return `•••• ${tail}`;
|
||||
};
|
||||
Reference in New Issue
Block a user