@tangle-network/ui 11.15.0 → 11.15.2

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.
@@ -25,6 +25,7 @@ import type { CustomToolRenderer } from "../types/tool-display";
25
25
  import { InlineToolItem } from "./inline-tool-item";
26
26
  import { InlineThinkingItem } from "./inline-thinking-item";
27
27
  import { Markdown } from "../markdown/markdown";
28
+ import { SyntheticText } from "../chat/synthetic-text";
28
29
 
29
30
  /**
30
31
  * One row on the run's timeline spine: a connector line + accent dot in a
@@ -319,25 +320,23 @@ export const RunGroup = memo(
319
320
  return true;
320
321
  }
321
322
 
322
- return part.type === "text" && !part.synthetic && part.text.trim().length > 0;
323
+ return part.type === "text" && part.text.trim().length > 0;
323
324
  });
324
325
 
325
- if (!hasRenderableParts) {
326
- if (!isStreaming) {
327
- return null;
328
- }
326
+ const pendingResponse = (
327
+ <AssistantShell branding={branding} isStreaming={true}>
328
+ <div className="flex items-center gap-2 px-0.5 py-0.5 text-sm text-[var(--text-muted)]">
329
+ <span className="flex gap-[5px]">
330
+ <span className="h-2 w-2 animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "0ms" }} />
331
+ <span className="h-2 w-2 animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "150ms" }} />
332
+ <span className="h-2 w-2 animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "300ms" }} />
333
+ </span>
334
+ </div>
335
+ </AssistantShell>
336
+ );
329
337
 
330
- return (
331
- <AssistantShell branding={branding} isStreaming={true}>
332
- <div className="flex items-center gap-2 px-0.5 py-0.5 text-sm text-[var(--text-muted)]">
333
- <span className="flex gap-[5px]">
334
- <span className="h-2 w-2 animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "0ms" }} />
335
- <span className="h-2 w-2 animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "150ms" }} />
336
- <span className="h-2 w-2 animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "300ms" }} />
337
- </span>
338
- </div>
339
- </AssistantShell>
340
- );
338
+ if (!hasRenderableParts) {
339
+ return isStreaming ? pendingResponse : null;
341
340
  }
342
341
 
343
342
  const showTraceChrome = allParts.some(({ part }) => {
@@ -353,72 +352,106 @@ export const RunGroup = memo(
353
352
  });
354
353
 
355
354
  if (!showTraceChrome) {
355
+ // Notes split authored blocks rather than living inside an Agent bubble.
356
+ // Original message/part indices remain available for keys and callbacks.
357
+ const segments: Array<
358
+ | { kind: "note"; key: string; text: string }
359
+ | { kind: "source"; key: string; entries: typeof allParts }
360
+ > = [];
361
+ let lastSourceSegment = -1;
362
+ for (const entry of allParts) {
363
+ const { part, msgId, index } = entry;
364
+ if (part.type === "text" && !part.text.trim()) continue;
365
+ const key = `${msgId}-${index}`;
366
+ if (part.type === "text" && part.synthetic) {
367
+ segments.push({ kind: "note", key, text: part.text });
368
+ } else {
369
+ const previous = segments[segments.length - 1];
370
+ if (previous?.kind === "source") {
371
+ previous.entries.push(entry);
372
+ } else {
373
+ segments.push({ kind: "source", key, entries: [entry] });
374
+ }
375
+ lastSourceSegment = segments.length - 1;
376
+ }
377
+ }
356
378
  return (
357
- <AssistantShell branding={branding} isStreaming={isStreaming}>
358
- {allParts.map(({ part, msgId, index }) => {
359
- const key = `${msgId}-${index}`;
360
-
361
- if (part.type === "tool" && isOpenUITool(part as ToolPart)) {
362
- const toolPart = part as ToolPart;
363
- const schema = extractOpenUISchema(toolPart.state.output);
364
- const summary = getOpenUISummary(toolPart.state.output);
379
+ <div className="space-y-3">
380
+ {segments.map((segment, segmentIndex) => segment.kind === "note" ? (
381
+ <SyntheticText key={segment.key} text={segment.text} />
382
+ ) : (
383
+ <AssistantShell
384
+ key={segment.key}
385
+ branding={branding}
386
+ isStreaming={isStreaming && segmentIndex === lastSourceSegment}
387
+ >
388
+ {segment.entries.map(({ part, msgId, index }) => {
389
+ const key = `${msgId}-${index}`;
365
390
 
366
- if (toolPart.state.status === "completed" && schema) {
367
- return (
368
- <div
369
- key={key}
370
- className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)]"
371
- >
372
- {summary ? (
373
- <div className="border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground">
374
- {summary}
391
+ if (part.type === "tool" && isOpenUITool(part as ToolPart)) {
392
+ const toolPart = part as ToolPart;
393
+ const schema = extractOpenUISchema(toolPart.state.output);
394
+ const summary = getOpenUISummary(toolPart.state.output);
395
+
396
+ if (toolPart.state.status === "completed" && schema) {
397
+ return (
398
+ <div
399
+ key={key}
400
+ className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)]"
401
+ >
402
+ {summary ? (
403
+ <div className="border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground">
404
+ {summary}
405
+ </div>
406
+ ) : null}
407
+ <div className="p-4">
408
+ <OpenUIArtifactRenderer schema={schema} />
375
409
  </div>
376
- ) : null}
377
- <div className="p-4">
378
- <OpenUIArtifactRenderer schema={schema} />
379
410
  </div>
380
- </div>
381
- );
411
+ );
412
+ }
413
+
414
+ if (toolPart.state.status === "running") {
415
+ return (
416
+ <div
417
+ key={key}
418
+ className="flex items-center gap-2 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)] px-4 py-3 text-sm text-muted-foreground"
419
+ >
420
+ <Loader2 className="h-4 w-4 animate-spin text-primary" />
421
+ Building view…
422
+ </div>
423
+ );
424
+ }
382
425
  }
383
426
 
384
- if (toolPart.state.status === "running") {
427
+ if (part.type === "text" && !part.synthetic && part.text.trim()) {
385
428
  return (
386
- <div
387
- key={key}
388
- className="flex items-center gap-2 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)] px-4 py-3 text-sm text-muted-foreground"
389
- >
390
- <Loader2 className="h-4 w-4 animate-spin text-primary" />
391
- Building view…
429
+ <div key={key} className="px-0.5">
430
+ <Markdown className="tangle-prose text-[15px] leading-7 text-[var(--text-primary)]">
431
+ {part.text}
432
+ </Markdown>
392
433
  </div>
393
434
  );
394
435
  }
395
- }
396
436
 
397
- if (part.type === "text" && !part.synthetic && part.text.trim()) {
398
- return (
399
- <div key={key} className="px-0.5">
400
- <Markdown className="tangle-prose text-[15px] leading-7 text-[var(--text-primary)]">
401
- {part.text}
402
- </Markdown>
403
- </div>
404
- );
405
- }
406
-
407
- return null;
408
- })}
409
- </AssistantShell>
437
+ return null;
438
+ })}
439
+ </AssistantShell>
440
+ ))}
441
+ {isStreaming && lastSourceSegment === -1 ? pendingResponse : null}
442
+ </div>
410
443
  );
411
444
  }
412
445
 
413
- // Renderable rows: skip empty/synthetic text so spine dots map to real steps.
446
+ // Keep notes in source order; response statistics still count authored text only.
414
447
  const rows = allParts.filter(({ part }) => {
415
448
  if (part.type === "tool" || part.type === "reasoning") return true;
416
- return part.type === "text" && !part.synthetic && part.text.trim().length > 0;
449
+ return part.type === "text" && part.text.trim().length > 0;
417
450
  });
418
451
 
419
452
  const dotAccent = (part: SessionPart): string => {
420
453
  if (part.type === "reasoning") return "bg-[var(--brand-glow)]";
421
- if (part.type === "text") return "bg-primary";
454
+ if (part.type === "text" && !part.synthetic) return "bg-primary";
422
455
  return "bg-[var(--border-hover)]";
423
456
  };
424
457
 
@@ -532,8 +565,10 @@ export const RunGroup = memo(
532
565
  node = (
533
566
  <InlineThinkingItem part={part as ReasoningPart} defaultOpen={isStreaming} />
534
567
  );
535
- } else if (part.type === "text" && !part.synthetic && part.text.trim()) {
536
- node = (
568
+ } else if (part.type === "text" && part.text.trim()) {
569
+ node = part.synthetic ? (
570
+ <SyntheticText text={part.text} />
571
+ ) : (
537
572
  <div className="px-1 py-0.5">
538
573
  <Markdown className="tangle-prose text-[15px] leading-7">{part.text}</Markdown>
539
574
  </div>