@springbrand/message-panel 0.3.0-alpha.5 → 0.3.0-alpha.8
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.
|
@@ -237,6 +237,7 @@ function UserBubble({
|
|
|
237
237
|
|
|
238
238
|
function ActionPresentationBlock({
|
|
239
239
|
presentation,
|
|
240
|
+
compact = false,
|
|
240
241
|
failureCount = 1,
|
|
241
242
|
partial = false,
|
|
242
243
|
renderFile,
|
|
@@ -245,6 +246,7 @@ function ActionPresentationBlock({
|
|
|
245
246
|
onOpenArtifact,
|
|
246
247
|
}: {
|
|
247
248
|
presentation: CloudOsActionPresentation;
|
|
249
|
+
compact?: boolean;
|
|
248
250
|
failureCount?: number;
|
|
249
251
|
partial?: boolean;
|
|
250
252
|
renderFile?: FileRenderer;
|
|
@@ -254,16 +256,41 @@ function ActionPresentationBlock({
|
|
|
254
256
|
}) {
|
|
255
257
|
const { state } = presentation;
|
|
256
258
|
if (state === "ready") {
|
|
259
|
+
const hasFileContent = presentation.content.length > 0;
|
|
260
|
+
const statusLabel = hasFileContent
|
|
261
|
+
? presentation.capabilityName || "Completed"
|
|
262
|
+
: presentation.toolBrief || "Completed";
|
|
263
|
+
const hasStatusMetadata = hasFileContent
|
|
264
|
+
? Boolean(presentation.capabilityName || presentation.iconUrl)
|
|
265
|
+
: Boolean(presentation.toolBrief || presentation.iconUrl);
|
|
266
|
+
const status = (
|
|
267
|
+
<span
|
|
268
|
+
className="flex shrink-0 items-center gap-2 text-[14px] leading-5 text-kumo-subtle"
|
|
269
|
+
data-action-status=""
|
|
270
|
+
>
|
|
271
|
+
{presentation.iconUrl && (
|
|
272
|
+
<img
|
|
273
|
+
src={presentation.iconUrl}
|
|
274
|
+
alt=""
|
|
275
|
+
aria-hidden="true"
|
|
276
|
+
className="size-4 rounded-sm object-contain"
|
|
277
|
+
data-action-icon=""
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
280
|
+
<span>{statusLabel}</span>
|
|
281
|
+
</span>
|
|
282
|
+
);
|
|
257
283
|
return (
|
|
258
284
|
<div
|
|
259
285
|
data-action-presentation={state}
|
|
260
286
|
role="status"
|
|
261
|
-
aria-label=
|
|
262
|
-
className=
|
|
287
|
+
aria-label={statusLabel}
|
|
288
|
+
className={compact
|
|
289
|
+
? "inline-flex items-center rounded-full border border-kumo-line bg-kumo-elevated px-3 py-1.5"
|
|
290
|
+
: "space-y-2"}
|
|
263
291
|
>
|
|
264
|
-
{presentation.content.length === 0
|
|
265
|
-
|
|
266
|
-
: presentation.content.map((file, index) => {
|
|
292
|
+
{presentation.content.length === 0 && status}
|
|
293
|
+
{presentation.content.map((file, index) => {
|
|
267
294
|
const url = resolveUrl?.(file.url) ?? file.url;
|
|
268
295
|
const artifactId = resolveArtifactId?.(url);
|
|
269
296
|
return (
|
|
@@ -276,6 +303,7 @@ function ActionPresentationBlock({
|
|
|
276
303
|
},
|
|
277
304
|
placement: "assistant-message",
|
|
278
305
|
variant: "product",
|
|
306
|
+
leadingMeta: hasStatusMetadata ? status : undefined,
|
|
279
307
|
onOpen: file.mediaType?.startsWith("image/") && artifactId && onOpenArtifact
|
|
280
308
|
? () => onOpenArtifact(artifactId)
|
|
281
309
|
: undefined,
|
|
@@ -897,10 +925,40 @@ export function CloudOsChatMessages({
|
|
|
897
925
|
|
|
898
926
|
{actionResultBlocks.length > 0 && (
|
|
899
927
|
<div data-action-results="" className="space-y-4">
|
|
928
|
+
{actionResultBlocks.filter((block) =>
|
|
929
|
+
block.presentation.state === "ready" &&
|
|
930
|
+
block.presentation.content.length === 0,
|
|
931
|
+
).length > 0 && (
|
|
932
|
+
<div data-action-empty-results="" className="flex flex-wrap gap-2">
|
|
933
|
+
{actionResultBlocks.map((block) => {
|
|
934
|
+
if (
|
|
935
|
+
block.presentation.state !== "ready" ||
|
|
936
|
+
block.presentation.content.length !== 0 ||
|
|
937
|
+
(block.presentation.failureCode === "insufficient_credits" &&
|
|
938
|
+
block !== firstInsufficientFailure)
|
|
939
|
+
) return null;
|
|
940
|
+
return (
|
|
941
|
+
<ActionPresentationBlock
|
|
942
|
+
key={block.key}
|
|
943
|
+
presentation={block.presentation}
|
|
944
|
+
compact
|
|
945
|
+
failureCount={insufficientFailures.length}
|
|
946
|
+
partial={hasCompletedAction}
|
|
947
|
+
renderFile={renderFile}
|
|
948
|
+
resolveUrl={resolveUrl}
|
|
949
|
+
resolveArtifactId={resolveArtifactId}
|
|
950
|
+
onOpenArtifact={onOpenArtifact}
|
|
951
|
+
/>
|
|
952
|
+
);
|
|
953
|
+
})}
|
|
954
|
+
</div>
|
|
955
|
+
)}
|
|
900
956
|
{actionResultBlocks.map((block) => {
|
|
901
957
|
if (
|
|
902
958
|
block.presentation.failureCode === "insufficient_credits" &&
|
|
903
|
-
block !== firstInsufficientFailure
|
|
959
|
+
block !== firstInsufficientFailure ||
|
|
960
|
+
(block.presentation.state === "ready" &&
|
|
961
|
+
block.presentation.content.length === 0)
|
|
904
962
|
) return null;
|
|
905
963
|
return (
|
|
906
964
|
<ActionPresentationBlock
|
|
@@ -62,6 +62,9 @@ export interface CloudOsActionPresentation {
|
|
|
62
62
|
executionId: string;
|
|
63
63
|
state: "running" | "ready" | "failed" | "outcome_unknown";
|
|
64
64
|
failureCode?: "insufficient_credits";
|
|
65
|
+
capabilityName?: string;
|
|
66
|
+
toolBrief?: string;
|
|
67
|
+
iconUrl?: string;
|
|
65
68
|
produces?: CloudOsActionProduces;
|
|
66
69
|
content: CloudOsActionPresentationFile[];
|
|
67
70
|
}
|
|
@@ -338,6 +341,23 @@ function actionPresentationOf(value: unknown): CloudOsActionPresentation | null
|
|
|
338
341
|
}
|
|
339
342
|
if (state !== "ready" && content.length > 0) return null;
|
|
340
343
|
|
|
344
|
+
const toolBrief = typeof presentation.toolBrief === "string" && presentation.toolBrief.trim()
|
|
345
|
+
? presentation.toolBrief.trim()
|
|
346
|
+
: undefined;
|
|
347
|
+
const capabilityName = typeof presentation.capabilityName === "string" && presentation.capabilityName.trim()
|
|
348
|
+
? presentation.capabilityName.trim()
|
|
349
|
+
: undefined;
|
|
350
|
+
let iconUrl: string | undefined;
|
|
351
|
+
if (presentation.iconUrl !== undefined) {
|
|
352
|
+
if (typeof presentation.iconUrl !== "string" || !presentation.iconUrl.trim()) return null;
|
|
353
|
+
try {
|
|
354
|
+
if (new URL(presentation.iconUrl).protocol !== "https:") return null;
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
iconUrl = presentation.iconUrl;
|
|
359
|
+
}
|
|
360
|
+
|
|
341
361
|
return {
|
|
342
362
|
type: "action-execution",
|
|
343
363
|
executionId: presentation.executionId,
|
|
@@ -345,6 +365,9 @@ function actionPresentationOf(value: unknown): CloudOsActionPresentation | null
|
|
|
345
365
|
...(state === "failed" && presentation.failureCode === "insufficient_credits"
|
|
346
366
|
? { failureCode: presentation.failureCode }
|
|
347
367
|
: {}),
|
|
368
|
+
...(capabilityName ? { capabilityName } : {}),
|
|
369
|
+
...(toolBrief ? { toolBrief } : {}),
|
|
370
|
+
...(iconUrl ? { iconUrl } : {}),
|
|
348
371
|
...(produces ? { produces } : {}),
|
|
349
372
|
content,
|
|
350
373
|
};
|
|
@@ -409,8 +432,17 @@ function assistantBlocks(
|
|
|
409
432
|
): AssistantBlock[] {
|
|
410
433
|
const blocks: AssistantBlock[] = [];
|
|
411
434
|
let pendingCalls: CloudOsToolCall[] = [];
|
|
435
|
+
let pendingActionCalls: CloudOsToolCall[] = [];
|
|
412
436
|
|
|
413
|
-
const
|
|
437
|
+
const flushActionCalls = () => {
|
|
438
|
+
if (pendingActionCalls.length === 0) return;
|
|
439
|
+
for (const group of buildToolCallGroups(pendingActionCalls)) {
|
|
440
|
+
blocks.push({ kind: "toolGroup", key: group.key, group });
|
|
441
|
+
}
|
|
442
|
+
pendingActionCalls = [];
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const flushRegularCalls = () => {
|
|
414
446
|
if (pendingCalls.length === 0) return;
|
|
415
447
|
for (const group of buildToolCallGroups(pendingCalls)) {
|
|
416
448
|
blocks.push({ kind: "toolGroup", key: group.key, group });
|
|
@@ -418,6 +450,11 @@ function assistantBlocks(
|
|
|
418
450
|
pendingCalls = [];
|
|
419
451
|
};
|
|
420
452
|
|
|
453
|
+
const flush = () => {
|
|
454
|
+
flushActionCalls();
|
|
455
|
+
flushRegularCalls();
|
|
456
|
+
};
|
|
457
|
+
|
|
421
458
|
message.parts.forEach((part, partIndex) => {
|
|
422
459
|
const key = `${message.id}:${partIndex}`;
|
|
423
460
|
const record = recordOf(part);
|
|
@@ -549,8 +586,14 @@ function assistantBlocks(
|
|
|
549
586
|
const call = toCloudOsToolCall(part, key, isActive);
|
|
550
587
|
const presentation = actionPresentationOf(call.output);
|
|
551
588
|
if (presentation) {
|
|
552
|
-
|
|
553
|
-
|
|
589
|
+
if (presentation.state === "running") {
|
|
590
|
+
flushActionCalls();
|
|
591
|
+
pendingCalls.push(call);
|
|
592
|
+
flush();
|
|
593
|
+
} else {
|
|
594
|
+
flushRegularCalls();
|
|
595
|
+
pendingActionCalls.push(call);
|
|
596
|
+
}
|
|
554
597
|
blocks.push({ kind: "actionPresentation", key, presentation });
|
|
555
598
|
return;
|
|
556
599
|
}
|
|
@@ -600,6 +643,7 @@ function assistantBlocks(
|
|
|
600
643
|
return;
|
|
601
644
|
}
|
|
602
645
|
|
|
646
|
+
flushActionCalls();
|
|
603
647
|
pendingCalls.push(call);
|
|
604
648
|
});
|
|
605
649
|
|
|
@@ -745,6 +789,25 @@ function markFinalTextBlocks(entries: CloudOsEntry[]): CloudOsEntry[] {
|
|
|
745
789
|
return entries.map((entry) => {
|
|
746
790
|
if (entry.type !== "assistant") return entry;
|
|
747
791
|
|
|
792
|
+
// suggest_followups is the UI boundary: narration before it is work,
|
|
793
|
+
// while text after it is the one final answer.
|
|
794
|
+
const followupsBoundary = entry.blocks.findLastIndex(
|
|
795
|
+
(block) => block.kind === "suggestions",
|
|
796
|
+
);
|
|
797
|
+
if (
|
|
798
|
+
followupsBoundary !== -1 &&
|
|
799
|
+
entry.blocks.slice(followupsBoundary + 1).some((block) => block.kind === "text")
|
|
800
|
+
) {
|
|
801
|
+
return {
|
|
802
|
+
...entry,
|
|
803
|
+
blocks: entry.blocks.map((block, index) =>
|
|
804
|
+
block.kind === "text"
|
|
805
|
+
? { ...block, final: index > followupsBoundary }
|
|
806
|
+
: block,
|
|
807
|
+
),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
748
811
|
const finalOutputBoundary = entry.blocks.findLastIndex(isFinalOutputBlock);
|
|
749
812
|
// Provider cleanup after final output must not pull the answer back into Work.
|
|
750
813
|
const searchBefore = finalOutputBoundary === -1
|
|
@@ -811,11 +874,19 @@ function dropSupersededActionPresentations(entries: CloudOsEntry[]): CloudOsEntr
|
|
|
811
874
|
return;
|
|
812
875
|
}
|
|
813
876
|
if (block.presentation.state !== "running") {
|
|
814
|
-
current.presentation =
|
|
877
|
+
current.presentation = {
|
|
878
|
+
...block.presentation,
|
|
879
|
+
capabilityName: block.presentation.capabilityName ?? current.presentation.capabilityName,
|
|
880
|
+
toolBrief: block.presentation.toolBrief ?? current.presentation.toolBrief,
|
|
881
|
+
iconUrl: block.presentation.iconUrl ?? current.presentation.iconUrl,
|
|
882
|
+
};
|
|
815
883
|
} else if (current.presentation.state === "running") {
|
|
816
884
|
current.presentation = {
|
|
817
885
|
...block.presentation,
|
|
818
886
|
produces: block.presentation.produces ?? current.presentation.produces,
|
|
887
|
+
capabilityName: block.presentation.capabilityName ?? current.presentation.capabilityName,
|
|
888
|
+
toolBrief: block.presentation.toolBrief ?? current.presentation.toolBrief,
|
|
889
|
+
iconUrl: block.presentation.iconUrl ?? current.presentation.iconUrl,
|
|
819
890
|
};
|
|
820
891
|
}
|
|
821
892
|
});
|
package/cloud-os/file-view.tsx
CHANGED
|
@@ -61,6 +61,7 @@ export interface FileViewProps {
|
|
|
61
61
|
file: FileViewFile;
|
|
62
62
|
placement: FileViewPlacement;
|
|
63
63
|
variant?: FileViewVariant;
|
|
64
|
+
leadingMeta?: ReactNode;
|
|
64
65
|
status?: FileViewStatus;
|
|
65
66
|
progress?: number;
|
|
66
67
|
onOpen?: () => void;
|
|
@@ -172,11 +173,13 @@ function ProductFileView({
|
|
|
172
173
|
file,
|
|
173
174
|
label,
|
|
174
175
|
placement,
|
|
176
|
+
leadingMeta,
|
|
175
177
|
onOpen,
|
|
176
178
|
}: {
|
|
177
179
|
file: FileViewFile;
|
|
178
180
|
label: string;
|
|
179
181
|
placement: Exclude<FileViewPlacement, "composer">;
|
|
182
|
+
leadingMeta?: ReactNode;
|
|
180
183
|
onOpen?: () => void;
|
|
181
184
|
}) {
|
|
182
185
|
const type = productTypeForMediaType(file.mediaType);
|
|
@@ -225,6 +228,8 @@ function ProductFileView({
|
|
|
225
228
|
<span className="flex w-fit max-w-full flex-col gap-2.5" data-product-file-view={type}>
|
|
226
229
|
{preview}
|
|
227
230
|
<span className="flex min-w-0 max-w-full items-center gap-1 text-xs text-kumo-inactive">
|
|
231
|
+
{leadingMeta}
|
|
232
|
+
{leadingMeta && <span aria-hidden="true">·</span>}
|
|
228
233
|
<span className="capitalize text-kumo-subtle">{type}</span>
|
|
229
234
|
<span aria-hidden="true">·</span>
|
|
230
235
|
<span className="min-w-0 truncate">{label}</span>
|
|
@@ -306,6 +311,7 @@ export function FileView({
|
|
|
306
311
|
file,
|
|
307
312
|
placement,
|
|
308
313
|
variant = "compact",
|
|
314
|
+
leadingMeta,
|
|
309
315
|
status = "ready",
|
|
310
316
|
progress = 0,
|
|
311
317
|
onOpen,
|
|
@@ -413,6 +419,7 @@ export function FileView({
|
|
|
413
419
|
file={file}
|
|
414
420
|
label={label}
|
|
415
421
|
placement={placement}
|
|
422
|
+
leadingMeta={leadingMeta}
|
|
416
423
|
onOpen={onOpen}
|
|
417
424
|
/>
|
|
418
425
|
);
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.1.10","results":[[":bot/cycles.test.ts",{"duration":
|
|
1
|
+
{"version":"4.1.10","results":[[":bot/cycles.test.ts",{"duration":54.568250000000006,"failed":false}],[":bot/skins.test.ts",{"duration":2830.836917,"failed":false}],[":bot/engine.test.ts",{"duration":80.72770900000003,"failed":false}],[":bot/shape.test.ts",{"duration":10.531834000000003,"failed":false}],[":bot/expressions.test.ts",{"duration":8.110375000000005,"failed":false}],[":bot/face.test.ts",{"duration":2.7700000000000102,"failed":false}],[":drag.test.ts",{"duration":1.9160829999999862,"failed":false}],[":animation.test.ts",{"duration":2.0596669999999904,"failed":false}]]}
|