ai-design-system 0.1.72 → 0.1.74
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/components/ai-elements/file-tree.tsx +12 -32
- package/components/ai-elements/node.tsx +41 -9
- package/components/ai-elements/prompt-input.tsx +8 -4
- package/components/ai-elements/tool.tsx +17 -3
- package/components/blocks/AIConversation/AIConversation.tsx +36 -3
- package/components/blocks/BmcCanvas/bmc-layout.ts +115 -0
- package/components/blocks/BmcCanvas/bmc-tooltips.ts +63 -0
- package/components/blocks/BmcCanvas/index.ts +3 -0
- package/components/blocks/BmcCanvas/interfaces.ts +58 -0
- package/components/blocks/WorkflowCanvas/WorkflowCanvas.tsx +3 -1
- package/components/blocks/WorkflowCanvas/interfaces.ts +6 -1
- package/components/blocks/index.ts +3 -0
- package/components/composites/AgentIndicator/AgentIndicator.tsx +1 -1
- package/components/composites/ApprovalCard/ApprovalCard.tsx +8 -3
- package/components/composites/DefaultSwitcher/DefaultSwitcher.tsx +5 -1
- package/components/composites/PromptInput/PromptInput.tsx +16 -5
- package/components/composites/SpatialContainerNode/SpatialContainerNode.stories.tsx +169 -0
- package/components/composites/SpatialContainerNode/SpatialContainerNode.tsx +379 -0
- package/components/composites/SpatialContainerNode/index.ts +6 -0
- package/components/composites/SpecialistMessage/SpecialistMessage.tsx +1 -1
- package/components/composites/StateNode/StateNode.tsx +3 -1
- package/components/composites/StreamingMarkdown.tsx +42 -3
- package/components/composites/SystemMessage/SystemMessage.stories.tsx +46 -0
- package/components/composites/SystemMessage/SystemMessage.tsx +54 -0
- package/components/composites/SystemMessage/index.ts +2 -0
- package/components/composites/ToolCallDisplay/ToolCallDisplay.tsx +1 -1
- package/components/composites/TransitionNode/TransitionNode.tsx +3 -1
- package/components/composites/TriggerNode/TriggerNode.tsx +3 -1
- package/components/composites/WorkflowToolbar/WorkflowToolbar.tsx +8 -9
- package/components/composites/WorkflowToolbar/interfaces.ts +1 -0
- package/components/composites/index.ts +8 -0
- package/components/features/{RefinementPanel/RefinementPanel.behaviors.stories.tsx → ChatPanel/ChatPanel.behaviors.stories.tsx} +6 -6
- package/components/features/{RefinementPanel/RefinementPanel.mocks.ts → ChatPanel/ChatPanel.mocks.ts} +1 -1
- package/components/features/ChatPanel/ChatPanel.stories.tsx +214 -0
- package/components/features/{RefinementPanel/RefinementPanel.tsx → ChatPanel/ChatPanel.tsx} +71 -46
- package/components/features/ChatPanel/index.ts +28 -0
- package/components/features/ChatPanel/useChatPanel.d.ts +68 -0
- package/components/features/{RefinementPanel/useRefinementPanel.mock.ts → ChatPanel/useChatPanel.mock.ts} +6 -6
- package/components/features/NodeEditor/NodeEditor.mocks.ts +196 -0
- package/components/features/NodeEditor/NodeEditor.stories.tsx +22 -1
- package/components/features/NodeEditor/NodeEditor.tsx +21 -11
- package/components/features/NodeEditor/useNodeEditor.d.ts +1 -0
- package/components/features/NodeEditor/useNodeEditor.mock.ts +5 -0
- package/components/features/PageLayout/PageLayout.mocks.ts +1 -1
- package/components/features/TextEditor/TextEditor.tsx +44 -12
- package/components/features/index.ts +3 -3
- package/components/index.ts +1 -1
- package/components/ui/button.tsx +2 -0
- package/dist/index.cjs +830 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +961 -0
- package/dist/index.d.ts +323 -179
- package/dist/index.js +830 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/utils/markdown-formatter.ts +78 -0
- package/components/features/RefinementPanel/RefinementPanel.stories.tsx +0 -287
- package/components/features/RefinementPanel/index.ts +0 -28
- package/components/features/RefinementPanel/useRefinementPanel.d.ts +0 -106
- /package/components/features/{RefinementPanel → ChatPanel}/README.md +0 -0
|
@@ -142,19 +142,13 @@ export const FileTreeFolder = ({
|
|
|
142
142
|
children,
|
|
143
143
|
...props
|
|
144
144
|
}: FileTreeFolderProps) => {
|
|
145
|
-
const { expandedPaths, togglePath
|
|
146
|
-
useContext(FileTreeContext);
|
|
145
|
+
const { expandedPaths, togglePath } = useContext(FileTreeContext);
|
|
147
146
|
const isExpanded = expandedPaths.has(path);
|
|
148
|
-
const isSelected = selectedPath === path;
|
|
149
147
|
|
|
150
148
|
const handleOpenChange = useCallback(() => {
|
|
151
149
|
togglePath(path);
|
|
152
150
|
}, [togglePath, path]);
|
|
153
151
|
|
|
154
|
-
const handleSelect = useCallback(() => {
|
|
155
|
-
onSelect?.(path);
|
|
156
|
-
}, [onSelect, path]);
|
|
157
|
-
|
|
158
152
|
const folderContextValue = useMemo(
|
|
159
153
|
() => ({ isExpanded, name, path }),
|
|
160
154
|
[isExpanded, name, path]
|
|
@@ -169,30 +163,16 @@ export const FileTreeFolder = ({
|
|
|
169
163
|
tabIndex={0}
|
|
170
164
|
{...props}
|
|
171
165
|
>
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
"flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50"
|
|
175
|
-
isSelected && "bg-muted"
|
|
176
|
-
)}
|
|
177
|
-
>
|
|
178
|
-
<CollapsibleTrigger asChild>
|
|
179
|
-
<button
|
|
180
|
-
className="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0"
|
|
181
|
-
type="button"
|
|
182
|
-
>
|
|
183
|
-
<ChevronRightIcon
|
|
184
|
-
className={cn(
|
|
185
|
-
"size-4 shrink-0 text-muted-foreground transition-transform",
|
|
186
|
-
isExpanded && "rotate-90"
|
|
187
|
-
)}
|
|
188
|
-
/>
|
|
189
|
-
</button>
|
|
190
|
-
</CollapsibleTrigger>
|
|
191
|
-
<button
|
|
192
|
-
className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left"
|
|
193
|
-
onClick={handleSelect}
|
|
194
|
-
type="button"
|
|
166
|
+
<CollapsibleTrigger asChild>
|
|
167
|
+
<div
|
|
168
|
+
className="flex w-full cursor-pointer items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50 select-none"
|
|
195
169
|
>
|
|
170
|
+
<ChevronRightIcon
|
|
171
|
+
className={cn(
|
|
172
|
+
"size-4 shrink-0 text-muted-foreground transition-transform",
|
|
173
|
+
isExpanded && "rotate-90"
|
|
174
|
+
)}
|
|
175
|
+
/>
|
|
196
176
|
<FileTreeIcon>
|
|
197
177
|
{isExpanded ? (
|
|
198
178
|
<FolderOpenIcon className="size-4 text-blue-500" />
|
|
@@ -201,8 +181,8 @@ export const FileTreeFolder = ({
|
|
|
201
181
|
)}
|
|
202
182
|
</FileTreeIcon>
|
|
203
183
|
<FileTreeName>{name}</FileTreeName>
|
|
204
|
-
</
|
|
205
|
-
</
|
|
184
|
+
</div>
|
|
185
|
+
</CollapsibleTrigger>
|
|
206
186
|
<CollapsibleContent>
|
|
207
187
|
<div className="ml-4 border-l pl-2">{children}</div>
|
|
208
188
|
</CollapsibleContent>
|
|
@@ -23,7 +23,7 @@ export type NodeProps = ComponentProps<typeof Card> & {
|
|
|
23
23
|
export const Node = ({ handles, className, status, ...props }: NodeProps) => (
|
|
24
24
|
<Card
|
|
25
25
|
className={cn(
|
|
26
|
-
"node-container relative h-[52px] w-[180px] gap-0 overflow-hidden rounded-md bg-card p-0 transition-all duration-200",
|
|
26
|
+
"node-container group relative h-[52px] w-[180px] gap-0 overflow-hidden rounded-md bg-card p-0 transition-all duration-200",
|
|
27
27
|
status === "success" && "border-green-500 border-2",
|
|
28
28
|
status === "error" && "border-red-500 border-2",
|
|
29
29
|
className
|
|
@@ -33,18 +33,18 @@ export const Node = ({ handles, className, status, ...props }: NodeProps) => (
|
|
|
33
33
|
{status === "running" && <AnimatedBorder />}
|
|
34
34
|
{handles.target && (
|
|
35
35
|
<>
|
|
36
|
-
<Handle id="target-top" position={Position.Top} type="target" />
|
|
37
|
-
<Handle id="target-right" position={Position.Right} type="target" />
|
|
38
|
-
<Handle id="target-bottom" position={Position.Bottom} type="target" />
|
|
39
|
-
<Handle id="target-left" position={Position.Left} type="target" />
|
|
36
|
+
<Handle id="target-top" position={Position.Top} type="target" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
37
|
+
<Handle id="target-right" position={Position.Right} type="target" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
38
|
+
<Handle id="target-bottom" position={Position.Bottom} type="target" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
39
|
+
<Handle id="target-left" position={Position.Left} type="target" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
40
40
|
</>
|
|
41
41
|
)}
|
|
42
42
|
{handles.source && (
|
|
43
43
|
<>
|
|
44
|
-
<Handle id="source-top" position={Position.Top} type="source" />
|
|
45
|
-
<Handle id="source-right" position={Position.Right} type="source" />
|
|
46
|
-
<Handle id="source-bottom" position={Position.Bottom} type="source" />
|
|
47
|
-
<Handle id="source-left" position={Position.Left} type="source" />
|
|
44
|
+
<Handle id="source-top" position={Position.Top} type="source" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
45
|
+
<Handle id="source-right" position={Position.Right} type="source" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
46
|
+
<Handle id="source-bottom" position={Position.Bottom} type="source" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
47
|
+
<Handle id="source-left" position={Position.Left} type="source" className="opacity-0 group-hover:opacity-100 group-[.selected]:opacity-100 transition-opacity duration-150" />
|
|
48
48
|
</>
|
|
49
49
|
)}
|
|
50
50
|
{props.children}
|
|
@@ -88,3 +88,35 @@ export const NodeFooter = ({ className, ...props }: NodeFooterProps) => (
|
|
|
88
88
|
{...props}
|
|
89
89
|
/>
|
|
90
90
|
);
|
|
91
|
+
|
|
92
|
+
export function getHighlightIconColor(
|
|
93
|
+
status?: string,
|
|
94
|
+
highlightStatus?: string,
|
|
95
|
+
defaultColorClass: string = "text-primary"
|
|
96
|
+
): string {
|
|
97
|
+
const effectiveHighlight =
|
|
98
|
+
highlightStatus ??
|
|
99
|
+
(status === "success" || status === "completed" || status === "done"
|
|
100
|
+
? "done"
|
|
101
|
+
: status === "pending_hitl" || status === "pending"
|
|
102
|
+
? "pending"
|
|
103
|
+
: status === "running" || status === "active"
|
|
104
|
+
? "active"
|
|
105
|
+
: status === "error" || status === "failed"
|
|
106
|
+
? "error"
|
|
107
|
+
: undefined);
|
|
108
|
+
|
|
109
|
+
switch (effectiveHighlight) {
|
|
110
|
+
case "done":
|
|
111
|
+
return "text-green-500";
|
|
112
|
+
case "pending":
|
|
113
|
+
return "text-amber-500";
|
|
114
|
+
case "active":
|
|
115
|
+
return "text-indigo-400";
|
|
116
|
+
case "error":
|
|
117
|
+
return "text-red-500";
|
|
118
|
+
default:
|
|
119
|
+
return defaultColorClass;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
@@ -963,22 +963,26 @@ export const PromptInputSubmit = ({
|
|
|
963
963
|
...props
|
|
964
964
|
}: PromptInputSubmitProps) => {
|
|
965
965
|
let Icon = <SendIcon className="size-4" />;
|
|
966
|
+
let buttonVariant = variant;
|
|
967
|
+
let customClass = className;
|
|
966
968
|
|
|
967
969
|
if (status === "submitted") {
|
|
968
970
|
Icon = <Loader2Icon className="size-4 animate-spin" />;
|
|
969
971
|
} else if (status === "streaming") {
|
|
970
|
-
Icon = <SquareIcon className="size-
|
|
972
|
+
Icon = <SquareIcon className="size-3.5 fill-white text-white" />;
|
|
973
|
+
buttonVariant = "outline-primary";
|
|
974
|
+
customClass = cn("rounded-full", className);
|
|
971
975
|
} else if (status === "error") {
|
|
972
976
|
Icon = <XIcon className="size-4" />;
|
|
973
977
|
}
|
|
974
978
|
|
|
975
979
|
return (
|
|
976
980
|
<InputGroupButton
|
|
977
|
-
aria-label="Submit"
|
|
978
|
-
className={
|
|
981
|
+
aria-label={status === "streaming" ? "Stop" : "Submit"}
|
|
982
|
+
className={customClass}
|
|
979
983
|
size={size}
|
|
980
984
|
type="submit"
|
|
981
|
-
variant={
|
|
985
|
+
variant={buttonVariant}
|
|
982
986
|
{...props}
|
|
983
987
|
>
|
|
984
988
|
{children ?? Icon}
|
|
@@ -36,6 +36,8 @@ import type { ComponentProps, ReactNode } from "react";
|
|
|
36
36
|
import { isValidElement } from "react";
|
|
37
37
|
import { CodeBlock } from "./code-block";
|
|
38
38
|
|
|
39
|
+
import { Shimmer } from "@/components/ai-elements/shimmer";
|
|
40
|
+
|
|
39
41
|
export type ToolProps = ComponentProps<typeof Collapsible>;
|
|
40
42
|
|
|
41
43
|
export const Tool = ({ className, ...props }: ToolProps) => (
|
|
@@ -65,7 +67,7 @@ const getStatusBadge = (status: ToolUIPart["state"]) => {
|
|
|
65
67
|
|
|
66
68
|
const icons: Record<ToolUIPart["state"], ReactNode> = {
|
|
67
69
|
"input-streaming": <CircleIcon className="size-4" />,
|
|
68
|
-
"input-available": <ClockIcon className="size-4 animate-
|
|
70
|
+
"input-available": <ClockIcon className="size-4 animate-spin text-primary" />,
|
|
69
71
|
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
|
|
70
72
|
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
|
|
71
73
|
"output-available": <CheckCircleIcon className="size-4 text-green-600" />,
|
|
@@ -73,10 +75,22 @@ const getStatusBadge = (status: ToolUIPart["state"]) => {
|
|
|
73
75
|
"output-denied": <XCircleIcon className="size-4 text-orange-600" />,
|
|
74
76
|
};
|
|
75
77
|
|
|
78
|
+
const isRunning = status === "input-available";
|
|
79
|
+
|
|
76
80
|
return (
|
|
77
|
-
<Badge
|
|
81
|
+
<Badge
|
|
82
|
+
className={cn(
|
|
83
|
+
"gap-1.5 rounded-full text-xs font-medium transition-all relative overflow-hidden",
|
|
84
|
+
isRunning && "border border-primary/30 bg-primary/10 text-primary shadow-xs"
|
|
85
|
+
)}
|
|
86
|
+
variant={isRunning ? "outline" : "secondary"}
|
|
87
|
+
>
|
|
78
88
|
{icons[status]}
|
|
79
|
-
{
|
|
89
|
+
{isRunning ? (
|
|
90
|
+
<Shimmer className="text-xs font-medium text-foreground inline-block">Running</Shimmer>
|
|
91
|
+
) : (
|
|
92
|
+
labels[status]
|
|
93
|
+
)}
|
|
80
94
|
</Badge>
|
|
81
95
|
);
|
|
82
96
|
};
|
|
@@ -10,6 +10,7 @@ import type { ToolCall, SubAgent } from "@/components/composites"
|
|
|
10
10
|
import { UserMessage } from "@/components/composites/UserMessage"
|
|
11
11
|
import { SpecialistMessage } from "@/components/composites/SpecialistMessage"
|
|
12
12
|
import { OrchestratorMessage } from "@/components/composites/OrchestratorMessage"
|
|
13
|
+
import { SystemMessage } from "@/components/composites/SystemMessage"
|
|
13
14
|
import { ToolCallDisplay } from "@/components/composites/ToolCallDisplay"
|
|
14
15
|
import { ReasoningDisplay } from "@/components/composites/ReasoningDisplay"
|
|
15
16
|
|
|
@@ -71,6 +72,11 @@ export interface AIConversationProps
|
|
|
71
72
|
* Callback fired when a tool action (like a link click) is triggered
|
|
72
73
|
*/
|
|
73
74
|
onToolAction?: (toolCall: ToolCall, action: string) => void
|
|
75
|
+
/**
|
|
76
|
+
* Custom renderer for system messages.
|
|
77
|
+
* Receives the raw content string and returns a ReactNode.
|
|
78
|
+
*/
|
|
79
|
+
renderSystemMessage?: (content: string) => React.ReactNode
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
/**
|
|
@@ -84,6 +90,7 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
84
90
|
selectedSubAgent,
|
|
85
91
|
emptyState,
|
|
86
92
|
onToolAction,
|
|
93
|
+
renderSystemMessage,
|
|
87
94
|
...conversationProps
|
|
88
95
|
}) => {
|
|
89
96
|
const isEmpty = React.useMemo(
|
|
@@ -117,6 +124,15 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
117
124
|
(!msg.toolCalls || msg.toolCalls.length === 0) &&
|
|
118
125
|
(!msg.subAgents || msg.subAgents.length === 0);
|
|
119
126
|
|
|
127
|
+
const currentGroupHasTools =
|
|
128
|
+
currentGroup &&
|
|
129
|
+
((currentGroup.toolCalls && currentGroup.toolCalls.length > 0) ||
|
|
130
|
+
(currentGroup.subAgents && currentGroup.subAgents.length > 0));
|
|
131
|
+
|
|
132
|
+
if (currentGroupHasTools && isFinalResponse) {
|
|
133
|
+
currentGroup = null;
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
if (currentGroup) {
|
|
121
137
|
if (msg.content) {
|
|
122
138
|
currentGroup.content = currentGroup.content
|
|
@@ -221,6 +237,23 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
221
237
|
const contentStr = toMessageString(message.content)
|
|
222
238
|
|
|
223
239
|
// Render based on role field
|
|
240
|
+
if (message.role === "system") {
|
|
241
|
+
if (!contentStr || !contentStr.trim()) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
return (
|
|
245
|
+
<SystemMessage
|
|
246
|
+
key={message.id}
|
|
247
|
+
message={{
|
|
248
|
+
id: message.id,
|
|
249
|
+
content: contentStr,
|
|
250
|
+
avatarName: message.avatarName,
|
|
251
|
+
}}
|
|
252
|
+
renderContent={renderSystemMessage}
|
|
253
|
+
/>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
224
257
|
if (message.role === "user") {
|
|
225
258
|
return (
|
|
226
259
|
<UserMessage
|
|
@@ -252,9 +285,9 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
252
285
|
const directToolCalls = allToolCalls.filter((tc) => tc.visibility !== "reasoning")
|
|
253
286
|
|
|
254
287
|
const hasReasoning = reasoningCalls.length > 0 || subAgents.length > 0 || directToolCalls.length > 0 || (message.isLoading && contentStr.trim() !== "");
|
|
255
|
-
const reasoningText = (hasReasoning && (!message.blocks || message.blocks.length === 0)) ? contentStr : undefined
|
|
256
|
-
const displayContentStr = hasReasoning ? "" : contentStr
|
|
257
|
-
const hasDisplayContent = displayContentStr.trim() !== ""
|
|
288
|
+
const reasoningText = (hasReasoning && (!message.blocks || message.blocks.length === 0)) ? contentStr : undefined;
|
|
289
|
+
const displayContentStr = hasReasoning ? "" : contentStr;
|
|
290
|
+
const hasDisplayContent = displayContentStr.trim() !== "";
|
|
258
291
|
|
|
259
292
|
if (!hasDisplayContent && directToolCalls.length === 0 && reasoningCalls.length === 0 && subAgents.length === 0 && !message.isLoading && (!message.blocks || message.blocks.length === 0)) {
|
|
260
293
|
return null;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { WorkflowNode, WorkflowEdge } from "@/components/blocks/WorkflowCanvas";
|
|
2
|
+
import type { SpatialContainerColorTheme, SpatialContainerVariant } from "@/components/composites/SpatialContainerNode";
|
|
3
|
+
import type { BmcBlock, BmcBlockId, BmcCanvasView, CompiledBmc } from "./interfaces";
|
|
4
|
+
|
|
5
|
+
interface BlockLayout {
|
|
6
|
+
id: string;
|
|
7
|
+
themeColor: SpatialContainerColorTheme;
|
|
8
|
+
variant: SpatialContainerVariant;
|
|
9
|
+
icon: string;
|
|
10
|
+
position: { x: number; y: number };
|
|
11
|
+
size: { width: number; height: number };
|
|
12
|
+
layout: 'column' | 'row';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const BLOCK_LAYOUTS: Record<BmcBlockId, BlockLayout> = {
|
|
16
|
+
key_partnerships: { id: 'cp', themeColor: 'blue', variant: 'extruded3d', icon: 'handshake', position: { x: 40, y: 40 }, size: { width: 280, height: 400 }, layout: 'column' },
|
|
17
|
+
key_activities: { id: 'ka', themeColor: 'emerald', variant: 'extruded3d', icon: 'activity', position: { x: 350, y: 40 }, size: { width: 280, height: 190 }, layout: 'column' },
|
|
18
|
+
key_resources: { id: 'kr', themeColor: 'emerald', variant: 'extruded3d', icon: 'database', position: { x: 350, y: 250 }, size: { width: 280, height: 190 }, layout: 'column' },
|
|
19
|
+
value_propositions: { id: 'vp', themeColor: 'indigo', variant: 'extruded3d', icon: 'box', position: { x: 660, y: 40 }, size: { width: 300, height: 400 }, layout: 'column' },
|
|
20
|
+
customer_relationships: { id: 'cr', themeColor: 'purple', variant: 'extruded3d', icon: 'heart', position: { x: 990, y: 40 }, size: { width: 300, height: 190 }, layout: 'column' },
|
|
21
|
+
channels: { id: 'ch', themeColor: 'amber', variant: 'extruded3d', icon: 'truck', position: { x: 990, y: 250 }, size: { width: 300, height: 190 }, layout: 'column' },
|
|
22
|
+
customer_segments: { id: 'cs', themeColor: 'purple', variant: 'extruded3d', icon: 'users', position: { x: 1320, y: 40 }, size: { width: 280, height: 400 }, layout: 'column' },
|
|
23
|
+
cost_structure: { id: 'cost', themeColor: 'rose', variant: 'extruded3d', icon: 'wallet', position: { x: 40, y: 465 }, size: { width: 760, height: 180 }, layout: 'row' },
|
|
24
|
+
revenue_streams: { id: 'rev', themeColor: 'cyan', variant: 'extruded3d', icon: 'wallet', position: { x: 840, y: 465 }, size: { width: 760, height: 180 }, layout: 'row' },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function blockEntries(bmc: CompiledBmc): Array<{ block: BmcBlock; layout: BlockLayout }> {
|
|
28
|
+
return [
|
|
29
|
+
{ block: bmc.keyPartnerships, layout: BLOCK_LAYOUTS.key_partnerships },
|
|
30
|
+
{ block: bmc.keyActivities, layout: BLOCK_LAYOUTS.key_activities },
|
|
31
|
+
{ block: bmc.keyResources, layout: BLOCK_LAYOUTS.key_resources },
|
|
32
|
+
{ block: bmc.valuePropositions, layout: BLOCK_LAYOUTS.value_propositions },
|
|
33
|
+
{ block: bmc.customerRelationships, layout: BLOCK_LAYOUTS.customer_relationships },
|
|
34
|
+
{ block: bmc.channels, layout: BLOCK_LAYOUTS.channels },
|
|
35
|
+
{ block: bmc.customerSegments, layout: BLOCK_LAYOUTS.customer_segments },
|
|
36
|
+
{ block: bmc.costStructure, layout: BLOCK_LAYOUTS.cost_structure },
|
|
37
|
+
{ block: bmc.revenueStreams, layout: BLOCK_LAYOUTS.revenue_streams },
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
import { BMC_TOOLTIPS } from "./bmc-tooltips";
|
|
42
|
+
|
|
43
|
+
export function bmcToCanvas(bmc: CompiledBmc | null, message?: string): BmcCanvasView {
|
|
44
|
+
if (!bmc) {
|
|
45
|
+
return {
|
|
46
|
+
canvasName: null,
|
|
47
|
+
nodes: [],
|
|
48
|
+
edges: [],
|
|
49
|
+
emptyBlocks: [],
|
|
50
|
+
message: message ?? 'No business model data available for this session',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const nodes: WorkflowNode[] = [];
|
|
55
|
+
|
|
56
|
+
for (const { block, layout } of blockEntries(bmc)) {
|
|
57
|
+
const itemCount = block.items.length;
|
|
58
|
+
const badgeText = block.empty || itemCount === 0 ? 'empty' : `${itemCount} item${itemCount === 1 ? '' : 's'}`;
|
|
59
|
+
|
|
60
|
+
const items = block.items.map((item, idx) => ({
|
|
61
|
+
id: `${layout.id}-${idx + 1}`,
|
|
62
|
+
label: item.name,
|
|
63
|
+
description: item.description,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
const blockId = layout.id === 'cp' ? 'key_partnerships'
|
|
67
|
+
: layout.id === 'ka' ? 'key_activities'
|
|
68
|
+
: layout.id === 'kr' ? 'key_resources'
|
|
69
|
+
: layout.id === 'vp' ? 'value_propositions'
|
|
70
|
+
: layout.id === 'cr' ? 'customer_relationships'
|
|
71
|
+
: layout.id === 'ch' ? 'channels'
|
|
72
|
+
: layout.id === 'cs' ? 'customer_segments'
|
|
73
|
+
: layout.id === 'cost' ? 'cost_structure'
|
|
74
|
+
: layout.id === 'rev' ? 'revenue_streams'
|
|
75
|
+
: '';
|
|
76
|
+
|
|
77
|
+
const tooltipInfo = BMC_TOOLTIPS[blockId];
|
|
78
|
+
const description = tooltipInfo
|
|
79
|
+
? `${tooltipInfo.description}\n\nKey Question: "${tooltipInfo.question}"\n\nExamples: ${tooltipInfo.examples.join(', ')}`
|
|
80
|
+
: block.name;
|
|
81
|
+
|
|
82
|
+
nodes.push({
|
|
83
|
+
id: layout.id,
|
|
84
|
+
type: 'spatialContainer',
|
|
85
|
+
position: layout.position,
|
|
86
|
+
style: layout.size,
|
|
87
|
+
data: {
|
|
88
|
+
label: block.name,
|
|
89
|
+
description,
|
|
90
|
+
type: 'spatialContainer',
|
|
91
|
+
themeColor: layout.themeColor,
|
|
92
|
+
variant: layout.variant,
|
|
93
|
+
icon: layout.icon,
|
|
94
|
+
badgeText,
|
|
95
|
+
items,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const edges: WorkflowEdge[] = [
|
|
101
|
+
{ id: 'e-ka-vp', source: 'ka', target: 'vp', type: 'animated' },
|
|
102
|
+
{ id: 'e-vp-cr', source: 'vp', target: 'cr', type: 'animated' },
|
|
103
|
+
{ id: 'e-cr-cs', source: 'cr', target: 'cs', type: 'animated' },
|
|
104
|
+
{ id: 'e-vp-ch', source: 'vp', target: 'ch', type: 'straight' },
|
|
105
|
+
{ id: 'e-ch-cs', source: 'ch', target: 'cs', type: 'animated' },
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
canvasName: bmc.canvas.name,
|
|
110
|
+
nodes,
|
|
111
|
+
edges,
|
|
112
|
+
emptyBlocks: bmc.emptyBlocks,
|
|
113
|
+
message,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface BmcTooltipContent {
|
|
2
|
+
title: string;
|
|
3
|
+
description: string;
|
|
4
|
+
question: string;
|
|
5
|
+
examples: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const BMC_TOOLTIPS: Record<string, BmcTooltipContent> = {
|
|
9
|
+
key_partnerships: {
|
|
10
|
+
title: "Key Partnerships",
|
|
11
|
+
description: "The external companies, suppliers, or vendors that help your business operate and grow.",
|
|
12
|
+
question: "Who do we rely on outside our company to make this business work?",
|
|
13
|
+
examples: ["Cloud hosting providers", "Payment gateways", "Material suppliers", "Strategic alliances"]
|
|
14
|
+
},
|
|
15
|
+
key_activities: {
|
|
16
|
+
title: "Key Activities",
|
|
17
|
+
description: "The most critical daily tasks and operations your team must execute to deliver your offer.",
|
|
18
|
+
question: "What core things must we do every day to operate and succeed?",
|
|
19
|
+
examples: ["Software development", "AI model training", "Content creation", "Customer support"]
|
|
20
|
+
},
|
|
21
|
+
key_resources: {
|
|
22
|
+
title: "Key Resources",
|
|
23
|
+
description: "The most vital assets—physical, intellectual, human, or financial—needed to run your business.",
|
|
24
|
+
question: "What essential assets do we need to own or access to deliver our value?",
|
|
25
|
+
examples: ["Skilled engineers", "Proprietary algorithms", "Brand identity", "Capital"]
|
|
26
|
+
},
|
|
27
|
+
value_propositions: {
|
|
28
|
+
title: "Value Propositions",
|
|
29
|
+
description: "The collection of products, services, and benefits that solve customer problems or satisfy their needs.",
|
|
30
|
+
question: "Why do customers choose us over competitors? What problem are we solving?",
|
|
31
|
+
examples: ["Time savings", "Cost reduction", "Ease of use", "Premium quality"]
|
|
32
|
+
},
|
|
33
|
+
customer_relationships: {
|
|
34
|
+
title: "Customer Relationships",
|
|
35
|
+
description: "The type of interaction and ongoing relationship you establish with each customer group.",
|
|
36
|
+
question: "How do we interact with our customers across their lifecycle?",
|
|
37
|
+
examples: ["Self-service onboarding", "Dedicated account management", "Automated support"]
|
|
38
|
+
},
|
|
39
|
+
channels: {
|
|
40
|
+
title: "Channels",
|
|
41
|
+
description: "The touchpoints and pathways you use to communicate with, sell to, and deliver value to customers.",
|
|
42
|
+
question: "How do customers find, evaluate, buy, and receive our product or service?",
|
|
43
|
+
examples: ["Direct web platform", "Sales team", "Mobile app", "Partner marketplaces"]
|
|
44
|
+
},
|
|
45
|
+
customer_segments: {
|
|
46
|
+
title: "Customer Segments",
|
|
47
|
+
description: "The specific groups of people or organizations your business aims to reach and serve.",
|
|
48
|
+
question: "Who are we creating value for? Who is our ideal buyer?",
|
|
49
|
+
examples: ["Enterprise B2B teams", "Thought leaders", "Small business owners"]
|
|
50
|
+
},
|
|
51
|
+
cost_structure: {
|
|
52
|
+
title: "Cost Structure",
|
|
53
|
+
description: "All major fixed and variable expenses required to operate your business model.",
|
|
54
|
+
question: "What are the biggest costs inherent in running our business?",
|
|
55
|
+
examples: ["Server/compute costs", "Team salaries", "R&D licensing", "Marketing"]
|
|
56
|
+
},
|
|
57
|
+
revenue_streams: {
|
|
58
|
+
title: "Revenue Streams",
|
|
59
|
+
description: "The different ways your business earns cash and captures value from each customer segment.",
|
|
60
|
+
question: "How does money flow into the business? What are customers paying for?",
|
|
61
|
+
examples: ["Monthly SaaS subscriptions", "Usage-based fees", "One-time setup charges"]
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { WorkflowEdge, WorkflowNode } from "@/components/blocks/WorkflowCanvas";
|
|
2
|
+
|
|
3
|
+
export type BmcBlockId =
|
|
4
|
+
| 'customer_segments'
|
|
5
|
+
| 'value_propositions'
|
|
6
|
+
| 'channels'
|
|
7
|
+
| 'customer_relationships'
|
|
8
|
+
| 'revenue_streams'
|
|
9
|
+
| 'key_resources'
|
|
10
|
+
| 'key_activities'
|
|
11
|
+
| 'key_partnerships'
|
|
12
|
+
| 'cost_structure';
|
|
13
|
+
|
|
14
|
+
export interface BmcItem {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
sourceRefs: string[];
|
|
19
|
+
[k: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BmcBlock {
|
|
23
|
+
key: BmcBlockId;
|
|
24
|
+
name: string;
|
|
25
|
+
items: BmcItem[];
|
|
26
|
+
empty: boolean;
|
|
27
|
+
derived: boolean;
|
|
28
|
+
sourceRefs: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CompiledBmc {
|
|
32
|
+
canvas: {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
derived: boolean;
|
|
37
|
+
source: { businessDocId?: string; productDocId?: string };
|
|
38
|
+
generatedAt: string;
|
|
39
|
+
};
|
|
40
|
+
customerSegments: BmcBlock;
|
|
41
|
+
valuePropositions: BmcBlock;
|
|
42
|
+
channels: BmcBlock;
|
|
43
|
+
customerRelationships: BmcBlock;
|
|
44
|
+
revenueStreams: BmcBlock;
|
|
45
|
+
keyResources: BmcBlock;
|
|
46
|
+
keyActivities: BmcBlock;
|
|
47
|
+
keyPartnerships: BmcBlock;
|
|
48
|
+
costStructure: BmcBlock & { strategicFocus?: string; drivers: string[] };
|
|
49
|
+
emptyBlocks: BmcBlockId[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BmcCanvasView {
|
|
53
|
+
canvasName: string | null;
|
|
54
|
+
nodes: WorkflowNode[];
|
|
55
|
+
edges: WorkflowEdge[];
|
|
56
|
+
emptyBlocks: BmcBlockId[];
|
|
57
|
+
message?: string;
|
|
58
|
+
}
|
|
@@ -16,6 +16,7 @@ import { Panel } from "@/components/ai-elements/panel";
|
|
|
16
16
|
import { StateNode } from "@/components/composites/StateNode";
|
|
17
17
|
import { TransitionNode } from "@/components/composites/TransitionNode";
|
|
18
18
|
import { TriggerNode } from "@/components/composites/TriggerNode";
|
|
19
|
+
import { SpatialContainerNode } from "@/components/composites/SpatialContainerNode";
|
|
19
20
|
import type { WorkflowCanvasProps, WorkflowEdge } from "./interfaces";
|
|
20
21
|
import "@xyflow/react/dist/style.css";
|
|
21
22
|
|
|
@@ -29,6 +30,7 @@ const nodeTypes = {
|
|
|
29
30
|
state: StateNode,
|
|
30
31
|
transition: TransitionNode,
|
|
31
32
|
trigger: TriggerNode,
|
|
33
|
+
spatialContainer: SpatialContainerNode,
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
function WorkflowCanvasInner({
|
|
@@ -79,7 +81,7 @@ function WorkflowCanvasInner({
|
|
|
79
81
|
}}
|
|
80
82
|
>
|
|
81
83
|
<Canvas
|
|
82
|
-
className="bg-background"
|
|
84
|
+
className="bg-background [&_.react-flow\_\_handle]:opacity-0 [&_.react-flow\_\_handle]:pointer-events-none [&_.react-flow\_\_node:hover_.react-flow\_\_handle]:opacity-100 [&_.react-flow\_\_node:hover_.react-flow\_\_handle]:pointer-events-auto [&_.react-flow\_\_node.selected_.react-flow\_\_handle]:opacity-100 [&_.react-flow\_\_node.selected_.react-flow\_\_handle]:pointer-events-auto [&_.react-flow\_\_handle]:transition-opacity [&_.react-flow\_\_handle]:duration-150"
|
|
83
85
|
connectionLineComponent={ConnectionLine}
|
|
84
86
|
connectionMode={ConnectionMode.Strict}
|
|
85
87
|
edges={edges}
|
|
@@ -3,8 +3,13 @@ import type React from "react";
|
|
|
3
3
|
import type { StateNodeData } from "@/components/composites/StateNode";
|
|
4
4
|
import type { TransitionNodeData } from "@/components/composites/TransitionNode";
|
|
5
5
|
import type { TriggerNodeData } from "@/components/composites/TriggerNode";
|
|
6
|
+
import type { SpatialContainerNodeData } from "@/components/composites/SpatialContainerNode";
|
|
6
7
|
|
|
7
|
-
export type WorkflowNodeData =
|
|
8
|
+
export type WorkflowNodeData =
|
|
9
|
+
| StateNodeData
|
|
10
|
+
| TransitionNodeData
|
|
11
|
+
| TriggerNodeData
|
|
12
|
+
| SpatialContainerNodeData;
|
|
8
13
|
export type WorkflowNode = Node<WorkflowNodeData>;
|
|
9
14
|
|
|
10
15
|
export interface WorkflowEdge {
|
|
@@ -16,6 +16,9 @@ export * from './SectionLayout'
|
|
|
16
16
|
export { WorkflowCanvas, getLayoutedElements } from './WorkflowCanvas'
|
|
17
17
|
export type { WorkflowCanvasProps, WorkflowNode, WorkflowEdge, WorkflowNodeData } from './WorkflowCanvas'
|
|
18
18
|
|
|
19
|
+
export { bmcToCanvas } from './BmcCanvas'
|
|
20
|
+
export type { BmcBlockId, BmcItem, BmcBlock, CompiledBmc, BmcCanvasView } from './BmcCanvas'
|
|
21
|
+
|
|
19
22
|
export { DashboardMetrics } from './DashboardMetrics'
|
|
20
23
|
export type { DashboardMetricsProps } from './DashboardMetrics'
|
|
21
24
|
|
|
@@ -16,7 +16,7 @@ export interface SubAgent {
|
|
|
16
16
|
subAgentName: string
|
|
17
17
|
input: string | Record<string, unknown>
|
|
18
18
|
output?: string | Record<string, unknown>
|
|
19
|
-
status: "pending" | "active" | "completed" | "error"
|
|
19
|
+
status: "pending" | "active" | "completed" | "error" | "running" | "in_progress"
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface AgentIndicatorProps {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ConfirmationActions,
|
|
14
14
|
ConfirmationAction,
|
|
15
15
|
} from "@/components/ai-elements/confirmation";
|
|
16
|
+
import { StreamingMarkdown } from "@/components/composites/StreamingMarkdown";
|
|
16
17
|
|
|
17
18
|
export interface ActionRequest {
|
|
18
19
|
name: string;
|
|
@@ -209,10 +210,14 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
209
210
|
className={cn("w-full", className)}
|
|
210
211
|
>
|
|
211
212
|
<ConfirmationTitle>
|
|
212
|
-
<div className="flex items-start justify-between gap-4
|
|
213
|
-
<div className="flex items-start gap-3">
|
|
213
|
+
<div className="flex items-start justify-between gap-4">
|
|
214
|
+
<div className="flex items-start gap-3 min-w-0 flex-1">
|
|
214
215
|
<QuestionIcon className="h-5 w-5 text-muted-foreground mt-0.5 flex-shrink-0" />
|
|
215
|
-
<
|
|
216
|
+
<div className="flex-1 min-w-0 max-h-60 overflow-y-auto pr-1 text-sm leading-relaxed text-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
|
|
217
|
+
<StreamingMarkdown controls={false}>
|
|
218
|
+
{currentQuestion.question}
|
|
219
|
+
</StreamingMarkdown>
|
|
220
|
+
</div>
|
|
216
221
|
</div>
|
|
217
222
|
{questions.length > 1 && (
|
|
218
223
|
<div className="flex items-center gap-1.5 text-xs text-muted-foreground flex-shrink-0 font-normal">
|
|
@@ -66,7 +66,11 @@ export const DefaultSwitcher = React.memo<DefaultSwitcherProps>(
|
|
|
66
66
|
</SelectTrigger>
|
|
67
67
|
<SelectContent>
|
|
68
68
|
{themes.map((theme) => (
|
|
69
|
-
<SelectItem
|
|
69
|
+
<SelectItem
|
|
70
|
+
key={theme.value}
|
|
71
|
+
value={theme.value}
|
|
72
|
+
onClick={() => onValueChange?.(theme.value)}
|
|
73
|
+
>
|
|
70
74
|
{theme.label}
|
|
71
75
|
</SelectItem>
|
|
72
76
|
))}
|