93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
import type { OSApi, Size } from "./types";
|
|
import { fs } from "./fs";
|
|
import { db } from "./db";
|
|
import { bus } from "./bus";
|
|
import { chat, chatStream, aiAvailable } from "./ai";
|
|
import { useOS } from "./store";
|
|
import { tokensFor, tokensAsCss } from "../design/tokens";
|
|
|
|
// Builds the sandboxed OS API handed to a single app window.
|
|
// Apps may ONLY reach the outside world through this object.
|
|
export function makeOSApi(appId: string, windowId: string): OSApi {
|
|
const store = () => useOS.getState();
|
|
|
|
return {
|
|
fs: {
|
|
read: (p) => fs.read(p),
|
|
write: (p, c) => fs.write(p, c),
|
|
list: (p) => fs.list(p),
|
|
delete: (p) => fs.delete(p),
|
|
mkdir: (p) => fs.mkdir(p),
|
|
exists: (p) => fs.exists(p),
|
|
},
|
|
|
|
window: {
|
|
setTitle: (title) => store().setWindowTitle(windowId, title),
|
|
close: () => store().closeWindow(windowId),
|
|
setSize: (size: Size) => store().resizeWindow(windowId, size),
|
|
minimize: () => store().minimizeWindow(windowId),
|
|
},
|
|
|
|
ai: {
|
|
// Getter, not a snapshot: the API key can be configured in Settings after
|
|
// an app window is already open, and the app must see the live value.
|
|
get available() {
|
|
return aiAvailable();
|
|
},
|
|
ask: (prompt, opts) => chat([{ role: "user", content: prompt }], { system: opts?.system }),
|
|
stream: (prompt, onToken, opts) =>
|
|
chatStream([{ role: "user", content: prompt }], onToken, { system: opts?.system }),
|
|
},
|
|
|
|
notify: (title, body, icon) => store().notify({ title, body, icon, appId }),
|
|
|
|
net: {
|
|
proxyUrl: (url: string) => {
|
|
const u = (url || "").trim();
|
|
if (!u) return u;
|
|
// In dev we have the /__proxy middleware; in a prod build there is none,
|
|
// so fall back to the raw URL (which the target may still block).
|
|
if (import.meta.env.DEV && /^https?:\/\//i.test(u)) {
|
|
return `/__proxy?url=${encodeURIComponent(u)}`;
|
|
}
|
|
return u;
|
|
},
|
|
},
|
|
|
|
storage: {
|
|
async get<T = unknown>(key: string): Promise<T | null> {
|
|
const d = await db();
|
|
const row = await d.get("appstore", `${appId}:${key}`);
|
|
return row ? (row.value as T) : null;
|
|
},
|
|
async set(key, value) {
|
|
const d = await db();
|
|
await d.put("appstore", { k: `${appId}:${key}`, appId, value });
|
|
},
|
|
async remove(key) {
|
|
const d = await db();
|
|
await d.delete("appstore", `${appId}:${key}`);
|
|
},
|
|
async keys() {
|
|
const d = await db();
|
|
const rows = await d.getAllFromIndex("appstore", "byApp", appId);
|
|
return rows.map((r) => r.k.slice(appId.length + 1));
|
|
},
|
|
},
|
|
|
|
theme: {
|
|
get tokens() {
|
|
return tokensAsCss(tokensFor(store().themeMode));
|
|
},
|
|
get mode() {
|
|
return store().themeMode;
|
|
},
|
|
},
|
|
|
|
bus: {
|
|
emit: (channel, payload) => bus.emit(`app:${channel}`, payload),
|
|
on: (channel, handler) => bus.on(`app:${channel}`, handler),
|
|
},
|
|
};
|
|
}
|