122 lines
2.7 KiB
TypeScript
122 lines
2.7 KiB
TypeScript
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}`;
|
|
};
|