cpns / apps /web /src /hooks /use-api-key.ts
pramudya-yb
Rename project from Labas to Pram
2b62b0c
Raw
History Blame Contribute Delete
3.06 kB
import { useState, useEffect, useCallback } from "react";
import { encryptText, decryptText } from "@/lib/crypto";
import { trackUmamiEvent, AnalyticsEvent } from "@/lib/umami";
const STORAGE_KEY = "pram_api_keys_v2";
export interface ApiKeyConfig {
id: string;
name: string;
provider: string;
baseUrl: string;
apiKey: string;
modelName: string;
maxTokens?: number;
}
function generateId() {
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
}
export function useApiKeys() {
const [configs, setConfigs] = useState<ApiKeyConfig[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
loadConfigs();
}, []);
const loadConfigs = async () => {
try {
const encrypted = localStorage.getItem(STORAGE_KEY);
if (encrypted) {
const decrypted = await decryptText(encrypted);
setConfigs(JSON.parse(decrypted));
}
} catch {
localStorage.removeItem(STORAGE_KEY);
} finally {
setIsLoading(false);
}
};
const persist = useCallback(async (next: ApiKeyConfig[]) => {
const encrypted = await encryptText(JSON.stringify(next));
localStorage.setItem(STORAGE_KEY, encrypted);
setConfigs(next);
}, []);
const addConfig = useCallback(
async (config: Omit<ApiKeyConfig, "id">) => {
const next = [...configs, { ...config, id: generateId() }];
await persist(next);
trackUmamiEvent(AnalyticsEvent.API_KEY_ADDED, { provider: config.provider });
return next[next.length - 1].id;
},
[configs, persist],
);
const updateConfig = useCallback(
async (id: string, updates: Partial<Omit<ApiKeyConfig, "id">>) => {
const next = configs.map((c) => {
if (c.id !== id) return c;
// If apiKey is empty string, keep the old one (don't overwrite)
const apiKey =
updates.apiKey === "" || updates.apiKey === undefined
? c.apiKey
: updates.apiKey;
return { ...c, ...updates, apiKey };
});
await persist(next);
},
[configs, persist],
);
const removeConfig = useCallback(
async (id: string) => {
const target = configs.find((c) => c.id === id);
const next = configs.filter((c) => c.id !== id);
await persist(next);
if (target) {
trackUmamiEvent(AnalyticsEvent.API_KEY_REMOVED, { provider: target.provider });
}
},
[configs, persist],
);
const getConfig = useCallback(
(id: string) => configs.find((c) => c.id === id),
[configs],
);
return {
configs,
isLoading,
addConfig,
updateConfig,
removeConfig,
getConfig,
hasConfigs: configs.length > 0,
};
}
// Backward-compatible hook for places that only need the first (default) key
export function useApiKey() {
const { configs, isLoading, addConfig, removeConfig } =
useApiKeys();
const storedKey = configs[0] ?? null;
return {
storedKey,
isLoading,
saveKey: addConfig,
removeKey: removeConfig,
hasKey: !!storedKey,
};
}