@steerable/agent-ui 0.2.3 → 0.2.4
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/dist/cards/ActionSegmentCard.d.ts +18 -0
- package/dist/cards/ActionSegmentCard.d.ts.map +1 -0
- package/dist/cards/ActionSegmentCard.js +26 -0
- package/dist/cards/ActionSegmentCard.js.map +1 -0
- package/dist/cards/AnalysisDocumentCard.d.ts +33 -0
- package/dist/cards/AnalysisDocumentCard.d.ts.map +1 -0
- package/dist/cards/AnalysisDocumentCard.js +11 -0
- package/dist/cards/AnalysisDocumentCard.js.map +1 -0
- package/dist/cards/AskUserQuestionsCard.d.ts +43 -0
- package/dist/cards/AskUserQuestionsCard.d.ts.map +1 -0
- package/dist/cards/AskUserQuestionsCard.js +171 -0
- package/dist/cards/AskUserQuestionsCard.js.map +1 -0
- package/dist/cards/CoverageReportCard.d.ts +41 -0
- package/dist/cards/CoverageReportCard.d.ts.map +1 -0
- package/dist/cards/CoverageReportCard.js +55 -0
- package/dist/cards/CoverageReportCard.js.map +1 -0
- package/dist/cards/OrchestrationPlanCard.d.ts +60 -0
- package/dist/cards/OrchestrationPlanCard.d.ts.map +1 -0
- package/dist/cards/OrchestrationPlanCard.js +103 -0
- package/dist/cards/OrchestrationPlanCard.js.map +1 -0
- package/dist/cards/PlanSelectorCard.d.ts +40 -0
- package/dist/cards/PlanSelectorCard.d.ts.map +1 -0
- package/dist/cards/PlanSelectorCard.js +106 -0
- package/dist/cards/PlanSelectorCard.js.map +1 -0
- package/dist/cards/PlanStepsCard.d.ts +18 -0
- package/dist/cards/PlanStepsCard.d.ts.map +1 -0
- package/dist/cards/PlanStepsCard.js +25 -0
- package/dist/cards/PlanStepsCard.js.map +1 -0
- package/dist/cards/QuizCard.d.ts +44 -0
- package/dist/cards/QuizCard.d.ts.map +1 -0
- package/dist/cards/QuizCard.js +113 -0
- package/dist/cards/QuizCard.js.map +1 -0
- package/dist/cards/ResearchPlanCard.d.ts +23 -0
- package/dist/cards/ResearchPlanCard.d.ts.map +1 -0
- package/dist/cards/ResearchPlanCard.js +50 -0
- package/dist/cards/ResearchPlanCard.js.map +1 -0
- package/dist/cards/SearchSourcesCard.d.ts +29 -0
- package/dist/cards/SearchSourcesCard.d.ts.map +1 -0
- package/dist/cards/SearchSourcesCard.js +83 -0
- package/dist/cards/SearchSourcesCard.js.map +1 -0
- package/dist/cards/SuggestedRepliesCard.d.ts +18 -0
- package/dist/cards/SuggestedRepliesCard.d.ts.map +1 -0
- package/dist/cards/SuggestedRepliesCard.js +14 -0
- package/dist/cards/SuggestedRepliesCard.js.map +1 -0
- package/dist/cards/SummaryMessageCard.d.ts +40 -0
- package/dist/cards/SummaryMessageCard.d.ts.map +1 -0
- package/dist/cards/SummaryMessageCard.js +56 -0
- package/dist/cards/SummaryMessageCard.js.map +1 -0
- package/dist/cards/ThinkingProcessCard.d.ts +46 -0
- package/dist/cards/ThinkingProcessCard.d.ts.map +1 -0
- package/dist/cards/ThinkingProcessCard.js +79 -0
- package/dist/cards/ThinkingProcessCard.js.map +1 -0
- package/dist/cards/ToolExecutionCard.d.ts +21 -0
- package/dist/cards/ToolExecutionCard.d.ts.map +1 -0
- package/dist/cards/ToolExecutionCard.js +65 -0
- package/dist/cards/ToolExecutionCard.js.map +1 -0
- package/dist/cards/icons.d.ts +38 -0
- package/dist/cards/icons.d.ts.map +1 -0
- package/dist/cards/icons.js +87 -0
- package/dist/cards/icons.js.map +1 -0
- package/dist/cards/index.d.ts +38 -0
- package/dist/cards/index.d.ts.map +1 -0
- package/dist/cards/index.js +24 -0
- package/dist/cards/index.js.map +1 -0
- package/dist/cards/types.d.ts +24 -0
- package/dist/cards/types.d.ts.map +1 -0
- package/dist/cards/types.js +7 -0
- package/dist/cards/types.js.map +1 -0
- package/dist/components/AgentSelector.d.ts +23 -0
- package/dist/components/AgentSelector.d.ts.map +1 -0
- package/dist/components/AgentSelector.js +63 -0
- package/dist/components/AgentSelector.js.map +1 -0
- package/dist/components/AgentSelector.stories.d.ts +12 -0
- package/dist/components/AgentSelector.stories.d.ts.map +1 -0
- package/dist/components/AgentSelector.stories.js +95 -0
- package/dist/components/AgentSelector.stories.js.map +1 -0
- package/dist/components/ChatPanel.d.ts +138 -21
- package/dist/components/ChatPanel.d.ts.map +1 -1
- package/dist/components/ChatPanel.js +181 -26
- package/dist/components/ChatPanel.js.map +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/state/ChatSessionProvider.d.ts +59 -0
- package/dist/state/ChatSessionProvider.d.ts.map +1 -0
- package/dist/state/ChatSessionProvider.js +71 -0
- package/dist/state/ChatSessionProvider.js.map +1 -0
- package/dist/state/MockChatStreamTransport.d.ts +54 -0
- package/dist/state/MockChatStreamTransport.d.ts.map +1 -0
- package/dist/state/MockChatStreamTransport.js +86 -0
- package/dist/state/MockChatStreamTransport.js.map +1 -0
- package/dist/state/MockChatStreamTransport.stories.d.ts +13 -0
- package/dist/state/MockChatStreamTransport.stories.d.ts.map +1 -0
- package/dist/state/MockChatStreamTransport.stories.js +60 -0
- package/dist/state/MockChatStreamTransport.stories.js.map +1 -0
- package/dist/state/bridgeLegacySSE.d.ts +42 -0
- package/dist/state/bridgeLegacySSE.d.ts.map +1 -0
- package/dist/state/bridgeLegacySSE.js +102 -0
- package/dist/state/bridgeLegacySSE.js.map +1 -0
- package/dist/state/index.d.ts +32 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +24 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/parseSSE.d.ts +50 -0
- package/dist/state/parseSSE.d.ts.map +1 -0
- package/dist/state/parseSSE.js +112 -0
- package/dist/state/parseSSE.js.map +1 -0
- package/dist/state/useChatComposer.d.ts +51 -0
- package/dist/state/useChatComposer.d.ts.map +1 -0
- package/dist/state/useChatComposer.js +82 -0
- package/dist/state/useChatComposer.js.map +1 -0
- package/dist/state/useChatList.d.ts +71 -0
- package/dist/state/useChatList.d.ts.map +1 -0
- package/dist/state/useChatList.js +158 -0
- package/dist/state/useChatList.js.map +1 -0
- package/dist/state/useChatSession.d.ts +29 -0
- package/dist/state/useChatSession.d.ts.map +1 -0
- package/dist/state/useChatSession.js +37 -0
- package/dist/state/useChatSession.js.map +1 -0
- package/dist/state/useScrollLock.d.ts +35 -0
- package/dist/state/useScrollLock.d.ts.map +1 -0
- package/dist/state/useScrollLock.js +74 -0
- package/dist/state/useScrollLock.js.map +1 -0
- package/dist/state/useToolCallStream.d.ts +38 -0
- package/dist/state/useToolCallStream.d.ts.map +1 -0
- package/dist/state/useToolCallStream.js +74 -0
- package/dist/state/useToolCallStream.js.map +1 -0
- package/package.json +10 -2
- package/src/cards/ActionSegmentCard.tsx +60 -0
- package/src/cards/AnalysisDocumentCard.tsx +81 -0
- package/src/cards/AskUserQuestionsCard.tsx +354 -0
- package/src/cards/CoverageReportCard.tsx +215 -0
- package/src/cards/OrchestrationPlanCard.tsx +328 -0
- package/src/cards/PlanSelectorCard.tsx +312 -0
- package/src/cards/PlanStepsCard.tsx +52 -0
- package/src/cards/QuizCard.tsx +309 -0
- package/src/cards/ResearchPlanCard.tsx +169 -0
- package/src/cards/SearchSourcesCard.tsx +244 -0
- package/src/cards/SuggestedRepliesCard.tsx +48 -0
- package/src/cards/SummaryMessageCard.tsx +145 -0
- package/src/cards/ThinkingProcessCard.tsx +155 -0
- package/src/cards/ToolExecutionCard.tsx +152 -0
- package/src/cards/cards.test.tsx +492 -0
- package/src/cards/icons.tsx +249 -0
- package/src/cards/index.ts +53 -0
- package/src/cards/types.ts +27 -0
- package/src/components/AgentSelector.stories.tsx +113 -0
- package/src/components/AgentSelector.test.tsx +70 -0
- package/src/components/AgentSelector.tsx +147 -0
- package/src/components/ChatPanel.test.tsx +130 -0
- package/src/components/ChatPanel.tsx +612 -138
- package/src/components/index.ts +13 -1
- package/src/index.ts +43 -0
- package/src/state/ChatSessionProvider.test.tsx +85 -0
- package/src/state/ChatSessionProvider.tsx +98 -0
- package/src/state/MockChatStreamTransport.stories.tsx +95 -0
- package/src/state/MockChatStreamTransport.test.ts +163 -0
- package/src/state/MockChatStreamTransport.ts +123 -0
- package/src/state/bridgeLegacySSE.test.ts +134 -0
- package/src/state/bridgeLegacySSE.ts +134 -0
- package/src/state/index.ts +82 -0
- package/src/state/parseSSE.test.ts +123 -0
- package/src/state/parseSSE.ts +123 -0
- package/src/state/useChatComposer.test.ts +145 -0
- package/src/state/useChatComposer.ts +117 -0
- package/src/state/useChatList.test.ts +135 -0
- package/src/state/useChatList.ts +217 -0
- package/src/state/useChatSession.test.ts +61 -0
- package/src/state/useChatSession.ts +64 -0
- package/src/state/useScrollLock.test.ts +99 -0
- package/src/state/useScrollLock.ts +105 -0
- package/src/state/useToolCallStream.test.ts +86 -0
- package/src/state/useToolCallStream.ts +110 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline SVG icons used by `@steerable/agent-ui/cards`.
|
|
3
|
+
*
|
|
4
|
+
* Defined here rather than pulling in `react-icons` / `lucide-react` so the
|
|
5
|
+
* cards package stays at exactly one runtime dep (`@steerable/agent-protocol`).
|
|
6
|
+
* Each icon has a tiny stroke-only path, parametrised by `size` and the usual
|
|
7
|
+
* SVG props so consumers can re-style via Tailwind / CSS.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
|
|
11
|
+
type IconProps = React.SVGProps<SVGSVGElement> & { size?: number };
|
|
12
|
+
|
|
13
|
+
function base({ size = 16, ...rest }: IconProps) {
|
|
14
|
+
return {
|
|
15
|
+
width: size,
|
|
16
|
+
height: size,
|
|
17
|
+
viewBox: '0 0 24 24',
|
|
18
|
+
fill: 'none',
|
|
19
|
+
stroke: 'currentColor',
|
|
20
|
+
strokeWidth: 1.75,
|
|
21
|
+
strokeLinecap: 'round' as const,
|
|
22
|
+
strokeLinejoin: 'round' as const,
|
|
23
|
+
...rest,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CheckIcon(props: IconProps) {
|
|
28
|
+
return (
|
|
29
|
+
<svg {...base(props)}>
|
|
30
|
+
<polyline points="20 6 9 17 4 12" />
|
|
31
|
+
</svg>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ChevronDownIcon(props: IconProps) {
|
|
36
|
+
return (
|
|
37
|
+
<svg {...base(props)}>
|
|
38
|
+
<polyline points="6 9 12 15 18 9" />
|
|
39
|
+
</svg>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ChevronRightIcon(props: IconProps) {
|
|
44
|
+
return (
|
|
45
|
+
<svg {...base(props)}>
|
|
46
|
+
<polyline points="9 18 15 12 9 6" />
|
|
47
|
+
</svg>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ChevronUpIcon(props: IconProps) {
|
|
52
|
+
return (
|
|
53
|
+
<svg {...base(props)}>
|
|
54
|
+
<polyline points="18 15 12 9 6 15" />
|
|
55
|
+
</svg>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AlertIcon(props: IconProps) {
|
|
60
|
+
return (
|
|
61
|
+
<svg {...base(props)}>
|
|
62
|
+
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
63
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
64
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
65
|
+
</svg>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function LoaderIcon(props: IconProps) {
|
|
70
|
+
return (
|
|
71
|
+
<svg {...base(props)} className={[props.className, 'animate-spin'].filter(Boolean).join(' ')}>
|
|
72
|
+
<line x1="12" y1="2" x2="12" y2="6" />
|
|
73
|
+
<line x1="12" y1="18" x2="12" y2="22" />
|
|
74
|
+
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76" />
|
|
75
|
+
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07" />
|
|
76
|
+
<line x1="2" y1="12" x2="6" y2="12" />
|
|
77
|
+
<line x1="18" y1="12" x2="22" y2="12" />
|
|
78
|
+
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24" />
|
|
79
|
+
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93" />
|
|
80
|
+
</svg>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function GitBranchIcon(props: IconProps) {
|
|
85
|
+
return (
|
|
86
|
+
<svg {...base(props)}>
|
|
87
|
+
<line x1="6" y1="3" x2="6" y2="15" />
|
|
88
|
+
<circle cx="18" cy="6" r="3" />
|
|
89
|
+
<circle cx="6" cy="18" r="3" />
|
|
90
|
+
<path d="M18 9a9 9 0 0 1-9 9" />
|
|
91
|
+
</svg>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function ListChecksIcon(props: IconProps) {
|
|
96
|
+
return (
|
|
97
|
+
<svg {...base(props)}>
|
|
98
|
+
<polyline points="3 7 5 9 9 5" />
|
|
99
|
+
<polyline points="3 17 5 19 9 15" />
|
|
100
|
+
<line x1="13" y1="7" x2="21" y2="7" />
|
|
101
|
+
<line x1="13" y1="17" x2="21" y2="17" />
|
|
102
|
+
</svg>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function TargetIcon(props: IconProps) {
|
|
107
|
+
return (
|
|
108
|
+
<svg {...base(props)}>
|
|
109
|
+
<circle cx="12" cy="12" r="10" />
|
|
110
|
+
<circle cx="12" cy="12" r="6" />
|
|
111
|
+
<circle cx="12" cy="12" r="2" />
|
|
112
|
+
</svg>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function GlobeIcon(props: IconProps) {
|
|
117
|
+
return (
|
|
118
|
+
<svg {...base(props)}>
|
|
119
|
+
<circle cx="12" cy="12" r="10" />
|
|
120
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
121
|
+
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
122
|
+
</svg>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function ExternalLinkIcon(props: IconProps) {
|
|
127
|
+
return (
|
|
128
|
+
<svg {...base(props)}>
|
|
129
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
130
|
+
<polyline points="15 3 21 3 21 9" />
|
|
131
|
+
<line x1="10" y1="14" x2="21" y2="3" />
|
|
132
|
+
</svg>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function MessageQuestionIcon(props: IconProps) {
|
|
137
|
+
return (
|
|
138
|
+
<svg {...base(props)}>
|
|
139
|
+
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
|
140
|
+
<path d="M9.5 9.5a2.5 2.5 0 1 1 3 2.5v1" />
|
|
141
|
+
<line x1="12" y1="15" x2="12.01" y2="15" />
|
|
142
|
+
</svg>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function ArchiveIcon(props: IconProps) {
|
|
147
|
+
return (
|
|
148
|
+
<svg {...base(props)}>
|
|
149
|
+
<polyline points="21 8 21 21 3 21 3 8" />
|
|
150
|
+
<rect x="1" y="3" width="22" height="5" />
|
|
151
|
+
<line x1="10" y1="12" x2="14" y2="12" />
|
|
152
|
+
</svg>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function ChartIcon(props: IconProps) {
|
|
157
|
+
return (
|
|
158
|
+
<svg {...base(props)}>
|
|
159
|
+
<line x1="18" y1="20" x2="18" y2="10" />
|
|
160
|
+
<line x1="12" y1="20" x2="12" y2="4" />
|
|
161
|
+
<line x1="6" y1="20" x2="6" y2="14" />
|
|
162
|
+
</svg>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function FlameIcon(props: IconProps) {
|
|
167
|
+
return (
|
|
168
|
+
<svg {...base(props)}>
|
|
169
|
+
<path d="M8.5 14.5A2.5 2.5 0 0 0 11 17v-3a2.5 2.5 0 1 1 0-5V6a3 3 0 0 0-3 3 3 3 0 0 0 .5 2.5C7.5 13 6 14.5 6 17a6 6 0 0 0 12 0c0-4-3-7-5-9 1.5 4-.5 5.5-2 6.5z" />
|
|
170
|
+
</svg>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function ShieldIcon(props: IconProps) {
|
|
175
|
+
return (
|
|
176
|
+
<svg {...base(props)}>
|
|
177
|
+
<path d="M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5l-8-3z" />
|
|
178
|
+
</svg>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function ClockIcon(props: IconProps) {
|
|
183
|
+
return (
|
|
184
|
+
<svg {...base(props)}>
|
|
185
|
+
<circle cx="12" cy="12" r="10" />
|
|
186
|
+
<polyline points="12 6 12 12 16 14" />
|
|
187
|
+
</svg>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function PlayIcon(props: IconProps) {
|
|
192
|
+
return (
|
|
193
|
+
<svg {...base(props)}>
|
|
194
|
+
<polygon points="5 3 19 12 5 21 5 3" />
|
|
195
|
+
</svg>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function StopIcon(props: IconProps) {
|
|
200
|
+
return (
|
|
201
|
+
<svg {...base(props)}>
|
|
202
|
+
<rect x="6" y="6" width="12" height="12" />
|
|
203
|
+
</svg>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function ZapIcon(props: IconProps) {
|
|
208
|
+
return (
|
|
209
|
+
<svg {...base(props)}>
|
|
210
|
+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
|
211
|
+
</svg>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function ClipboardIcon(props: IconProps) {
|
|
216
|
+
return (
|
|
217
|
+
<svg {...base(props)}>
|
|
218
|
+
<rect x="9" y="2" width="6" height="4" rx="1" />
|
|
219
|
+
<path d="M9 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-3" />
|
|
220
|
+
</svg>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function BookIcon(props: IconProps) {
|
|
225
|
+
return (
|
|
226
|
+
<svg {...base(props)}>
|
|
227
|
+
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
|
228
|
+
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
|
229
|
+
</svg>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function ThumbsUpIcon(props: IconProps) {
|
|
234
|
+
return (
|
|
235
|
+
<svg {...base(props)}>
|
|
236
|
+
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9A2 2 0 0 0 19.66 9H14z" />
|
|
237
|
+
<line x1="7" y1="22" x2="7" y2="11" />
|
|
238
|
+
</svg>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function PencilIcon(props: IconProps) {
|
|
243
|
+
return (
|
|
244
|
+
<svg {...base(props)}>
|
|
245
|
+
<path d="M12 20h9" />
|
|
246
|
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
|
247
|
+
</svg>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@steerable/agent-ui/cards`
|
|
3
|
+
*
|
|
4
|
+
* Subpath that bundles the 14 rich chat-block renderers used by deeppath /
|
|
5
|
+
* deeppath-agent / examples/web-shell. Every card consumes a typed `payload`
|
|
6
|
+
* sourced from the JSON schemas in `agent-protocol/spec/blocks/*.schema.json`
|
|
7
|
+
* (with TypeScript types generated by `pnpm gen`). All side-effects (submit,
|
|
8
|
+
* select, remediate, ...) flow through props -- apps stay in charge of state.
|
|
9
|
+
*/
|
|
10
|
+
export { OrchestrationPlanCard } from './OrchestrationPlanCard.js';
|
|
11
|
+
export type {
|
|
12
|
+
OrchestrationPlanCardProps,
|
|
13
|
+
OrchestrationTaskStatus,
|
|
14
|
+
} from './OrchestrationPlanCard.js';
|
|
15
|
+
|
|
16
|
+
export { QuizCard } from './QuizCard.js';
|
|
17
|
+
export type { QuizCardProps } from './QuizCard.js';
|
|
18
|
+
|
|
19
|
+
export { CoverageReportCard } from './CoverageReportCard.js';
|
|
20
|
+
export type { CoverageReportCardProps } from './CoverageReportCard.js';
|
|
21
|
+
|
|
22
|
+
export { AnalysisDocumentCard } from './AnalysisDocumentCard.js';
|
|
23
|
+
export type { AnalysisDocumentCardProps } from './AnalysisDocumentCard.js';
|
|
24
|
+
|
|
25
|
+
export { ResearchPlanCard } from './ResearchPlanCard.js';
|
|
26
|
+
export type { ResearchPlanCardProps } from './ResearchPlanCard.js';
|
|
27
|
+
|
|
28
|
+
export { SuggestedRepliesCard } from './SuggestedRepliesCard.js';
|
|
29
|
+
export type { SuggestedRepliesCardProps } from './SuggestedRepliesCard.js';
|
|
30
|
+
|
|
31
|
+
export { AskUserQuestionsCard } from './AskUserQuestionsCard.js';
|
|
32
|
+
export type { AskUserQuestionsCardProps } from './AskUserQuestionsCard.js';
|
|
33
|
+
|
|
34
|
+
export { ThinkingProcessCard } from './ThinkingProcessCard.js';
|
|
35
|
+
export type { ThinkingProcessCardProps } from './ThinkingProcessCard.js';
|
|
36
|
+
|
|
37
|
+
export { PlanStepsCard } from './PlanStepsCard.js';
|
|
38
|
+
export type { PlanStepsCardProps } from './PlanStepsCard.js';
|
|
39
|
+
|
|
40
|
+
export { PlanSelectorCard } from './PlanSelectorCard.js';
|
|
41
|
+
export type { PlanSelectorCardProps } from './PlanSelectorCard.js';
|
|
42
|
+
|
|
43
|
+
export { SearchSourcesCard } from './SearchSourcesCard.js';
|
|
44
|
+
export type { SearchSourcesCardProps } from './SearchSourcesCard.js';
|
|
45
|
+
|
|
46
|
+
export { SummaryMessageCard } from './SummaryMessageCard.js';
|
|
47
|
+
export type { SummaryMessageCardProps } from './SummaryMessageCard.js';
|
|
48
|
+
|
|
49
|
+
export { ActionSegmentCard } from './ActionSegmentCard.js';
|
|
50
|
+
export type { ActionSegmentCardProps } from './ActionSegmentCard.js';
|
|
51
|
+
|
|
52
|
+
export { ToolExecutionCard } from './ToolExecutionCard.js';
|
|
53
|
+
export type { ToolExecutionCardProps } from './ToolExecutionCard.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-agnostic helpers shared by the cards.
|
|
3
|
+
*/
|
|
4
|
+
import type * as React from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Return type for `renderMarkdown` / `renderLabel` / etc. slots.
|
|
8
|
+
*
|
|
9
|
+
* We deliberately accept `unknown` so the prop type doesn't anchor consumers
|
|
10
|
+
* to a specific `@types/react` major (`React.ReactNode` is nominally
|
|
11
|
+
* different between v18 and v19 even though structurally compatible -- a
|
|
12
|
+
* consumer on React 19 cannot pass a function declared to return
|
|
13
|
+
* `React.ReactNode` (v19) into a framework prop that wants v18's
|
|
14
|
+
* `React.ReactNode`). The card then casts back to `React.ReactNode` at the
|
|
15
|
+
* render site, where TypeScript only cares about runtime shape and `unknown`
|
|
16
|
+
* widens harmlessly.
|
|
17
|
+
*
|
|
18
|
+
* For consumers, this just means: "return any valid React child".
|
|
19
|
+
*/
|
|
20
|
+
export type RenderSlotResult = unknown;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Cast helper for use inside cards. Keeps render sites tidy.
|
|
24
|
+
*/
|
|
25
|
+
export function asReactNode(value: RenderSlotResult): React.ReactNode {
|
|
26
|
+
return value as React.ReactNode;
|
|
27
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import type { ChatAgent } from '@steerable/agent-protocol';
|
|
4
|
+
import { AgentSelector } from './AgentSelector.js';
|
|
5
|
+
|
|
6
|
+
const NOW = '2026-05-16T00:00:00Z';
|
|
7
|
+
|
|
8
|
+
const baseAgents: ChatAgent[] = [
|
|
9
|
+
{
|
|
10
|
+
id: 'local-assistant',
|
|
11
|
+
name: 'Local Assistant',
|
|
12
|
+
icon: '🤖',
|
|
13
|
+
createdAt: NOW,
|
|
14
|
+
updatedAt: NOW,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'researcher',
|
|
18
|
+
name: 'Researcher',
|
|
19
|
+
icon: '🔎',
|
|
20
|
+
createdAt: NOW,
|
|
21
|
+
updatedAt: NOW,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'planner',
|
|
25
|
+
name: 'Planner',
|
|
26
|
+
icon: '🧭',
|
|
27
|
+
createdAt: NOW,
|
|
28
|
+
updatedAt: NOW,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const meta: Meta<typeof AgentSelector> = {
|
|
33
|
+
title: 'Components/AgentSelector',
|
|
34
|
+
component: AgentSelector,
|
|
35
|
+
parameters: {
|
|
36
|
+
layout: 'centered',
|
|
37
|
+
actions: { argTypesRegex: '^on[A-Z].*' },
|
|
38
|
+
docs: {
|
|
39
|
+
description: {
|
|
40
|
+
component:
|
|
41
|
+
'Segmented selector for choosing the active chat agent. Supports keyboard navigation and custom rendering via `renderAgent`.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
agents: baseAgents,
|
|
47
|
+
selectedId: baseAgents[0].id,
|
|
48
|
+
onSelect: () => {},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default meta;
|
|
53
|
+
type Story = StoryObj<typeof AgentSelector>;
|
|
54
|
+
|
|
55
|
+
export const Default: Story = {};
|
|
56
|
+
|
|
57
|
+
export const Disabled: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
disabled: true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const SingleAgent: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
agents: [baseAgents[0]],
|
|
66
|
+
selectedId: baseAgents[0].id,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const LongNames: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
agents: [
|
|
73
|
+
{
|
|
74
|
+
id: 'very-long',
|
|
75
|
+
name: 'This is a very long agent name that should truncate gracefully',
|
|
76
|
+
icon: '🧪',
|
|
77
|
+
createdAt: NOW,
|
|
78
|
+
updatedAt: NOW,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'also-long',
|
|
82
|
+
name: 'Another very long and descriptive specialist agent name',
|
|
83
|
+
icon: '🛠️',
|
|
84
|
+
createdAt: NOW,
|
|
85
|
+
updatedAt: NOW,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
selectedId: 'very-long',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const Loading: Story = {
|
|
93
|
+
args: {
|
|
94
|
+
loading: true,
|
|
95
|
+
agents: [],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const Interactive: Story = {
|
|
100
|
+
render: (args) => {
|
|
101
|
+
const InteractiveSelector = () => {
|
|
102
|
+
const [selectedId, setSelectedId] = useState(args.selectedId);
|
|
103
|
+
return (
|
|
104
|
+
<AgentSelector
|
|
105
|
+
{...args}
|
|
106
|
+
selectedId={selectedId}
|
|
107
|
+
onSelect={setSelectedId}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
return <InteractiveSelector />;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import type { ChatAgent } from '@steerable/agent-protocol';
|
|
4
|
+
import { AgentSelector } from './AgentSelector';
|
|
5
|
+
|
|
6
|
+
const NOW = '2026-05-16T00:00:00Z';
|
|
7
|
+
const AGENTS: ChatAgent[] = [
|
|
8
|
+
{ id: 'a1', name: 'Alpha', createdAt: NOW, updatedAt: NOW },
|
|
9
|
+
{ id: 'a2', name: 'Beta', createdAt: NOW, updatedAt: NOW },
|
|
10
|
+
{ id: 'a3', name: 'Gamma', createdAt: NOW, updatedAt: NOW },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
describe('AgentSelector', () => {
|
|
14
|
+
it('renders all agents and marks the selected radio', () => {
|
|
15
|
+
render(
|
|
16
|
+
<AgentSelector agents={AGENTS} selectedId="a2" onSelect={() => {}} />,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(
|
|
20
|
+
screen.getByRole('radio', { name: 'Alpha' }).getAttribute('aria-checked'),
|
|
21
|
+
).toBe('false');
|
|
22
|
+
expect(
|
|
23
|
+
screen.getByRole('radio', { name: 'Beta' }).getAttribute('aria-checked'),
|
|
24
|
+
).toBe('true');
|
|
25
|
+
expect(
|
|
26
|
+
screen.getByRole('radio', { name: 'Gamma' }).getAttribute('aria-checked'),
|
|
27
|
+
).toBe('false');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('calls onSelect on click', () => {
|
|
31
|
+
const onSelect = vi.fn();
|
|
32
|
+
render(<AgentSelector agents={AGENTS} selectedId="a1" onSelect={onSelect} />);
|
|
33
|
+
|
|
34
|
+
fireEvent.click(screen.getByRole('radio', { name: 'Gamma' }));
|
|
35
|
+
expect(onSelect).toHaveBeenCalledWith('a3');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('supports keyboard navigation (Arrow, Home, End)', () => {
|
|
39
|
+
const onSelect = vi.fn();
|
|
40
|
+
render(<AgentSelector agents={AGENTS} selectedId="a2" onSelect={onSelect} />);
|
|
41
|
+
|
|
42
|
+
const beta = screen.getByRole('radio', { name: 'Beta' });
|
|
43
|
+
fireEvent.keyDown(beta, { key: 'ArrowRight' });
|
|
44
|
+
fireEvent.keyDown(beta, { key: 'ArrowLeft' });
|
|
45
|
+
fireEvent.keyDown(beta, { key: 'Home' });
|
|
46
|
+
fireEvent.keyDown(beta, { key: 'End' });
|
|
47
|
+
|
|
48
|
+
expect(onSelect).toHaveBeenNthCalledWith(1, 'a3');
|
|
49
|
+
expect(onSelect).toHaveBeenNthCalledWith(2, 'a1');
|
|
50
|
+
expect(onSelect).toHaveBeenNthCalledWith(3, 'a1');
|
|
51
|
+
expect(onSelect).toHaveBeenNthCalledWith(4, 'a3');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does not call onSelect when disabled', () => {
|
|
55
|
+
const onSelect = vi.fn();
|
|
56
|
+
render(
|
|
57
|
+
<AgentSelector
|
|
58
|
+
agents={AGENTS}
|
|
59
|
+
selectedId="a1"
|
|
60
|
+
onSelect={onSelect}
|
|
61
|
+
disabled
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const alpha = screen.getByRole('radio', { name: 'Alpha' });
|
|
66
|
+
fireEvent.click(alpha);
|
|
67
|
+
fireEvent.keyDown(alpha, { key: 'ArrowRight' });
|
|
68
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { ChatAgent } from '@steerable/agent-protocol';
|
|
3
|
+
import { cn } from './cn.js';
|
|
4
|
+
|
|
5
|
+
export interface AgentSelectorProps {
|
|
6
|
+
agents: ChatAgent[];
|
|
7
|
+
selectedId: string;
|
|
8
|
+
onSelect: (id: string) => void;
|
|
9
|
+
renderAgent?: (agent: ChatAgent, state: { selected: boolean }) => React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function defaultRenderAgent(agent: ChatAgent) {
|
|
16
|
+
return (
|
|
17
|
+
<span className="inline-flex min-w-0 items-center gap-1.5">
|
|
18
|
+
{agent.icon ? (
|
|
19
|
+
<span aria-hidden className="text-sm leading-none">
|
|
20
|
+
{agent.icon}
|
|
21
|
+
</span>
|
|
22
|
+
) : null}
|
|
23
|
+
<span className="truncate">{agent.name}</span>
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* AgentSelector
|
|
30
|
+
*
|
|
31
|
+
* Keyboard support:
|
|
32
|
+
* - ArrowLeft / ArrowUp: move selection to previous agent
|
|
33
|
+
* - ArrowRight / ArrowDown: move selection to next agent
|
|
34
|
+
* - Home / End: jump to first / last agent
|
|
35
|
+
* - Enter / Space: select focused agent (native button behavior)
|
|
36
|
+
*/
|
|
37
|
+
export function AgentSelector(props: AgentSelectorProps) {
|
|
38
|
+
const {
|
|
39
|
+
agents,
|
|
40
|
+
selectedId,
|
|
41
|
+
onSelect,
|
|
42
|
+
renderAgent = defaultRenderAgent,
|
|
43
|
+
className,
|
|
44
|
+
disabled = false,
|
|
45
|
+
loading = false,
|
|
46
|
+
} = props;
|
|
47
|
+
|
|
48
|
+
const selectedIndex = useMemo(
|
|
49
|
+
() => agents.findIndex((a) => a.id === selectedId),
|
|
50
|
+
[agents, selectedId],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (loading) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={cn(
|
|
57
|
+
'inline-flex h-9 items-center rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-sm text-agent-muted-foreground',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
aria-live="polite"
|
|
61
|
+
>
|
|
62
|
+
Loading agents…
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (agents.length === 0) {
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
'inline-flex h-9 items-center rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-sm text-agent-muted-foreground',
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
No agents
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
role="radiogroup"
|
|
83
|
+
aria-label="Agent selector"
|
|
84
|
+
className={cn(
|
|
85
|
+
'inline-flex max-w-full items-center gap-1 rounded-agent-lg border border-agent-border bg-agent-canvas p-1',
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
{agents.map((agent, index) => {
|
|
90
|
+
const selected = agent.id === selectedId;
|
|
91
|
+
const tabIndex =
|
|
92
|
+
selected
|
|
93
|
+
? 0
|
|
94
|
+
: selectedIndex >= 0
|
|
95
|
+
? -1
|
|
96
|
+
: index === 0
|
|
97
|
+
? 0
|
|
98
|
+
: -1;
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<button
|
|
102
|
+
key={agent.id}
|
|
103
|
+
type="button"
|
|
104
|
+
role="radio"
|
|
105
|
+
aria-checked={selected}
|
|
106
|
+
aria-label={agent.name}
|
|
107
|
+
tabIndex={tabIndex}
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
onClick={() => onSelect(agent.id)}
|
|
110
|
+
onKeyDown={(event) => {
|
|
111
|
+
if (disabled) return;
|
|
112
|
+
|
|
113
|
+
let nextIndex = index;
|
|
114
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
115
|
+
nextIndex = Math.min(agents.length - 1, index + 1);
|
|
116
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
117
|
+
nextIndex = Math.max(0, index - 1);
|
|
118
|
+
} else if (event.key === 'Home') {
|
|
119
|
+
nextIndex = 0;
|
|
120
|
+
} else if (event.key === 'End') {
|
|
121
|
+
nextIndex = agents.length - 1;
|
|
122
|
+
} else {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (nextIndex !== index) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
const next = agents[nextIndex];
|
|
129
|
+
onSelect(next.id);
|
|
130
|
+
}
|
|
131
|
+
}}
|
|
132
|
+
className={cn(
|
|
133
|
+
'inline-flex h-7 min-w-0 max-w-[220px] items-center rounded-agent-md px-2.5 text-sm transition-colors',
|
|
134
|
+
selected
|
|
135
|
+
? 'bg-agent-accent text-agent-accent-foreground'
|
|
136
|
+
: 'text-agent-foreground hover:bg-agent-muted',
|
|
137
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
138
|
+
)}
|
|
139
|
+
title={agent.name}
|
|
140
|
+
>
|
|
141
|
+
{renderAgent(agent, { selected })}
|
|
142
|
+
</button>
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|