@opengeni/react 7.4.0 → 7.5.0-canary.36927100590001
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 +124 -27
- package/dist/artifacts-document.js +3 -2
- package/dist/artifacts-presentation.js +3 -2
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +516 -1413
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-2E6R2Y4W.js → browser-viewer-Z7S7QEJL.js} +6 -5
- package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
- package/dist/{chunk-Y3QTZN2F.js → chunk-2VFNCX4E.js} +6 -6
- package/dist/{chunk-UDD6DLLT.js → chunk-3IJJEYW4.js} +15 -3
- package/dist/chunk-3IJJEYW4.js.map +1 -0
- package/dist/chunk-4RVYOY4O.js +379 -0
- package/dist/chunk-4RVYOY4O.js.map +1 -0
- package/dist/chunk-5CVXZA3Y.js +292 -0
- package/dist/chunk-5CVXZA3Y.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-5J4FOW6Q.js} +2 -10
- package/dist/chunk-5J4FOW6Q.js.map +1 -0
- package/dist/{chunk-KVERXCFY.js → chunk-5KYZIQFY.js} +40 -3
- package/dist/chunk-5KYZIQFY.js.map +1 -0
- package/dist/{chunk-KNDIXZN6.js → chunk-7UAMYXJY.js} +25 -18
- package/dist/{chunk-KNDIXZN6.js.map → chunk-7UAMYXJY.js.map} +1 -1
- package/dist/{chunk-SEEVCT36.js → chunk-A4UJB2MM.js} +2 -2
- package/dist/{chunk-X4HIN6TV.js → chunk-AFLPWUQ7.js} +71 -162
- package/dist/chunk-AFLPWUQ7.js.map +1 -0
- package/dist/chunk-B3V6TG77.js +41 -0
- package/dist/chunk-B3V6TG77.js.map +1 -0
- package/dist/{chunk-XEJEOAOV.js → chunk-B7WMS3CS.js} +51 -40
- package/dist/chunk-B7WMS3CS.js.map +1 -0
- package/dist/{chunk-S5JQ4R22.js → chunk-BDKR6BAR.js} +54 -24
- package/dist/chunk-BDKR6BAR.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-DYWBUX5Q.js} +24 -12
- package/dist/chunk-DYWBUX5Q.js.map +1 -0
- package/dist/{chunk-YZ3YYMOM.js → chunk-E3G7SAYZ.js} +252 -624
- package/dist/chunk-E3G7SAYZ.js.map +1 -0
- package/dist/chunk-JT4JW7BC.js +54 -0
- package/dist/chunk-JT4JW7BC.js.map +1 -0
- package/dist/{chunk-5AZSK6MV.js → chunk-LWNLETQT.js} +5 -5
- package/dist/{chunk-USXVQACU.js → chunk-M7LBYP63.js} +5 -4
- package/dist/{chunk-USXVQACU.js.map → chunk-M7LBYP63.js.map} +1 -1
- package/dist/{chunk-L4OSXQ6H.js → chunk-NBOHNRLJ.js} +1289 -1060
- package/dist/chunk-NBOHNRLJ.js.map +1 -0
- package/dist/chunk-RHREZ7BO.js +11 -0
- package/dist/chunk-RHREZ7BO.js.map +1 -0
- package/dist/chunk-SCOTPE2I.js +589 -0
- package/dist/chunk-SCOTPE2I.js.map +1 -0
- package/dist/chunk-WEHMPOXK.js +39 -0
- package/dist/chunk-WEHMPOXK.js.map +1 -0
- package/dist/chunk-WRHIZYZF.js +988 -0
- package/dist/chunk-WRHIZYZF.js.map +1 -0
- package/dist/{chunk-3E2AITTZ.js → chunk-WUISZ3WU.js} +32 -28
- package/dist/{chunk-3E2AITTZ.js.map → chunk-WUISZ3WU.js.map} +1 -1
- package/dist/chunk-XCHWDJ22.js +107 -0
- package/dist/chunk-XCHWDJ22.js.map +1 -0
- package/dist/{chunk-DUEBTL3N.js → chunk-Y2PRMYLC.js} +4 -3
- package/dist/{chunk-DUEBTL3N.js.map → chunk-Y2PRMYLC.js.map} +1 -1
- package/dist/chunk-YBXQDQFX.js +1772 -0
- package/dist/chunk-YBXQDQFX.js.map +1 -0
- package/dist/chunk-YV4L3ILF.js +9 -0
- package/dist/chunk-YV4L3ILF.js.map +1 -0
- package/dist/{chunk-V2ABIVYJ.js → chunk-ZXSEDZP7.js} +70 -6
- package/dist/chunk-ZXSEDZP7.js.map +1 -0
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
- package/dist/components/artifacts/index.d.ts +7 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +23 -0
- package/dist/components/browser-select-control.d.ts +3 -1
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +20 -3
- package/dist/components/open-geni-links.d.ts +16 -0
- package/dist/components/session-conversation.d.ts +20 -1
- package/dist/components/timeline-pill-geometry.d.ts +8 -0
- package/dist/components/timeline-pill-placement.d.ts +24 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/composer.js +5 -5
- package/dist/{computer-viewer-WMJE6OA3.js → computer-viewer-KJWLH3GI.js} +7 -5
- package/dist/connect.js +7 -6
- package/dist/connect.js.map +1 -1
- package/dist/conversation-timeline.d.ts +1 -1
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +26 -0
- package/dist/desktop.js.map +1 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +21 -0
- package/dist/editor.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +1 -1
- package/dist/hooks/use-composer.d.ts +4 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +460 -1386
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +8 -6
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-QB4EUCW5.js → latest-question-IS445HOW.js} +3 -3
- package/dist/lib/format.d.ts +6 -1
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/machines.js +2 -2
- package/dist/{markdown-table-layout-PFUEZ2U4.js → markdown-table-layout-QXSLK4BY.js} +37 -5
- package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
- package/dist/{queue-surface-implementation-IM2HD3SK.js → queue-surface-implementation-2CAQAIHM.js} +2 -2
- package/dist/realtime.js +3 -3
- package/dist/{sandbox-files-PXPIZAFG.js → sandbox-files-AEJTWGT4.js} +8 -5
- package/dist/sandbox-files-AEJTWGT4.js.map +1 -0
- package/dist/session-ui.d.ts +9 -2
- package/dist/session-ui.js +39 -20
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +4 -4
- package/dist/sites.js +4 -289
- package/dist/sites.js.map +1 -1
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +31 -0
- package/dist/terminal.js.map +1 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/genie-loading.d.ts +3 -1
- package/dist/timeline/types.d.ts +8 -1
- package/dist/{timeline-annotation-selection-ODLVF4OI.js → timeline-annotation-selection-DMLMM2TX.js} +6 -1
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
- package/dist/timeline-pill-geometry-Z23V243M.js +110 -0
- package/dist/timeline-pill-geometry-Z23V243M.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +1073 -0
- package/dist/usage.js.map +1 -0
- package/package.json +19 -3
- package/src/artifacts.ts +37 -0
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +185 -0
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +3 -1
- package/src/components/artifacts/editable-artifact-view.tsx +187 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +34 -36
- package/src/components/artifacts/editable-presentation.tsx +21 -16
- package/src/components/artifacts/index.ts +41 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
- package/src/components/artifacts/site-view.tsx +116 -0
- package/src/components/browser-select-control.tsx +4 -1
- package/src/components/browser-viewer.tsx +81 -22
- package/src/components/code-editor.tsx +13 -16
- package/src/components/computer-viewer.tsx +51 -23
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/markdown-table-layout.ts +49 -5
- package/src/components/markdown.tsx +5 -2
- package/src/components/message-timeline.tsx +557 -516
- package/src/components/open-geni-links.tsx +24 -0
- package/src/components/sandbox-terminal.tsx +48 -11
- package/src/components/session-chrome.tsx +22 -3
- package/src/components/session-conversation.tsx +195 -102
- package/src/components/timeline-annotation-selection.tsx +7 -0
- package/src/components/timeline-pill-geometry.ts +169 -0
- package/src/components/timeline-pill-placement.ts +99 -0
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/conversation-timeline.ts +10 -2
- package/src/desktop.ts +24 -0
- package/src/editor.ts +13 -0
- package/src/hooks/use-available-models.ts +1 -1
- package/src/hooks/use-composer.ts +57 -2
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-usage.ts +100 -0
- package/src/index.ts +22 -0
- package/src/lib/format.ts +32 -6
- package/src/lib/workbench-peers.ts +62 -0
- package/src/session-ui.ts +22 -0
- package/src/terminal.ts +26 -0
- package/src/timeline/activity-rail.tsx +5 -3
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/genie-loading.tsx +16 -6
- package/src/timeline/projection.ts +78 -4
- package/src/timeline/types.ts +8 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +50 -0
- package/styles/compiled.css +325 -0
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-KVERXCFY.js.map +0 -1
- package/dist/chunk-L4OSXQ6H.js.map +0 -1
- package/dist/chunk-S5JQ4R22.js.map +0 -1
- package/dist/chunk-UDD6DLLT.js.map +0 -1
- package/dist/chunk-UGMKPUEM.js +0 -299
- package/dist/chunk-UGMKPUEM.js.map +0 -1
- package/dist/chunk-V2ABIVYJ.js.map +0 -1
- package/dist/chunk-X4HIN6TV.js.map +0 -1
- package/dist/chunk-XEJEOAOV.js.map +0 -1
- package/dist/chunk-YZ3YYMOM.js.map +0 -1
- package/dist/markdown-table-layout-PFUEZ2U4.js.map +0 -1
- package/dist/timeline-annotation-selection-ODLVF4OI.js.map +0 -1
- /package/dist/{browser-viewer-2E6R2Y4W.js.map → browser-viewer-Z7S7QEJL.js.map} +0 -0
- /package/dist/{computer-viewer-WMJE6OA3.js.map → chat-interactive-block-UGBTNB3L.js.map} +0 -0
- /package/dist/{chunk-Y3QTZN2F.js.map → chunk-2VFNCX4E.js.map} +0 -0
- /package/dist/{chunk-SEEVCT36.js.map → chunk-A4UJB2MM.js.map} +0 -0
- /package/dist/{chunk-5AZSK6MV.js.map → chunk-LWNLETQT.js.map} +0 -0
- /package/dist/{sandbox-files-PXPIZAFG.js.map → computer-viewer-KJWLH3GI.js.map} +0 -0
- /package/dist/{latest-question-QB4EUCW5.js.map → latest-question-IS445HOW.js.map} +0 -0
- /package/dist/{queue-surface-implementation-IM2HD3SK.js.map → queue-surface-implementation-2CAQAIHM.js.map} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseMediaGenerationResult,
|
|
3
3
|
parseToolDisplayMetadata,
|
|
4
|
+
EMPTY_FINAL_REPLY_NOTICE,
|
|
5
|
+
turnCompletedWithEmptyFinalReply,
|
|
4
6
|
type HumanInputAnswer,
|
|
5
7
|
type HumanInputQuestion,
|
|
6
8
|
type HumanInputResponse,
|
|
@@ -9,6 +11,10 @@ import {
|
|
|
9
11
|
type TimelineAnnotation,
|
|
10
12
|
} from "@opengeni/sdk";
|
|
11
13
|
import fleetDecisionItem from "./fleet-decision-projection";
|
|
14
|
+
import {
|
|
15
|
+
allowanceExhaustedMessage,
|
|
16
|
+
parseAllowanceExhaustedRefusal,
|
|
17
|
+
} from "@opengeni/sdk/allowance-refusal";
|
|
12
18
|
import {
|
|
13
19
|
CREDIT_EXHAUSTION_MESSAGE,
|
|
14
20
|
presentFailure,
|
|
@@ -145,6 +151,34 @@ export function buildTimeline(
|
|
|
145
151
|
options: { partialStart?: boolean } = {},
|
|
146
152
|
): TimelineItem[] {
|
|
147
153
|
const items: TimelineItem[] = [];
|
|
154
|
+
const allowanceNotices = new Set<string>();
|
|
155
|
+
const allowanceNotice = (event: SessionEvent, turnId: string | null) => {
|
|
156
|
+
const refusal = parseAllowanceExhaustedRefusal(event.payload);
|
|
157
|
+
if (!refusal) return null;
|
|
158
|
+
const text = allowanceExhaustedMessage(refusal);
|
|
159
|
+
const key = JSON.stringify([
|
|
160
|
+
turnId,
|
|
161
|
+
event.turnAttemptId,
|
|
162
|
+
startupRecoveryRevisionByTurn.get(turnId ?? "") ?? 0,
|
|
163
|
+
refusal.scope,
|
|
164
|
+
refusal.subjectId,
|
|
165
|
+
refusal.resetsAt,
|
|
166
|
+
]);
|
|
167
|
+
if (!allowanceNotices.has(key)) {
|
|
168
|
+
allowanceNotices.add(key);
|
|
169
|
+
items.push({
|
|
170
|
+
kind: "notice",
|
|
171
|
+
id: `${event.id}-allowance`,
|
|
172
|
+
tone: "failed",
|
|
173
|
+
text,
|
|
174
|
+
// The canonical sentence replaces upstream prose, so a host renderer
|
|
175
|
+
// reading `message` never shows a wrapper's private wording.
|
|
176
|
+
allowance: { ...refusal, message: text },
|
|
177
|
+
occurredAt: event.occurredAt,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return text;
|
|
181
|
+
};
|
|
148
182
|
const prescan = prescanTurnAnchors(events);
|
|
149
183
|
const ordered = orderTimelineEvents(events, prescan);
|
|
150
184
|
// A bounded replay can begin halfway through a message (including inside an
|
|
@@ -1200,6 +1234,11 @@ export function buildTimeline(
|
|
|
1200
1234
|
break;
|
|
1201
1235
|
}
|
|
1202
1236
|
|
|
1237
|
+
case "usage.exhausted": {
|
|
1238
|
+
allowanceNotice(event, turnId);
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1203
1242
|
case "turn.completed": {
|
|
1204
1243
|
// A standalone manual compaction uses the turn ledger for fencing and
|
|
1205
1244
|
// recovery, but it is maintenance rather than a conversational turn.
|
|
@@ -1218,6 +1257,15 @@ export function buildTimeline(
|
|
|
1218
1257
|
// Rendering it as a clean "complete" turn is a lie that leaves the
|
|
1219
1258
|
// session looking healthy while every future turn silently dies, so it
|
|
1220
1259
|
// projects exactly like a failed turn plus an explicit notice.
|
|
1260
|
+
const allowance = parseAllowanceExhaustedRefusal(payload);
|
|
1261
|
+
if (allowance) {
|
|
1262
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1263
|
+
takeAgentResponse(turnId);
|
|
1264
|
+
takePendingWaitOutcome(turnId);
|
|
1265
|
+
items.push(turnEndItem(event, "failed", ALLOWANCE_TURN_END_TEXT));
|
|
1266
|
+
allowanceNotice(event, turnId);
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1221
1269
|
if (isCreditExhaustionPayload(payload)) {
|
|
1222
1270
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1223
1271
|
takeAgentResponse(turnId);
|
|
@@ -1283,6 +1331,16 @@ export function buildTimeline(
|
|
|
1283
1331
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1284
1332
|
const completedTurn = turnEndItem(event, "complete", null);
|
|
1285
1333
|
items.push(completedTurn);
|
|
1334
|
+
if (turnCompletedWithEmptyFinalReply(payload)) {
|
|
1335
|
+
items.push({
|
|
1336
|
+
kind: "notice",
|
|
1337
|
+
id: `${event.id}-empty-final-reply`,
|
|
1338
|
+
tone: "input",
|
|
1339
|
+
text: EMPTY_FINAL_REPLY_NOTICE,
|
|
1340
|
+
recordedOutcome: true,
|
|
1341
|
+
occurredAt: event.occurredAt,
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1286
1344
|
const hasCompletedFinalResponse =
|
|
1287
1345
|
latestAgentResponse?.completed === true &&
|
|
1288
1346
|
latestAgentResponse.item.phase !== "commentary" &&
|
|
@@ -1317,16 +1375,21 @@ export function buildTimeline(
|
|
|
1317
1375
|
// Credit death can hide behind fields `failureMessage` doesn't read
|
|
1318
1376
|
// (detail/segmentLimit), so classify the whole payload before falling
|
|
1319
1377
|
// back to the generic error/message extraction.
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
|
|
1378
|
+
const allowance = parseAllowanceExhaustedRefusal(payload);
|
|
1379
|
+
const failureText = allowance
|
|
1380
|
+
? ALLOWANCE_TURN_END_TEXT
|
|
1381
|
+
: isCreditExhaustionPayload(payload)
|
|
1382
|
+
? CREDIT_EXHAUSTION_MESSAGE
|
|
1383
|
+
: failureMessage(payload);
|
|
1323
1384
|
// The TURN failed — the in-flight items did not. Chip doctrine: red is
|
|
1324
1385
|
// spent once, on the turn-level outcome. Items caught mid-flight read
|
|
1325
1386
|
// as calm "interrupted" (same as turn.cancelled); an item that itself
|
|
1326
1387
|
// failed keeps its own failed status from its output event.
|
|
1327
1388
|
finalizeOpen(turnId, "cancelled", event.occurredAt);
|
|
1328
1389
|
items.push(turnEndItem(event, "failed", failureText));
|
|
1329
|
-
if (
|
|
1390
|
+
if (allowance) {
|
|
1391
|
+
allowanceNotice(event, turnId);
|
|
1392
|
+
} else if (!hadActivity) {
|
|
1330
1393
|
items.push({
|
|
1331
1394
|
kind: "notice",
|
|
1332
1395
|
id: event.id,
|
|
@@ -1688,8 +1751,16 @@ export function stripOpaqueCitationTokens(text: string): string {
|
|
|
1688
1751
|
return text.replace(/\s*cite(?:[^]+)+/gu, "");
|
|
1689
1752
|
}
|
|
1690
1753
|
|
|
1754
|
+
/**
|
|
1755
|
+
* The turn summary's short outcome for a usage ceiling. The adjacent notice row
|
|
1756
|
+
* carries who can raise it and when it resets (and is host-customizable), so
|
|
1757
|
+
* the summary line stays a neutral fact instead of repeating remedy prose.
|
|
1758
|
+
*/
|
|
1759
|
+
const ALLOWANCE_TURN_END_TEXT = "Usage limit reached";
|
|
1760
|
+
|
|
1691
1761
|
/** The turn-end payload shape, as `isCreditExhaustion` wants it. */
|
|
1692
1762
|
function isCreditExhaustionPayload(payload: Record<string, unknown>): boolean {
|
|
1763
|
+
if (payload.code === "allowance_exhausted") return false;
|
|
1693
1764
|
return isCreditExhaustion({
|
|
1694
1765
|
error: typeof payload.error === "string" ? payload.error : null,
|
|
1695
1766
|
detail: typeof payload.detail === "string" ? payload.detail : null,
|
|
@@ -2348,6 +2419,9 @@ export function isTurnExecutionEvidence(type: string): boolean {
|
|
|
2348
2419
|
isAgentActivityEvent(type) ||
|
|
2349
2420
|
type === "turn.completed" ||
|
|
2350
2421
|
type === "turn.failed" ||
|
|
2422
|
+
// An admission refusal can settle a queued turn before it ever starts:
|
|
2423
|
+
// its prompt still belongs above the "usage limit reached" row.
|
|
2424
|
+
type === "usage.exhausted" ||
|
|
2351
2425
|
type === "turn.superseded" ||
|
|
2352
2426
|
type === "turn.recovery.requested" ||
|
|
2353
2427
|
type === "turn.capacity_waiting" ||
|
package/src/timeline/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AllowanceExhaustedRefusal,
|
|
2
3
|
HumanInputQuestion,
|
|
3
4
|
HumanInputResponse,
|
|
4
5
|
MediaGenerationResult,
|
|
@@ -56,6 +57,7 @@ export type UserMessageItem = {
|
|
|
56
57
|
state: "sending" | "queued" | "failed";
|
|
57
58
|
error?: string | undefined;
|
|
58
59
|
onRetry?: (() => void) | undefined;
|
|
60
|
+
onEdit?: (() => void) | undefined;
|
|
59
61
|
onRemove?: (() => void) | undefined;
|
|
60
62
|
}
|
|
61
63
|
| undefined;
|
|
@@ -391,6 +393,12 @@ export type NoticeItem = {
|
|
|
391
393
|
/** Optional evidence kept inspectable without overwhelming the main rail. */
|
|
392
394
|
details?: { label: string; value: unknown };
|
|
393
395
|
action?: { label: string; url: string };
|
|
396
|
+
/**
|
|
397
|
+
* A usage ceiling refused further work. `text` keeps the canonical sentence
|
|
398
|
+
* for plain-text consumers; `MessageTimeline` renders this structured row
|
|
399
|
+
* (customizable with `renderAllowanceExhausted` / `allowanceExhaustedLabels`).
|
|
400
|
+
*/
|
|
401
|
+
allowance?: AllowanceExhaustedRefusal;
|
|
394
402
|
occurredAt: string;
|
|
395
403
|
};
|
|
396
404
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Shared, replaceable words for usage allowances. The timeline refusal row
|
|
2
|
+
// (session graph) and the opt-in `@opengeni/react/usage` surfaces both read
|
|
3
|
+
// these, so a host that renames "workspace" or "admin" does it once.
|
|
4
|
+
//
|
|
5
|
+
// Product rule: never show raw credit amounts here. Hosts that want money or
|
|
6
|
+
// plan multiples opt in with `formatAmount` on the meter components.
|
|
7
|
+
|
|
8
|
+
/** Which ceiling ran out: the shared workspace pool, or one member's share of it. */
|
|
9
|
+
export type AllowanceScope = "workspace" | "member";
|
|
10
|
+
|
|
11
|
+
export type AllowanceLabels = {
|
|
12
|
+
/** "Usage limit reached" for a member ceiling. */
|
|
13
|
+
memberLimitReachedTitle: string;
|
|
14
|
+
/** "Workspace usage limit reached" for the shared pool. */
|
|
15
|
+
workspaceLimitReachedTitle: string;
|
|
16
|
+
/** Who can fix a member ceiling. */
|
|
17
|
+
memberRemedy: string;
|
|
18
|
+
/** Who can fix the workspace pool. */
|
|
19
|
+
workspaceRemedy: string;
|
|
20
|
+
/** "Resets Nov 1". `date` is already formatted by `formatDate`. */
|
|
21
|
+
resets: (date: string) => string;
|
|
22
|
+
/** Shown when a ceiling has no automatic reset. */
|
|
23
|
+
noReset: string;
|
|
24
|
+
/** Formats a reset instant for `resets`. Defaults to a short local date. */
|
|
25
|
+
formatDate: (iso: string) => string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function formatAllowanceDate(iso: string, now: Date = new Date()): string {
|
|
29
|
+
const date = new Date(iso);
|
|
30
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
31
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
32
|
+
month: "short",
|
|
33
|
+
day: "numeric",
|
|
34
|
+
...(date.getFullYear() === now.getFullYear() ? {} : { year: "numeric" }),
|
|
35
|
+
}).format(date);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Full local date and time, for a title/tooltip next to the short date. */
|
|
39
|
+
export function formatAllowanceInstant(iso: string): string {
|
|
40
|
+
const date = new Date(iso);
|
|
41
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
42
|
+
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(
|
|
43
|
+
date,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const DEFAULT_ALLOWANCE_LABELS: AllowanceLabels = {
|
|
48
|
+
memberLimitReachedTitle: "Usage limit reached",
|
|
49
|
+
workspaceLimitReachedTitle: "Workspace usage limit reached",
|
|
50
|
+
memberRemedy: "A workspace admin can raise this limit.",
|
|
51
|
+
workspaceRemedy: "An organization admin can raise the workspace budget.",
|
|
52
|
+
resets: (date) => `Resets ${date}.`,
|
|
53
|
+
noReset: "It doesn't reset automatically.",
|
|
54
|
+
formatDate: (iso) => formatAllowanceDate(iso),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function allowanceLabels(overrides?: Partial<AllowanceLabels>): AllowanceLabels {
|
|
58
|
+
return overrides ? { ...DEFAULT_ALLOWANCE_LABELS, ...overrides } : DEFAULT_ALLOWANCE_LABELS;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** "Resets Nov 1." or the no-reset sentence. */
|
|
62
|
+
export function allowanceResetSentence(labels: AllowanceLabels, resetsAt: string | null): string {
|
|
63
|
+
return resetsAt === null ? labels.noReset : labels.resets(labels.formatDate(resetsAt));
|
|
64
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MemberAllowanceUsage,
|
|
3
|
+
UsageAllowanceStatus,
|
|
4
|
+
WorkspaceAllowanceUsage,
|
|
5
|
+
WorkspaceUsageResponse,
|
|
6
|
+
} from "@opengeni/sdk/usage-allowances";
|
|
7
|
+
|
|
8
|
+
import type { AllowanceScope } from "./allowance-copy";
|
|
9
|
+
|
|
10
|
+
/** One ceiling as a person reads it: how much of it is used, and when it resets. */
|
|
11
|
+
export type UsageReading = {
|
|
12
|
+
scope: AllowanceScope;
|
|
13
|
+
/** Used share of the ceiling. Can exceed 1 after post-call overshoot. */
|
|
14
|
+
fraction: number;
|
|
15
|
+
status: UsageAllowanceStatus;
|
|
16
|
+
resetsAt: string | null;
|
|
17
|
+
/** USD micros. Only shown when a host opts in with `formatAmount`. */
|
|
18
|
+
used: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
remaining: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* What a member can still do: `unlimited` when no ceiling applies, otherwise
|
|
25
|
+
* the state of whichever ceiling stops them first (`binding`).
|
|
26
|
+
*/
|
|
27
|
+
export type UsageSummary = {
|
|
28
|
+
state: "unlimited" | "ok" | "warning" | "exhausted";
|
|
29
|
+
/** The ceiling a person would ask to raise; null when unlimited. */
|
|
30
|
+
binding: UsageReading | null;
|
|
31
|
+
/** Their own ceiling, when one applies. */
|
|
32
|
+
member: UsageReading | null;
|
|
33
|
+
/** The shared workspace pool, when one is set. */
|
|
34
|
+
workspace: UsageReading | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function reading(
|
|
38
|
+
scope: AllowanceScope,
|
|
39
|
+
usage: Pick<
|
|
40
|
+
WorkspaceAllowanceUsage | MemberAllowanceUsage,
|
|
41
|
+
"limit" | "used" | "remaining" | "fraction" | "status" | "resetsAt"
|
|
42
|
+
>,
|
|
43
|
+
): UsageReading | null {
|
|
44
|
+
if (usage.limit === null) return null;
|
|
45
|
+
return {
|
|
46
|
+
scope,
|
|
47
|
+
fraction: usage.fraction ?? (usage.limit === 0 ? 1 : usage.used / usage.limit),
|
|
48
|
+
status: usage.status,
|
|
49
|
+
resetsAt: usage.resetsAt,
|
|
50
|
+
used: usage.used,
|
|
51
|
+
limit: usage.limit,
|
|
52
|
+
remaining: Math.max(0, usage.remaining ?? usage.limit - usage.used),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fold a usage response (normally `/usage/me`) into what to tell one member.
|
|
58
|
+
* The server's `status` decides warning/exhausted (it applies the configured
|
|
59
|
+
* thresholds); when both ceilings apply, the one with less room left binds.
|
|
60
|
+
*/
|
|
61
|
+
export function summarizeUsage(
|
|
62
|
+
usage: WorkspaceUsageResponse,
|
|
63
|
+
subjectId?: string | undefined,
|
|
64
|
+
): UsageSummary {
|
|
65
|
+
const row =
|
|
66
|
+
subjectId === undefined
|
|
67
|
+
? usage.members[0]
|
|
68
|
+
: usage.members.find((member) => member.subjectId === subjectId);
|
|
69
|
+
const member = row ? reading("member", row) : null;
|
|
70
|
+
const workspace = reading("workspace", usage.workspace);
|
|
71
|
+
const readings = [member, workspace].filter((value): value is UsageReading => value !== null);
|
|
72
|
+
if (readings.length === 0) return { state: "unlimited", binding: null, member, workspace };
|
|
73
|
+
// Raising a member ceiling cannot help while the shared pool is spent, so an
|
|
74
|
+
// exhausted workspace binds first.
|
|
75
|
+
const exhausted =
|
|
76
|
+
(workspace?.status === "exhausted" ? workspace : null) ??
|
|
77
|
+
(member?.status === "exhausted" ? member : null);
|
|
78
|
+
if (exhausted) return { state: "exhausted", binding: exhausted, member, workspace };
|
|
79
|
+
const warnings = readings.filter((value) => value.status === "warning");
|
|
80
|
+
const candidates = warnings.length > 0 ? warnings : readings;
|
|
81
|
+
const binding = candidates.reduce((tightest, value) =>
|
|
82
|
+
value.remaining < tightest.remaining ? value : tightest,
|
|
83
|
+
);
|
|
84
|
+
return { state: warnings.length > 0 ? "warning" : "ok", binding, member, workspace };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A whole-number percentage for display: "<1" for a sliver, never "0" for some use. */
|
|
88
|
+
export function usagePercentLabel(fraction: number): string {
|
|
89
|
+
if (!Number.isFinite(fraction) || fraction <= 0) return "0";
|
|
90
|
+
if (fraction < 0.01) return "<1";
|
|
91
|
+
if (fraction < 1 && Math.round(fraction * 100) === 100) return "99";
|
|
92
|
+
return String(Math.round(fraction * 100));
|
|
93
|
+
}
|
package/src/usage.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @opengeni/react/usage — usage allowances for people, kept out of the package
|
|
2
|
+
// root and the session entry so hosts that don't meter usage never load it.
|
|
3
|
+
//
|
|
4
|
+
// Member-facing: `useUsage` + `<UsageMeter>` read the signed-in member's own
|
|
5
|
+
// `/usage/me` (the session proxy serves exactly that route) and
|
|
6
|
+
// `<UsageLimitNotice>` is the calm near/at-limit line for a composer header.
|
|
7
|
+
// Admin-facing: `<UsageMemberList>` shows every member against their limit and
|
|
8
|
+
// edits it with a share-of-budget slider; your backend saves the rule.
|
|
9
|
+
//
|
|
10
|
+
// Shares and percentages only by default. Pass `formatAmount` / `amounts` to
|
|
11
|
+
// show money, credits, or plan multiples in your own unit.
|
|
12
|
+
export { useUsage } from "./hooks/use-usage";
|
|
13
|
+
export type { UsageClientLike, UseUsageOptions, UseUsageResult } from "./hooks/use-usage";
|
|
14
|
+
export { summarizeUsage, usagePercentLabel } from "./usage/summary";
|
|
15
|
+
export type { UsageReading, UsageSummary } from "./usage/summary";
|
|
16
|
+
export {
|
|
17
|
+
DEFAULT_ALLOWANCE_LABELS,
|
|
18
|
+
allowanceLabels,
|
|
19
|
+
allowanceResetSentence,
|
|
20
|
+
formatAllowanceDate,
|
|
21
|
+
} from "./usage/allowance-copy";
|
|
22
|
+
export type { AllowanceLabels, AllowanceScope } from "./usage/allowance-copy";
|
|
23
|
+
export { DEFAULT_USAGE_METER_LABELS, UsageMeter, UsageMeterView } from "./components/usage-meter";
|
|
24
|
+
export type {
|
|
25
|
+
UsageMeterBaseProps,
|
|
26
|
+
UsageMeterLabels,
|
|
27
|
+
UsageMeterProps,
|
|
28
|
+
UsageMeterViewProps,
|
|
29
|
+
} from "./components/usage-meter";
|
|
30
|
+
export {
|
|
31
|
+
DEFAULT_USAGE_LIMIT_NOTICE_LABELS,
|
|
32
|
+
UsageLimitNotice,
|
|
33
|
+
UsageLimitNoticeView,
|
|
34
|
+
} from "./components/usage-limit-notice";
|
|
35
|
+
export type {
|
|
36
|
+
UsageLimitNoticeLabels,
|
|
37
|
+
UsageLimitNoticeProps,
|
|
38
|
+
} from "./components/usage-limit-notice";
|
|
39
|
+
export {
|
|
40
|
+
DEFAULT_USAGE_MEMBER_LIST_LABELS,
|
|
41
|
+
UsageMemberList,
|
|
42
|
+
describeMemberLimit,
|
|
43
|
+
} from "./components/usage-member-list";
|
|
44
|
+
export type {
|
|
45
|
+
UsageAmountFormat,
|
|
46
|
+
UsageMemberIdentity,
|
|
47
|
+
UsageMemberListLabels,
|
|
48
|
+
UsageMemberListProps,
|
|
49
|
+
} from "./components/usage-member-list";
|
|
50
|
+
export type { RenderAllowanceExhausted } from "./timeline/allowance-exhausted-row";
|