@alpaca-editor/core 1.0.4135 → 1.0.4141

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 (149) hide show
  1. package/dist/components/index.d.ts +2 -0
  2. package/dist/components/index.js +1 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/components/ui/select.d.ts +2 -1
  5. package/dist/components/ui/select.js +2 -2
  6. package/dist/components/ui/select.js.map +1 -1
  7. package/dist/components/ui/tabs.d.ts +17 -0
  8. package/dist/components/ui/tabs.js +27 -0
  9. package/dist/components/ui/tabs.js.map +1 -0
  10. package/dist/config/config.js +7 -1
  11. package/dist/config/config.js.map +1 -1
  12. package/dist/editor/FieldListField.js +3 -4
  13. package/dist/editor/FieldListField.js.map +1 -1
  14. package/dist/editor/ImageEditButton.d.ts +2 -1
  15. package/dist/editor/ImageEditButton.js +4 -4
  16. package/dist/editor/ImageEditButton.js.map +1 -1
  17. package/dist/editor/PictureEditor.js +42 -1
  18. package/dist/editor/PictureEditor.js.map +1 -1
  19. package/dist/editor/Terminal.js +1 -1
  20. package/dist/editor/Terminal.js.map +1 -1
  21. package/dist/editor/Titlebar.js +0 -1
  22. package/dist/editor/Titlebar.js.map +1 -1
  23. package/dist/editor/ai/AgentCostDisplay.d.ts +3 -1
  24. package/dist/editor/ai/AgentCostDisplay.js +26 -2
  25. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  26. package/dist/editor/ai/AgentStatusBadge.d.ts +26 -0
  27. package/dist/editor/ai/AgentStatusBadge.js +110 -0
  28. package/dist/editor/ai/AgentStatusBadge.js.map +1 -0
  29. package/dist/editor/ai/AgentTerminal.js +289 -198
  30. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  31. package/dist/editor/ai/Agents.d.ts +2 -2
  32. package/dist/editor/ai/Agents.js +115 -19
  33. package/dist/editor/ai/Agents.js.map +1 -1
  34. package/dist/editor/ai/AiResponseMessage.js +259 -45
  35. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  36. package/dist/editor/ai/ContextInfoBar.js +124 -113
  37. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  38. package/dist/editor/ai/ToolCallDisplay.d.ts +1 -0
  39. package/dist/editor/ai/ToolCallDisplay.js +70 -58
  40. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  41. package/dist/editor/ai/useAgentStatus.d.ts +13 -0
  42. package/dist/editor/ai/useAgentStatus.js +101 -0
  43. package/dist/editor/ai/useAgentStatus.js.map +1 -0
  44. package/dist/editor/client/EditorShell.js +23 -8
  45. package/dist/editor/client/EditorShell.js.map +1 -1
  46. package/dist/editor/client/itemsRepository.js.map +1 -1
  47. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js +5 -5
  48. package/dist/editor/commands/localizeItem/LocalizeItemDialog.js.map +1 -1
  49. package/dist/editor/control-center/About.js +1 -1
  50. package/dist/editor/control-center/About.js.map +1 -1
  51. package/dist/editor/control-center/AllAgentsPanel.d.ts +5 -0
  52. package/dist/editor/control-center/AllAgentsPanel.js +126 -0
  53. package/dist/editor/control-center/AllAgentsPanel.js.map +1 -0
  54. package/dist/editor/control-center/WebSocketMessages.js +1 -0
  55. package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
  56. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +42 -7
  57. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -1
  58. package/dist/editor/media-selector/AiImageSearch.d.ts +1 -1
  59. package/dist/editor/media-selector/AiImageSearch.js +162 -103
  60. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  61. package/dist/editor/media-selector/TreeSelector.js +20 -4
  62. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  63. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -2
  64. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  65. package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
  66. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +7 -5
  67. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  68. package/dist/editor/page-viewer/DeviceToolbar.js +2 -2
  69. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewerFrame.js +18 -11
  71. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  72. package/dist/editor/services/agentService.d.ts +53 -48
  73. package/dist/editor/services/agentService.js +137 -79
  74. package/dist/editor/services/agentService.js.map +1 -1
  75. package/dist/editor/services/aiService.d.ts +1 -1
  76. package/dist/editor/services/editService.js +1 -0
  77. package/dist/editor/services/editService.js.map +1 -1
  78. package/dist/editor/sidebar/GraphQL.js +20 -7
  79. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  80. package/dist/editor/sidebar/SEOInfo.js +1 -2
  81. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  82. package/dist/editor/sidebar/Translations.js +10 -7
  83. package/dist/editor/sidebar/Translations.js.map +1 -1
  84. package/dist/editor/ui/ItemNameDialogNew.js +1 -1
  85. package/dist/editor/ui/ItemSearch.js +10 -4
  86. package/dist/editor/ui/ItemSearch.js.map +1 -1
  87. package/dist/index.d.ts +5 -1
  88. package/dist/index.js +4 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/page-wizard/steps/CollectStep.js +2 -2
  91. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  92. package/dist/page-wizard/steps/FieldEditor.js +2 -2
  93. package/dist/page-wizard/steps/FieldEditor.js.map +1 -1
  94. package/dist/revision.d.ts +2 -2
  95. package/dist/revision.js +2 -2
  96. package/dist/splash-screen/NewPage.js +2 -2
  97. package/dist/splash-screen/NewPage.js.map +1 -1
  98. package/dist/splash-screen/RecentPages.js +1 -1
  99. package/dist/splash-screen/RecentPages.js.map +1 -1
  100. package/dist/styles.css +167 -15
  101. package/dist/tour/Tour.js +15 -11
  102. package/dist/tour/Tour.js.map +1 -1
  103. package/package.json +1 -1
  104. package/src/components/index.ts +2 -0
  105. package/src/components/ui/select.tsx +3 -0
  106. package/src/components/ui/tabs.tsx +87 -0
  107. package/src/config/config.tsx +7 -1
  108. package/src/editor/FieldListField.tsx +13 -13
  109. package/src/editor/ImageEditButton.tsx +5 -3
  110. package/src/editor/PictureEditor.tsx +48 -1
  111. package/src/editor/Terminal.tsx +1 -1
  112. package/src/editor/Titlebar.tsx +0 -1
  113. package/src/editor/ai/AgentCostDisplay.tsx +57 -1
  114. package/src/editor/ai/AgentStatusBadge.tsx +144 -0
  115. package/src/editor/ai/AgentTerminal.tsx +345 -219
  116. package/src/editor/ai/Agents.tsx +179 -30
  117. package/src/editor/ai/AiResponseMessage.tsx +411 -114
  118. package/src/editor/ai/ContextInfoBar.tsx +134 -131
  119. package/src/editor/ai/ToolCallDisplay.tsx +217 -176
  120. package/src/editor/ai/useAgentStatus.ts +123 -0
  121. package/src/editor/client/EditorShell.tsx +34 -8
  122. package/src/editor/client/itemsRepository.ts +1 -2
  123. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +5 -5
  124. package/src/editor/control-center/About.tsx +0 -14
  125. package/src/editor/control-center/AllAgentsPanel.tsx +300 -0
  126. package/src/editor/control-center/WebSocketMessages.tsx +1 -0
  127. package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +49 -8
  128. package/src/editor/media-selector/AiImageSearch.tsx +162 -172
  129. package/src/editor/media-selector/TreeSelector.tsx +137 -116
  130. package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +9 -1
  131. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +7 -4
  132. package/src/editor/page-viewer/DeviceToolbar.tsx +15 -11
  133. package/src/editor/page-viewer/PageViewerFrame.tsx +20 -14
  134. package/src/editor/services/agentService.ts +217 -129
  135. package/src/editor/services/aiService.ts +2 -2
  136. package/src/editor/services/editService.ts +1 -0
  137. package/src/editor/sidebar/GraphQL.tsx +143 -117
  138. package/src/editor/sidebar/SEOInfo.tsx +1 -2
  139. package/src/editor/sidebar/Translations.tsx +14 -12
  140. package/src/editor/ui/ItemNameDialogNew.tsx +1 -1
  141. package/src/editor/ui/ItemSearch.tsx +11 -4
  142. package/src/editor/ui/SimpleTabs.tsx +1 -1
  143. package/src/index.ts +6 -0
  144. package/src/page-wizard/steps/CollectStep.tsx +2 -2
  145. package/src/page-wizard/steps/FieldEditor.tsx +13 -15
  146. package/src/revision.ts +2 -2
  147. package/src/splash-screen/NewPage.tsx +2 -2
  148. package/src/splash-screen/RecentPages.tsx +1 -1
  149. package/src/tour/Tour.tsx +61 -48
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils";
5
+
6
+ export interface TabViewProps {
7
+ activeIndex?: number;
8
+ onTabChange?: (e: { index: number }) => void;
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ export interface TabPanelProps {
15
+ header: string;
16
+ children: React.ReactNode;
17
+ className?: string;
18
+ }
19
+
20
+ export function TabView({
21
+ activeIndex = 0,
22
+ onTabChange,
23
+ className,
24
+ style,
25
+ children,
26
+ }: TabViewProps) {
27
+ const [internalActiveIndex, setInternalActiveIndex] = React.useState(activeIndex);
28
+
29
+ const currentActiveIndex = activeIndex !== undefined ? activeIndex : internalActiveIndex;
30
+
31
+ const handleTabClick = (index: number) => {
32
+ if (onTabChange) {
33
+ onTabChange({ index });
34
+ } else {
35
+ setInternalActiveIndex(index);
36
+ }
37
+ };
38
+
39
+ // Extract tab headers and content from children
40
+ const tabPanels = React.Children.toArray(children).filter(
41
+ (child): child is React.ReactElement<TabPanelProps> =>
42
+ React.isValidElement(child) && child.type === TabPanel
43
+ );
44
+
45
+ return (
46
+ <div className={cn("flex flex-col h-full", className)} style={style}>
47
+ {/* Tab Headers */}
48
+ <div className="flex border-b border-gray-200 px-4">
49
+ {tabPanels.map((panel, index) => (
50
+ <button
51
+ key={index}
52
+ onClick={() => handleTabClick(index)}
53
+ className={cn(
54
+ "px-6 py-3 text-sm font-medium border-b-2 transition-colors",
55
+ currentActiveIndex === index
56
+ ? "border-blue-500 text-blue-600 bg-blue-50"
57
+ : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
58
+ )}
59
+ >
60
+ {panel.props.header}
61
+ </button>
62
+ ))}
63
+ </div>
64
+
65
+ {/* Tab Content */}
66
+ <div className="flex-1 overflow-auto p-4">
67
+ {tabPanels.map((panel, index) => (
68
+ <div
69
+ key={index}
70
+ className={cn(
71
+ "h-full",
72
+ currentActiveIndex === index ? "block" : "hidden"
73
+ )}
74
+ >
75
+ {panel.props.children}
76
+ </div>
77
+ ))}
78
+ </div>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ export function TabPanel({ header, children, className }: TabPanelProps) {
84
+ // This component is used for structure but doesn't render directly
85
+ // The TabView component extracts the props and renders the content
86
+ return null;
87
+ }
@@ -80,6 +80,7 @@ import { QuotaInfo } from "../editor/control-center/QuotaInfo";
80
80
  import { About } from "../editor/control-center/About";
81
81
  import { WebSocketMessages } from "../editor/control-center/WebSocketMessages";
82
82
  import { LatestFeedback } from "../editor/control-center/LatestFeedback";
83
+ import { AllAgentsPanel } from "../editor/control-center/AllAgentsPanel";
83
84
  import { DbSetupStep } from "../editor/control-center/setup-steps/DbSetupStep";
84
85
  import { SettingsSetupStep } from "../editor/control-center/setup-steps/SettingsSetupStep";
85
86
  import { AiSetupStep } from "../editor/control-center/setup-steps/AiSetupStep";
@@ -287,7 +288,6 @@ export const getConfiguration = (): EditorConfiguration => {
287
288
  },
288
289
  image: {
289
290
  editor: ImageFieldEditor,
290
- buttons: [],
291
291
  },
292
292
  droplink: {
293
293
  editor: DropLinkEditor,
@@ -445,6 +445,12 @@ export const getConfiguration = (): EditorConfiguration => {
445
445
  icon: <i className="pi pi-comments" />,
446
446
  content: <WebSocketMessages />,
447
447
  },
448
+ {
449
+ id: "all-agents",
450
+ title: "All Agents",
451
+ icon: <i className="pi pi-users" />,
452
+ content: <AllAgentsPanel />,
453
+ },
448
454
  ],
449
455
  },
450
456
  {
@@ -1,5 +1,3 @@
1
- import { Tooltip } from "primereact/tooltip";
2
-
3
1
  import { useEditContext, useFieldsEditContext } from "./client/editContext";
4
2
  import { useFieldModification } from "./client/fieldModificationStore";
5
3
  import { EditorConfiguration } from "../config/types";
@@ -35,7 +33,7 @@ import {
35
33
  PopoverTrigger,
36
34
  } from "../components/ui/popover";
37
35
  import {
38
- Tooltip as InternalTooltip,
36
+ Tooltip,
39
37
  TooltipContent,
40
38
  TooltipTrigger,
41
39
  } from "../components/ui/tooltip";
@@ -244,19 +242,21 @@ export default function FieldListField({
244
242
  className={classNames}
245
243
  >
246
244
  {validationBarClassNames && (
247
- <>
248
- <div
249
- className={validationBarClassNames}
250
- id={"validation_" + field.id}
251
- />
252
- <Tooltip target={"#validation_" + field.id}>
245
+ <Tooltip>
246
+ <TooltipTrigger asChild>
247
+ <div
248
+ className={validationBarClassNames}
249
+ id={"validation_" + field.id}
250
+ />
251
+ </TooltipTrigger>
252
+ <TooltipContent>
253
253
  {validators
254
254
  .filter((x) => x.result > 1)
255
255
  .map((x) => (
256
256
  <div key={x.validator}>{x.message}</div>
257
257
  ))}
258
- </Tooltip>
259
- </>
258
+ </TooltipContent>
259
+ </Tooltip>
260
260
  )}
261
261
 
262
262
  <div className="flex-1">
@@ -443,7 +443,7 @@ export default function FieldListField({
443
443
  />
444
444
  )}
445
445
  {!readonly && renderGeneratorButtons}
446
- <InternalTooltip>
446
+ <Tooltip>
447
447
  <Popover
448
448
  open={fieldHistoryOpen}
449
449
  onOpenChange={(open) => {
@@ -476,7 +476,7 @@ export default function FieldListField({
476
476
  </PopoverContent>
477
477
  </Popover>
478
478
  <TooltipContent>Field History</TooltipContent>
479
- </InternalTooltip>
479
+ </Tooltip>
480
480
  </div>
481
481
  </>
482
482
  )}
@@ -31,6 +31,7 @@ interface ImageEditButtonProps {
31
31
  testId?: string;
32
32
  /** Additional CSS classes for the trigger button */
33
33
  buttonClassName?: string;
34
+ defaultActionId?: string;
34
35
  }
35
36
 
36
37
  export function ImageEditButton({
@@ -41,10 +42,11 @@ export function ImageEditButton({
41
42
  buttonIcon = "pi pi-ellipsis-v",
42
43
  testId = "menu-toggle-button",
43
44
  buttonClassName,
45
+ defaultActionId = "select",
44
46
  }: ImageEditButtonProps) {
45
47
  const [showMenu, setShowMenu] = useState(false);
46
48
 
47
- const selectAction = actions.find((a) => a.id === "select");
49
+ const defaultAction = actions.find((a) => a.id === defaultActionId);
48
50
 
49
51
  const defaultTriggerButton = (
50
52
  <div
@@ -54,13 +56,13 @@ export function ImageEditButton({
54
56
  e.stopPropagation();
55
57
  }}
56
58
  >
57
- {selectAction && (
59
+ {defaultAction && (
58
60
  <button
59
61
  type="button"
60
62
  className="hover:bg-gray-5 cursor-pointer px-2 py-1.5"
61
63
  onClick={(e) => {
62
64
  e.stopPropagation();
63
- selectAction.onClick?.(e);
65
+ defaultAction.onClick?.(e);
64
66
  setShowMenu(false);
65
67
  }}
66
68
  data-testid={`${testId}-quick-select`}
@@ -10,6 +10,7 @@ import {
10
10
  FieldActionsOverlayRef,
11
11
  } from "./FieldActionsOverlay";
12
12
  import { ImageEditButton, MenuAction } from "./ImageEditButton";
13
+ import { DamSelector, DamImageValue, DamSelectorProps, SharedienDamExtension } from "@alpaca-editor/sharedien-dam";
13
14
 
14
15
  export function PictureEditor({
15
16
  field,
@@ -147,10 +148,52 @@ export function PictureEditor({
147
148
  if (selectedVideoId) videoSelected(selectedVideoId);
148
149
  }
149
150
 
151
+ async function selectFromDam() {
152
+ const data = await editContext?.openDialog<DamImageValue, DamSelectorProps>(DamSelector, {
153
+ language: field.descriptor.item.language
154
+ });
155
+
156
+ if (data?.mediaId || data?.videoId) {
157
+ // Create a new variant with the selected DAM asset
158
+ const selected = raw.Variants?.find((x) => x.Name == variantName);
159
+ if (!selected) {
160
+ if (!raw.Variants) raw.Variants = [];
161
+ raw.Variants.push({
162
+ Name: variantName,
163
+ MediaId: data.mediaId,
164
+ VideoId: data.videoId,
165
+ });
166
+ } else {
167
+ selected.MediaId = data.mediaId;
168
+ selected.VideoId = data.videoId;
169
+ }
170
+
171
+ editContext!.operations.editField({
172
+ field: field.descriptor,
173
+ rawValue: JSON.stringify(raw),
174
+ refresh: "immediate",
175
+ });
176
+ }
177
+ }
178
+
150
179
  const notEmpty = variant?.mediaId;
151
180
 
181
+ // Check if DAM selector should be enabled from configuration
182
+ const sharedienDamConfig = editContext?.configuration?.extensions?.sharedienDam as SharedienDamExtension;
183
+ const enableDamSelector = sharedienDamConfig?.enableDamSelector ?? false;
184
+
152
185
  // Build actions for the dropdown menu
153
186
  const menuActions: MenuAction[] = [
187
+ ...(enableDamSelector
188
+ ? [
189
+ {
190
+ id: "select-dam",
191
+ label: "Select from DAM",
192
+ icon: "pi pi-external-link",
193
+ onClick: () => selectFromDam(),
194
+ },
195
+ ]
196
+ : []),
154
197
  {
155
198
  id: "select",
156
199
  label: "Select",
@@ -235,7 +278,11 @@ export function PictureEditor({
235
278
  style={style}
236
279
  data-testid="select-media"
237
280
  >
238
- <ImageEditButton actions={menuActions} compact={true} />
281
+ <ImageEditButton
282
+ actions={menuActions}
283
+ compact={true}
284
+ defaultActionId={enableDamSelector ? "select-dam" : "select"}
285
+ />
239
286
 
240
287
  {showCropper && (
241
288
  <PictureCropper
@@ -97,7 +97,7 @@ export const Terminal = forwardRef<
97
97
  setMessages([...messageRef.current, { type: "response", text }]);
98
98
  }
99
99
  };
100
- // @ts-ignore primereact TerminalService event bus
100
+ // @ts-ignore
101
101
  TerminalService.on("response", responseHandler);
102
102
  return () => {
103
103
  // @ts-ignore
@@ -36,7 +36,6 @@ export function Titlebar() {
36
36
  const isInsideMenu = menuElement && menuElement.contains(target);
37
37
  const isInsideButton = buttonElement && buttonElement.contains(target);
38
38
 
39
- // Check if the click is inside a PrimeReact overlay (which renders in portals)
40
39
  const isInsideOverlay = target.closest(".p-overlaypanel") !== null;
41
40
 
42
41
  // Close menu only if click is outside menu, button, and overlays
@@ -33,12 +33,15 @@ interface AgentCostDisplayProps {
33
33
  outputCost: number;
34
34
  cachedCost: number;
35
35
  };
36
+ /** Cost limit for the agent (in USD) */
37
+ costLimit?: number | null;
36
38
  }
37
39
 
38
40
  export function AgentCostDisplay({
39
41
  className = "",
40
42
  response,
41
43
  totalTokens,
44
+ costLimit,
42
45
  }: AgentCostDisplayProps) {
43
46
  const [showCostPopover, setShowCostPopover] = useState(false);
44
47
  const costPopoverRef = useRef<HTMLDivElement>(null);
@@ -118,16 +121,31 @@ export function AgentCostDisplay({
118
121
  return null;
119
122
  }
120
123
 
124
+ // Calculate percentage of limit used
125
+ const percentUsed = costLimit && costLimit > 0
126
+ ? Math.min((displayTotalCost / costLimit) * 100, 100)
127
+ : null;
128
+
129
+ // Determine color based on usage
130
+ const getColorClass = () => {
131
+ if (!percentUsed) return "text-gray-400";
132
+ if (percentUsed >= 100) return "text-red-500";
133
+ if (percentUsed >= 80) return "text-orange-500";
134
+ if (percentUsed >= 60) return "text-yellow-600";
135
+ return "text-gray-400";
136
+ };
137
+
121
138
  return (
122
139
  <div className={`relative ${className}`} ref={costPopoverRef}>
123
140
  <Popover open={showCostPopover} onOpenChange={setShowCostPopover}>
124
141
  <PopoverTrigger asChild>
125
142
  <div
126
- className="cursor-pointer text-right text-gray-400 hover:text-gray-600"
143
+ className={`cursor-pointer text-right hover:text-gray-600 ${getColorClass()}`}
127
144
  style={{ fontSize: "10px" }}
128
145
  onClick={() => setShowCostPopover(!showCostPopover)}
129
146
  >
130
147
  Total Cost: {displayTotalCost.toFixed(2)} {currency}
148
+ {costLimit && costLimit > 0 && ` / ${costLimit.toFixed(2)} ${currency}`}
131
149
  </div>
132
150
  </PopoverTrigger>
133
151
  <PopoverContent className="w-80 p-4" align="end" side="bottom">
@@ -137,10 +155,48 @@ export function AgentCostDisplay({
137
155
  <div>Cost Breakdown</div>
138
156
  <span className="text-sm font-semibold">
139
157
  {displayTotalCost.toFixed(2)} {currency}
158
+ {costLimit && costLimit > 0 && (
159
+ <span className="ml-1 text-xs text-gray-500">
160
+ / {costLimit.toFixed(2)} {currency}
161
+ </span>
162
+ )}
140
163
  </span>
141
164
  </h4>
142
165
  </div>
143
166
 
167
+ {/* Cost limit warning */}
168
+ {costLimit && costLimit > 0 && (
169
+ <div className="rounded-md bg-gray-50 p-3">
170
+ <div className="mb-2 flex items-center justify-between text-xs">
171
+ <span className="font-medium text-gray-700">Cost Limit Usage</span>
172
+ <span className={`font-semibold ${getColorClass()}`}>
173
+ {percentUsed?.toFixed(1)}%
174
+ </span>
175
+ </div>
176
+ <div className="h-2 w-full overflow-hidden rounded-full bg-gray-200">
177
+ <div
178
+ className={`h-full transition-all ${
179
+ percentUsed && percentUsed >= 100
180
+ ? "bg-red-500"
181
+ : percentUsed && percentUsed >= 80
182
+ ? "bg-orange-500"
183
+ : percentUsed && percentUsed >= 60
184
+ ? "bg-yellow-500"
185
+ : "bg-blue-500"
186
+ }`}
187
+ style={{ width: `${Math.min(percentUsed || 0, 100)}%` }}
188
+ />
189
+ </div>
190
+ {percentUsed && percentUsed >= 90 && (
191
+ <p className="mt-2 text-xs text-gray-600">
192
+ {percentUsed >= 100
193
+ ? "Cost limit reached. Execution will stop on next API call."
194
+ : "Approaching cost limit. Consider extending if needed."}
195
+ </p>
196
+ )}
197
+ </div>
198
+ )}
199
+
144
200
  <div className="space-y-3">
145
201
  {/* Session Cost Breakdown */}
146
202
  {(hasTotalData || hasTokenData) && (
@@ -0,0 +1,144 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+ import { AgentStatusSummary } from "./useAgentStatus";
4
+ import { Agent } from "../services/agentService";
5
+
6
+ interface AgentStatusBadgeProps {
7
+ status: AgentStatusSummary;
8
+ className?: string;
9
+ }
10
+
11
+ interface BadgeConfig {
12
+ baseClasses: string;
13
+ bgColor: string;
14
+ title: string;
15
+ content?: React.ReactNode;
16
+ }
17
+
18
+ export interface AgentStatusConfig {
19
+ color: string;
20
+ label: string;
21
+ shouldPulse: boolean;
22
+ }
23
+
24
+ /**
25
+ * Helper function to get status configuration for an individual agent
26
+ * Handles both numeric and string status values
27
+ * Enum: New=0, Running=1, WaitingForApproval=2, Completed=3, Error=4, Closed=5, Idle=6
28
+ */
29
+ export function getAgentStatusConfig(agent: Agent): AgentStatusConfig {
30
+ const status: string | number = agent.status;
31
+
32
+ // Handle both string (camelCase from backend) and numeric (enum value) status
33
+ if (status === "new" || status === 0) {
34
+ return {
35
+ color: "bg-gray-400",
36
+ label: "Status: New",
37
+ shouldPulse: false,
38
+ };
39
+ } else if (status === "running" || status === 1) {
40
+ return {
41
+ color: "bg-blue-500",
42
+ label: "Status: Running",
43
+ shouldPulse: true,
44
+ };
45
+ } else if (status === "waitingForApproval" || status === 2) {
46
+ return {
47
+ color: "bg-amber-500",
48
+ label: "Status: Waiting for Approval",
49
+ shouldPulse: false,
50
+ };
51
+ } else if (status === "completed" || status === 3) {
52
+ return {
53
+ color: "bg-green-500",
54
+ label: "Status: Completed",
55
+ shouldPulse: false,
56
+ };
57
+ } else if (status === "error" || status === 4) {
58
+ return {
59
+ color: "bg-red-500",
60
+ label: "Status: Error",
61
+ shouldPulse: false,
62
+ };
63
+ } else if (status === "closed" || status === 5) {
64
+ return {
65
+ color: "bg-gray-400",
66
+ label: "Status: Closed",
67
+ shouldPulse: false,
68
+ };
69
+ } else if (status === "idle" || status === 6) {
70
+ return {
71
+ color: "bg-gray-300",
72
+ label: "Status: Idle",
73
+ shouldPulse: false,
74
+ };
75
+ }
76
+
77
+ // Default fallback - log for debugging
78
+ console.warn("Unknown agent status:", status, typeof status);
79
+ return {
80
+ color: "bg-gray-400",
81
+ label: `Status: Unknown (${status})`,
82
+ shouldPulse: false,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Badge that overlays on the agent icon to show status
88
+ * Always displays the total number of active agents with color-coded status:
89
+ * - Amber: Agents waiting for approval (highest priority)
90
+ * - Blue (pulsing): Agents actively running
91
+ * - Green: Other active agents
92
+ */
93
+ export function AgentStatusBadge({ status, className }: AgentStatusBadgeProps) {
94
+ // No activity - don't show anything
95
+ if (!status.hasActivity) {
96
+ return null;
97
+ }
98
+
99
+ const getBadgeConfig = (): BadgeConfig => {
100
+ // Waiting for approval takes priority (most urgent)
101
+ if (status.waitingForApproval > 0) {
102
+ return {
103
+ baseClasses: "-right-2 -top-2",
104
+ bgColor: "bg-amber-500",
105
+ title: `${status.waitingForApproval} agent${status.waitingForApproval > 1 ? "s" : ""} waiting for approval (${status.total} total active)`,
106
+ content: <span className="text-[8px] font-bold">{status.total}</span>,
107
+ };
108
+ }
109
+
110
+ // Running agents - show count with pulsing indicator
111
+ if (status.running > 0) {
112
+ return {
113
+ baseClasses: "-right-2 -top-2",
114
+ bgColor: "bg-blue-500 animate-pulse",
115
+ title: `${status.running} agent${status.running > 1 ? "s" : ""} running (${status.total} total active)`,
116
+ content: <span className="text-[8px] font-bold">{status.total}</span>,
117
+ };
118
+ }
119
+
120
+ // Other active agents - show count
121
+ return {
122
+ baseClasses: "-right-2 -top-2",
123
+ bgColor: "bg-green-500",
124
+ title: `${status.total} active agent${status.total > 1 ? "s" : ""}`,
125
+ content: <span className="text-[8px] font-bold">{status.total}</span>,
126
+ };
127
+ };
128
+
129
+ const config = getBadgeConfig();
130
+
131
+ return (
132
+ <div
133
+ className={cn(
134
+ "absolute flex h-3 w-3 items-center justify-center rounded-full text-white shadow-md ring-1 ring-white",
135
+ config.baseClasses,
136
+ config.bgColor,
137
+ className,
138
+ )}
139
+ title={config.title}
140
+ >
141
+ {config.content}
142
+ </div>
143
+ );
144
+ }