@holaboss/client 0.2.0-beta.2 → 0.2.0-beta.20
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/dist/index.cjs +5 -5
- package/dist/index.d.cts +525 -20
- package/dist/index.d.ts +525 -20
- package/dist/index.js +5 -5
- package/package.json +6 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
/** A file the employee produced (image/video/audio/other). In-turn artifacts (a turn's
|
|
@@ -61,6 +61,9 @@ type HolaMessage = {
|
|
|
61
61
|
type HolaSubscribeHandlers = {
|
|
62
62
|
onMessage: (message: HolaMessage) => void;
|
|
63
63
|
onError?: (error: HolaError) => void;
|
|
64
|
+
/** A poll completed cleanly. Fires every successful cycle (even with no new messages) so a
|
|
65
|
+
* consumer can clear a stale error — the receive channel retries, so one blip is not an outage. */
|
|
66
|
+
onHealthy?: () => void;
|
|
64
67
|
};
|
|
65
68
|
type HolaSubscribeOptions = {
|
|
66
69
|
/** Resume from this cursor; omit to read the thread from the start. */
|
|
@@ -97,9 +100,6 @@ type HolaTransport = {
|
|
|
97
100
|
* async artifact. Returns an unsubscribe. Optional: a transport with no receive channel omits
|
|
98
101
|
* it (callers guard on its presence). */
|
|
99
102
|
subscribe?(handlers: HolaSubscribeHandlers, options?: HolaSubscribeOptions): () => void;
|
|
100
|
-
/** "Talk to a person": ask for a human to take over this conversation. The AI pauses until a
|
|
101
|
-
* teammate replies or hands back; the person's messages arrive via `subscribe`. Optional. */
|
|
102
|
-
requestHuman?(): Promise<void>;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
type ApiTransportConfig = {
|
|
@@ -108,10 +108,19 @@ type ApiTransportConfig = {
|
|
|
108
108
|
/** End-user access token for the Authorization header. A string, or a getter so a
|
|
109
109
|
* refreshed token is used per request. */
|
|
110
110
|
token: string | (() => string | Promise<string>);
|
|
111
|
+
/**
|
|
112
|
+
* Which of the customer's projects this conversation is about.
|
|
113
|
+
*
|
|
114
|
+
* Omit for the general thread — the one running conversation with the company,
|
|
115
|
+
* and where everything that predates per-project threads still lives. The
|
|
116
|
+
* server checks the project belongs to this customer before it trusts it, so
|
|
117
|
+
* naming someone else's is a 404 rather than a way into their thread.
|
|
118
|
+
*/
|
|
119
|
+
project?: string;
|
|
111
120
|
};
|
|
112
121
|
/** The Bearer-authed transport for the signed-in end-user PORTAL (`/api/v1/end-user/*`). Same
|
|
113
122
|
* client surface as the anonymous widget transport — the token (not a path param) resolves the
|
|
114
|
-
* customer
|
|
123
|
+
* customer; an optional `project` picks which of their threads. The portal is buffered (no SSE) and one-thread-per-user,
|
|
115
124
|
* so `sendMessage` returns just the reply and everything out-of-turn — the AI's turns, a human's
|
|
116
125
|
* reply, an async artifact (video) — arrives via `subscribe`. */
|
|
117
126
|
declare const createApiTransport: (config: ApiTransportConfig) => HolaTransport;
|
|
@@ -122,7 +131,7 @@ declare const useArtifactUrl: (dataBase64: string, mimeType: string) => string;
|
|
|
122
131
|
|
|
123
132
|
/** A minimal, dependency-free chat surface for the employee. Drop it inside a
|
|
124
133
|
* `<HolaProvider>`. For custom UI, use `useChat(client)` directly. */
|
|
125
|
-
declare const Chat: () =>
|
|
134
|
+
declare const Chat: () => react.JSX.Element;
|
|
126
135
|
|
|
127
136
|
type WidgetTransportConfig = {
|
|
128
137
|
/** API origin that fronts the Holaboss employee endpoint, e.g. "https://api.holaos.ai". */
|
|
@@ -149,13 +158,6 @@ type CreateHolaClientOptions = WidgetTransportConfig | {
|
|
|
149
158
|
* agnostic — the current transport is an internal detail, not the SDK's identity. */
|
|
150
159
|
declare const createHolaClient: (options: CreateHolaClientOptions) => HolaClient;
|
|
151
160
|
|
|
152
|
-
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
153
|
-
declare const textDeltaOf: (event: unknown) => string | null;
|
|
154
|
-
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
155
|
-
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
156
|
-
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
157
|
-
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
158
|
-
|
|
159
161
|
/** A column, as the company defined it. */
|
|
160
162
|
type PortalField = {
|
|
161
163
|
key: string;
|
|
@@ -174,11 +176,22 @@ type PortalTable = {
|
|
|
174
176
|
writable_fields: string[];
|
|
175
177
|
row_count: number;
|
|
176
178
|
};
|
|
179
|
+
/** Who last wrote a row. `employee` is the company's AI employee, `org_member` its
|
|
180
|
+
* staff, `end_user` this customer. Null on rows written before the platform
|
|
181
|
+
* recorded it — unknown rather than assumed. */
|
|
182
|
+
type PortalRowAuthor = {
|
|
183
|
+
kind: "org_member" | "employee" | "end_user";
|
|
184
|
+
id: string | null;
|
|
185
|
+
};
|
|
177
186
|
type PortalRow = {
|
|
178
187
|
rowId: string;
|
|
179
188
|
data: Record<string, unknown>;
|
|
180
189
|
createdAt: string;
|
|
181
190
|
updatedAt: string;
|
|
191
|
+
/** Bumped on every write. Quote it back on a patch (`If-Match`) and a save made
|
|
192
|
+
* against a value that has since moved is refused instead of overwriting it. */
|
|
193
|
+
revision: number;
|
|
194
|
+
updatedBy: PortalRowAuthor | null;
|
|
182
195
|
};
|
|
183
196
|
type PortalRowPage = {
|
|
184
197
|
rows: PortalRow[];
|
|
@@ -235,8 +248,14 @@ type PortalData = {
|
|
|
235
248
|
insertRow(slug: string, row: Record<string, unknown>): Promise<string>;
|
|
236
249
|
/** Pass an empty string to clear a field. */
|
|
237
250
|
patchRow(slug: string, rowId: string, patch: Record<string, unknown>): Promise<number>;
|
|
238
|
-
/**
|
|
239
|
-
*
|
|
251
|
+
/**
|
|
252
|
+
* Only what the company allows a customer to delete, and only when nothing
|
|
253
|
+
* depends on it. Rejects with the reason otherwise.
|
|
254
|
+
*
|
|
255
|
+
* Genuinely any table: which ones are deletable, and what holds a row back, is
|
|
256
|
+
* declared in the company's own schema rather than known to the platform. This
|
|
257
|
+
* used to look general and answer for two hard-coded slugs.
|
|
258
|
+
*/
|
|
240
259
|
deleteRow(slug: string, rowId: string): Promise<number>;
|
|
241
260
|
/** Everything exchanged with this customer, newest first. */
|
|
242
261
|
listFiles(): Promise<PortalFile[]>;
|
|
@@ -279,12 +298,477 @@ type PortalData = {
|
|
|
279
298
|
};
|
|
280
299
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
281
300
|
|
|
301
|
+
/** Build a `PortalField[]` from a slice's rows, typed by the dominant JSON value type per key.
|
|
302
|
+
* `select` is never inferred (it needs an option set only a template carries) — such columns fall
|
|
303
|
+
* back to `text`. Returns [] for an empty slice. */
|
|
304
|
+
declare function deriveFields(rows: readonly {
|
|
305
|
+
data: Record<string, unknown>;
|
|
306
|
+
}[]): PortalField[];
|
|
307
|
+
|
|
308
|
+
/** How a metric column is reduced to a single number. */
|
|
309
|
+
type Agg = "sum" | "avg" | "count" | "min" | "max" | "last";
|
|
310
|
+
type WidgetType = "stat" | "line" | "bar" | "donut" | "table" | "text";
|
|
311
|
+
/** A headline number: one metric reduced by `agg` (omit `metric` to count the rows). */
|
|
312
|
+
interface StatWidget {
|
|
313
|
+
type: "stat";
|
|
314
|
+
/** Which table to read. Absent = the dashboard's primary table. See `Widget.source`. */
|
|
315
|
+
source?: string | undefined;
|
|
316
|
+
label?: string | undefined;
|
|
317
|
+
metric?: string | undefined;
|
|
318
|
+
agg?: Agg | undefined;
|
|
319
|
+
w?: number | undefined;
|
|
320
|
+
h?: number | undefined;
|
|
321
|
+
gx?: number | undefined;
|
|
322
|
+
gy?: number | undefined;
|
|
323
|
+
}
|
|
324
|
+
/** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
|
|
325
|
+
* rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
|
|
326
|
+
interface SeriesWidget {
|
|
327
|
+
type: "line" | "bar";
|
|
328
|
+
/** Which table to read. Absent = the dashboard's primary table. See `Widget.source`. */
|
|
329
|
+
source?: string | undefined;
|
|
330
|
+
title?: string | undefined;
|
|
331
|
+
x: string;
|
|
332
|
+
y?: string | undefined;
|
|
333
|
+
agg?: Agg | undefined;
|
|
334
|
+
w?: number | undefined;
|
|
335
|
+
h?: number | undefined;
|
|
336
|
+
gx?: number | undefined;
|
|
337
|
+
gy?: number | undefined;
|
|
338
|
+
}
|
|
339
|
+
/** A breakdown per distinct value of `groupBy`.
|
|
340
|
+
*
|
|
341
|
+
* `metric` + `agg` say WHAT is measured for each group; omit `metric` and it counts rows, which was
|
|
342
|
+
* the only thing this could do and stays the default — every donut authored before this renders
|
|
343
|
+
* identically. With one, "how many newsletters per status" becomes "how many RECIPIENTS per
|
|
344
|
+
* status", which is usually the number the dashboard exists to show. */
|
|
345
|
+
interface DonutWidget {
|
|
346
|
+
type: "donut";
|
|
347
|
+
/** Which table to read. Absent = the dashboard's primary table. See `Widget.source`. */
|
|
348
|
+
source?: string | undefined;
|
|
349
|
+
title?: string | undefined;
|
|
350
|
+
groupBy: string;
|
|
351
|
+
metric?: string | undefined;
|
|
352
|
+
agg?: Agg | undefined;
|
|
353
|
+
w?: number | undefined;
|
|
354
|
+
h?: number | undefined;
|
|
355
|
+
gx?: number | undefined;
|
|
356
|
+
gy?: number | undefined;
|
|
357
|
+
}
|
|
358
|
+
/** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
|
|
359
|
+
interface TableWidget {
|
|
360
|
+
type: "table";
|
|
361
|
+
/** Which table to read. Absent = the dashboard's primary table. See `Widget.source`. */
|
|
362
|
+
source?: string | undefined;
|
|
363
|
+
title?: string | undefined;
|
|
364
|
+
columns?: string[] | undefined;
|
|
365
|
+
w?: number | undefined;
|
|
366
|
+
h?: number | undefined;
|
|
367
|
+
gx?: number | undefined;
|
|
368
|
+
gy?: number | undefined;
|
|
369
|
+
}
|
|
370
|
+
/** Static prose — a note above or between the data widgets. */
|
|
371
|
+
interface TextWidget {
|
|
372
|
+
type: "text";
|
|
373
|
+
title?: string | undefined;
|
|
374
|
+
body: string;
|
|
375
|
+
w?: number | undefined;
|
|
376
|
+
h?: number | undefined;
|
|
377
|
+
gx?: number | undefined;
|
|
378
|
+
gy?: number | undefined;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* `source` — which table a widget reads.
|
|
382
|
+
*
|
|
383
|
+
* One dashboard, many tables: a service's work items, its spend, a channel breakdown. Absent means
|
|
384
|
+
* the primary table, which is what every spec authored before this said implicitly, so an old spec
|
|
385
|
+
* keeps rendering unchanged. The renderer supplies the named tables; a widget naming one it was not
|
|
386
|
+
* given is dropped rather than silently redrawn against the primary — a chart of the wrong table is
|
|
387
|
+
* worse than no chart, because it looks right.
|
|
388
|
+
*/
|
|
389
|
+
type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
|
|
390
|
+
interface DashboardSpec {
|
|
391
|
+
version: number;
|
|
392
|
+
title?: string | undefined;
|
|
393
|
+
widgets: Widget[];
|
|
394
|
+
}
|
|
395
|
+
/** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
|
|
396
|
+
* for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
|
|
397
|
+
* caller falls back to inference instead of showing a blank dashboard. */
|
|
398
|
+
declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
|
|
399
|
+
|
|
400
|
+
declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
|
|
401
|
+
|
|
402
|
+
/** A point on a line/bar chart or a slice of a donut. */
|
|
403
|
+
interface Point {
|
|
404
|
+
label: string;
|
|
405
|
+
value: number;
|
|
406
|
+
}
|
|
407
|
+
/** A widget resolved against the data — everything a renderer needs, already computed. */
|
|
408
|
+
type ResolvedWidget = {
|
|
409
|
+
kind: "stat";
|
|
410
|
+
label: string;
|
|
411
|
+
value: number;
|
|
412
|
+
w?: number | undefined;
|
|
413
|
+
h?: number | undefined;
|
|
414
|
+
gx?: number | undefined;
|
|
415
|
+
gy?: number | undefined;
|
|
416
|
+
} | {
|
|
417
|
+
kind: "series";
|
|
418
|
+
draw: "line" | "bar";
|
|
419
|
+
title: string;
|
|
420
|
+
points: Point[];
|
|
421
|
+
w?: number | undefined;
|
|
422
|
+
h?: number | undefined;
|
|
423
|
+
gx?: number | undefined;
|
|
424
|
+
gy?: number | undefined;
|
|
425
|
+
} | {
|
|
426
|
+
kind: "donut";
|
|
427
|
+
title: string;
|
|
428
|
+
points: Point[];
|
|
429
|
+
w?: number | undefined;
|
|
430
|
+
h?: number | undefined;
|
|
431
|
+
gx?: number | undefined;
|
|
432
|
+
gy?: number | undefined;
|
|
433
|
+
} | {
|
|
434
|
+
kind: "table";
|
|
435
|
+
title?: string | undefined;
|
|
436
|
+
columns: PortalField[];
|
|
437
|
+
rows: PortalRow[];
|
|
438
|
+
w?: number | undefined;
|
|
439
|
+
h?: number | undefined;
|
|
440
|
+
gx?: number | undefined;
|
|
441
|
+
gy?: number | undefined;
|
|
442
|
+
} | {
|
|
443
|
+
kind: "text";
|
|
444
|
+
title?: string | undefined;
|
|
445
|
+
body: string;
|
|
446
|
+
w?: number | undefined;
|
|
447
|
+
h?: number | undefined;
|
|
448
|
+
gx?: number | undefined;
|
|
449
|
+
gy?: number | undefined;
|
|
450
|
+
};
|
|
451
|
+
/** One table a dashboard can draw from. */
|
|
452
|
+
interface DataSource {
|
|
453
|
+
fields: PortalField[];
|
|
454
|
+
rows: PortalRow[];
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Resolve a whole spec against the data. The result is render-ready and free of widgets that would
|
|
458
|
+
* draw nothing.
|
|
459
|
+
*
|
|
460
|
+
* `fields`/`rows` are the PRIMARY source — what a widget binds to when it names none, and what every
|
|
461
|
+
* dashboard used to be limited to. `others` adds further tables by name, which is what lets one
|
|
462
|
+
* dashboard mix them: a widget's `source` picks which table it reads, so the posts chart and the
|
|
463
|
+
* spend stat can sit on the same canvas over different data.
|
|
464
|
+
*
|
|
465
|
+
* A widget naming a source we were not given is DROPPED, like any other widget that would draw
|
|
466
|
+
* nothing. Falling back to the primary would be worse than silence: it would draw a real chart, with
|
|
467
|
+
* a plausible shape, of the wrong table.
|
|
468
|
+
*/
|
|
469
|
+
declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[], others?: Record<string, DataSource>): ResolvedWidget[];
|
|
470
|
+
|
|
471
|
+
type Band = "stat" | "chart" | "block";
|
|
472
|
+
type StatView = Extract<ResolvedWidget, {
|
|
473
|
+
kind: "stat";
|
|
474
|
+
}>;
|
|
475
|
+
type ChartView = Extract<ResolvedWidget, {
|
|
476
|
+
kind: "series" | "donut";
|
|
477
|
+
}>;
|
|
478
|
+
/** The dashboard grid is a fixed 6 columns wide, at every width. */
|
|
479
|
+
declare const GRID_COLS = 6;
|
|
480
|
+
declare const GRID_MAX_COLS = 6;
|
|
481
|
+
declare const GRID_MAX_ROWS = 4;
|
|
482
|
+
/** The band a resolved widget belongs to. */
|
|
483
|
+
declare function bandOfKind(kind: ResolvedWidget["kind"]): Band;
|
|
484
|
+
/** The band a spec widget TYPE belongs to — for the editor, which lays out widgets before they resolve
|
|
485
|
+
* (a half-configured widget still needs a cell). Mirrors bandOfKind. */
|
|
486
|
+
declare function bandOfType(type: WidgetType): Band;
|
|
487
|
+
/** A band's default cell size. */
|
|
488
|
+
declare function footprintOf(band: Band): {
|
|
489
|
+
w: number;
|
|
490
|
+
h: number;
|
|
491
|
+
};
|
|
492
|
+
/** A widget's effective cell size: its stored `w`×`h` (clamped to the grid), else the band default.
|
|
493
|
+
* The renderers and the editor both size cells through this, so a widget looks the same everywhere. */
|
|
494
|
+
declare function footprintWith(band: Band, w: number | undefined, h: number | undefined): {
|
|
495
|
+
w: number;
|
|
496
|
+
h: number;
|
|
497
|
+
};
|
|
498
|
+
/** A widget placed on the grid — the view-model plus its top-left cell and size in cells. */
|
|
499
|
+
interface PlacedWidget {
|
|
500
|
+
widget: ResolvedWidget;
|
|
501
|
+
gx: number;
|
|
502
|
+
gy: number;
|
|
503
|
+
w: number;
|
|
504
|
+
h: number;
|
|
505
|
+
}
|
|
506
|
+
/** Place every widget on the fixed grid: honor its stored `gx`/`gy` when set (and free), else auto-
|
|
507
|
+
* place it first-fit (top-to-bottom, left-to-right) at its footprint. Deterministic + collision-free,
|
|
508
|
+
* so a legacy spec (no positions) and a positioned one both render stably, and a stale position that
|
|
509
|
+
* now overlaps (e.g. after a neighbour grew) falls back to a free slot instead of stacking. */
|
|
510
|
+
declare function placeWidgets(resolved: ResolvedWidget[]): PlacedWidget[];
|
|
511
|
+
/** A dashboard split into the two things that lay out differently: square TILES (stats + charts) that
|
|
512
|
+
* pack into the fixed grid, and full-width BLOCKS (tables + notes) that stack UNDER the grid at their
|
|
513
|
+
* content height. A table's height depends on its rows, not the column width, so it can't share the
|
|
514
|
+
* square-cell grid without leaving slack — it flows below instead, exactly as tall as its content. */
|
|
515
|
+
interface DashboardLayout {
|
|
516
|
+
/** Stats + charts, placed on the fixed grid. */
|
|
517
|
+
tiles: PlacedWidget[];
|
|
518
|
+
/** Tables + notes, full-width and content-height, in spec order (top to bottom). */
|
|
519
|
+
blocks: ResolvedWidget[];
|
|
520
|
+
}
|
|
521
|
+
/** Split resolved widgets into grid tiles and stacked full-width blocks (see {@link DashboardLayout}).
|
|
522
|
+
* Every surface (portal, console) renders these the same way, so a dashboard reads identically. */
|
|
523
|
+
declare function splitLayout(resolved: ResolvedWidget[]): DashboardLayout;
|
|
524
|
+
|
|
525
|
+
type DeliverableItemKind = "file" | "link" | "text" | "table";
|
|
526
|
+
type DeliverableItem = {
|
|
527
|
+
kind: DeliverableItemKind;
|
|
528
|
+
value: string;
|
|
529
|
+
/** Version this item was delivered in, numbered from 1 (absent = v1). */
|
|
530
|
+
v?: number;
|
|
531
|
+
};
|
|
532
|
+
/** One delivered version — a bundle of items sent together, numbered from 1. */
|
|
533
|
+
type DeliverableVersion = {
|
|
534
|
+
v: number;
|
|
535
|
+
items: DeliverableItem[];
|
|
536
|
+
};
|
|
537
|
+
declare function normalizeDeliverableItem(x: unknown): DeliverableItem | null;
|
|
538
|
+
declare function groupDeliverableVersions(items: DeliverableItem[]): DeliverableVersion[];
|
|
539
|
+
/** Parse a deliverable row's items from its `data`. Tolerant of the legacy single `file` field and of
|
|
540
|
+
* bare-string refs; returns [] when there is nothing. */
|
|
541
|
+
declare function parseDeliverableItems(data: Record<string, unknown>): DeliverableItem[];
|
|
542
|
+
type DeliverableReviewState = "awaiting" | "accepted" | "revise";
|
|
543
|
+
declare function deliverableStatus(items: DeliverableItem[], publishedVersion: number, verdictById: Map<string, string>,
|
|
544
|
+
/** The client's verdict on the delivered TABLE (from the deliverable's response), if any. */
|
|
545
|
+
tableVerdict?: "" | "accepted" | "revise"): DeliverableReviewState;
|
|
546
|
+
/** The version the client currently sees — the EXPLICIT `published_version` pointer the operator set
|
|
547
|
+
* (0 = nothing published). No `done → latest` fallback: that followed the latest version, so a new
|
|
548
|
+
* version the team delivered would auto-appear to the client before it was published. */
|
|
549
|
+
declare function publishedVersionOf(data: Record<string, unknown>): number;
|
|
550
|
+
|
|
551
|
+
/** A column type. `select` is an open enum edited as text; the rest drive how a cell reads. */
|
|
552
|
+
type DeliverableColType = "text" | "number" | "url" | "date" | "boolean" | "select";
|
|
553
|
+
type DeliverableColumn = {
|
|
554
|
+
key: string;
|
|
555
|
+
label: string;
|
|
556
|
+
type: DeliverableColType;
|
|
557
|
+
/** The customer may fill this column in from their portal — their values live in the deliverable's
|
|
558
|
+
* `responses`, layered over the operator's cells. Absent = read-only (the default). */
|
|
559
|
+
clientEditable?: boolean;
|
|
560
|
+
};
|
|
561
|
+
type DeliverableRow = {
|
|
562
|
+
id: string;
|
|
563
|
+
cells: Record<string, unknown>;
|
|
564
|
+
};
|
|
565
|
+
type DeliverableTable = {
|
|
566
|
+
/** The operator's name for this table (e.g. "Twitter influencers"). Optional for older payloads. */
|
|
567
|
+
name?: string;
|
|
568
|
+
columns: DeliverableColumn[];
|
|
569
|
+
rows: DeliverableRow[];
|
|
570
|
+
};
|
|
571
|
+
/** Legacy tables used `link`/`checkbox`; the current vocabulary is `url`/`boolean`. */
|
|
572
|
+
declare function normalizeDeliverableColType(t: unknown): DeliverableColType;
|
|
573
|
+
/** Parse a table item's JSON `value` into the grid model, tolerating the legacy id-keyed shape (its
|
|
574
|
+
* columns carried `id` not `key`, so a legacy column keeps its cells by keying on that id). Returns
|
|
575
|
+
* null when `value` is not a table payload. */
|
|
576
|
+
declare function parseDeliverableTable(value: string): DeliverableTable | null;
|
|
577
|
+
/** A cell value as display text (a stored null/undefined reads as empty). */
|
|
578
|
+
declare function deliverableCellText(v: unknown): string;
|
|
579
|
+
/** Whether a `boolean` cell is checked — tolerant of a real boolean or the string forms it was stored
|
|
580
|
+
* as across versions. */
|
|
581
|
+
declare function isDeliverableChecked(v: unknown): boolean;
|
|
582
|
+
type TableReviewVerdict = "" | "accepted" | "revise";
|
|
583
|
+
type TableResponses = {
|
|
584
|
+
/** The client's edits to client-editable columns: rowId → columnKey → value. */
|
|
585
|
+
cells: Record<string, Record<string, unknown>>;
|
|
586
|
+
/** The client's review of the table — empty until they submit. */
|
|
587
|
+
verdict: TableReviewVerdict;
|
|
588
|
+
/** The client's comment when sending back (or a note on approve). */
|
|
589
|
+
comment: string;
|
|
590
|
+
};
|
|
591
|
+
declare function emptyTableResponses(): TableResponses;
|
|
592
|
+
/** Parse a deliverable's `responses` field (client_tasks.responses). Tolerant of the legacy bare
|
|
593
|
+
* rowId→colKey map (read as edits with no review yet). */
|
|
594
|
+
declare function parseTableResponses(value: unknown): TableResponses;
|
|
595
|
+
/** The value to SHOW for a cell: the customer's edit on a client-editable column, else the operator's
|
|
596
|
+
* authored value. */
|
|
597
|
+
declare function cellValue(col: DeliverableColumn, row: DeliverableRow, responses: TableResponses): unknown;
|
|
598
|
+
/** Merge one client cell edit into a response (immutably) — for the client's local draft. */
|
|
599
|
+
declare function setResponse(responses: TableResponses, rowId: string, colKey: string, value: unknown): TableResponses;
|
|
600
|
+
/** Set the review verdict + comment (immutably) — for the submit (approve / send back) action. */
|
|
601
|
+
declare function setTableReview(responses: TableResponses, verdict: TableReviewVerdict, comment: string): TableResponses;
|
|
602
|
+
/** A `date` cell stores YYYY-MM-DD; format it as e.g. "Aug 18, 2026" (raw string if unparseable). */
|
|
603
|
+
declare function formatDeliverableDate(v: string): string;
|
|
604
|
+
|
|
605
|
+
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
606
|
+
declare const textDeltaOf: (event: unknown) => string | null;
|
|
607
|
+
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
608
|
+
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
609
|
+
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
610
|
+
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
611
|
+
|
|
612
|
+
type PortalShopConfig = {
|
|
613
|
+
/** API origin, e.g. "https://api.holaos.ai" — or empty for a portal whose own worker answers
|
|
614
|
+
* `/api/v1/end-user/…` same-origin, which is how the iMerch template is wired. */
|
|
615
|
+
baseUrl: string;
|
|
616
|
+
/**
|
|
617
|
+
* The tenant's publishable key, which is the whole of the authentication here.
|
|
618
|
+
*
|
|
619
|
+
* A plain string rather than the getter `ApiTransportConfig.token` takes: an end-user token
|
|
620
|
+
* refreshes and a publishable key does not — it is baked into the page source a visitor can read,
|
|
621
|
+
* which is what makes it publishable. There is deliberately no token field on this config at all,
|
|
622
|
+
* so nothing in a storefront can come to depend on being signed in to browse.
|
|
623
|
+
*/
|
|
624
|
+
publishableKey: string;
|
|
625
|
+
};
|
|
626
|
+
/** How a product is taken: `buy` charges for it, `ask` opens the conversation the customer already
|
|
627
|
+
* has with the company, `apply` posts the form `apply()` sends. */
|
|
628
|
+
type ShopMode = "buy" | "ask" | "apply";
|
|
629
|
+
/**
|
|
630
|
+
* What a product costs — exactly one of four shapes, never two.
|
|
631
|
+
*
|
|
632
|
+
* The number is in whatever unit the OPERATOR prices in: credits for an org that sells them, money
|
|
633
|
+
* for one that does not — and the wire says which, on `ShopProduct.currency` beside this. It used
|
|
634
|
+
* to say otherwise, that the figure travelled bare and the unit "belongs to the template that knows
|
|
635
|
+
* the tenant", which was how this contract was written before `pricing_unit` existed and was false
|
|
636
|
+
* the day the server started sending the field. A template built on that sentence prints a
|
|
637
|
+
* hardcoded `$` in front of a credit price — wrong by the whole book rate. Nothing here should ever
|
|
638
|
+
* print a `$` it did not read off `currency`.
|
|
639
|
+
*
|
|
640
|
+
* `quoted` is not a price. It is the honest answer for everything in a real catalogue that reads
|
|
641
|
+
* `5k – 10k`, `20k+` or 待定 — a statement that a human has to quote this. Render it as words, and
|
|
642
|
+
* never behind a Buy button that cannot compute a total.
|
|
643
|
+
*/
|
|
644
|
+
type ShopPricing = {
|
|
645
|
+
kind: "flat";
|
|
646
|
+
price: number;
|
|
647
|
+
} | {
|
|
648
|
+
kind: "per_unit";
|
|
649
|
+
unit: string;
|
|
650
|
+
unitPrice: number;
|
|
651
|
+
unitStep: number;
|
|
652
|
+
} | {
|
|
653
|
+
kind: "variants";
|
|
654
|
+
variants: {
|
|
655
|
+
name: string;
|
|
656
|
+
price: number | null;
|
|
657
|
+
}[];
|
|
658
|
+
} | {
|
|
659
|
+
kind: "quoted";
|
|
660
|
+
};
|
|
661
|
+
/** One part of a bundle, as the shelf is allowed to describe it: what it is called, what it covers,
|
|
662
|
+
* how long it runs — and no price, for the reason at the top of this file. */
|
|
663
|
+
type ShopInclusion = {
|
|
664
|
+
name: string;
|
|
665
|
+
content?: string;
|
|
666
|
+
period?: string;
|
|
667
|
+
};
|
|
668
|
+
/** A named tier — 基础 / 标准 / 高阶 — and what it covers. Its PRICE is not here: it is in
|
|
669
|
+
* `pricing` (`kind: "variants"`), matched to this by name, so that there is one place the money
|
|
670
|
+
* comes from and no way for the two to disagree. */
|
|
671
|
+
type ShopVariant = {
|
|
672
|
+
name: string;
|
|
673
|
+
includes: ShopInclusion[];
|
|
674
|
+
};
|
|
675
|
+
/** One product as a visitor sees it. */
|
|
676
|
+
type ShopProduct = {
|
|
677
|
+
id: string;
|
|
678
|
+
name: string;
|
|
679
|
+
summary: string;
|
|
680
|
+
description: string;
|
|
681
|
+
/**
|
|
682
|
+
* One of `ShopMode` for anything authored in the console — but typed as the string it is on the
|
|
683
|
+
* wire, because the server sends the row's own value and a product from before the field existed
|
|
684
|
+
* answers `""`. A storefront that switched over the union would be handed a value the type swore
|
|
685
|
+
* could not occur, so match the three and keep a default arm.
|
|
686
|
+
*/
|
|
687
|
+
mode: string;
|
|
688
|
+
category: string;
|
|
689
|
+
platform: string;
|
|
690
|
+
cadence: string;
|
|
691
|
+
period: string;
|
|
692
|
+
/** Image URLs for the card and the product page; empty when the operator set none. */
|
|
693
|
+
media: string[];
|
|
694
|
+
pricing: ShopPricing;
|
|
695
|
+
/** What THIS product's numbers are in — an ISO code, or the literal "credits". It sits on the
|
|
696
|
+
* product rather than on the shelf because a shelf can be mixed: once an org sells credits, its
|
|
697
|
+
* buyable products are priced in them while a 20k engagement it merely quotes stays in money.
|
|
698
|
+
* Empty from a backend older than the field, which is a shelf that has not SAID — and is not the
|
|
699
|
+
* same as one that said dollars, so print the bare figure rather than guessing a symbol. */
|
|
700
|
+
currency: string;
|
|
701
|
+
/** Bounds on a per-unit purchase, when the product states them. Public because the quantity
|
|
702
|
+
* picker has to honour them — a customer who meets a limit they were never shown reads it as a
|
|
703
|
+
* broken shop. */
|
|
704
|
+
minQty?: number;
|
|
705
|
+
maxQty?: number;
|
|
706
|
+
includes: ShopInclusion[];
|
|
707
|
+
/** Present only where the tiers differ in scope; a product without them has one implicit tier,
|
|
708
|
+
* which is its own `pricing` and its own `includes`. */
|
|
709
|
+
variants?: ShopVariant[];
|
|
710
|
+
};
|
|
711
|
+
type ShopShelf = {
|
|
712
|
+
products: ShopProduct[];
|
|
713
|
+
/**
|
|
714
|
+
* The catalogue is bigger than what came back.
|
|
715
|
+
*
|
|
716
|
+
* Kept rather than dropped for the reason `listRows` returns a page: the server caps a shelf read,
|
|
717
|
+
* and a shop that quietly ends at the cap is a customer who cannot see half of it and nobody who
|
|
718
|
+
* can tell. The same argument the server makes when it bothers to send the flag.
|
|
719
|
+
*/
|
|
720
|
+
truncated: boolean;
|
|
721
|
+
};
|
|
722
|
+
/** An application against a product, or against nothing in particular — a general enquiry that
|
|
723
|
+
* names no product is a real thing to receive, so `productId` is optional. */
|
|
724
|
+
type ShopApplication = {
|
|
725
|
+
name: string;
|
|
726
|
+
/**
|
|
727
|
+
* Where the operator answers. Required, and checked by the server — but a malformed one is
|
|
728
|
+
* dropped SILENTLY (see `apply` below), so a form that wants to tell someone their address is
|
|
729
|
+
* wrong has to say so itself, before it sends.
|
|
730
|
+
*/
|
|
731
|
+
email: string;
|
|
732
|
+
phone?: string;
|
|
733
|
+
message?: string;
|
|
734
|
+
productId?: string;
|
|
735
|
+
};
|
|
736
|
+
/** The one answer every application gets. See `apply` for why it never varies. */
|
|
737
|
+
type ShopApplyResult = {
|
|
738
|
+
/**
|
|
739
|
+
* Always `true` — including for a submission that was dropped. It is a receipt that the request
|
|
740
|
+
* was accepted, never evidence that a row exists.
|
|
741
|
+
*/
|
|
742
|
+
ok: boolean;
|
|
743
|
+
/** The server's own thank-you line. Shown as sent rather than reworded, so the operator's copy is
|
|
744
|
+
* the copy the applicant reads. */
|
|
745
|
+
message: string;
|
|
746
|
+
};
|
|
747
|
+
type PortalShop = {
|
|
748
|
+
/** Everything on sale, in the operator's own order. Listed products only — a draft is something
|
|
749
|
+
* still being written and an archived one is off sale but kept because orders point at it. */
|
|
750
|
+
listProducts(): Promise<ShopShelf>;
|
|
751
|
+
/**
|
|
752
|
+
* Send an application. No account, no session — this is the one write on the platform a total
|
|
753
|
+
* stranger can make.
|
|
754
|
+
*
|
|
755
|
+
* It answers the SAME "thanks, we have your details" to an unknown key, a body that did not
|
|
756
|
+
* parse and a write that failed as it does to an application that landed, and that is deliberate:
|
|
757
|
+
* anything else would turn this into a way to ask which publishable keys reach a real company —
|
|
758
|
+
* and, once signups are invite-only, which addresses one already has. The cost is that a
|
|
759
|
+
* miswired form gets a cheerful reply and files nothing, so when applications are not arriving,
|
|
760
|
+
* the answer is in the operator's server logs and cannot be here.
|
|
761
|
+
*/
|
|
762
|
+
apply(application: ShopApplication): Promise<ShopApplyResult>;
|
|
763
|
+
};
|
|
764
|
+
declare const createPortalShop: (config: PortalShopConfig) => PortalShop;
|
|
765
|
+
|
|
282
766
|
/** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
|
|
283
767
|
* Create the client once (e.g. `useMemo`) so its session persists across renders. */
|
|
284
768
|
declare const HolaProvider: ({ client, children, }: {
|
|
285
769
|
client: HolaClient;
|
|
286
770
|
children: ReactNode;
|
|
287
|
-
}) =>
|
|
771
|
+
}) => react.JSX.Element;
|
|
288
772
|
declare const useHolaClient: () => HolaClient;
|
|
289
773
|
|
|
290
774
|
type ChatMessage = {
|
|
@@ -305,6 +789,30 @@ type UseChat = {
|
|
|
305
789
|
/** Chat state for one employee client: greeting, message list, streaming send. */
|
|
306
790
|
declare const useChat: (client: HolaClient) => UseChat;
|
|
307
791
|
|
|
792
|
+
type UseShop = {
|
|
793
|
+
products: ShopProduct[];
|
|
794
|
+
/** In flight — the first read, and every `reload`. The products from the previous read stay put
|
|
795
|
+
* underneath it, so a refresh does not blank a shop that is already on screen. */
|
|
796
|
+
loading: boolean;
|
|
797
|
+
/** The catalogue is bigger than `products`; the server capped the read. */
|
|
798
|
+
truncated: boolean;
|
|
799
|
+
/** Why the last read failed, as the SDK's own code — `rate_limited` is the one worth wording
|
|
800
|
+
* differently, since it is the only failure that fixes itself. */
|
|
801
|
+
error: string | null;
|
|
802
|
+
reload: () => void;
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* The public shelf, loaded on mount.
|
|
806
|
+
*
|
|
807
|
+
* Hold `shop` still: build it once at module scope, the way a portal builds its client (`export
|
|
808
|
+
* const holaShop = createPortalShop(config)`), and hand the same object in on every render. A
|
|
809
|
+
* `createPortalShop({...})` written inside the component body is a new object each time, and since
|
|
810
|
+
* that identity is what this re-reads on, the storefront would fetch its own shelf in a loop —
|
|
811
|
+
* against a route that is rate-limited per visitor, so the shop would end up empty for the person
|
|
812
|
+
* least at fault.
|
|
813
|
+
*/
|
|
814
|
+
declare const useShop: (shop: PortalShop) => UseShop;
|
|
815
|
+
|
|
308
816
|
/** A thread message; `pending` marks an optimistic echo of the visitor's own send, shown
|
|
309
817
|
* instantly and replaced by the authoritative message when the receive channel delivers it. */
|
|
310
818
|
type ThreadMessage = HolaMessage & {
|
|
@@ -318,9 +826,6 @@ type UseThread = {
|
|
|
318
826
|
sending: boolean;
|
|
319
827
|
error: string | null;
|
|
320
828
|
};
|
|
321
|
-
/** Merge one incoming thread message: dedupe by id, and replace a pending optimistic echo of the
|
|
322
|
-
* visitor's own send (same text) with the authoritative one. Pure — the hook's core, so the
|
|
323
|
-
* merge rule is unit-checkable without React. */
|
|
324
829
|
declare function mergeMessage(list: ThreadMessage[], incoming: HolaMessage): ThreadMessage[];
|
|
325
830
|
/** A poll-based UNIFIED thread: the whole conversation — visitor, agent, and HUMAN turns — as one
|
|
326
831
|
* live list via the receive channel (`client.subscribe`). Unlike `useChat` (streaming, one turn
|
|
@@ -331,4 +836,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
331
836
|
pollMs?: number;
|
|
332
837
|
}) => UseThread;
|
|
333
838
|
|
|
334
|
-
export { type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ThreadMessage, type UseChat, type UseThread, type WidgetTransportConfig, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, mergeMessage, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
|
|
839
|
+
export { type Agg, type ApiTransportConfig, type Band, type ChartView, Chat, type ChatMessage, type CreateHolaClientOptions, type DashboardLayout, type DashboardSpec, type DataSource, type DeliverableColType, type DeliverableColumn, type DeliverableItem, type DeliverableItemKind, type DeliverableReviewState, type DeliverableRow, type DeliverableTable, type DeliverableVersion, type DonutWidget, GRID_COLS, GRID_MAX_COLS, GRID_MAX_ROWS, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, type PlacedWidget, type Point, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowAuthor, type PortalRowPage, type PortalShop, type PortalShopConfig, type PortalTable, type ResolvedWidget, type SeriesWidget, type ShopApplication, type ShopApplyResult, type ShopInclusion, type ShopMode, type ShopPricing, type ShopProduct, type ShopShelf, type ShopVariant, type StatView, type StatWidget, type TableResponses, type TableReviewVerdict, type TableWidget, type TextWidget, type ThreadMessage, type UseChat, type UseShop, type UseThread, type Widget, type WidgetTransportConfig, type WidgetType, bandOfKind, bandOfType, cellValue, createApiTransport, createHolaClient, createPortalData, createPortalShop, createWidgetTransport, deliverableCellText, deliverableStatus, deriveFields, emptyTableResponses, footprintOf, footprintWith, formatDeliverableDate, groupDeliverableVersions, inferSpec, isDeliverableChecked, mergeMessage, normalizeDeliverableColType, normalizeDeliverableItem, parseDashboardSpec, parseDeliverableItems, parseDeliverableTable, parseTableResponses, placeWidgets, publishedVersionOf, renderModel, setResponse, setTableReview, splitLayout, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useShop, useThread };
|