@flamingo-stack/openframe-frontend-core 0.0.351 → 0.0.352

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.
Files changed (100) hide show
  1. package/dist/{chunk-DJZ64AF6.js → chunk-3QWEET5C.js} +2 -2
  2. package/dist/{chunk-VZ7TICMR.cjs → chunk-3SYTPXCP.cjs} +37 -37
  3. package/dist/{chunk-VZ7TICMR.cjs.map → chunk-3SYTPXCP.cjs.map} +1 -1
  4. package/dist/{chunk-63ISTNQW.cjs → chunk-42PSJKMY.cjs} +25 -25
  5. package/dist/{chunk-63ISTNQW.cjs.map → chunk-42PSJKMY.cjs.map} +1 -1
  6. package/dist/{chunk-YBL3JURC.js → chunk-66D3JL6J.js} +4 -4
  7. package/dist/{chunk-VY6AAUCK.js → chunk-7CTSKYAV.js} +2 -2
  8. package/dist/{chunk-QGBJKB6V.cjs → chunk-DY4Y7QQB.cjs} +31 -31
  9. package/dist/{chunk-QGBJKB6V.cjs.map → chunk-DY4Y7QQB.cjs.map} +1 -1
  10. package/dist/{chunk-46Z3O6SN.cjs → chunk-ED72ZQV7.cjs} +5 -5
  11. package/dist/{chunk-46Z3O6SN.cjs.map → chunk-ED72ZQV7.cjs.map} +1 -1
  12. package/dist/{chunk-MLW25MGY.js → chunk-FQ4VGHPO.js} +2 -2
  13. package/dist/{chunk-GJZL72ON.cjs → chunk-FU5KF6MA.cjs} +7 -7
  14. package/dist/{chunk-GJZL72ON.cjs.map → chunk-FU5KF6MA.cjs.map} +1 -1
  15. package/dist/{chunk-WB2EWYSF.js → chunk-GGCOLMNS.js} +2 -2
  16. package/dist/{chunk-NEIOYJIE.cjs → chunk-HPVOWKIO.cjs} +15 -15
  17. package/dist/{chunk-NEIOYJIE.cjs.map → chunk-HPVOWKIO.cjs.map} +1 -1
  18. package/dist/{chunk-A6YFD2SK.js → chunk-HSZJGPVA.js} +173 -116
  19. package/dist/chunk-HSZJGPVA.js.map +1 -0
  20. package/dist/{chunk-NIRMBPFP.cjs → chunk-IHXJKIFM.cjs} +12 -12
  21. package/dist/{chunk-NIRMBPFP.cjs.map → chunk-IHXJKIFM.cjs.map} +1 -1
  22. package/dist/{chunk-5F45BY24.js → chunk-IM5HXLFT.js} +2 -2
  23. package/dist/{chunk-JBWB3U3P.js → chunk-JQGLBAGS.js} +2 -2
  24. package/dist/{chunk-BMQ5CBFD.cjs → chunk-K2NJ5VHQ.cjs} +62 -62
  25. package/dist/{chunk-BMQ5CBFD.cjs.map → chunk-K2NJ5VHQ.cjs.map} +1 -1
  26. package/dist/{chunk-Q3VK7WI2.js → chunk-M247JWGY.js} +4 -4
  27. package/dist/{chunk-4WVVMKMC.js → chunk-N7BREY67.js} +4 -4
  28. package/dist/{chunk-YIFJJCFB.js → chunk-ONMA32BY.js} +2 -2
  29. package/dist/{chunk-HMUIRZ46.cjs → chunk-QP7TLRLH.cjs} +172 -115
  30. package/dist/chunk-QP7TLRLH.cjs.map +1 -0
  31. package/dist/{chunk-UQFTSFIF.cjs → chunk-RTKRXPPG.cjs} +26 -26
  32. package/dist/{chunk-UQFTSFIF.cjs.map → chunk-RTKRXPPG.cjs.map} +1 -1
  33. package/dist/{chunk-TH3HPNMO.cjs → chunk-T2ARJIH6.cjs} +56 -56
  34. package/dist/{chunk-TH3HPNMO.cjs.map → chunk-T2ARJIH6.cjs.map} +1 -1
  35. package/dist/{chunk-EKU6D26E.js → chunk-TM455OW5.js} +4 -4
  36. package/dist/{chunk-XZVVLO4V.cjs → chunk-ZRLPXSZC.cjs} +9 -9
  37. package/dist/{chunk-XZVVLO4V.cjs.map → chunk-ZRLPXSZC.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/approval-batch-message.d.ts +7 -1
  41. package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
  42. package/dist/components/chat/approval-request-message.d.ts.map +1 -1
  43. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  44. package/dist/components/chat/index.cjs +2 -2
  45. package/dist/components/chat/index.js +1 -1
  46. package/dist/components/chat/tool-call-blocks.d.ts.map +1 -1
  47. package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
  48. package/dist/components/chat/types/component.types.d.ts +6 -0
  49. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  50. package/dist/components/contact/index.cjs +3 -3
  51. package/dist/components/contact/index.js +2 -2
  52. package/dist/components/docs/index.cjs +5 -5
  53. package/dist/components/docs/index.js +4 -4
  54. package/dist/components/embeds/index.cjs +3 -3
  55. package/dist/components/embeds/index.js +2 -2
  56. package/dist/components/faq/index.cjs +3 -3
  57. package/dist/components/faq/index.js +2 -2
  58. package/dist/components/features/index.cjs +2 -2
  59. package/dist/components/features/index.js +1 -1
  60. package/dist/components/help-center-pages/index.cjs +20 -20
  61. package/dist/components/help-center-pages/index.js +11 -11
  62. package/dist/components/index.cjs +120 -120
  63. package/dist/components/index.js +9 -9
  64. package/dist/components/navigation/index.cjs +2 -2
  65. package/dist/components/navigation/index.js +1 -1
  66. package/dist/components/onboarding-guides/index.cjs +5 -5
  67. package/dist/components/onboarding-guides/index.js +4 -4
  68. package/dist/components/related-content/index.cjs +3 -3
  69. package/dist/components/related-content/index.js +2 -2
  70. package/dist/components/tickets/index.cjs +5 -5
  71. package/dist/components/tickets/index.js +4 -4
  72. package/dist/components/ui/index.cjs +2 -2
  73. package/dist/components/ui/index.js +1 -1
  74. package/dist/index.cjs +2 -2
  75. package/dist/index.js +1 -1
  76. package/package.json +1 -1
  77. package/src/components/chat/approval-batch-message.tsx +82 -27
  78. package/src/components/chat/approval-request-message.tsx +27 -19
  79. package/src/components/chat/chat-message-enhanced.tsx +3 -0
  80. package/src/components/chat/tool-call-blocks.tsx +17 -6
  81. package/src/components/chat/tool-execution-display.tsx +23 -8
  82. package/src/components/chat/types/component.types.ts +6 -0
  83. package/src/stories/ApprovalBatchMessage.stories.tsx +169 -0
  84. package/src/stories/ApprovalBatchMessageClient.stories.tsx +172 -0
  85. package/src/stories/ApprovalRequestMessage.stories.tsx +95 -0
  86. package/src/stories/ToolExecutionDisplay.stories.tsx +1 -1
  87. package/src/stories/ToolExecutionDisplayClient.stories.tsx +64 -0
  88. package/dist/chunk-A6YFD2SK.js.map +0 -1
  89. package/dist/chunk-HMUIRZ46.cjs.map +0 -1
  90. /package/dist/{chunk-DJZ64AF6.js.map → chunk-3QWEET5C.js.map} +0 -0
  91. /package/dist/{chunk-YBL3JURC.js.map → chunk-66D3JL6J.js.map} +0 -0
  92. /package/dist/{chunk-VY6AAUCK.js.map → chunk-7CTSKYAV.js.map} +0 -0
  93. /package/dist/{chunk-MLW25MGY.js.map → chunk-FQ4VGHPO.js.map} +0 -0
  94. /package/dist/{chunk-WB2EWYSF.js.map → chunk-GGCOLMNS.js.map} +0 -0
  95. /package/dist/{chunk-5F45BY24.js.map → chunk-IM5HXLFT.js.map} +0 -0
  96. /package/dist/{chunk-JBWB3U3P.js.map → chunk-JQGLBAGS.js.map} +0 -0
  97. /package/dist/{chunk-Q3VK7WI2.js.map → chunk-M247JWGY.js.map} +0 -0
  98. /package/dist/{chunk-4WVVMKMC.js.map → chunk-N7BREY67.js.map} +0 -0
  99. /package/dist/{chunk-YIFJJCFB.js.map → chunk-ONMA32BY.js.map} +0 -0
  100. /package/dist/{chunk-EKU6D26E.js.map → chunk-TM455OW5.js.map} +0 -0
@@ -4,7 +4,7 @@ import { forwardRef, useState } from "react"
4
4
  import { cn } from "../../utils/cn"
5
5
  import { Button } from "../ui/button"
6
6
  import { Tag } from "../ui/tag"
7
- import { CheckCircle, XCircle } from "lucide-react"
7
+ import { Ban, CheckCircle, XCircle } from "lucide-react"
8
8
  import type { ApprovalRequestMessageProps } from "./types"
9
9
  import type { ApprovalRequestField } from "./types/message.types"
10
10
 
@@ -18,13 +18,16 @@ import type { ApprovalRequestField } from "./types/message.types"
18
18
  */
19
19
  function ApprovalFieldList({ fields }: { fields: ApprovalRequestField[] }) {
20
20
  return (
21
- <dl className="flex flex-col gap-2.5 mt-1">
21
+ <dl className="flex flex-col gap-2.5 mt-[var(--spacing-system-xxs)]">
22
22
  {fields.map((f, i) => (
23
23
  <div key={i} className="flex flex-col gap-0.5">
24
+ {/* NOTE: 11px DM Sans small-caps label has no ODS composite token
25
+ (h5 is Azeret Mono, h6 is 14px) — flagged for a future caption
26
+ token; kept as-is rather than forcing a mismatched h-class. */}
24
27
  <dt className="font-['DM_Sans'] font-semibold text-[11px] uppercase tracking-wide text-ods-text-tertiary leading-4">
25
28
  {f.label}
26
29
  </dt>
27
- <dd className="font-['DM_Sans'] text-sm text-ods-text-primary leading-5 whitespace-pre-wrap break-words">
30
+ <dd className="text-h6 text-ods-text-primary whitespace-pre-wrap break-words">
28
31
  {f.value}
29
32
  </dd>
30
33
  </div>
@@ -49,9 +52,9 @@ function ApprovalCardBody({
49
52
  data: ApprovalRequestMessageProps['data']
50
53
  }) {
51
54
  return (
52
- <div className="flex flex-col gap-1">
53
- <div className="bg-ods-bg border border-ods-border rounded-md p-3 flex gap-2 items-start max-h-32 overflow-y-auto">
54
- <code className="font-['DM_Sans'] font-medium text-sm text-ods-text-primary flex-1 leading-5 whitespace-pre-wrap break-words">
55
+ <div className="flex flex-col gap-[var(--spacing-system-xxs)]">
56
+ <div className="bg-ods-bg border border-ods-border rounded-md p-[var(--spacing-system-sf)] flex gap-[var(--spacing-system-xsf)] items-start max-h-32 overflow-y-auto">
57
+ <code className="text-h6 text-ods-text-primary flex-1 whitespace-pre-wrap break-words">
55
58
  {data.command}
56
59
  </code>
57
60
  {data.icon && (
@@ -64,7 +67,7 @@ function ApprovalCardBody({
64
67
  <ApprovalFieldList fields={data.fields} />
65
68
  ) : (
66
69
  data.explanation && (
67
- <p className="font-['DM_Sans'] font-medium text-sm text-ods-text-secondary leading-5 whitespace-pre-line break-words">
70
+ <p className="text-h6 text-ods-text-secondary whitespace-pre-line break-words">
68
71
  {data.explanation}
69
72
  </p>
70
73
  )
@@ -74,7 +77,10 @@ function ApprovalCardBody({
74
77
  }
75
78
 
76
79
  const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessageProps>(
77
- ({ className, data, onApprove, onReject, status = 'pending', ...props }, ref) => {
80
+ // `assistantType` is accepted for prop-parity with the batch card (so hosts
81
+ // can forward it uniformly); the legacy single-command card has no
82
+ // icon/divider to vary, so it renders identically for admin and client today.
83
+ ({ className, data, onApprove, onReject, status = 'pending', assistantType: _assistantType, ...props }, ref) => {
78
84
  const [isProcessing, setIsProcessing] = useState(false)
79
85
 
80
86
  const handleApprove = async () => {
@@ -99,14 +105,14 @@ const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessage
99
105
  <div
100
106
  ref={ref}
101
107
  className={cn(
102
- "bg-ods-card border border-ods-border rounded-md p-4 mb-2 flex flex-col gap-4",
108
+ "bg-ods-card border border-ods-border rounded-md p-[var(--spacing-system-mf)] mb-[var(--spacing-system-xsf)] flex flex-col gap-[var(--spacing-system-mf)]",
103
109
  className
104
110
  )}
105
111
  {...props}
106
112
  >
107
113
  <ApprovalCardBody data={data} />
108
114
  {status === 'pending' ? (
109
- <div className="flex gap-4 items-center">
115
+ <div className="flex gap-[var(--spacing-system-mf)] items-center">
110
116
  <Button
111
117
  size="small-legacy"
112
118
  variant="accent"
@@ -114,8 +120,8 @@ const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessage
114
120
  disabled={isProcessing}
115
121
  className={cn(
116
122
  "bg-ods-accent hover:bg-ods-accent/90",
117
- "font-mono font-medium md:!text-sm text-ods-bg uppercase tracking-[-0.28px]",
118
- "px-2 py-1 h-auto"
123
+ "text-h5 text-ods-bg",
124
+ "px-[var(--spacing-system-xsf)] h-8"
119
125
  )}
120
126
  >
121
127
  Approve
@@ -127,8 +133,8 @@ const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessage
127
133
  disabled={isProcessing}
128
134
  className={cn(
129
135
  "bg-ods-card border-ods-border",
130
- "font-mono font-medium md:!text-sm text-ods-text-primary uppercase tracking-[-0.28px]",
131
- "hover:bg-ods-bg px-2 py-1 h-auto"
136
+ "text-h5 text-ods-text-primary",
137
+ "hover:bg-ods-bg px-[var(--spacing-system-xsf)] h-8"
132
138
  )}
133
139
  >
134
140
  Reject
@@ -136,11 +142,13 @@ const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessage
136
142
  </div>
137
143
  ) : (
138
144
  <div className="flex">
139
- <Tag
140
- label={status === 'approved' ? 'Approved' : 'Rejected'}
141
- variant={status === 'approved' ? 'success' : 'error'}
142
- icon={status === 'approved' ? <CheckCircle className="w-4 h-4" /> : <XCircle className="w-4 h-4" />}
143
- />
145
+ {status === 'approved' ? (
146
+ <Tag label="Approved" variant="success" icon={<CheckCircle className="w-4 h-4" />} />
147
+ ) : status === 'cancelled' ? (
148
+ <Tag label="Canceled" variant="grey" icon={<Ban className="w-4 h-4" />} />
149
+ ) : (
150
+ <Tag label="Rejected" variant="error" icon={<XCircle className="w-4 h-4" />} />
151
+ )}
144
152
  </div>
145
153
  )}
146
154
  </div>
@@ -537,6 +537,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
537
537
  <ToolExecutionDisplay
538
538
  key={index}
539
539
  message={segment.data}
540
+ assistantType={assistantType}
540
541
  />
541
542
  )
542
543
  } else if (segment.type === 'approval_request') {
@@ -547,6 +548,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
547
548
  status={segment.status}
548
549
  onApprove={segment.onApprove}
549
550
  onReject={segment.onReject}
551
+ assistantType={assistantType}
550
552
  />
551
553
  )
552
554
  } else if (segment.type === 'approval_batch') {
@@ -558,6 +560,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
558
560
  resolvedByName={segment.resolvedByName}
559
561
  onApprove={segment.onApprove}
560
562
  onReject={segment.onReject}
563
+ assistantType={assistantType}
561
564
  />
562
565
  )
563
566
  } else if (segment.type === 'error') {
@@ -3,6 +3,17 @@
3
3
  import { cn } from "../../utils/cn"
4
4
  import { formatToolArgValue, formatToolResult } from "./utils/tool-call-helpers"
5
5
 
6
+ /**
7
+ * Monospace code body shared by ArgRow / ResultBlock.
8
+ *
9
+ * NOTE: ODS exposes no mono/code typography token (only the composite
10
+ * `text-h1`–`text-h6` DM Sans / Azeret-Mono headings), so the 12px code size
11
+ * stays as `text-xs` here. Flagged for a future `text-code` token rather than
12
+ * forcing an h-class that would change the intended density.
13
+ */
14
+ const CODE_PRE_CLASS =
15
+ "bg-ods-bg border border-ods-border rounded-md p-[var(--spacing-system-sf)] w-full max-h-64 overflow-auto text-xs leading-5 text-ods-text-primary font-mono whitespace-pre"
16
+
6
17
  /**
7
18
  * Single arg row: inline `key: value` or labeled `<pre>` block.
8
19
  * Shared between ApprovalBatchMessage and ToolExecutionDisplay so both
@@ -13,7 +24,7 @@ export function ArgRow({ argKey, value }: { argKey: string; value: unknown }) {
13
24
 
14
25
  if (formatted.kind === "inline") {
15
26
  return (
16
- <div className="flex gap-1 items-start w-full">
27
+ <div className="flex gap-[var(--spacing-system-xxs)] items-start w-full">
17
28
  <span className="shrink-0 text-ods-text-secondary">{argKey}:</span>
18
29
  <span className="flex-1 min-w-0 text-ods-text-primary break-all">{formatted.text}</span>
19
30
  </div>
@@ -21,9 +32,9 @@ export function ArgRow({ argKey, value }: { argKey: string; value: unknown }) {
21
32
  }
22
33
 
23
34
  return (
24
- <div className="flex flex-col gap-1 items-start w-full">
35
+ <div className="flex flex-col gap-[var(--spacing-system-xxs)] items-start w-full">
25
36
  <span className="text-ods-text-secondary">{argKey}:</span>
26
- <pre className="bg-ods-bg border border-ods-border rounded-md p-3 w-full max-h-64 overflow-auto text-xs leading-5 text-ods-text-primary font-mono whitespace-pre">
37
+ <pre className={CODE_PRE_CLASS}>
27
38
  <code>{formatted.text}</code>
28
39
  </pre>
29
40
  </div>
@@ -40,7 +51,7 @@ export function ResultBlock({ result, className }: { result: string | undefined
40
51
 
41
52
  if (formatted.kind === "inline") {
42
53
  return (
43
- <div className={cn("flex flex-col gap-1 items-start w-full", className)}>
54
+ <div className={cn("flex flex-col gap-[var(--spacing-system-xxs)] items-start w-full", className)}>
44
55
  <span className="text-ods-text-secondary">Result:</span>
45
56
  <span className="text-ods-text-primary break-all">{formatted.text}</span>
46
57
  </div>
@@ -48,9 +59,9 @@ export function ResultBlock({ result, className }: { result: string | undefined
48
59
  }
49
60
 
50
61
  return (
51
- <div className={cn("flex flex-col gap-1 items-start w-full", className)}>
62
+ <div className={cn("flex flex-col gap-[var(--spacing-system-xxs)] items-start w-full", className)}>
52
63
  <span className="text-ods-text-secondary">Result:</span>
53
- <pre className="bg-ods-bg border border-ods-border rounded-md p-3 w-full max-h-64 overflow-auto text-xs leading-5 text-ods-text-primary font-mono whitespace-pre">
64
+ <pre className={CODE_PRE_CLASS}>
54
65
  <code>{formatted.text}</code>
55
66
  </pre>
56
67
  </div>
@@ -15,9 +15,10 @@ import type { ToolExecutionDisplayProps } from "./types"
15
15
  const COMMAND_BODY_KEYS = new Set<string>(COMMAND_BODY_ARG_KEYS)
16
16
 
17
17
  const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProps>(
18
- ({ className, message, ...props }, ref) => {
18
+ ({ className, message, assistantType, ...props }, ref) => {
19
19
  const [expanded, setExpanded] = useState(false)
20
20
  const { innerRef, containerStyle } = useCollapsible({ expanded })
21
+ const isClient = assistantType === "fae"
21
22
 
22
23
  const isExecuting = message.type === "EXECUTING_TOOL"
23
24
  const isExecuted = message.type === "EXECUTED_TOOL"
@@ -54,6 +55,8 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
54
55
  <div
55
56
  ref={ref}
56
57
  className={cn(
58
+ // The command running block keeps its bordered box in both chats
59
+ // (Figma 1972-6109). CLIENT (Fae) only drops the tool icon below.
57
60
  "bg-ods-card border border-ods-border rounded-[6px] overflow-hidden w-full flex flex-col",
58
61
  className,
59
62
  )}
@@ -64,12 +67,14 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
64
67
  className="flex gap-[var(--spacing-system-xs)] items-start p-[var(--spacing-system-s)] cursor-pointer text-left w-full"
65
68
  onClick={() => setExpanded((prev) => !prev)}
66
69
  >
67
- <div className="flex items-center justify-center shrink-0 w-5 h-5">
68
- <ToolIcon toolType={integratedToolType} size={16} />
69
- </div>
70
+ {!isClient && (
71
+ <div className="flex items-center justify-center shrink-0 w-5 h-5">
72
+ <ToolIcon toolType={integratedToolType} size={16} />
73
+ </div>
74
+ )}
70
75
  <div
71
76
  className={cn(
72
- "flex-1 min-w-0 text-sm font-medium leading-5",
77
+ "flex-1 min-w-0 text-h6",
73
78
  expanded
74
79
  ? "text-ods-text-primary whitespace-pre-wrap break-all"
75
80
  : "text-ods-text-secondary line-clamp-2 max-h-10 break-all",
@@ -86,13 +91,23 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
86
91
  <div className="w-full" style={containerStyle}>
87
92
  <div ref={innerRef}>
88
93
  {hasBody && (
89
- <div className="flex flex-col gap-2 items-start w-full text-sm font-medium leading-5 p-3 bg-ods-card">
94
+ <div className="flex flex-col gap-0 items-start w-full text-h6 p-[var(--spacing-system-sf)] bg-ods-card">
90
95
  {argEntries.map(([key, value]) => (
91
96
  <ArgRow key={key} argKey={key} value={value} />
92
97
  ))}
93
- {hasResult && <ResultBlock result={message.result} />}
98
+ {hasResult && (
99
+ <ResultBlock
100
+ result={message.result}
101
+ className={argEntries.length > 0 ? "mt-[var(--spacing-system-xsf)]" : undefined}
102
+ />
103
+ )}
94
104
  {isExecuting && (
95
- <div className="flex flex-col gap-1 items-start w-full">
105
+ <div
106
+ className={cn(
107
+ "flex flex-col gap-[var(--spacing-system-xxs)] items-start w-full",
108
+ argEntries.length > 0 && "mt-[var(--spacing-system-xsf)]",
109
+ )}
110
+ >
96
111
  <span className="text-ods-text-secondary">Result:</span>
97
112
  <DotsLoaderIcon size={16} className="text-ods-text-secondary" />
98
113
  </div>
@@ -461,6 +461,9 @@ export interface ChatTypingIndicatorProps extends HTMLAttributes<HTMLDivElement>
461
461
 
462
462
  export interface ToolExecutionDisplayProps extends HTMLAttributes<HTMLDivElement> {
463
463
  message: ToolExecutionData
464
+ /** Chat identity. `'fae'` (client) hides the tool icon; `'mingo'`/undefined
465
+ * keep the admin layout. */
466
+ assistantType?: AssistantType
464
467
  }
465
468
 
466
469
  // ========== Approval Request Message Props ==========
@@ -471,6 +474,9 @@ export interface ApprovalRequestMessageProps extends HTMLAttributes<HTMLDivEleme
471
474
  onReject?: (requestId?: string) => void | Promise<void>
472
475
  status?: ChatApprovalStatus
473
476
  disabled?: boolean
477
+ /** Chat identity; drives the CLIENT (Fae) styling. Accepted for parity with
478
+ * the batch card. `'fae'` = client, `'mingo'`/undefined = admin. */
479
+ assistantType?: AssistantType
474
480
  }
475
481
 
476
482
  // ========== Error Message Display Props ==========
@@ -0,0 +1,169 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { ApprovalBatchMessage } from "../components/chat/approval-batch-message";
5
+ import type {
6
+ ApprovalBatchData,
7
+ ApprovalBatchExecutionState,
8
+ PendingToolCallData,
9
+ } from "../components/chat/types";
10
+
11
+ /**
12
+ * The "Command Block" from Figma (open-design-system, node 1092-2807).
13
+ *
14
+ * A single command/tool call the AI wants to run, rendered as an approvable
15
+ * card: a command title line, an expandable body with the call arguments
16
+ * (RunAsUser, timeoutSeconds, …) and — once executed — a Result block. The
17
+ * footer flips between Approve/Reject buttons (pending) and a resolved status
18
+ * tag ("Approved / Rejected / Cancelled by {name}").
19
+ *
20
+ * Click a row to expand/collapse its body.
21
+ */
22
+
23
+ const LONG_COMMAND = `Get-CimInstance Win32_Process |
24
+ Select-Object ProcessId, Name, Path, CommandLine |
25
+ Where-Object { $_.Path -like 'C:\\Users\\Public\\*' }`;
26
+
27
+ const RESULT = `pid | name | path | cmdline
28
+ 4821 | suspicious.exe | C:\\Users\\Public\\suspicious.exe | suspicious.exe --hidden --connect 185.220.101.45`;
29
+
30
+ const REQUEST_ID = "req-1";
31
+
32
+ const toolCall: PendingToolCallData = {
33
+ toolExecutionRequestId: REQUEST_ID,
34
+ toolName: "run_script",
35
+ toolTitle: "Run Script",
36
+ toolExplanation:
37
+ "Runs a diagnostic script on the endpoint to inspect running processes.",
38
+ toolType: "TACTICAL_RMM",
39
+ requiresApproval: true,
40
+ approvalType: "ADMIN",
41
+ toolCallArguments: {
42
+ command: LONG_COMMAND,
43
+ RunAsUser: "False",
44
+ timeoutSeconds: 60,
45
+ },
46
+ };
47
+
48
+ const makeData = (
49
+ executions?: Record<string, ApprovalBatchExecutionState>,
50
+ ): ApprovalBatchData => ({
51
+ approvalRequestId: "batch-1",
52
+ approvalType: "ADMIN",
53
+ toolCalls: [toolCall],
54
+ executions,
55
+ });
56
+
57
+ const executingData = makeData({ [REQUEST_ID]: { status: "executing" } });
58
+ const doneData = makeData({
59
+ [REQUEST_ID]: { status: "done", result: RESULT, success: true },
60
+ });
61
+
62
+ const constrainedDecorator =
63
+ (width?: number) => (Story: React.ComponentType) => (
64
+ <div style={{ maxWidth: width ?? 400, background: "var(--color-bg)" }}>
65
+ <Story />
66
+ </div>
67
+ );
68
+
69
+ const meta = {
70
+ title: "Chat/Admin/ApprovalBatchMessage",
71
+ component: ApprovalBatchMessage,
72
+ tags: ["autodocs"],
73
+ parameters: {
74
+ docs: {
75
+ description: {
76
+ component:
77
+ "Command Block (Figma node 1092-2807). Approvable tool-call card with pending / approved / rejected / cancelled states, an expandable body of call arguments, and a Result block after execution.",
78
+ },
79
+ },
80
+ },
81
+ decorators: [constrainedDecorator()],
82
+ } satisfies Meta<typeof ApprovalBatchMessage>;
83
+
84
+ export default meta;
85
+ type Story = StoryObj<typeof meta>;
86
+
87
+ /** Pending — Approve / Reject buttons shown. Click the row to expand args. */
88
+ export const Pending: Story = {
89
+ args: {
90
+ data: makeData(),
91
+ status: "pending",
92
+ onApprove: () => {},
93
+ onReject: () => {},
94
+ },
95
+ };
96
+
97
+ /** Approved and still executing — grey loader on the row, no result yet. */
98
+ export const ApprovedExecuting: Story = {
99
+ args: {
100
+ data: executingData,
101
+ status: "approved",
102
+ resolvedByName: "Michael Johnson",
103
+ },
104
+ };
105
+
106
+ /** Approved and finished — green check + expandable Result block. */
107
+ export const ApprovedDone: Story = {
108
+ args: {
109
+ data: doneData,
110
+ status: "approved",
111
+ resolvedByName: "Michael Johnson",
112
+ },
113
+ };
114
+
115
+ /** Rejected by a user — red status tag, no execution. */
116
+ export const Rejected: Story = {
117
+ args: {
118
+ data: makeData(),
119
+ status: "rejected",
120
+ resolvedByName: "Michael Johnson",
121
+ },
122
+ };
123
+
124
+ /** Cancelled — grey status tag, no execution. */
125
+ export const Cancelled: Story = {
126
+ args: {
127
+ data: makeData(),
128
+ status: "cancelled",
129
+ resolvedByName: "Michael Johnson",
130
+ },
131
+ };
132
+
133
+ /**
134
+ * All states side by side — mirrors the Figma "Command Blocks" board so the
135
+ * full set can be reviewed at a glance.
136
+ */
137
+ export const AllStates: Story = {
138
+ decorators: [constrainedDecorator(420)],
139
+ render: () => (
140
+ <div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
141
+ <ApprovalBatchMessage
142
+ data={makeData()}
143
+ status="pending"
144
+ onApprove={() => {}}
145
+ onReject={() => {}}
146
+ />
147
+ <ApprovalBatchMessage
148
+ data={executingData}
149
+ status="approved"
150
+ resolvedByName="Michael Johnson"
151
+ />
152
+ <ApprovalBatchMessage
153
+ data={doneData}
154
+ status="approved"
155
+ resolvedByName="Michael Johnson"
156
+ />
157
+ <ApprovalBatchMessage
158
+ data={makeData()}
159
+ status="rejected"
160
+ resolvedByName="Michael Johnson"
161
+ />
162
+ <ApprovalBatchMessage
163
+ data={makeData()}
164
+ status="cancelled"
165
+ resolvedByName="Michael Johnson"
166
+ />
167
+ </div>
168
+ ),
169
+ };
@@ -0,0 +1,172 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { ApprovalBatchMessage } from "../components/chat/approval-batch-message";
5
+ import type {
6
+ ApprovalBatchData,
7
+ ApprovalBatchExecutionState,
8
+ PendingToolCallData,
9
+ } from "../components/chat/types";
10
+
11
+ /**
12
+ * CLIENT (Fae) variant of the Command Block — Figma node 1092-2807.
13
+ *
14
+ * Same component and props as the Admin card, driven by `assistantType="fae"`.
15
+ * Differences vs Admin: no tool icon next to the command, no footer divider,
16
+ * and a single full-text status pill ("Approved by Michael Johnson") instead
17
+ * of a compact tag + separate "by {name}".
18
+ */
19
+
20
+ const LONG_COMMAND = `Get-CimInstance Win32_Process |
21
+ Select-Object ProcessId, Name, Path, CommandLine |
22
+ Where-Object { $_.Path -like 'C:\\Users\\Public\\*' }`;
23
+
24
+ const RESULT = `pid | name | path | cmdline
25
+ 4821 | suspicious.exe | C:\\Users\\Public\\suspicious.exe | suspicious.exe --hidden --connect 185.220.101.45`;
26
+
27
+ const REQUEST_ID = "req-1";
28
+
29
+ const toolCall: PendingToolCallData = {
30
+ toolExecutionRequestId: REQUEST_ID,
31
+ toolName: "run_script",
32
+ toolTitle: "Run Script",
33
+ toolType: "TACTICAL_RMM",
34
+ requiresApproval: true,
35
+ approvalType: "CLIENT",
36
+ toolCallArguments: {
37
+ command: LONG_COMMAND,
38
+ RunAsUser: "False",
39
+ timeoutSeconds: 60,
40
+ },
41
+ };
42
+
43
+ const makeData = (
44
+ executions?: Record<string, ApprovalBatchExecutionState>,
45
+ ): ApprovalBatchData => ({
46
+ approvalRequestId: "batch-1",
47
+ approvalType: "CLIENT",
48
+ toolCalls: [toolCall],
49
+ executions,
50
+ });
51
+
52
+ const executingData = makeData({ [REQUEST_ID]: { status: "executing" } });
53
+ const doneData = makeData({
54
+ [REQUEST_ID]: { status: "done", result: RESULT, success: true },
55
+ });
56
+
57
+ /**
58
+ * The assistant's message text that precedes the command block in the real
59
+ * chat (Figma "Command description text."). It is NOT part of the component —
60
+ * shown here only so the story matches the Figma frame.
61
+ */
62
+ const CommandHeading = () => (
63
+ <p className="text-h4 text-ods-text-primary mb-[var(--spacing-system-xxs)]">
64
+ Command description text.
65
+ </p>
66
+ );
67
+
68
+ const constrainedDecorator =
69
+ (width?: number) => (Story: React.ComponentType) => (
70
+ <div style={{ maxWidth: width ?? 400, background: "var(--color-bg)" }}>
71
+ <CommandHeading />
72
+ <Story />
73
+ </div>
74
+ );
75
+
76
+ const plainDecorator =
77
+ (width?: number) => (Story: React.ComponentType) => (
78
+ <div style={{ maxWidth: width ?? 400, background: "var(--color-bg)" }}>
79
+ <Story />
80
+ </div>
81
+ );
82
+
83
+ const meta = {
84
+ title: "Chat/Client/ApprovalBatchMessage",
85
+ component: ApprovalBatchMessage,
86
+ tags: ["autodocs"],
87
+ args: { assistantType: "fae" },
88
+ parameters: {
89
+ docs: {
90
+ description: {
91
+ component:
92
+ "CLIENT (Fae) Command Block — no tool icon, no divider, full-text status pill. Same component/props as Admin, gated by assistantType='fae' (Figma 1092-2807).",
93
+ },
94
+ },
95
+ },
96
+ decorators: [constrainedDecorator()],
97
+ } satisfies Meta<typeof ApprovalBatchMessage>;
98
+
99
+ export default meta;
100
+ type Story = StoryObj<typeof meta>;
101
+
102
+ /** Pending — Approve / Reject buttons. Click the row to expand args. */
103
+ export const Pending: Story = {
104
+ args: {
105
+ data: makeData(),
106
+ status: "pending",
107
+ onApprove: () => {},
108
+ onReject: () => {},
109
+ },
110
+ };
111
+
112
+ /** Approved and still executing — grey loader on the row, no result yet. */
113
+ export const ApprovedExecuting: Story = {
114
+ args: {
115
+ data: executingData,
116
+ status: "approved",
117
+ resolvedByName: "Michael Johnson",
118
+ },
119
+ };
120
+
121
+ /** Approved and finished — green check + expandable Result block. */
122
+ export const ApprovedDone: Story = {
123
+ args: {
124
+ data: doneData,
125
+ status: "approved",
126
+ resolvedByName: "Michael Johnson",
127
+ },
128
+ };
129
+
130
+ /** Rejected by a user — full-text red status pill. */
131
+ export const Rejected: Story = {
132
+ args: {
133
+ data: makeData(),
134
+ status: "rejected",
135
+ resolvedByName: "Michael Johnson",
136
+ },
137
+ };
138
+
139
+ /** Cancelled — full-text grey status pill. */
140
+ export const Cancelled: Story = {
141
+ args: {
142
+ data: makeData(),
143
+ status: "cancelled",
144
+ resolvedByName: "Michael Johnson",
145
+ },
146
+ };
147
+
148
+ /**
149
+ * All states side by side — mirrors the Figma CLIENT "Command Blocks" board.
150
+ */
151
+ export const AllStates: Story = {
152
+ decorators: [plainDecorator(420)],
153
+ render: () => {
154
+ const states = [
155
+ { data: makeData(), status: "pending" as const, onApprove: () => {}, onReject: () => {} },
156
+ { data: executingData, status: "approved" as const, resolvedByName: "Michael Johnson" },
157
+ { data: doneData, status: "approved" as const, resolvedByName: "Michael Johnson" },
158
+ { data: makeData(), status: "rejected" as const, resolvedByName: "Michael Johnson" },
159
+ { data: makeData(), status: "cancelled" as const, resolvedByName: "Michael Johnson" },
160
+ ];
161
+ return (
162
+ <div style={{ display: "flex", flexDirection: "column", gap: 32 }}>
163
+ {states.map((s, i) => (
164
+ <div key={i}>
165
+ <CommandHeading />
166
+ <ApprovalBatchMessage assistantType="fae" {...s} />
167
+ </div>
168
+ ))}
169
+ </div>
170
+ );
171
+ },
172
+ };