@palettelab/sdk 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -2
- package/dist/components/index.d.mts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.d.ts +2 -2
- package/dist/{index-Bu5EQGYo.d.mts → index-BGT6xBGD.d.mts} +1 -1
- package/dist/{index-BDVWt7DE.d.ts → index-Beo6F2Fr.d.ts} +1 -1
- package/dist/index.d.mts +38 -4
- package/dist/index.d.ts +38 -4
- package/dist/index.js +410 -32
- package/dist/index.mjs +411 -31
- package/dist/{plugin-UV46q1mU.d.mts → plugin-CG6spHKI.d.mts} +3 -1
- package/dist/{plugin-UV46q1mU.d.ts → plugin-CG6spHKI.d.ts} +3 -1
- package/dist/router/index.d.mts +346 -0
- package/dist/router/index.d.ts +346 -0
- package/dist/router/index.js +247 -0
- package/dist/router/index.mjs +225 -0
- package/dist/types/index.d.mts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +6 -1
package/dist/index.mjs
CHANGED
|
@@ -171,6 +171,7 @@ function createMockPlatformContext(overrides = {}) {
|
|
|
171
171
|
apiFetch: async () => new Response(JSON.stringify({}), { status: 200 }),
|
|
172
172
|
navigate: () => {
|
|
173
173
|
},
|
|
174
|
+
routePath: "/",
|
|
174
175
|
showToast: () => {
|
|
175
176
|
},
|
|
176
177
|
...overrides
|
|
@@ -302,6 +303,8 @@ function hasAllPermissions(ctx, permissions) {
|
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
// src/storage.ts
|
|
306
|
+
var DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;
|
|
307
|
+
var MIN_CHUNK_SIZE = 256 * 1024;
|
|
305
308
|
async function uploadToSignedUrl(uploadUrl, file, contentType = "application/octet-stream") {
|
|
306
309
|
const res = await fetch(uploadUrl, {
|
|
307
310
|
method: "PUT",
|
|
@@ -310,10 +313,178 @@ async function uploadToSignedUrl(uploadUrl, file, contentType = "application/oct
|
|
|
310
313
|
});
|
|
311
314
|
if (!res.ok) throw new Error(`Upload failed: ${res.statusText}`);
|
|
312
315
|
}
|
|
316
|
+
function normalizeChunkSize(value) {
|
|
317
|
+
const raw = Math.max(value || DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE);
|
|
318
|
+
return Math.ceil(raw / MIN_CHUNK_SIZE) * MIN_CHUNK_SIZE;
|
|
319
|
+
}
|
|
320
|
+
function defaultPluginId() {
|
|
321
|
+
if (typeof window === "undefined") return "";
|
|
322
|
+
const match = window.location.pathname.match(/\/apps\/([^/?#]+)/);
|
|
323
|
+
return match ? decodeURIComponent(match[1]) : "";
|
|
324
|
+
}
|
|
325
|
+
function fileFingerprint(pluginId, file, key) {
|
|
326
|
+
return [pluginId, key || file.name, file.size, file.lastModified].join(":");
|
|
327
|
+
}
|
|
328
|
+
function sessionStorageKey(pluginId, fingerprint) {
|
|
329
|
+
return `palette:storage-upload:${pluginId}:${fingerprint}`;
|
|
330
|
+
}
|
|
331
|
+
function readStoredSession(pluginId, fingerprint) {
|
|
332
|
+
if (typeof window === "undefined") return null;
|
|
333
|
+
try {
|
|
334
|
+
const raw = window.localStorage.getItem(sessionStorageKey(pluginId, fingerprint));
|
|
335
|
+
if (!raw) return null;
|
|
336
|
+
const session = JSON.parse(raw);
|
|
337
|
+
return session.plugin_id === pluginId && session.fingerprint === fingerprint ? session : null;
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function writeStoredSession(pluginId, fingerprint, session) {
|
|
343
|
+
if (typeof window === "undefined") return;
|
|
344
|
+
const stored = { ...session, plugin_id: pluginId, fingerprint, updated_at: Date.now() };
|
|
345
|
+
window.localStorage.setItem(sessionStorageKey(pluginId, fingerprint), JSON.stringify(stored));
|
|
346
|
+
}
|
|
347
|
+
function clearStoredSession(pluginId, fingerprint) {
|
|
348
|
+
if (typeof window === "undefined") return;
|
|
349
|
+
window.localStorage.removeItem(sessionStorageKey(pluginId, fingerprint));
|
|
350
|
+
}
|
|
351
|
+
function absoluteApiUrl(pathOrUrl) {
|
|
352
|
+
return /^https?:\/\//i.test(pathOrUrl) ? pathOrUrl : `${getBaseUrl()}${pathOrUrl}`;
|
|
353
|
+
}
|
|
354
|
+
function isPaletteApiUpload(url) {
|
|
355
|
+
return url.startsWith(getBaseUrl()) || url.startsWith("/");
|
|
356
|
+
}
|
|
357
|
+
function report(loaded, total, chunkIndex, chunkCount, state, onProgress) {
|
|
358
|
+
onProgress?.({
|
|
359
|
+
loaded,
|
|
360
|
+
total,
|
|
361
|
+
percentage: total > 0 ? Math.min(100, Math.round(loaded / total * 1e4) / 100) : 100,
|
|
362
|
+
chunkIndex,
|
|
363
|
+
chunkCount,
|
|
364
|
+
state
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function xhrPut(url, body, headers, signal, onUploadProgress) {
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
const xhr = new XMLHttpRequest();
|
|
370
|
+
xhr.open("PUT", absoluteApiUrl(url), true);
|
|
371
|
+
xhr.withCredentials = isPaletteApiUpload(url);
|
|
372
|
+
for (const [key, value] of Object.entries(headers)) xhr.setRequestHeader(key, value);
|
|
373
|
+
xhr.upload.onprogress = (event) => {
|
|
374
|
+
if (event.lengthComputable) onUploadProgress?.(event.loaded);
|
|
375
|
+
};
|
|
376
|
+
xhr.onload = () => {
|
|
377
|
+
const ok = [200, 201, 204, 308].includes(xhr.status);
|
|
378
|
+
if (!ok) reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
|
|
379
|
+
else resolve({ status: xhr.status, range: xhr.getResponseHeader("Range") });
|
|
380
|
+
};
|
|
381
|
+
xhr.onerror = () => reject(new Error("Upload failed"));
|
|
382
|
+
xhr.onabort = () => reject(new DOMException("Upload aborted", "AbortError"));
|
|
383
|
+
if (signal) {
|
|
384
|
+
if (signal.aborted) {
|
|
385
|
+
xhr.abort();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
signal.addEventListener("abort", () => xhr.abort(), { once: true });
|
|
389
|
+
}
|
|
390
|
+
xhr.send(body);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function uploadedFromRange(range) {
|
|
394
|
+
if (!range) return 0;
|
|
395
|
+
const match = range.match(/bytes=0-(\d+)/);
|
|
396
|
+
return match ? Number(match[1]) + 1 : 0;
|
|
397
|
+
}
|
|
398
|
+
async function queryGcsOffset(session) {
|
|
399
|
+
try {
|
|
400
|
+
const result = await xhrPut(session.upload_url, new Blob([]), {
|
|
401
|
+
"Content-Range": `bytes */${session.size}`
|
|
402
|
+
});
|
|
403
|
+
if ([200, 201, 204].includes(result.status)) return session.size;
|
|
404
|
+
return uploadedFromRange(result.range);
|
|
405
|
+
} catch {
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function queryLocalOffset(session) {
|
|
410
|
+
const res = await fetch(absoluteApiUrl(session.status_url), { credentials: "include" });
|
|
411
|
+
if (!res.ok) return 0;
|
|
412
|
+
const body = await res.json();
|
|
413
|
+
return Number(body.uploaded_bytes || 0);
|
|
414
|
+
}
|
|
415
|
+
async function queryOffset(session) {
|
|
416
|
+
if (session.mode === "local_resumable") return queryLocalOffset(session);
|
|
417
|
+
return queryGcsOffset(session);
|
|
418
|
+
}
|
|
313
419
|
var StorageClient = class {
|
|
314
|
-
constructor() {
|
|
420
|
+
constructor(ctx) {
|
|
421
|
+
this.ctx = ctx;
|
|
315
422
|
this.uploadToSignedUrl = uploadToSignedUrl;
|
|
316
423
|
}
|
|
424
|
+
pluginId(explicit) {
|
|
425
|
+
const resolved = explicit || this.ctx?.pluginId || defaultPluginId();
|
|
426
|
+
if (!resolved) throw new Error("pluginId is required for app storage uploads");
|
|
427
|
+
return resolved;
|
|
428
|
+
}
|
|
429
|
+
async createSession(pluginId, file, options) {
|
|
430
|
+
const res = await (this.ctx?.apiFetch || apiFetch)(`/api/v1/app-storage/${encodeURIComponent(pluginId)}/uploads`, {
|
|
431
|
+
method: "POST",
|
|
432
|
+
body: JSON.stringify({
|
|
433
|
+
filename: file.name,
|
|
434
|
+
content_type: options.contentType || file.type || "application/octet-stream",
|
|
435
|
+
size: file.size,
|
|
436
|
+
key: options.key || null,
|
|
437
|
+
resumable: options.resumable ?? true,
|
|
438
|
+
chunk_size: normalizeChunkSize(options.chunkSize)
|
|
439
|
+
})
|
|
440
|
+
});
|
|
441
|
+
return res.json();
|
|
442
|
+
}
|
|
443
|
+
async upload(file, options = {}) {
|
|
444
|
+
const pluginId = this.pluginId(options.pluginId);
|
|
445
|
+
const fingerprint = fileFingerprint(pluginId, file, options.key);
|
|
446
|
+
const stored = options.resumable === false ? null : readStoredSession(pluginId, fingerprint);
|
|
447
|
+
const session = stored || await this.createSession(pluginId, file, options);
|
|
448
|
+
writeStoredSession(pluginId, fingerprint, session);
|
|
449
|
+
const chunkSize = normalizeChunkSize(options.chunkSize || session.chunk_size);
|
|
450
|
+
const chunkCount = Math.max(1, Math.ceil(file.size / chunkSize));
|
|
451
|
+
let offset = stored ? await queryOffset(session) : 0;
|
|
452
|
+
report(offset, file.size, Math.floor(offset / chunkSize), chunkCount, "starting", options.onProgress);
|
|
453
|
+
while (offset < file.size) {
|
|
454
|
+
if (options.signal?.aborted) throw new DOMException("Upload aborted", "AbortError");
|
|
455
|
+
const start = offset;
|
|
456
|
+
const end = Math.min(file.size, start + chunkSize) - 1;
|
|
457
|
+
const chunk = file.slice(start, end + 1);
|
|
458
|
+
const chunkIndex = Math.floor(start / chunkSize) + 1;
|
|
459
|
+
const lastReportedBase = start;
|
|
460
|
+
await xhrPut(
|
|
461
|
+
session.upload_url,
|
|
462
|
+
chunk,
|
|
463
|
+
{
|
|
464
|
+
"Content-Type": session.content_type,
|
|
465
|
+
"Content-Range": `bytes ${start}-${end}/${file.size}`
|
|
466
|
+
},
|
|
467
|
+
options.signal,
|
|
468
|
+
(loaded) => report(lastReportedBase + loaded, file.size, chunkIndex, chunkCount, "uploading", options.onProgress)
|
|
469
|
+
);
|
|
470
|
+
offset = end + 1;
|
|
471
|
+
report(offset, file.size, chunkIndex, chunkCount, offset >= file.size ? "complete" : "uploading", options.onProgress);
|
|
472
|
+
writeStoredSession(pluginId, fingerprint, session);
|
|
473
|
+
}
|
|
474
|
+
clearStoredSession(pluginId, fingerprint);
|
|
475
|
+
return {
|
|
476
|
+
uploadId: session.upload_id,
|
|
477
|
+
mode: session.mode,
|
|
478
|
+
bucket: session.bucket,
|
|
479
|
+
objectPath: session.object_path,
|
|
480
|
+
fileUrl: session.file_url,
|
|
481
|
+
contentType: session.content_type,
|
|
482
|
+
size: session.size
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async resume(file, options = {}) {
|
|
486
|
+
return this.upload(file, { ...options, resumable: true });
|
|
487
|
+
}
|
|
317
488
|
};
|
|
318
489
|
|
|
319
490
|
// src/user-org.ts
|
|
@@ -391,7 +562,7 @@ var OrganizationClient = class {
|
|
|
391
562
|
|
|
392
563
|
// src/palette-client.ts
|
|
393
564
|
function createPaletteClient(ctx) {
|
|
394
|
-
const
|
|
565
|
+
const defaultPluginId2 = () => {
|
|
395
566
|
if (ctx?.pluginId) return ctx.pluginId;
|
|
396
567
|
if (typeof window === "undefined") return "";
|
|
397
568
|
const match = window.location.pathname.match(/\/apps\/([^/?#]+)/);
|
|
@@ -401,15 +572,15 @@ function createPaletteClient(ctx) {
|
|
|
401
572
|
user: new UserClient(ctx),
|
|
402
573
|
organization: new OrganizationClient(ctx),
|
|
403
574
|
dataRooms: new DataRoomClient(),
|
|
404
|
-
storage: new StorageClient(),
|
|
575
|
+
storage: new StorageClient(ctx),
|
|
405
576
|
config: {
|
|
406
577
|
get: (pluginId = ctx?.pluginId ?? "") => {
|
|
407
|
-
const resolved = pluginId ||
|
|
578
|
+
const resolved = pluginId || defaultPluginId2();
|
|
408
579
|
if (!resolved) throw new Error("pluginId is required to read install config");
|
|
409
580
|
return getInstallConfig(resolved);
|
|
410
581
|
},
|
|
411
582
|
update: (values, pluginId = ctx?.pluginId ?? "") => {
|
|
412
|
-
const resolved = pluginId ||
|
|
583
|
+
const resolved = pluginId || defaultPluginId2();
|
|
413
584
|
if (!resolved) throw new Error("pluginId is required to update install config");
|
|
414
585
|
return updateInstallConfig(resolved, values);
|
|
415
586
|
}
|
|
@@ -490,14 +661,216 @@ function usePluginTranslations(resources, options = {}) {
|
|
|
490
661
|
};
|
|
491
662
|
}
|
|
492
663
|
|
|
664
|
+
// src/router.tsx
|
|
665
|
+
import {
|
|
666
|
+
Component,
|
|
667
|
+
createContext as createContext2,
|
|
668
|
+
createElement as createElement2,
|
|
669
|
+
useCallback as useCallback2,
|
|
670
|
+
useContext as useContext2,
|
|
671
|
+
useEffect,
|
|
672
|
+
useMemo,
|
|
673
|
+
useState
|
|
674
|
+
} from "react";
|
|
675
|
+
var RouterCtx = createContext2(null);
|
|
676
|
+
var NOT_FOUND = /* @__PURE__ */ Symbol.for("palette.router.not-found");
|
|
677
|
+
function notFound() {
|
|
678
|
+
throw Object.assign(new Error("Palette route not found"), { code: NOT_FOUND });
|
|
679
|
+
}
|
|
680
|
+
function normalizePath(path) {
|
|
681
|
+
const clean = String(path || "/").split("#")[0].split("?")[0] || "/";
|
|
682
|
+
return `/${clean.replace(/^\/+/, "").replace(/\/+$/, "")}`.replace(/^\/$/, "/");
|
|
683
|
+
}
|
|
684
|
+
function splitPath(path) {
|
|
685
|
+
return normalizePath(path).split("/").filter(Boolean).map(decodeURIComponent);
|
|
686
|
+
}
|
|
687
|
+
function routeScore(route) {
|
|
688
|
+
if (typeof route.score === "number") return route.score;
|
|
689
|
+
return route.segments.reduce((score, segment) => {
|
|
690
|
+
if (segment.kind === "static") return score + 10;
|
|
691
|
+
if (segment.kind === "dynamic") return score + 5;
|
|
692
|
+
return score + (segment.optional ? 1 : 2);
|
|
693
|
+
}, route.segments.length);
|
|
694
|
+
}
|
|
695
|
+
function matchRoute(route, path) {
|
|
696
|
+
const parts = splitPath(path);
|
|
697
|
+
const params = {};
|
|
698
|
+
let index = 0;
|
|
699
|
+
for (const segment of route.segments) {
|
|
700
|
+
if (segment.kind === "catchAll") {
|
|
701
|
+
const rest = parts.slice(index);
|
|
702
|
+
if (!segment.optional && rest.length === 0) return null;
|
|
703
|
+
params[segment.name] = rest;
|
|
704
|
+
index = parts.length;
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
const value = parts[index];
|
|
708
|
+
if (value === void 0) return null;
|
|
709
|
+
if (segment.kind === "static") {
|
|
710
|
+
if (value !== segment.value) return null;
|
|
711
|
+
} else {
|
|
712
|
+
params[segment.name] = value;
|
|
713
|
+
}
|
|
714
|
+
index += 1;
|
|
715
|
+
}
|
|
716
|
+
return index === parts.length ? params : null;
|
|
717
|
+
}
|
|
718
|
+
function currentPluginPath(pluginId, routePath) {
|
|
719
|
+
if (routePath) return normalizePath(routePath);
|
|
720
|
+
if (typeof window === "undefined") return "/";
|
|
721
|
+
const path = window.location.pathname;
|
|
722
|
+
if (pluginId) {
|
|
723
|
+
const prefix = `/apps/${pluginId}`;
|
|
724
|
+
if (path === prefix) return "/";
|
|
725
|
+
if (path.startsWith(`${prefix}/`)) return normalizePath(path.slice(prefix.length));
|
|
726
|
+
}
|
|
727
|
+
return normalizePath(path);
|
|
728
|
+
}
|
|
729
|
+
function currentSearchParams() {
|
|
730
|
+
if (typeof window === "undefined") return new URLSearchParams();
|
|
731
|
+
return new URLSearchParams(window.location.search);
|
|
732
|
+
}
|
|
733
|
+
var PaletteRouteErrorBoundary = class extends Component {
|
|
734
|
+
constructor() {
|
|
735
|
+
super(...arguments);
|
|
736
|
+
this.state = { error: null };
|
|
737
|
+
this.reset = () => this.setState({ error: null });
|
|
738
|
+
}
|
|
739
|
+
static getDerivedStateFromError(error) {
|
|
740
|
+
return { error };
|
|
741
|
+
}
|
|
742
|
+
componentDidCatch(_error, _info) {
|
|
743
|
+
}
|
|
744
|
+
render() {
|
|
745
|
+
const error = this.state.error;
|
|
746
|
+
if (!error) return this.props.children;
|
|
747
|
+
if (error.code === NOT_FOUND && this.props.notFound) {
|
|
748
|
+
return createElement2(this.props.notFound);
|
|
749
|
+
}
|
|
750
|
+
if (this.props.fallback) {
|
|
751
|
+
return createElement2(this.props.fallback, { error, reset: this.reset });
|
|
752
|
+
}
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
function renderRoute(route) {
|
|
757
|
+
const page = createElement2(route.page);
|
|
758
|
+
const wrapped = (route.layouts || []).reduceRight(
|
|
759
|
+
(children, Layout) => createElement2(Layout, null, children),
|
|
760
|
+
page
|
|
761
|
+
);
|
|
762
|
+
return createElement2(
|
|
763
|
+
PaletteRouteErrorBoundary,
|
|
764
|
+
{ fallback: route.error, notFound: route.notFound },
|
|
765
|
+
wrapped
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
function PaletteAppRouter({
|
|
769
|
+
routes,
|
|
770
|
+
notFound: NotFound
|
|
771
|
+
}) {
|
|
772
|
+
const platform = usePlatform();
|
|
773
|
+
const [location, setLocation] = useState(() => ({
|
|
774
|
+
pathname: currentPluginPath(platform.pluginId, platform.routePath),
|
|
775
|
+
searchParams: currentSearchParams()
|
|
776
|
+
}));
|
|
777
|
+
useEffect(() => {
|
|
778
|
+
setLocation({
|
|
779
|
+
pathname: currentPluginPath(platform.pluginId, platform.routePath),
|
|
780
|
+
searchParams: currentSearchParams()
|
|
781
|
+
});
|
|
782
|
+
}, [platform.pluginId, platform.routePath]);
|
|
783
|
+
useEffect(() => {
|
|
784
|
+
const sync = () => setLocation({
|
|
785
|
+
pathname: currentPluginPath(platform.pluginId, platform.routePath),
|
|
786
|
+
searchParams: currentSearchParams()
|
|
787
|
+
});
|
|
788
|
+
window.addEventListener("popstate", sync);
|
|
789
|
+
return () => window.removeEventListener("popstate", sync);
|
|
790
|
+
}, [platform.pluginId, platform.routePath]);
|
|
791
|
+
const sortedRoutes = useMemo(
|
|
792
|
+
() => [...routes].sort((a, b) => routeScore(b) - routeScore(a)),
|
|
793
|
+
[routes]
|
|
794
|
+
);
|
|
795
|
+
const matched = useMemo(() => {
|
|
796
|
+
for (const route of sortedRoutes) {
|
|
797
|
+
const params = matchRoute(route, location.pathname);
|
|
798
|
+
if (params) return { route, params };
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}, [location.pathname, sortedRoutes]);
|
|
802
|
+
const navigate = useCallback2((target, replace = false) => {
|
|
803
|
+
const [pathPart, queryPart = ""] = String(target || "/").split("?");
|
|
804
|
+
const pathname = normalizePath(pathPart);
|
|
805
|
+
const next = `${pathname}${queryPart ? `?${queryPart}` : ""}`;
|
|
806
|
+
setLocation({ pathname, searchParams: new URLSearchParams(queryPart) });
|
|
807
|
+
const currentPath = typeof window === "undefined" ? "" : window.location.pathname;
|
|
808
|
+
const inPalettePath = platform.pluginId && (currentPath.startsWith(`/apps/${platform.pluginId}`) || platform.routePath !== void 0);
|
|
809
|
+
const osPath = inPalettePath && platform.pluginId ? `/apps/${platform.pluginId}${pathname === "/" ? "" : pathname}${queryPart ? `?${queryPart}` : ""}` : next;
|
|
810
|
+
if (replace && typeof window !== "undefined") window.history.replaceState(null, "", osPath);
|
|
811
|
+
else platform.navigate(osPath);
|
|
812
|
+
}, [platform]);
|
|
813
|
+
const state = useMemo(() => ({
|
|
814
|
+
pathname: location.pathname,
|
|
815
|
+
searchParams: location.searchParams,
|
|
816
|
+
params: matched?.params || {},
|
|
817
|
+
push: (path) => navigate(path, false),
|
|
818
|
+
replace: (path) => navigate(path, true)
|
|
819
|
+
}), [location.pathname, location.searchParams, matched?.params, navigate]);
|
|
820
|
+
const content = matched ? renderRoute(matched.route) : NotFound ? createElement2(NotFound) : createElement2("div", null, "Page not found");
|
|
821
|
+
return createElement2(RouterCtx.Provider, { value: state }, content);
|
|
822
|
+
}
|
|
823
|
+
function useRouterState() {
|
|
824
|
+
const value = useContext2(RouterCtx);
|
|
825
|
+
if (!value) throw new Error("Palette app router hooks must be used inside PaletteAppRouter");
|
|
826
|
+
return value;
|
|
827
|
+
}
|
|
828
|
+
function useRouter() {
|
|
829
|
+
const { push, replace } = useRouterState();
|
|
830
|
+
return { push, replace, back: () => window.history.back(), forward: () => window.history.forward() };
|
|
831
|
+
}
|
|
832
|
+
function usePathname() {
|
|
833
|
+
return useRouterState().pathname;
|
|
834
|
+
}
|
|
835
|
+
function useSearchParams() {
|
|
836
|
+
return useRouterState().searchParams;
|
|
837
|
+
}
|
|
838
|
+
function useParams() {
|
|
839
|
+
return useRouterState().params;
|
|
840
|
+
}
|
|
841
|
+
function Link({
|
|
842
|
+
href,
|
|
843
|
+
replace,
|
|
844
|
+
onClick,
|
|
845
|
+
children,
|
|
846
|
+
...props
|
|
847
|
+
}) {
|
|
848
|
+
const router = useRouter();
|
|
849
|
+
return createElement2(
|
|
850
|
+
"a",
|
|
851
|
+
{
|
|
852
|
+
...props,
|
|
853
|
+
href,
|
|
854
|
+
onClick: (event) => {
|
|
855
|
+
onClick?.(event);
|
|
856
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
857
|
+
event.preventDefault();
|
|
858
|
+
if (replace) router.replace(href);
|
|
859
|
+
else router.push(href);
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
children
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
|
|
493
866
|
// src/hooks/use-plugin-tasks.ts
|
|
494
|
-
import { useCallback as
|
|
867
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
|
|
495
868
|
function usePluginTasks(agentId) {
|
|
496
869
|
const { apiFetch: apiFetch2 } = usePlatform();
|
|
497
|
-
const [tasks, setTasks] =
|
|
498
|
-
const [stats, setStats] =
|
|
499
|
-
const [loading, setLoading] =
|
|
500
|
-
const fetchTasks =
|
|
870
|
+
const [tasks, setTasks] = useState2([]);
|
|
871
|
+
const [stats, setStats] = useState2(null);
|
|
872
|
+
const [loading, setLoading] = useState2(true);
|
|
873
|
+
const fetchTasks = useCallback3(async () => {
|
|
501
874
|
try {
|
|
502
875
|
const params = new URLSearchParams();
|
|
503
876
|
if (agentId) params.set("agent_id", String(agentId));
|
|
@@ -506,18 +879,18 @@ function usePluginTasks(agentId) {
|
|
|
506
879
|
} catch {
|
|
507
880
|
}
|
|
508
881
|
}, [apiFetch2, agentId]);
|
|
509
|
-
const fetchStats =
|
|
882
|
+
const fetchStats = useCallback3(async () => {
|
|
510
883
|
try {
|
|
511
884
|
const res = await apiFetch2("/api/v1/tasks/stats");
|
|
512
885
|
setStats(await res.json());
|
|
513
886
|
} catch {
|
|
514
887
|
}
|
|
515
888
|
}, [apiFetch2]);
|
|
516
|
-
|
|
889
|
+
useEffect2(() => {
|
|
517
890
|
setLoading(true);
|
|
518
891
|
Promise.all([fetchTasks(), fetchStats()]).finally(() => setLoading(false));
|
|
519
892
|
}, [fetchTasks, fetchStats]);
|
|
520
|
-
const createTask =
|
|
893
|
+
const createTask = useCallback3(async (payload) => {
|
|
521
894
|
const res = await apiFetch2("/api/v1/tasks", {
|
|
522
895
|
method: "POST",
|
|
523
896
|
body: JSON.stringify(payload)
|
|
@@ -526,7 +899,7 @@ function usePluginTasks(agentId) {
|
|
|
526
899
|
await Promise.all([fetchTasks(), fetchStats()]);
|
|
527
900
|
return task;
|
|
528
901
|
}, [apiFetch2, fetchTasks, fetchStats]);
|
|
529
|
-
const updateTask =
|
|
902
|
+
const updateTask = useCallback3(async (taskId, payload) => {
|
|
530
903
|
const res = await apiFetch2(`/api/v1/tasks/${taskId}`, {
|
|
531
904
|
method: "PATCH",
|
|
532
905
|
body: JSON.stringify(payload)
|
|
@@ -535,7 +908,7 @@ function usePluginTasks(agentId) {
|
|
|
535
908
|
await Promise.all([fetchTasks(), fetchStats()]);
|
|
536
909
|
return task;
|
|
537
910
|
}, [apiFetch2, fetchTasks, fetchStats]);
|
|
538
|
-
const deleteTask =
|
|
911
|
+
const deleteTask = useCallback3(async (taskId) => {
|
|
539
912
|
await apiFetch2(`/api/v1/tasks/${taskId}`, { method: "DELETE" });
|
|
540
913
|
await Promise.all([fetchTasks(), fetchStats()]);
|
|
541
914
|
}, [apiFetch2, fetchTasks, fetchStats]);
|
|
@@ -543,23 +916,23 @@ function usePluginTasks(agentId) {
|
|
|
543
916
|
}
|
|
544
917
|
|
|
545
918
|
// src/hooks/use-plugin-data-rooms.ts
|
|
546
|
-
import { useCallback as
|
|
919
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useState as useState3 } from "react";
|
|
547
920
|
function usePluginDataRooms() {
|
|
548
921
|
const { apiFetch: apiFetch2 } = usePlatform();
|
|
549
|
-
const [rooms, setRooms] =
|
|
550
|
-
const [loading, setLoading] =
|
|
551
|
-
const fetchRooms =
|
|
922
|
+
const [rooms, setRooms] = useState3([]);
|
|
923
|
+
const [loading, setLoading] = useState3(true);
|
|
924
|
+
const fetchRooms = useCallback4(async () => {
|
|
552
925
|
try {
|
|
553
926
|
const res = await apiFetch2("/api/v1/data-rooms");
|
|
554
927
|
setRooms(await res.json());
|
|
555
928
|
} catch {
|
|
556
929
|
}
|
|
557
930
|
}, [apiFetch2]);
|
|
558
|
-
|
|
931
|
+
useEffect3(() => {
|
|
559
932
|
setLoading(true);
|
|
560
933
|
fetchRooms().finally(() => setLoading(false));
|
|
561
934
|
}, [fetchRooms]);
|
|
562
|
-
const fetchFolder =
|
|
935
|
+
const fetchFolder = useCallback4(async (roomId, folderId) => {
|
|
563
936
|
const path = folderId ? `/api/v1/data-rooms/${roomId}/folders/${folderId}` : `/api/v1/data-rooms/${roomId}`;
|
|
564
937
|
const res = await apiFetch2(path);
|
|
565
938
|
return res.json();
|
|
@@ -568,19 +941,19 @@ function usePluginDataRooms() {
|
|
|
568
941
|
}
|
|
569
942
|
|
|
570
943
|
// src/hooks/use-plugin-chat.ts
|
|
571
|
-
import { useCallback as
|
|
944
|
+
import { useCallback as useCallback5, useRef, useState as useState4 } from "react";
|
|
572
945
|
function usePluginChat(agentId) {
|
|
573
946
|
const { apiFetch: apiFetch2 } = usePlatform();
|
|
574
|
-
const [threads, setThreads] =
|
|
575
|
-
const [messages, setMessages] =
|
|
576
|
-
const [streaming, setStreaming] =
|
|
577
|
-
const [activeThreadId, setActiveThreadId] =
|
|
947
|
+
const [threads, setThreads] = useState4([]);
|
|
948
|
+
const [messages, setMessages] = useState4([]);
|
|
949
|
+
const [streaming, setStreaming] = useState4(false);
|
|
950
|
+
const [activeThreadId, setActiveThreadId] = useState4(null);
|
|
578
951
|
const abortRef = useRef(null);
|
|
579
|
-
const fetchThreads =
|
|
952
|
+
const fetchThreads = useCallback5(async () => {
|
|
580
953
|
const res = await apiFetch2(`/api/v1/chat/${agentId}/threads`);
|
|
581
954
|
setThreads(await res.json());
|
|
582
955
|
}, [apiFetch2, agentId]);
|
|
583
|
-
const createThread =
|
|
956
|
+
const createThread = useCallback5(async () => {
|
|
584
957
|
const res = await apiFetch2(`/api/v1/chat/${agentId}/threads`, { method: "POST" });
|
|
585
958
|
const thread = await res.json();
|
|
586
959
|
setActiveThreadId(thread.id);
|
|
@@ -588,13 +961,13 @@ function usePluginChat(agentId) {
|
|
|
588
961
|
await fetchThreads();
|
|
589
962
|
return thread;
|
|
590
963
|
}, [apiFetch2, agentId, fetchThreads]);
|
|
591
|
-
const fetchMessages =
|
|
964
|
+
const fetchMessages = useCallback5(async (threadId) => {
|
|
592
965
|
const res = await apiFetch2(`/api/v1/chat/threads/${threadId}/messages`);
|
|
593
966
|
const msgs = await res.json();
|
|
594
967
|
setMessages(msgs);
|
|
595
968
|
setActiveThreadId(threadId);
|
|
596
969
|
}, [apiFetch2]);
|
|
597
|
-
const sendMessage =
|
|
970
|
+
const sendMessage = useCallback5(async (threadId, content) => {
|
|
598
971
|
setStreaming(true);
|
|
599
972
|
abortRef.current = new AbortController();
|
|
600
973
|
const userMsg = {
|
|
@@ -662,7 +1035,7 @@ function usePluginChat(agentId) {
|
|
|
662
1035
|
abortRef.current = null;
|
|
663
1036
|
}
|
|
664
1037
|
}, []);
|
|
665
|
-
const stopStreaming =
|
|
1038
|
+
const stopStreaming = useCallback5(() => {
|
|
666
1039
|
abortRef.current?.abort();
|
|
667
1040
|
}, []);
|
|
668
1041
|
return {
|
|
@@ -679,8 +1052,10 @@ function usePluginChat(agentId) {
|
|
|
679
1052
|
}
|
|
680
1053
|
export {
|
|
681
1054
|
DataRoomClient,
|
|
1055
|
+
Link,
|
|
682
1056
|
OrganizationClient,
|
|
683
1057
|
PaletteApiError,
|
|
1058
|
+
PaletteAppRouter,
|
|
684
1059
|
PlatformCtx,
|
|
685
1060
|
PluginProvider,
|
|
686
1061
|
StorageClient,
|
|
@@ -700,14 +1075,19 @@ export {
|
|
|
700
1075
|
isPaletteApiError,
|
|
701
1076
|
isSandboxRuntime,
|
|
702
1077
|
normalizePaletteLanguage,
|
|
1078
|
+
notFound,
|
|
703
1079
|
setBaseUrl,
|
|
704
1080
|
translate,
|
|
705
1081
|
updateInstallConfig,
|
|
706
1082
|
uploadToSignedUrl,
|
|
1083
|
+
useParams,
|
|
1084
|
+
usePathname,
|
|
707
1085
|
usePlatform,
|
|
708
1086
|
usePluginChat,
|
|
709
1087
|
usePluginDataRooms,
|
|
710
1088
|
usePluginTasks,
|
|
711
1089
|
usePluginTranslations,
|
|
1090
|
+
useRouter,
|
|
1091
|
+
useSearchParams,
|
|
712
1092
|
withPluginProvider
|
|
713
1093
|
};
|
|
@@ -106,7 +106,7 @@ interface PluginManifest {
|
|
|
106
106
|
frontend?: {
|
|
107
107
|
entry: string;
|
|
108
108
|
sandbox?: boolean;
|
|
109
|
-
framework?: "react" | "next";
|
|
109
|
+
framework?: "react" | "next" | "palette-app";
|
|
110
110
|
config?: string;
|
|
111
111
|
};
|
|
112
112
|
backend?: {
|
|
@@ -165,6 +165,8 @@ interface PlatformContext {
|
|
|
165
165
|
apiFetch: (path: string, init?: RequestInit) => Promise<Response>;
|
|
166
166
|
/** Navigate to a platform route */
|
|
167
167
|
navigate: (path: string) => void;
|
|
168
|
+
/** Current route path within the plugin app, when mounted by Palette OS */
|
|
169
|
+
routePath?: string;
|
|
168
170
|
/** Show a toast notification */
|
|
169
171
|
showToast: (message: string, type?: "success" | "error" | "info") => void;
|
|
170
172
|
/** Permissions declared for the current plugin install/runtime */
|
|
@@ -106,7 +106,7 @@ interface PluginManifest {
|
|
|
106
106
|
frontend?: {
|
|
107
107
|
entry: string;
|
|
108
108
|
sandbox?: boolean;
|
|
109
|
-
framework?: "react" | "next";
|
|
109
|
+
framework?: "react" | "next" | "palette-app";
|
|
110
110
|
config?: string;
|
|
111
111
|
};
|
|
112
112
|
backend?: {
|
|
@@ -165,6 +165,8 @@ interface PlatformContext {
|
|
|
165
165
|
apiFetch: (path: string, init?: RequestInit) => Promise<Response>;
|
|
166
166
|
/** Navigate to a platform route */
|
|
167
167
|
navigate: (path: string) => void;
|
|
168
|
+
/** Current route path within the plugin app, when mounted by Palette OS */
|
|
169
|
+
routePath?: string;
|
|
168
170
|
/** Show a toast notification */
|
|
169
171
|
showToast: (message: string, type?: "success" | "error" | "info") => void;
|
|
170
172
|
/** Permissions declared for the current plugin install/runtime */
|