@stigmer/react 0.0.36

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 (190) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +248 -0
  3. package/agent/components/AgentCard.d.ts +9 -0
  4. package/agent/components/AgentCard.d.ts.map +1 -0
  5. package/agent/components/AgentCard.js +26 -0
  6. package/agent/components/AgentCard.js.map +1 -0
  7. package/agent/components/AgentOverview.d.ts +7 -0
  8. package/agent/components/AgentOverview.d.ts.map +1 -0
  9. package/agent/components/AgentOverview.js +36 -0
  10. package/agent/components/AgentOverview.js.map +1 -0
  11. package/agent/components/AgentPicker.d.ts +17 -0
  12. package/agent/components/AgentPicker.d.ts.map +1 -0
  13. package/agent/components/AgentPicker.js +86 -0
  14. package/agent/components/AgentPicker.js.map +1 -0
  15. package/agent/hooks/useAgentSearch.d.ts +28 -0
  16. package/agent/hooks/useAgentSearch.d.ts.map +1 -0
  17. package/agent/hooks/useAgentSearch.js +63 -0
  18. package/agent/hooks/useAgentSearch.js.map +1 -0
  19. package/agent/index.d.ts +9 -0
  20. package/agent/index.d.ts.map +1 -0
  21. package/agent/index.js +7 -0
  22. package/agent/index.js.map +1 -0
  23. package/agent-execution/components/ApprovalControls.d.ts +10 -0
  24. package/agent-execution/components/ApprovalControls.d.ts.map +1 -0
  25. package/agent-execution/components/ApprovalControls.js +19 -0
  26. package/agent-execution/components/ApprovalControls.js.map +1 -0
  27. package/agent-execution/components/ExecutionStatus.d.ts +8 -0
  28. package/agent-execution/components/ExecutionStatus.d.ts.map +1 -0
  29. package/agent-execution/components/ExecutionStatus.js +14 -0
  30. package/agent-execution/components/ExecutionStatus.js.map +1 -0
  31. package/agent-execution/components/ExecutionStream.d.ts +16 -0
  32. package/agent-execution/components/ExecutionStream.d.ts.map +1 -0
  33. package/agent-execution/components/ExecutionStream.js +39 -0
  34. package/agent-execution/components/ExecutionStream.js.map +1 -0
  35. package/agent-execution/components/MessageEntry.d.ts +17 -0
  36. package/agent-execution/components/MessageEntry.d.ts.map +1 -0
  37. package/agent-execution/components/MessageEntry.js +36 -0
  38. package/agent-execution/components/MessageEntry.js.map +1 -0
  39. package/agent-execution/components/MessageInput.d.ts +10 -0
  40. package/agent-execution/components/MessageInput.d.ts.map +1 -0
  41. package/agent-execution/components/MessageInput.js +27 -0
  42. package/agent-execution/components/MessageInput.js.map +1 -0
  43. package/agent-execution/components/OutputBlock.d.ts +9 -0
  44. package/agent-execution/components/OutputBlock.d.ts.map +1 -0
  45. package/agent-execution/components/OutputBlock.js +15 -0
  46. package/agent-execution/components/OutputBlock.js.map +1 -0
  47. package/agent-execution/components/SubAgentCard.d.ts +11 -0
  48. package/agent-execution/components/SubAgentCard.d.ts.map +1 -0
  49. package/agent-execution/components/SubAgentCard.js +19 -0
  50. package/agent-execution/components/SubAgentCard.js.map +1 -0
  51. package/agent-execution/components/ToolCallCard.d.ts +11 -0
  52. package/agent-execution/components/ToolCallCard.d.ts.map +1 -0
  53. package/agent-execution/components/ToolCallCard.js +25 -0
  54. package/agent-execution/components/ToolCallCard.js.map +1 -0
  55. package/agent-execution/helpers.d.ts +35 -0
  56. package/agent-execution/helpers.d.ts.map +1 -0
  57. package/agent-execution/helpers.js +157 -0
  58. package/agent-execution/helpers.js.map +1 -0
  59. package/agent-execution/hooks/useAgentExecution.d.ts +21 -0
  60. package/agent-execution/hooks/useAgentExecution.d.ts.map +1 -0
  61. package/agent-execution/hooks/useAgentExecution.js +99 -0
  62. package/agent-execution/hooks/useAgentExecution.js.map +1 -0
  63. package/agent-execution/hooks/useApproval.d.ts +12 -0
  64. package/agent-execution/hooks/useApproval.d.ts.map +1 -0
  65. package/agent-execution/hooks/useApproval.js +32 -0
  66. package/agent-execution/hooks/useApproval.js.map +1 -0
  67. package/agent-execution/index.d.ts +14 -0
  68. package/agent-execution/index.d.ts.map +1 -0
  69. package/agent-execution/index.js +15 -0
  70. package/agent-execution/index.js.map +1 -0
  71. package/catalog/components/ResourceSearchCard.d.ts +23 -0
  72. package/catalog/components/ResourceSearchCard.d.ts.map +1 -0
  73. package/catalog/components/ResourceSearchCard.js +36 -0
  74. package/catalog/components/ResourceSearchCard.js.map +1 -0
  75. package/catalog/index.d.ts +4 -0
  76. package/catalog/index.d.ts.map +1 -0
  77. package/catalog/index.js +5 -0
  78. package/catalog/index.js.map +1 -0
  79. package/catalog/internal/time.d.ts +13 -0
  80. package/catalog/internal/time.d.ts.map +1 -0
  81. package/catalog/internal/time.js +41 -0
  82. package/catalog/internal/time.js.map +1 -0
  83. package/context.d.ts +12 -0
  84. package/context.d.ts.map +1 -0
  85. package/context.js +13 -0
  86. package/context.js.map +1 -0
  87. package/hooks.d.ts +19 -0
  88. package/hooks.d.ts.map +1 -0
  89. package/hooks.js +28 -0
  90. package/hooks.js.map +1 -0
  91. package/index.d.ts +4 -0
  92. package/index.d.ts.map +1 -0
  93. package/index.js +6 -0
  94. package/index.js.map +1 -0
  95. package/internal/badge.d.ts +8 -0
  96. package/internal/badge.d.ts.map +1 -0
  97. package/internal/badge.js +34 -0
  98. package/internal/badge.js.map +1 -0
  99. package/internal/button.d.ts +9 -0
  100. package/internal/button.d.ts.map +1 -0
  101. package/internal/button.js +36 -0
  102. package/internal/button.js.map +1 -0
  103. package/internal/collapsible.d.ts +6 -0
  104. package/internal/collapsible.d.ts.map +1 -0
  105. package/internal/collapsible.js +14 -0
  106. package/internal/collapsible.js.map +1 -0
  107. package/internal/section.d.ts +8 -0
  108. package/internal/section.d.ts.map +1 -0
  109. package/internal/section.js +6 -0
  110. package/internal/section.js.map +1 -0
  111. package/internal/textarea.d.ts +4 -0
  112. package/internal/textarea.d.ts.map +1 -0
  113. package/internal/textarea.js +9 -0
  114. package/internal/textarea.js.map +1 -0
  115. package/mcp-server/hooks/useMcpServerSearch.d.ts +25 -0
  116. package/mcp-server/hooks/useMcpServerSearch.d.ts.map +1 -0
  117. package/mcp-server/hooks/useMcpServerSearch.js +57 -0
  118. package/mcp-server/hooks/useMcpServerSearch.js.map +1 -0
  119. package/mcp-server/index.d.ts +3 -0
  120. package/mcp-server/index.d.ts.map +1 -0
  121. package/mcp-server/index.js +3 -0
  122. package/mcp-server/index.js.map +1 -0
  123. package/package.json +75 -0
  124. package/provider.d.ts +55 -0
  125. package/provider.d.ts.map +1 -0
  126. package/provider.js +34 -0
  127. package/provider.js.map +1 -0
  128. package/session/components/AgentSessionHistory.d.ts +8 -0
  129. package/session/components/AgentSessionHistory.d.ts.map +1 -0
  130. package/session/components/AgentSessionHistory.js +11 -0
  131. package/session/components/AgentSessionHistory.js.map +1 -0
  132. package/session/components/SessionCard.d.ts +8 -0
  133. package/session/components/SessionCard.d.ts.map +1 -0
  134. package/session/components/SessionCard.js +57 -0
  135. package/session/components/SessionCard.js.map +1 -0
  136. package/session/hooks/useAgentSessionList.d.ts +21 -0
  137. package/session/hooks/useAgentSessionList.d.ts.map +1 -0
  138. package/session/hooks/useAgentSessionList.js +90 -0
  139. package/session/hooks/useAgentSessionList.js.map +1 -0
  140. package/session/index.d.ts +7 -0
  141. package/session/index.d.ts.map +1 -0
  142. package/session/index.js +6 -0
  143. package/session/index.js.map +1 -0
  144. package/skill/hooks/useSkillSearch.d.ts +25 -0
  145. package/skill/hooks/useSkillSearch.d.ts.map +1 -0
  146. package/skill/hooks/useSkillSearch.js +57 -0
  147. package/skill/hooks/useSkillSearch.js.map +1 -0
  148. package/skill/index.d.ts +3 -0
  149. package/skill/index.d.ts.map +1 -0
  150. package/skill/index.js +3 -0
  151. package/skill/index.js.map +1 -0
  152. package/src/agent/components/AgentCard.tsx +125 -0
  153. package/src/agent/components/AgentOverview.tsx +209 -0
  154. package/src/agent/components/AgentPicker.tsx +255 -0
  155. package/src/agent/hooks/useAgentSearch.ts +94 -0
  156. package/src/agent/index.ts +17 -0
  157. package/src/agent-execution/components/ApprovalControls.tsx +99 -0
  158. package/src/agent-execution/components/ExecutionStatus.tsx +33 -0
  159. package/src/agent-execution/components/ExecutionStream.tsx +148 -0
  160. package/src/agent-execution/components/MessageEntry.tsx +125 -0
  161. package/src/agent-execution/components/MessageInput.tsx +70 -0
  162. package/src/agent-execution/components/OutputBlock.tsx +43 -0
  163. package/src/agent-execution/components/SubAgentCard.tsx +138 -0
  164. package/src/agent-execution/components/ToolCallCard.tsx +153 -0
  165. package/src/agent-execution/helpers.ts +193 -0
  166. package/src/agent-execution/hooks/useAgentExecution.ts +147 -0
  167. package/src/agent-execution/hooks/useApproval.ts +56 -0
  168. package/src/agent-execution/index.ts +46 -0
  169. package/src/catalog/components/ResourceSearchCard.tsx +137 -0
  170. package/src/catalog/index.ts +6 -0
  171. package/src/catalog/internal/time.ts +40 -0
  172. package/src/context.ts +15 -0
  173. package/src/hooks.ts +32 -0
  174. package/src/index.ts +6 -0
  175. package/src/internal/badge.tsx +52 -0
  176. package/src/internal/button.tsx +60 -0
  177. package/src/internal/collapsible.tsx +21 -0
  178. package/src/internal/section.tsx +18 -0
  179. package/src/internal/textarea.tsx +23 -0
  180. package/src/mcp-server/hooks/useMcpServerSearch.ts +79 -0
  181. package/src/mcp-server/index.ts +6 -0
  182. package/src/provider.tsx +73 -0
  183. package/src/session/components/AgentSessionHistory.tsx +109 -0
  184. package/src/session/components/SessionCard.tsx +113 -0
  185. package/src/session/hooks/useAgentSessionList.ts +117 -0
  186. package/src/session/index.ts +13 -0
  187. package/src/skill/hooks/useSkillSearch.ts +79 -0
  188. package/src/skill/index.ts +6 -0
  189. package/src/styles.css +72 -0
  190. package/styles.css +2 -0
@@ -0,0 +1,33 @@
1
+ import { Badge } from "../../internal/badge";
2
+ import { phaseLabel, phaseVariant, isTerminalPhase } from "../helpers";
3
+ import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
4
+ import { cn } from "@stigmer/theme";
5
+ import { Loader2 } from "lucide-react";
6
+
7
+ interface ExecutionStatusProps {
8
+ phase: ExecutionPhase;
9
+ className?: string;
10
+ }
11
+
12
+ export function ExecutionStatus({ phase, className }: ExecutionStatusProps) {
13
+ const isActive =
14
+ phase === ExecutionPhase.EXECUTION_IN_PROGRESS ||
15
+ phase === ExecutionPhase.EXECUTION_PENDING;
16
+ const isWaiting = phase === ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
17
+
18
+ return (
19
+ <Badge
20
+ variant={phaseVariant(phase)}
21
+ className={cn(isWaiting && "animate-pulse", className)}
22
+ >
23
+ {isActive && (
24
+ <Loader2 className="size-3 animate-spin" data-icon="inline-start" />
25
+ )}
26
+ {phaseLabel(phase)}
27
+ {isTerminalPhase(phase) &&
28
+ phase === ExecutionPhase.EXECUTION_COMPLETED && (
29
+ <span data-icon="inline-end">✓</span>
30
+ )}
31
+ </Badge>
32
+ );
33
+ }
@@ -0,0 +1,148 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
+ import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
+ import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
7
+ import { ExecutionStatus } from "./ExecutionStatus";
8
+ import { MessageEntry, HumanMessageBubble } from "./MessageEntry";
9
+ import { MessageInput } from "./MessageInput";
10
+ import { buildSubAgentIndex, isTerminalPhase } from "../helpers";
11
+ import { cn } from "@stigmer/theme";
12
+ import { Button } from "../../internal/button";
13
+ import { ArrowDown, AlertCircle } from "lucide-react";
14
+
15
+ interface ExecutionStreamProps {
16
+ execution: AgentExecution | null;
17
+ phase: ExecutionPhase;
18
+ isConnected: boolean;
19
+ error: string | null;
20
+ onApproval?: (
21
+ toolCallId: string,
22
+ action: ApprovalAction,
23
+ comment?: string,
24
+ ) => Promise<void>;
25
+ isApprovalSubmitting?: boolean;
26
+ onSendMessage?: (message: string) => void;
27
+ className?: string;
28
+ }
29
+
30
+ export function ExecutionStream(props: ExecutionStreamProps) {
31
+ const {
32
+ execution,
33
+ phase,
34
+ isConnected: _isConnected,
35
+ error,
36
+ onApproval,
37
+ isApprovalSubmitting = false,
38
+ onSendMessage,
39
+ className,
40
+ } = props;
41
+ const scrollRef = useRef<HTMLDivElement>(null);
42
+ const bottomRef = useRef<HTMLDivElement>(null);
43
+ const [isAtBottom, setIsAtBottom] = useState(true);
44
+
45
+ const messages = execution?.status?.messages ?? [];
46
+ const subAgentIndex = useMemo(
47
+ () => (execution ? buildSubAgentIndex(execution) : new Map()),
48
+ [execution],
49
+ );
50
+
51
+ useEffect(() => {
52
+ if (isAtBottom) {
53
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
54
+ }
55
+ }, [messages.length, isAtBottom]);
56
+
57
+ const handleScroll = useCallback(() => {
58
+ const el = scrollRef.current;
59
+ if (!el) return;
60
+ const threshold = 48;
61
+ const atBottom =
62
+ el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
63
+ setIsAtBottom(atBottom);
64
+ }, []);
65
+
66
+ const scrollToBottom = useCallback(() => {
67
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
68
+ setIsAtBottom(true);
69
+ }, []);
70
+
71
+ const isTerminal = isTerminalPhase(phase);
72
+ const canSendMessage = isTerminal && !!onSendMessage;
73
+
74
+ return (
75
+ <div className={cn("flex flex-col", className)}>
76
+ <div className="flex items-center justify-between border-b px-4 py-2">
77
+ <ExecutionStatus phase={phase} />
78
+ {execution?.status?.startedAt && (
79
+ <time
80
+ dateTime={execution.status.startedAt}
81
+ className="text-muted-foreground text-xs"
82
+ >
83
+ {new Date(execution.status.startedAt).toLocaleTimeString()}
84
+ </time>
85
+ )}
86
+ </div>
87
+
88
+ <div
89
+ ref={scrollRef}
90
+ onScroll={handleScroll}
91
+ className="relative flex-1 overflow-y-auto"
92
+ >
93
+ <div className="space-y-4 p-4">
94
+ {messages.length === 0 && execution?.spec?.message && (
95
+ <HumanMessageBubble content={execution.spec.message} />
96
+ )}
97
+
98
+ {messages.map((msg, index) => (
99
+ <MessageEntry
100
+ key={index}
101
+ message={msg}
102
+ subAgentIndex={subAgentIndex}
103
+ onApproval={onApproval}
104
+ isApprovalSubmitting={isApprovalSubmitting}
105
+ />
106
+ ))}
107
+
108
+ {error && (
109
+ <div className="border-destructive/30 bg-destructive/5 text-destructive flex items-start gap-2 rounded-lg border p-3 text-sm">
110
+ <AlertCircle className="mt-0.5 size-4 shrink-0" />
111
+ <p>{error}</p>
112
+ </div>
113
+ )}
114
+
115
+ {execution?.status?.error && isTerminal && (
116
+ <div className="border-destructive/30 bg-destructive/5 text-destructive flex items-start gap-2 rounded-lg border p-3 text-sm">
117
+ <AlertCircle className="mt-0.5 size-4 shrink-0" />
118
+ <p>{execution.status.error}</p>
119
+ </div>
120
+ )}
121
+
122
+ <div ref={bottomRef} />
123
+ </div>
124
+
125
+ {!isAtBottom && (
126
+ <Button
127
+ size="icon"
128
+ variant="secondary"
129
+ onClick={scrollToBottom}
130
+ className="absolute right-4 bottom-4 z-10 rounded-full shadow-md"
131
+ aria-label="Scroll to bottom"
132
+ >
133
+ <ArrowDown className="size-4" />
134
+ </Button>
135
+ )}
136
+ </div>
137
+
138
+ {canSendMessage && (
139
+ <div className="border-t p-4">
140
+ <MessageInput
141
+ onSend={onSendMessage}
142
+ placeholder="Send a follow-up message..."
143
+ />
144
+ </div>
145
+ )}
146
+ </div>
147
+ );
148
+ }
@@ -0,0 +1,125 @@
1
+ import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
2
+ import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
3
+ import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
4
+ import { OutputBlock } from "./OutputBlock";
5
+ import { ToolCallCard } from "./ToolCallCard";
6
+ import { SubAgentCard } from "./SubAgentCard";
7
+ import { isHumanMessage, isAiMessage, isSystemMessage } from "../helpers";
8
+ import { User, BotMessageSquare, Info } from "lucide-react";
9
+
10
+ export interface MessageEntryProps {
11
+ message: AgentMessage;
12
+ subAgentIndex: Map<string, SubAgentExecution>;
13
+ onApproval?: (
14
+ toolCallId: string,
15
+ action: ApprovalAction,
16
+ comment?: string,
17
+ ) => Promise<void>;
18
+ isApprovalSubmitting?: boolean;
19
+ }
20
+
21
+ export function MessageEntry({
22
+ message,
23
+ subAgentIndex,
24
+ onApproval,
25
+ isApprovalSubmitting,
26
+ }: MessageEntryProps) {
27
+ if (isHumanMessage(message.type)) {
28
+ return <HumanMessageBubble content={message.content} />;
29
+ }
30
+
31
+ if (isAiMessage(message.type)) {
32
+ return (
33
+ <AiMessageBlock
34
+ message={message}
35
+ subAgentIndex={subAgentIndex}
36
+ onApproval={onApproval}
37
+ isApprovalSubmitting={isApprovalSubmitting}
38
+ />
39
+ );
40
+ }
41
+
42
+ if (isSystemMessage(message.type)) {
43
+ return <SystemMessageBlock content={message.content} />;
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ export function HumanMessageBubble({ content }: { content: string }) {
50
+ return (
51
+ <div className="flex items-start gap-3">
52
+ <div className="bg-muted flex size-7 shrink-0 items-center justify-center rounded-full">
53
+ <User className="text-muted-foreground size-3.5" />
54
+ </div>
55
+ <div className="bg-muted min-w-0 flex-1 rounded-lg px-3 py-2 text-sm">
56
+ <p className="whitespace-pre-wrap">{content}</p>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
61
+
62
+ function AiMessageBlock({
63
+ message,
64
+ subAgentIndex,
65
+ onApproval,
66
+ isApprovalSubmitting,
67
+ }: {
68
+ message: AgentMessage;
69
+ subAgentIndex: Map<string, SubAgentExecution>;
70
+ onApproval?: (
71
+ toolCallId: string,
72
+ action: ApprovalAction,
73
+ comment?: string,
74
+ ) => Promise<void>;
75
+ isApprovalSubmitting?: boolean;
76
+ }) {
77
+ return (
78
+ <div className="flex items-start gap-3">
79
+ <div className="bg-primary/10 flex size-7 shrink-0 items-center justify-center rounded-full">
80
+ <BotMessageSquare className="text-primary size-3.5" />
81
+ </div>
82
+ <div className="min-w-0 flex-1 space-y-2">
83
+ {message.content && (
84
+ <OutputBlock
85
+ content={message.content}
86
+ isStreaming={message.isStreaming}
87
+ model={message.model}
88
+ />
89
+ )}
90
+
91
+ {message.toolCalls.map((tc) => {
92
+ const subAgent = subAgentIndex.get(tc.id);
93
+ if (subAgent) {
94
+ return (
95
+ <SubAgentCard
96
+ key={tc.id}
97
+ subAgent={subAgent}
98
+ onApproval={onApproval}
99
+ isApprovalSubmitting={isApprovalSubmitting}
100
+ />
101
+ );
102
+ }
103
+ return (
104
+ <ToolCallCard
105
+ key={tc.id}
106
+ toolCall={tc}
107
+ onApproval={onApproval}
108
+ isApprovalSubmitting={isApprovalSubmitting}
109
+ />
110
+ );
111
+ })}
112
+ </div>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ export function SystemMessageBlock({ content }: { content: string }) {
118
+ if (!content) return null;
119
+ return (
120
+ <div className="text-muted-foreground flex items-center gap-2 text-xs">
121
+ <Info className="size-3 shrink-0" />
122
+ <p>{content}</p>
123
+ </div>
124
+ );
125
+ }
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { useCallback, useRef, type KeyboardEvent } from "react";
4
+ import { Textarea } from "../../internal/textarea";
5
+ import { Button } from "../../internal/button";
6
+ import { cn } from "@stigmer/theme";
7
+ import { SendHorizontal, Loader2 } from "lucide-react";
8
+
9
+ interface MessageInputProps {
10
+ onSend: (message: string) => void;
11
+ disabled?: boolean;
12
+ isLoading?: boolean;
13
+ placeholder?: string;
14
+ className?: string;
15
+ }
16
+
17
+ export function MessageInput({
18
+ onSend,
19
+ disabled = false,
20
+ isLoading = false,
21
+ placeholder = "Send a message...",
22
+ className,
23
+ }: MessageInputProps) {
24
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
25
+
26
+ const handleSend = useCallback(() => {
27
+ const value = textareaRef.current?.value.trim();
28
+ if (!value || disabled || isLoading) return;
29
+ onSend(value);
30
+ if (textareaRef.current) {
31
+ textareaRef.current.value = "";
32
+ }
33
+ }, [onSend, disabled, isLoading]);
34
+
35
+ const handleKeyDown = useCallback(
36
+ (e: KeyboardEvent<HTMLTextAreaElement>) => {
37
+ if (e.key === "Enter" && !e.shiftKey) {
38
+ e.preventDefault();
39
+ handleSend();
40
+ }
41
+ },
42
+ [handleSend],
43
+ );
44
+
45
+ return (
46
+ <div className={cn("flex items-end gap-2", className)}>
47
+ <Textarea
48
+ ref={textareaRef}
49
+ placeholder={placeholder}
50
+ disabled={disabled || isLoading}
51
+ onKeyDown={handleKeyDown}
52
+ className="min-h-10 resize-none"
53
+ rows={1}
54
+ />
55
+ <Button
56
+ size="icon"
57
+ onClick={handleSend}
58
+ disabled={disabled || isLoading}
59
+ className="shrink-0"
60
+ aria-label="Send message"
61
+ >
62
+ {isLoading ? (
63
+ <Loader2 className="size-4 animate-spin" />
64
+ ) : (
65
+ <SendHorizontal className="size-4" />
66
+ )}
67
+ </Button>
68
+ </div>
69
+ );
70
+ }
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import { memo } from "react";
4
+ import ReactMarkdown from "react-markdown";
5
+ import remarkGfm from "remark-gfm";
6
+ import { cn } from "@stigmer/theme";
7
+
8
+ interface OutputBlockProps {
9
+ content: string;
10
+ isStreaming?: boolean;
11
+ model?: string;
12
+ className?: string;
13
+ }
14
+
15
+ export const OutputBlock = memo(function OutputBlock({
16
+ content,
17
+ isStreaming = false,
18
+ model,
19
+ className,
20
+ }: OutputBlockProps) {
21
+ if (!content && !isStreaming) return null;
22
+
23
+ return (
24
+ <div className={cn("relative", className)}>
25
+ <div className="prose prose-sm dark:prose-invert [&_pre]:bg-muted [&_code]:bg-muted max-w-none break-words [&_code]:rounded [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-xs [&_code]:before:content-none [&_code]:after:content-none [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:p-3 [&_pre]:text-xs [&_table]:text-xs [&_td]:px-2 [&_td]:py-1 [&_th]:px-2 [&_th]:py-1">
26
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
27
+ {isStreaming && <StreamingCursor />}
28
+ </div>
29
+ {model && !isStreaming && (
30
+ <p className="text-muted-foreground/60 mt-1.5 text-[10px]">{model}</p>
31
+ )}
32
+ </div>
33
+ );
34
+ });
35
+
36
+ function StreamingCursor() {
37
+ return (
38
+ <span
39
+ className="bg-foreground ml-0.5 inline-block h-4 w-0.5 animate-pulse align-text-bottom"
40
+ aria-label="Generating..."
41
+ />
42
+ );
43
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Badge } from "../../internal/badge";
5
+ import {
6
+ Collapsible,
7
+ CollapsibleContent,
8
+ CollapsibleTrigger,
9
+ } from "../../internal/collapsible";
10
+ import { OutputBlock } from "./OutputBlock";
11
+ import { ToolCallCard } from "./ToolCallCard";
12
+ import {
13
+ subAgentStatusLabel,
14
+ subAgentStatusVariant,
15
+ formatDuration,
16
+ isAiMessage,
17
+ } from "../helpers";
18
+ import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
19
+ import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
20
+ import { SubAgentStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
21
+ import { cn } from "@stigmer/theme";
22
+ import { ChevronRight, Bot, Loader2 } from "lucide-react";
23
+
24
+ interface SubAgentCardProps {
25
+ subAgent: SubAgentExecution;
26
+ onApproval?: (
27
+ toolCallId: string,
28
+ action: ApprovalAction,
29
+ comment?: string,
30
+ ) => Promise<void>;
31
+ isApprovalSubmitting?: boolean;
32
+ className?: string;
33
+ }
34
+
35
+ export function SubAgentCard({
36
+ subAgent,
37
+ onApproval,
38
+ isApprovalSubmitting = false,
39
+ className,
40
+ }: SubAgentCardProps) {
41
+ const [open, setOpen] = useState(false);
42
+ const isActive = subAgent.status === SubAgentStatus.SUB_AGENT_IN_PROGRESS;
43
+ const duration = formatDuration(subAgent.startedAt, subAgent.completedAt);
44
+ const aiMessages = subAgent.messages.filter((m) => isAiMessage(m.type));
45
+
46
+ return (
47
+ <Collapsible open={open} onOpenChange={setOpen}>
48
+ <div
49
+ className={cn(
50
+ "bg-card text-card-foreground rounded-lg border border-dashed text-sm",
51
+ className,
52
+ )}
53
+ >
54
+ <CollapsibleTrigger className="hover:bg-muted/50 flex w-full items-center gap-2 rounded-t-lg px-3 py-2 text-left transition-colors">
55
+ <ChevronRight
56
+ className={cn(
57
+ "text-muted-foreground size-3.5 shrink-0 transition-transform",
58
+ open && "rotate-90",
59
+ )}
60
+ />
61
+ {isActive ? (
62
+ <Loader2 className="text-muted-foreground size-3.5 shrink-0 animate-spin" />
63
+ ) : (
64
+ <Bot className="text-muted-foreground size-3.5 shrink-0" />
65
+ )}
66
+ <span className="truncate text-xs font-medium">
67
+ {subAgent.subject || subAgent.name}
68
+ </span>
69
+ <Badge
70
+ variant={subAgentStatusVariant(subAgent.status)}
71
+ className="ml-auto shrink-0 text-[10px]"
72
+ >
73
+ {subAgentStatusLabel(subAgent.status)}
74
+ </Badge>
75
+ {duration && (
76
+ <span className="text-muted-foreground shrink-0 text-[10px]">
77
+ {duration}
78
+ </span>
79
+ )}
80
+ {subAgent.toolCalls.length > 0 && (
81
+ <span className="text-muted-foreground shrink-0 text-[10px]">
82
+ {subAgent.toolCalls.length} tool
83
+ {subAgent.toolCalls.length !== 1 && "s"}
84
+ </span>
85
+ )}
86
+ </CollapsibleTrigger>
87
+
88
+ <CollapsibleContent>
89
+ <div className="space-y-3 border-t px-3 py-2">
90
+ {subAgent.input && (
91
+ <div>
92
+ <p className="text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase">
93
+ Task
94
+ </p>
95
+ <p className="text-muted-foreground text-xs">
96
+ {subAgent.input}
97
+ </p>
98
+ </div>
99
+ )}
100
+
101
+ {aiMessages.map((msg, i) => (
102
+ <OutputBlock
103
+ key={i}
104
+ content={msg.content}
105
+ isStreaming={msg.isStreaming}
106
+ model={msg.model}
107
+ />
108
+ ))}
109
+
110
+ {subAgent.toolCalls.map((tc) => (
111
+ <ToolCallCard
112
+ key={tc.id}
113
+ toolCall={tc}
114
+ onApproval={onApproval}
115
+ isApprovalSubmitting={isApprovalSubmitting}
116
+ />
117
+ ))}
118
+
119
+ {subAgent.error && (
120
+ <p className="text-destructive text-xs">{subAgent.error}</p>
121
+ )}
122
+
123
+ {subAgent.output && (
124
+ <div>
125
+ <p className="text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase">
126
+ Result
127
+ </p>
128
+ <pre className="bg-muted overflow-x-auto rounded p-2 text-[11px] leading-relaxed whitespace-pre-wrap">
129
+ {subAgent.output}
130
+ </pre>
131
+ </div>
132
+ )}
133
+ </div>
134
+ </CollapsibleContent>
135
+ </div>
136
+ </Collapsible>
137
+ );
138
+ }