@lotics/app-sdk 0.35.0 → 0.36.0
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/AGENTS.md +8 -0
- package/dist/src/hooks.d.ts +46 -0
- package/dist/src/hooks.js +52 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -43,6 +43,14 @@ Pick by intent. (→ open the `.d.ts` for the exact signature.)
|
|
|
43
43
|
- **Upload a file** — **`useFileUpload()`** → `{ upload, uploading, error }`. Mints a presigned URL
|
|
44
44
|
and PUTs the bytes straight to storage; the file is inert until a workflow attaches it to a
|
|
45
45
|
`files` field. Emits `app_file_uploaded`.
|
|
46
|
+
- **Composer attachments (with instant preview)** — **`useAttachments()`** → `{ files, add, remove,
|
|
47
|
+
clear, uploading, fileIds }`. The chat's optimistic-preview UX in one hook: `add(File[])` shows a
|
|
48
|
+
local object-URL preview AT ONCE and uploads in the background; each `files[]` entry carries
|
|
49
|
+
`{ id, filename, mime_type, preview_url, status, file_id? }`; previews are revoked on remove/clear.
|
|
50
|
+
Picking is yours (button → `@lotics/ui` `pickFiles`, or paste/drop) — pass the `File[]` to `add`.
|
|
51
|
+
Wire to `@lotics/ui` `Composer`: `actionsButton` triggers the pick, `files` maps to
|
|
52
|
+
`<FileThumbnail … uploading={f.status === "uploading"} />`, `sendDisabled` gates on `uploading`,
|
|
53
|
+
and the send payload is `fileIds`. Don't hand-roll `createObjectURL`/upload/revoke per app.
|
|
46
54
|
- **List members** — **`useMembers(opts?)`** → `{ members: {id,name,email,image}[], loading, error }`
|
|
47
55
|
— the candidate set for an assign/picker. Gated: the app must declare a `member`-typed input
|
|
48
56
|
(`opts.group` restricts to a declared group). Render with `@lotics/ui` `MemberSelect` / `MemberChip`.
|
package/dist/src/hooks.d.ts
CHANGED
|
@@ -314,6 +314,52 @@ interface FileUploadState {
|
|
|
314
314
|
* ```
|
|
315
315
|
*/
|
|
316
316
|
export declare function useFileUpload(): FileUploadState;
|
|
317
|
+
/** A file attached to a composer — its local preview is available immediately,
|
|
318
|
+
* its stored `file_id` once the background upload completes. */
|
|
319
|
+
export interface AttachedFile {
|
|
320
|
+
/** Stable local id — the React key and the `remove(id)` handle. */
|
|
321
|
+
id: string;
|
|
322
|
+
filename: string;
|
|
323
|
+
mime_type: string;
|
|
324
|
+
/** Local object-URL preview, available the INSTANT the file is added (before upload). */
|
|
325
|
+
preview_url: string;
|
|
326
|
+
status: "uploading" | "ready" | "error";
|
|
327
|
+
/** The stored file id, set once `status` is `"ready"` — pass to a workflow/agent. */
|
|
328
|
+
file_id?: string;
|
|
329
|
+
}
|
|
330
|
+
interface AttachmentsState {
|
|
331
|
+
/** The current attachments, in the order added. */
|
|
332
|
+
files: AttachedFile[];
|
|
333
|
+
/** Add picked/pasted/dropped files — each shows its local preview at once and
|
|
334
|
+
* uploads in the background. Picking is the app's choice: wire a button to
|
|
335
|
+
* `@lotics/ui` `pickFiles`, or a paste/drop handler, then call this. */
|
|
336
|
+
add: (files: File[]) => void;
|
|
337
|
+
/** Remove one attachment and revoke its preview URL. */
|
|
338
|
+
remove: (id: string) => void;
|
|
339
|
+
/** Remove all attachments and revoke their preview URLs. */
|
|
340
|
+
clear: () => void;
|
|
341
|
+
/** True while any attachment is still uploading — gate Send on it. */
|
|
342
|
+
uploading: boolean;
|
|
343
|
+
/** Stored file ids of the completed uploads — the workflow/agent payload. */
|
|
344
|
+
fileIds: string[];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Composer attachments with the chat's optimistic-preview UX: a local object-URL
|
|
348
|
+
* preview shows the INSTANT a file is added, the upload runs in the background,
|
|
349
|
+
* and the stored `file_id` lands in `files` when it completes. Previews are
|
|
350
|
+
* revoked on remove/clear. Picking is the app's (button / paste / drop) — pass
|
|
351
|
+
* the resulting `File[]` to `add`; the hook owns the preview → upload → id →
|
|
352
|
+
* revoke lifecycle so apps don't re-implement it.
|
|
353
|
+
*
|
|
354
|
+
* ```tsx
|
|
355
|
+
* const { files, add, remove, clear, uploading, fileIds } = useAttachments();
|
|
356
|
+
* const design = useWorkflow("design");
|
|
357
|
+
* // attach: <Button icon="paperclip" onPress={() => pickFiles({ accept: "image/*" }).then(add)} />
|
|
358
|
+
* // preview: files.map((f) => <FileThumbnail file={f} uploading={f.status === "uploading"} onRemove={() => remove(f.id)} />)
|
|
359
|
+
* // send: design({ photo: fileIds[0] }); clear();
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
export declare function useAttachments(): AttachmentsState;
|
|
317
363
|
interface MembersState {
|
|
318
364
|
/** Members of the app's organization, for assign / member-picker UIs. */
|
|
319
365
|
members: ResolvedMember[];
|
package/dist/src/hooks.js
CHANGED
|
@@ -246,6 +246,58 @@ export function useFileUpload() {
|
|
|
246
246
|
}, []);
|
|
247
247
|
return { upload, uploading: inFlight > 0, error };
|
|
248
248
|
}
|
|
249
|
+
let attachSeq = 0;
|
|
250
|
+
/**
|
|
251
|
+
* Composer attachments with the chat's optimistic-preview UX: a local object-URL
|
|
252
|
+
* preview shows the INSTANT a file is added, the upload runs in the background,
|
|
253
|
+
* and the stored `file_id` lands in `files` when it completes. Previews are
|
|
254
|
+
* revoked on remove/clear. Picking is the app's (button / paste / drop) — pass
|
|
255
|
+
* the resulting `File[]` to `add`; the hook owns the preview → upload → id →
|
|
256
|
+
* revoke lifecycle so apps don't re-implement it.
|
|
257
|
+
*
|
|
258
|
+
* ```tsx
|
|
259
|
+
* const { files, add, remove, clear, uploading, fileIds } = useAttachments();
|
|
260
|
+
* const design = useWorkflow("design");
|
|
261
|
+
* // attach: <Button icon="paperclip" onPress={() => pickFiles({ accept: "image/*" }).then(add)} />
|
|
262
|
+
* // preview: files.map((f) => <FileThumbnail file={f} uploading={f.status === "uploading"} onRemove={() => remove(f.id)} />)
|
|
263
|
+
* // send: design({ photo: fileIds[0] }); clear();
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function useAttachments() {
|
|
267
|
+
const { upload } = useFileUpload();
|
|
268
|
+
const [files, setFiles] = useState([]);
|
|
269
|
+
const add = useCallback((incoming) => {
|
|
270
|
+
for (const file of incoming) {
|
|
271
|
+
const id = `att_${(attachSeq += 1)}`;
|
|
272
|
+
const previewUrl = URL.createObjectURL(file);
|
|
273
|
+
setFiles((prev) => [
|
|
274
|
+
...prev,
|
|
275
|
+
{ id, filename: file.name, mime_type: file.type, preview_url: previewUrl, status: "uploading" },
|
|
276
|
+
]);
|
|
277
|
+
upload(file)
|
|
278
|
+
.then((uploaded) => setFiles((prev) => prev.map((f) => (f.id === id ? { ...f, status: "ready", file_id: uploaded.id } : f))))
|
|
279
|
+
.catch(() => setFiles((prev) => prev.map((f) => (f.id === id ? { ...f, status: "error" } : f))));
|
|
280
|
+
}
|
|
281
|
+
}, [upload]);
|
|
282
|
+
const remove = useCallback((id) => {
|
|
283
|
+
setFiles((prev) => {
|
|
284
|
+
const target = prev.find((f) => f.id === id);
|
|
285
|
+
if (target)
|
|
286
|
+
URL.revokeObjectURL(target.preview_url);
|
|
287
|
+
return prev.filter((f) => f.id !== id);
|
|
288
|
+
});
|
|
289
|
+
}, []);
|
|
290
|
+
const clear = useCallback(() => {
|
|
291
|
+
setFiles((prev) => {
|
|
292
|
+
for (const f of prev)
|
|
293
|
+
URL.revokeObjectURL(f.preview_url);
|
|
294
|
+
return [];
|
|
295
|
+
});
|
|
296
|
+
}, []);
|
|
297
|
+
const uploading = files.some((f) => f.status === "uploading");
|
|
298
|
+
const fileIds = files.flatMap((f) => (f.status === "ready" && f.file_id ? [f.file_id] : []));
|
|
299
|
+
return { files, add, remove, clear, uploading, fileIds };
|
|
300
|
+
}
|
|
249
301
|
/**
|
|
250
302
|
* List the members of the app's organization — the candidate set for an
|
|
251
303
|
* "assign to a member" picker. Each member is `{ id, name, email, image }`
|
package/dist/src/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export { mount } from "./mount.js";
|
|
18
18
|
export type { MountOptions } from "./mount.js";
|
|
19
|
-
export { useWorkflow, useQuery, useInfiniteQuery, usePaginatedQuery, useFieldOptions, useFileUpload, useMembers, useAgentRun, useAgentRuns, } from "./hooks.js";
|
|
20
|
-
export type { UploadedFile, BaseQueryOptions, QueryOptions, InfiniteQueryOptions, PaginatedQueryOptions, QuerySortKey, QueryFilter, QueryFilterCondition, QueryFilterGroup, WorkflowResult, MembersOptions, AgentRunOptions, UseAgentRun, AgentRunRecord, AgentRunState, AgentRunStep, FieldOptions, FieldOptionsState, FieldOptionsOptions, } from "./hooks.js";
|
|
19
|
+
export { useWorkflow, useQuery, useInfiniteQuery, usePaginatedQuery, useFieldOptions, useFileUpload, useAttachments, useMembers, useAgentRun, useAgentRuns, } from "./hooks.js";
|
|
20
|
+
export type { UploadedFile, AttachedFile, BaseQueryOptions, QueryOptions, InfiniteQueryOptions, PaginatedQueryOptions, QuerySortKey, QueryFilter, QueryFilterCondition, QueryFilterGroup, WorkflowResult, MembersOptions, AgentRunOptions, UseAgentRun, AgentRunRecord, AgentRunState, AgentRunStep, FieldOptions, FieldOptionsState, FieldOptionsOptions, } from "./hooks.js";
|
|
21
21
|
export { useComments, useCommentCounts } from "./comments.js";
|
|
22
22
|
export type { AppComment, AppCommentFile, CommentsState, UseCommentsArgs, CommentCountsState, UseCommentCountsArgs, } from "./comments.js";
|
|
23
23
|
export { useViewer } from "./viewer.js";
|
package/dist/src/index.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* not raw HTML/CSS. See `docs/apps.md` → "Styling & components".
|
|
16
16
|
*/
|
|
17
17
|
export { mount } from "./mount.js";
|
|
18
|
-
export { useWorkflow, useQuery, useInfiniteQuery, usePaginatedQuery, useFieldOptions, useFileUpload, useMembers, useAgentRun, useAgentRuns, } from "./hooks.js";
|
|
18
|
+
export { useWorkflow, useQuery, useInfiniteQuery, usePaginatedQuery, useFieldOptions, useFileUpload, useAttachments, useMembers, useAgentRun, useAgentRuns, } from "./hooks.js";
|
|
19
19
|
export { useComments, useCommentCounts } from "./comments.js";
|
|
20
20
|
export { useViewer } from "./viewer.js";
|
|
21
21
|
export { requestGeofencedLocation, isWithinZone } from "./geolocation.js";
|