cookAIware / web /src /storage /hfDataset.ts
Juan Jimenez Carrero
feat: sync robot preferences to HF, thinking movement + wait/announce on plan
b5352be
Raw
History Blame Contribute Delete
3.04 kB
import { createRepo, uploadFiles, downloadFile, whoAmI } from "@huggingface/hub";
import type { AppData, SyncableSettings } from "./store";
import { validateData } from "./store";
/** Optional sync of the app data to a private HF dataset owned by the user.
* Requires a user-pasted fine-grained token (write access to the dataset);
* the host shell's OAuth token is short-lived and read-only, so it can't be
* used for this. The token never leaves the browser except toward hf.co. */
const DATASET_NAME = "cookaiware-data";
const DATA_FILE = "cookaiware-data.json";
const SETTINGS_FILE = "cookaiware-settings.json";
export interface HfSyncIdentity {
userName: string;
repoId: string;
}
export async function resolveIdentity(token: string): Promise<HfSyncIdentity> {
const me = await whoAmI({ accessToken: token });
return { userName: me.name, repoId: `datasets/${me.name}/${DATASET_NAME}` };
}
async function ensureRepo(token: string, repoId: string): Promise<void> {
try {
await createRepo({ repo: repoId, accessToken: token, private: true });
} catch (err) {
// The repo usually already exists (every save after the first), and the
// Hub wording for that error varies — so never fail the save here. If the
// repo is genuinely missing/unreachable, uploadFiles will raise the real
// error right after and that one is surfaced.
console.debug("[hf-sync] createRepo skipped:", err instanceof Error ? err.message : err);
}
}
export async function uploadData(
token: string,
data: AppData,
settings?: SyncableSettings,
): Promise<HfSyncIdentity> {
const identity = await resolveIdentity(token);
await ensureRepo(token, identity.repoId);
const files = [
{ path: DATA_FILE, content: new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }) },
];
if (settings) {
files.push({
path: SETTINGS_FILE,
content: new Blob([JSON.stringify(settings, null, 2)], { type: "application/json" }),
});
}
await uploadFiles({
repo: identity.repoId,
accessToken: token,
files,
commitTitle: "Update CookAIware data",
});
return identity;
}
export async function downloadData(token: string): Promise<AppData | null> {
const identity = await resolveIdentity(token);
const res = await downloadFile({ repo: identity.repoId, path: DATA_FILE, accessToken: token });
if (!res) return null;
return validateData(JSON.parse(await res.text()));
}
/** Fetch the synced settings subset, or null if none has been uploaded yet
* (older datasets, or a device that never enabled sync). */
export async function downloadSettings(token: string): Promise<unknown | null> {
try {
const identity = await resolveIdentity(token);
const res = await downloadFile({ repo: identity.repoId, path: SETTINGS_FILE, accessToken: token });
if (!res) return null;
return JSON.parse(await res.text());
} catch (err) {
console.debug("[hf-sync] no settings file:", err instanceof Error ? err.message : err);
return null;
}
}