orchid-ai 2.0.1 → 2.0.3

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/orchid-ai.css CHANGED
@@ -244,6 +244,9 @@
244
244
  border: 1px solid #e5e7eb;
245
245
  border-radius: 12px;
246
246
  padding: 16px 20px;
247
+ width: 100%;
248
+ max-width: 420px;
249
+ box-sizing: border-box;
247
250
  }
248
251
 
249
252
  .ai-chat-suggestions span {
@@ -260,6 +263,7 @@
260
263
  display: flex;
261
264
  flex-direction: column;
262
265
  gap: 8px;
266
+ padding: 0;
263
267
  }
264
268
 
265
269
  .ai-chat-suggestions li {
@@ -271,6 +275,8 @@
271
275
  border-radius: 8px;
272
276
  cursor: pointer;
273
277
  transition: background 0.15s, border-color 0.15s, color 0.15s;
278
+ width: 100%;
279
+ box-sizing: border-box;
274
280
  }
275
281
 
276
282
  .ai-chat-suggestions li:hover {
@@ -279,6 +285,17 @@
279
285
  color: #1eaaf1;
280
286
  }
281
287
 
288
+ .ai-chat-suggestions--disabled li {
289
+ opacity: 0.45;
290
+ cursor: not-allowed;
291
+ }
292
+
293
+ .ai-chat-suggestions--disabled li:hover {
294
+ background: #f9fafb;
295
+ border-color: #e5e7eb;
296
+ color: #4b5563;
297
+ }
298
+
282
299
  /* ── Messages ── */
283
300
 
284
301
  .ai-chat-message {
@@ -347,6 +364,16 @@
347
364
  border-bottom-right-radius: 4px;
348
365
  }
349
366
 
367
+ .ai-chat-bubble.user .ai-chat-user-link {
368
+ color: #e0f2fe;
369
+ text-decoration: underline;
370
+ word-break: break-all;
371
+ }
372
+
373
+ .ai-chat-bubble.user .ai-chat-user-link:hover {
374
+ color: #ffffff;
375
+ }
376
+
350
377
  .ai-chat-bubble.assistant {
351
378
  position: relative;
352
379
  background: #ffffff;
@@ -355,6 +382,323 @@
355
382
  border-bottom-left-radius: 4px;
356
383
  }
357
384
 
385
+ /* ── Tool / preamble trace (Claude-style timeline, light theme) ── */
386
+
387
+ .ai-chat-process-trace {
388
+ --ai-process-bg: #f4f6f8;
389
+ --ai-process-border: #e4e8ed;
390
+ --ai-process-rail: #d8dde4;
391
+ --ai-process-text: #5c6570;
392
+ --ai-process-text-strong: #2d3748;
393
+ --ai-process-dot: #9aa5b1;
394
+ --ai-process-dot-tool: #059669;
395
+ --ai-process-dot-compile: #4f46e5;
396
+ --ai-process-dot-mind: #94a3b8;
397
+ --ai-process-interim-fg: #475569;
398
+
399
+ margin: 0 0 12px;
400
+ border-radius: 12px;
401
+ border: 1px solid transparent;
402
+ background: transparent;
403
+ box-shadow: none;
404
+ font-size: 12.5px;
405
+ line-height: 1.45;
406
+ color: var(--ai-process-text);
407
+ overflow: visible;
408
+ transition:
409
+ background 0.22s ease,
410
+ border-color 0.22s ease,
411
+ box-shadow 0.22s ease;
412
+ }
413
+
414
+ /* Collapsed: no chrome — summary reads as muted underlined text on the bubble background */
415
+ .ai-chat-process-trace:not([open]) {
416
+ overflow: visible;
417
+ }
418
+
419
+ .ai-chat-process-trace[open] {
420
+ overflow: hidden;
421
+ border-color: var(--ai-process-border);
422
+ background: linear-gradient(165deg, var(--ai-process-bg) 0%, #eef1f4 100%);
423
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
424
+ }
425
+
426
+ .ai-chat-process-trace__summary {
427
+ cursor: pointer;
428
+ list-style: none;
429
+ display: flex;
430
+ align-items: center;
431
+ gap: 8px;
432
+ padding: 4px 0 10px;
433
+ margin: 0;
434
+ font-weight: 500;
435
+ font-size: 13px;
436
+ letter-spacing: 0.01em;
437
+ text-transform: none;
438
+ color: #6b7280;
439
+ text-decoration: none;
440
+ user-select: none;
441
+ background: transparent;
442
+ border-bottom: none;
443
+ transition:
444
+ color 0.18s ease,
445
+ padding 0.2s ease,
446
+ font-size 0.2s ease,
447
+ font-weight 0.2s ease,
448
+ letter-spacing 0.2s ease,
449
+ background 0.22s ease,
450
+ border-color 0.22s ease;
451
+ }
452
+
453
+ /* Collapsed: underline only the label — chevron / dots sit beside it, not underlined */
454
+ .ai-chat-process-trace:not([open]) > .ai-chat-process-trace__summary {
455
+ gap: 5px;
456
+ width: fit-content;
457
+ max-width: 100%;
458
+ }
459
+
460
+ .ai-chat-process-trace:not([open]) > .ai-chat-process-trace__summary:hover {
461
+ color: #4b5563;
462
+ }
463
+
464
+ .ai-chat-process-trace:not([open]) > .ai-chat-process-trace__summary:hover .ai-chat-process-trace__summary-text {
465
+ text-decoration-color: #4b5563;
466
+ }
467
+
468
+ .ai-chat-process-trace:not([open]) > .ai-chat-process-trace__summary:focus-visible {
469
+ outline: 2px solid #93c5fd;
470
+ outline-offset: 2px;
471
+ border-radius: 4px;
472
+ }
473
+
474
+ /* Expanded: restore panel header strip inside the tinted card */
475
+ .ai-chat-process-trace[open] > .ai-chat-process-trace__summary {
476
+ padding: 10px 14px;
477
+ font-weight: 600;
478
+ font-size: 12px;
479
+ letter-spacing: 0.02em;
480
+ text-transform: uppercase;
481
+ color: var(--ai-process-text-strong);
482
+ text-decoration: none;
483
+ background: rgba(255, 255, 255, 0.45);
484
+ border-bottom: 1px solid var(--ai-process-border);
485
+ }
486
+
487
+ .ai-chat-process-trace__summary::-webkit-details-marker {
488
+ display: none;
489
+ }
490
+
491
+ .ai-chat-process-trace__summary-text {
492
+ flex: 1;
493
+ text-decoration: none;
494
+ }
495
+
496
+ .ai-chat-process-trace:not([open]) .ai-chat-process-trace__summary-text {
497
+ flex: 0 1 auto;
498
+ text-decoration: underline;
499
+ text-underline-offset: 3px;
500
+ text-decoration-thickness: 1px;
501
+ transition: text-decoration-color 0.18s ease;
502
+ }
503
+
504
+ .ai-chat-process-trace[open] .ai-chat-process-trace__summary-text {
505
+ flex: 1;
506
+ text-decoration: none;
507
+ }
508
+
509
+ /* Single expand chevron (right); hidden while streaming — dots replace it. */
510
+ .ai-chat-process-trace__summary-chevron {
511
+ display: inline-flex;
512
+ align-items: center;
513
+ justify-content: center;
514
+ flex-shrink: 0;
515
+ min-width: 1.25em;
516
+ font-size: 15px;
517
+ font-weight: 700;
518
+ line-height: 1;
519
+ opacity: 0.55;
520
+ text-decoration: none;
521
+ transition: transform 0.18s ease, opacity 0.15s ease;
522
+ }
523
+
524
+ .ai-chat-process-trace:not([open]) .ai-chat-process-trace__summary-chevron {
525
+ opacity: 0.45;
526
+ min-width: auto;
527
+ }
528
+
529
+ .ai-chat-process-trace:not([open]) .ai-chat-process-trace__mini-typing span {
530
+ opacity: 0.72;
531
+ }
532
+
533
+ .ai-chat-process-trace[open] > .ai-chat-process-trace__summary .ai-chat-process-trace__summary-chevron {
534
+ transform: rotate(90deg);
535
+ opacity: 0.78;
536
+ }
537
+
538
+ .ai-chat-process-trace__mini-typing {
539
+ display: inline-flex;
540
+ align-items: center;
541
+ flex-shrink: 0;
542
+ gap: 3px;
543
+ }
544
+
545
+ .ai-chat-process-trace__mini-typing span {
546
+ width: 4px;
547
+ height: 4px;
548
+ border-radius: 50%;
549
+ background: var(--ai-process-dot-tool);
550
+ opacity: 0.85;
551
+ animation: ai-chat-process-mini-dot 1.05s ease-in-out infinite;
552
+ }
553
+
554
+ .ai-chat-process-trace__mini-typing span:nth-child(2) {
555
+ animation-delay: 0.12s;
556
+ }
557
+
558
+ .ai-chat-process-trace__mini-typing span:nth-child(3) {
559
+ animation-delay: 0.24s;
560
+ }
561
+
562
+ @keyframes ai-chat-process-mini-dot {
563
+ 0%,
564
+ 80%,
565
+ 100% {
566
+ transform: translateY(0);
567
+ opacity: 0.35;
568
+ }
569
+ 40% {
570
+ transform: translateY(-2px);
571
+ opacity: 1;
572
+ }
573
+ }
574
+
575
+ @keyframes ai-chat-process-pulse {
576
+ 0% {
577
+ box-shadow: 0 0 0 0 rgba(5, 150, 105, 0.35);
578
+ }
579
+ 70% {
580
+ box-shadow: 0 0 0 6px rgba(5, 150, 105, 0);
581
+ }
582
+ 100% {
583
+ box-shadow: 0 0 0 0 rgba(5, 150, 105, 0);
584
+ }
585
+ }
586
+
587
+ .ai-chat-process-trace__panel {
588
+ padding: 10px 12px 12px 8px;
589
+ }
590
+
591
+ .ai-chat-process-trace__timeline {
592
+ list-style: none;
593
+ margin: 0;
594
+ padding: 0;
595
+ }
596
+
597
+ .ai-chat-process-trace__step {
598
+ display: flex;
599
+ align-items: stretch;
600
+ gap: 10px;
601
+ margin: 0;
602
+ padding: 0 0 14px;
603
+ }
604
+
605
+ .ai-chat-process-trace__step:last-child {
606
+ padding-bottom: 0;
607
+ }
608
+
609
+ .ai-chat-process-trace__rail {
610
+ position: relative;
611
+ width: 18px;
612
+ flex-shrink: 0;
613
+ display: flex;
614
+ justify-content: center;
615
+ padding-top: 4px;
616
+ }
617
+
618
+ .ai-chat-process-trace__rail::after {
619
+ content: "";
620
+ position: absolute;
621
+ top: 13px;
622
+ bottom: -14px;
623
+ left: 50%;
624
+ width: 1px;
625
+ transform: translateX(-50%);
626
+ background: var(--ai-process-rail);
627
+ }
628
+
629
+ .ai-chat-process-trace__step:last-child .ai-chat-process-trace__rail::after {
630
+ display: none;
631
+ }
632
+
633
+ .ai-chat-process-trace__dot {
634
+ position: relative;
635
+ z-index: 1;
636
+ width: 7px;
637
+ height: 7px;
638
+ border-radius: 50%;
639
+ background: var(--ai-process-dot);
640
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85);
641
+ }
642
+
643
+ .ai-chat-process-trace__step--tool .ai-chat-process-trace__dot {
644
+ background: var(--ai-process-dot-tool);
645
+ }
646
+
647
+ .ai-chat-process-trace__step--compile .ai-chat-process-trace__dot {
648
+ background: var(--ai-process-dot-compile);
649
+ }
650
+
651
+ .ai-chat-process-trace__step--mind .ai-chat-process-trace__dot {
652
+ background: var(--ai-process-dot-mind);
653
+ }
654
+
655
+ .ai-chat-process-trace__step--text .ai-chat-process-trace__dot {
656
+ background: var(--ai-process-dot);
657
+ }
658
+
659
+ .ai-chat-process-trace__step--interim .ai-chat-process-trace__dot {
660
+ background: var(--ai-process-dot);
661
+ animation: ai-chat-process-pulse 1.4s ease-out infinite;
662
+ }
663
+
664
+ .ai-chat-process-trace__step--live .ai-chat-process-trace__dot {
665
+ animation: ai-chat-process-pulse 1.2s ease-out infinite;
666
+ }
667
+
668
+ .ai-chat-process-trace__body {
669
+ flex: 1;
670
+ min-width: 0;
671
+ padding-top: 1px;
672
+ }
673
+
674
+ .ai-chat-process-trace__line {
675
+ display: block;
676
+ color: var(--ai-process-text-strong);
677
+ font-weight: 500;
678
+ }
679
+
680
+ .ai-chat-process-trace__step--tool .ai-chat-process-trace__line {
681
+ color: #047857;
682
+ }
683
+
684
+ .ai-chat-process-trace__step--compile .ai-chat-process-trace__line {
685
+ color: #4338ca;
686
+ }
687
+
688
+ .ai-chat-process-trace__prose {
689
+ margin: 0;
690
+ white-space: pre-wrap;
691
+ word-break: break-word;
692
+ color: var(--ai-process-interim-fg);
693
+ font-weight: 400;
694
+ }
695
+
696
+ .ai-chat-process-trace__step--interim .ai-chat-process-trace__prose {
697
+ border-left: 2px solid var(--ai-process-rail);
698
+ padding-left: 10px;
699
+ margin-left: 2px;
700
+ }
701
+
358
702
  /* ── Markdown Typography (assistant bubbles) ── */
359
703
 
360
704
  .ai-chat-bubble.assistant p {
@@ -1789,6 +2133,29 @@
1789
2133
  animation-delay: 0.4s;
1790
2134
  }
1791
2135
 
2136
+ /* Smaller dots for inline status (next to “Compiling…”) */
2137
+ .ai-chat-typing--inline {
2138
+ gap: 4px;
2139
+ padding: 0;
2140
+ }
2141
+
2142
+ .ai-chat-typing--inline span {
2143
+ width: 5px;
2144
+ height: 5px;
2145
+ }
2146
+
2147
+ .ai-chat-streaming-status {
2148
+ display: flex;
2149
+ align-items: center;
2150
+ gap: 8px;
2151
+ flex-wrap: wrap;
2152
+ margin-bottom: 8px;
2153
+ }
2154
+
2155
+ .ai-chat-streaming-status .ai-chat-status-text {
2156
+ margin: 0;
2157
+ }
2158
+
1792
2159
  @keyframes typing {
1793
2160
  0%,
1794
2161
  60%,
@@ -1802,6 +2169,48 @@
1802
2169
  }
1803
2170
  }
1804
2171
 
2172
+ /* ── Building Block (streaming code-block placeholder) ── */
2173
+
2174
+ .ai-building-block {
2175
+ display: flex;
2176
+ align-items: center;
2177
+ gap: 10px;
2178
+ flex-wrap: wrap;
2179
+ padding: 8px 12px;
2180
+ border-radius: 8px;
2181
+ margin: 6px 0 0;
2182
+ background: #f3f4f6;
2183
+ border: 1px solid #e5e7eb;
2184
+ }
2185
+
2186
+ .ai-building-block__label {
2187
+ font-size: 13px;
2188
+ font-weight: 600;
2189
+ color: #4b5563;
2190
+ }
2191
+
2192
+ .ai-building-block__dots {
2193
+ display: flex;
2194
+ align-items: center;
2195
+ gap: 4px;
2196
+ }
2197
+
2198
+ .ai-building-block__dot {
2199
+ width: 5px;
2200
+ height: 5px;
2201
+ border-radius: 50%;
2202
+ background: #9ca3af;
2203
+ animation: typing 1.4s infinite;
2204
+ }
2205
+
2206
+ .ai-building-block__dots .ai-building-block__dot:nth-child(2) {
2207
+ animation-delay: 0.2s;
2208
+ }
2209
+
2210
+ .ai-building-block__dots .ai-building-block__dot:nth-child(3) {
2211
+ animation-delay: 0.4s;
2212
+ }
2213
+
1805
2214
  /* ── Input Area ── */
1806
2215
 
1807
2216
  .ai-chat-input-form {
@@ -1917,47 +2326,61 @@
1917
2326
  /* ── Print (Single Response) ── */
1918
2327
 
1919
2328
  @media print {
1920
- @page {
1921
- margin: 12mm;
2329
+ @page { margin: 12mm; }
2330
+
2331
+ /*
2332
+ * visibility:hidden on body allows #ai-cortex-print-section descendants to
2333
+ * override with visibility:visible — this is a CSS guarantee that display:none
2334
+ * does NOT offer (Chrome's print engine ignores the specificity override for
2335
+ * display:none !important on body > *).
2336
+ *
2337
+ * Siblings are collapsed to height:0 so no blank space precedes the section.
2338
+ * position:absolute (not fixed) allows content to paginate across pages.
2339
+ */
2340
+ body.ai-chat-printing {
2341
+ visibility: hidden !important;
2342
+ position: relative !important;
1922
2343
  }
1923
2344
 
1924
- /* Hide everything on the page; show only the isolated print section. */
1925
- body.ai-chat-printing > * {
1926
- display: none !important;
2345
+ body.ai-chat-printing > *:not(#ai-cortex-print-section) {
2346
+ height: 0 !important;
2347
+ overflow: hidden !important;
1927
2348
  }
1928
2349
 
1929
2350
  body.ai-chat-printing #ai-cortex-print-section {
2351
+ visibility: visible !important;
1930
2352
  display: block !important;
2353
+ position: absolute !important;
2354
+ top: 0 !important;
2355
+ left: 0 !important;
2356
+ width: 100% !important;
2357
+ padding: 0 !important;
2358
+ background: #ffffff;
2359
+ color: #1f2937;
2360
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2361
+ font-size: 14px;
2362
+ line-height: 1.6;
1931
2363
  }
1932
2364
 
1933
- /*
1934
- * Kill all CSS animations and transitions inside the print section.
1935
- * Browsers reset animations during the print reflow, catching elements at
1936
- * their `from` keyframe (opacity: 0, scale: 0) rather than their final state.
1937
- * Disabling animations lets elements fall back to their base CSS styles,
1938
- * which are always the fully-visible final appearance.
1939
- *
1940
- * print-color-adjust: exact forces Chrome to render background colours and
1941
- * images — without it Chrome strips inline background fills (e.g. dot colours
1942
- * on scatter/dot charts) and the dots appear as white outlines only.
1943
- */
1944
2365
  body.ai-chat-printing #ai-cortex-print-section * {
2366
+ visibility: visible !important;
1945
2367
  animation: none !important;
1946
2368
  transition: none !important;
2369
+ /* Kill any opacity:0 left by a stopped animation */
2370
+ opacity: 1 !important;
1947
2371
  -webkit-print-color-adjust: exact !important;
1948
2372
  print-color-adjust: exact !important;
1949
2373
  }
1950
2374
 
1951
- /* Clean up the bubble for print */
1952
2375
  body.ai-chat-printing #ai-cortex-print-section .ai-chat-bubble.assistant {
1953
- max-width: none;
1954
- width: 100%;
1955
- border: none;
1956
- border-radius: 0;
1957
- padding: 0;
1958
- background: #ffffff;
1959
- color: #111827;
1960
- box-shadow: none;
2376
+ max-width: none !important;
2377
+ width: 100% !important;
2378
+ border: none !important;
2379
+ border-radius: 0 !important;
2380
+ padding: 0 !important;
2381
+ background: #ffffff !important;
2382
+ color: #1f2937 !important;
2383
+ box-shadow: none !important;
1961
2384
  }
1962
2385
  }
1963
2386
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchid-ai",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Shared Orchid AI chat UI and visualization components",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -1,8 +1,14 @@
1
1
  import React, { useRef, useEffect } from 'react';
2
2
  import Message from './Message';
3
3
 
4
+ const DEFAULT_SUGGESTIONS = [
5
+ 'Give me brief tips for navigating iLink.',
6
+ 'What should I check before starting a dispatch?',
7
+ 'How do I narrow down a search in Hermes command search (⌘K)?',
8
+ ];
9
+
4
10
  /**
5
- * Hermes chat window (Markdown + optional orchid-ai-chart fenced blocks).
11
+ * Orchid AI chat window (Markdown + optional orchid-ai-chart fenced blocks).
6
12
  */
7
13
  export default function ChatWindow({
8
14
  messages,
@@ -11,15 +17,24 @@ export default function ChatWindow({
11
17
  onSuggestionClick,
12
18
  aiEnabled,
13
19
  organisationName,
20
+ appName = "Hermes Chat",
21
+ unavailableMessage,
22
+ emptyDescription,
23
+ suggestions = DEFAULT_SUGGESTIONS,
24
+ suggestionsDisabled = false,
25
+ showProcessTracePanel = true,
14
26
  }) {
27
+ const exportPrefix = appName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
15
28
  const bottomRef = useRef(null);
16
29
 
17
30
  useEffect(() => {
18
31
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
19
- }, [messages, loading]);
32
+ }, [messages, loading, statusText]);
20
33
 
21
34
  const renderEmptyState = () => {
22
35
  if (!aiEnabled) {
36
+ const msg = unavailableMessage ??
37
+ `${appName} needs an Anthropic API key on the server. Contact your administrator if this persists.`;
23
38
  return (
24
39
  <div className="ai-chat-empty">
25
40
  <div className="ai-chat-empty-icon">
@@ -37,16 +52,14 @@ export default function ChatWindow({
37
52
  <line x1="4.93" y1="4.93" x2="19.07" y2="19.07" />
38
53
  </svg>
39
54
  </div>
40
- <h2>Hermes Chat unavailable</h2>
41
- <p>
42
- Hermes Chat needs an Anthropic API key on the server and an active organisation. Contact your administrator
43
- if this persists.
44
- </p>
55
+ <h2>{appName} unavailable</h2>
56
+ <p>{msg}</p>
45
57
  </div>
46
58
  );
47
59
  }
48
60
 
49
61
  const scope = organisationName || 'your organisation';
62
+ const description = emptyDescription ?? `Ask about ${scope} — shipments, schedules, data insights, or what to explore next.`;
50
63
 
51
64
  return (
52
65
  <div className="ai-chat-empty">
@@ -67,32 +80,53 @@ export default function ChatWindow({
67
80
  </svg>
68
81
  </div>
69
82
  <h2>How can I help?</h2>
70
- <p>{`Ask about ${scope} — jobs, routes, schedules, reminders, or what to explore next.`}</p>
71
- <div className="ai-chat-suggestions">
72
- <span>Try asking:</span>
73
- <ul>
74
- <li onClick={() => onSuggestionClick('Give me brief tips for navigating iLink.')}>
75
- Give me brief tips for navigating iLink.
76
- </li>
77
- <li onClick={() => onSuggestionClick('What should I check before starting a dispatch?')}>
78
- What should I check before starting a dispatch?
79
- </li>
80
- <li onClick={() => onSuggestionClick('How do I narrow down a search in Hermes command search (⌘K)?')}>
81
- How do I narrow down a search in Hermes command search (⌘K)?
82
- </li>
83
- </ul>
84
- </div>
83
+ <p>{description}</p>
84
+ {suggestions.length > 0 && (
85
+ <div className={`ai-chat-suggestions${suggestionsDisabled ? ' ai-chat-suggestions--disabled' : ''}`}>
86
+ <span>Try asking:</span>
87
+ <ul>
88
+ {suggestions.map((s) => (
89
+ <li
90
+ key={s}
91
+ onClick={suggestionsDisabled ? undefined : () => onSuggestionClick(s)}
92
+ aria-disabled={suggestionsDisabled || undefined}
93
+ >
94
+ {s}
95
+ </li>
96
+ ))}
97
+ </ul>
98
+ </div>
99
+ )}
85
100
  </div>
86
101
  );
87
102
  };
88
103
 
104
+ const lastMsg = messages?.[messages.length - 1];
105
+ const hasStreamingMessage = lastMsg?.isStreaming === true;
106
+
89
107
  return (
90
108
  <div className="ai-chat-window">
91
109
  {messages?.length === 0 && !loading && renderEmptyState()}
92
- {(messages ?? []).map((msg, i) => (
93
- <Message key={i} role={msg.role} content={msg.content} truncated={msg.truncated} />
94
- ))}
95
- {loading && (
110
+ {(messages ?? []).map((msg, i) => {
111
+ const isLast = i === (messages?.length ?? 0) - 1;
112
+ const streamingStatusText =
113
+ loading && isLast && msg.role === 'assistant' && msg.isStreaming ? statusText : undefined;
114
+ return (
115
+ <Message
116
+ key={i}
117
+ role={msg.role}
118
+ content={msg.content}
119
+ truncated={msg.truncated}
120
+ exportPrefix={exportPrefix}
121
+ isStreaming={msg.isStreaming}
122
+ streamingStatusText={streamingStatusText}
123
+ processTrace={msg.processTrace}
124
+ processInterimLive={msg.processInterimLive}
125
+ showProcessTracePanel={showProcessTracePanel}
126
+ />
127
+ );
128
+ })}
129
+ {loading && !hasStreamingMessage && (
96
130
  <div className="ai-chat-message assistant">
97
131
  <div className="ai-chat-avatar assistant">AI</div>
98
132
  <div className="ai-chat-bubble assistant">