@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.
- package/LICENSE +190 -0
- package/README.md +248 -0
- package/agent/components/AgentCard.d.ts +9 -0
- package/agent/components/AgentCard.d.ts.map +1 -0
- package/agent/components/AgentCard.js +26 -0
- package/agent/components/AgentCard.js.map +1 -0
- package/agent/components/AgentOverview.d.ts +7 -0
- package/agent/components/AgentOverview.d.ts.map +1 -0
- package/agent/components/AgentOverview.js +36 -0
- package/agent/components/AgentOverview.js.map +1 -0
- package/agent/components/AgentPicker.d.ts +17 -0
- package/agent/components/AgentPicker.d.ts.map +1 -0
- package/agent/components/AgentPicker.js +86 -0
- package/agent/components/AgentPicker.js.map +1 -0
- package/agent/hooks/useAgentSearch.d.ts +28 -0
- package/agent/hooks/useAgentSearch.d.ts.map +1 -0
- package/agent/hooks/useAgentSearch.js +63 -0
- package/agent/hooks/useAgentSearch.js.map +1 -0
- package/agent/index.d.ts +9 -0
- package/agent/index.d.ts.map +1 -0
- package/agent/index.js +7 -0
- package/agent/index.js.map +1 -0
- package/agent-execution/components/ApprovalControls.d.ts +10 -0
- package/agent-execution/components/ApprovalControls.d.ts.map +1 -0
- package/agent-execution/components/ApprovalControls.js +19 -0
- package/agent-execution/components/ApprovalControls.js.map +1 -0
- package/agent-execution/components/ExecutionStatus.d.ts +8 -0
- package/agent-execution/components/ExecutionStatus.d.ts.map +1 -0
- package/agent-execution/components/ExecutionStatus.js +14 -0
- package/agent-execution/components/ExecutionStatus.js.map +1 -0
- package/agent-execution/components/ExecutionStream.d.ts +16 -0
- package/agent-execution/components/ExecutionStream.d.ts.map +1 -0
- package/agent-execution/components/ExecutionStream.js +39 -0
- package/agent-execution/components/ExecutionStream.js.map +1 -0
- package/agent-execution/components/MessageEntry.d.ts +17 -0
- package/agent-execution/components/MessageEntry.d.ts.map +1 -0
- package/agent-execution/components/MessageEntry.js +36 -0
- package/agent-execution/components/MessageEntry.js.map +1 -0
- package/agent-execution/components/MessageInput.d.ts +10 -0
- package/agent-execution/components/MessageInput.d.ts.map +1 -0
- package/agent-execution/components/MessageInput.js +27 -0
- package/agent-execution/components/MessageInput.js.map +1 -0
- package/agent-execution/components/OutputBlock.d.ts +9 -0
- package/agent-execution/components/OutputBlock.d.ts.map +1 -0
- package/agent-execution/components/OutputBlock.js +15 -0
- package/agent-execution/components/OutputBlock.js.map +1 -0
- package/agent-execution/components/SubAgentCard.d.ts +11 -0
- package/agent-execution/components/SubAgentCard.d.ts.map +1 -0
- package/agent-execution/components/SubAgentCard.js +19 -0
- package/agent-execution/components/SubAgentCard.js.map +1 -0
- package/agent-execution/components/ToolCallCard.d.ts +11 -0
- package/agent-execution/components/ToolCallCard.d.ts.map +1 -0
- package/agent-execution/components/ToolCallCard.js +25 -0
- package/agent-execution/components/ToolCallCard.js.map +1 -0
- package/agent-execution/helpers.d.ts +35 -0
- package/agent-execution/helpers.d.ts.map +1 -0
- package/agent-execution/helpers.js +157 -0
- package/agent-execution/helpers.js.map +1 -0
- package/agent-execution/hooks/useAgentExecution.d.ts +21 -0
- package/agent-execution/hooks/useAgentExecution.d.ts.map +1 -0
- package/agent-execution/hooks/useAgentExecution.js +99 -0
- package/agent-execution/hooks/useAgentExecution.js.map +1 -0
- package/agent-execution/hooks/useApproval.d.ts +12 -0
- package/agent-execution/hooks/useApproval.d.ts.map +1 -0
- package/agent-execution/hooks/useApproval.js +32 -0
- package/agent-execution/hooks/useApproval.js.map +1 -0
- package/agent-execution/index.d.ts +14 -0
- package/agent-execution/index.d.ts.map +1 -0
- package/agent-execution/index.js +15 -0
- package/agent-execution/index.js.map +1 -0
- package/catalog/components/ResourceSearchCard.d.ts +23 -0
- package/catalog/components/ResourceSearchCard.d.ts.map +1 -0
- package/catalog/components/ResourceSearchCard.js +36 -0
- package/catalog/components/ResourceSearchCard.js.map +1 -0
- package/catalog/index.d.ts +4 -0
- package/catalog/index.d.ts.map +1 -0
- package/catalog/index.js +5 -0
- package/catalog/index.js.map +1 -0
- package/catalog/internal/time.d.ts +13 -0
- package/catalog/internal/time.d.ts.map +1 -0
- package/catalog/internal/time.js +41 -0
- package/catalog/internal/time.js.map +1 -0
- package/context.d.ts +12 -0
- package/context.d.ts.map +1 -0
- package/context.js +13 -0
- package/context.js.map +1 -0
- package/hooks.d.ts +19 -0
- package/hooks.d.ts.map +1 -0
- package/hooks.js +28 -0
- package/hooks.js.map +1 -0
- package/index.d.ts +4 -0
- package/index.d.ts.map +1 -0
- package/index.js +6 -0
- package/index.js.map +1 -0
- package/internal/badge.d.ts +8 -0
- package/internal/badge.d.ts.map +1 -0
- package/internal/badge.js +34 -0
- package/internal/badge.js.map +1 -0
- package/internal/button.d.ts +9 -0
- package/internal/button.d.ts.map +1 -0
- package/internal/button.js +36 -0
- package/internal/button.js.map +1 -0
- package/internal/collapsible.d.ts +6 -0
- package/internal/collapsible.d.ts.map +1 -0
- package/internal/collapsible.js +14 -0
- package/internal/collapsible.js.map +1 -0
- package/internal/section.d.ts +8 -0
- package/internal/section.d.ts.map +1 -0
- package/internal/section.js +6 -0
- package/internal/section.js.map +1 -0
- package/internal/textarea.d.ts +4 -0
- package/internal/textarea.d.ts.map +1 -0
- package/internal/textarea.js +9 -0
- package/internal/textarea.js.map +1 -0
- package/mcp-server/hooks/useMcpServerSearch.d.ts +25 -0
- package/mcp-server/hooks/useMcpServerSearch.d.ts.map +1 -0
- package/mcp-server/hooks/useMcpServerSearch.js +57 -0
- package/mcp-server/hooks/useMcpServerSearch.js.map +1 -0
- package/mcp-server/index.d.ts +3 -0
- package/mcp-server/index.d.ts.map +1 -0
- package/mcp-server/index.js +3 -0
- package/mcp-server/index.js.map +1 -0
- package/package.json +75 -0
- package/provider.d.ts +55 -0
- package/provider.d.ts.map +1 -0
- package/provider.js +34 -0
- package/provider.js.map +1 -0
- package/session/components/AgentSessionHistory.d.ts +8 -0
- package/session/components/AgentSessionHistory.d.ts.map +1 -0
- package/session/components/AgentSessionHistory.js +11 -0
- package/session/components/AgentSessionHistory.js.map +1 -0
- package/session/components/SessionCard.d.ts +8 -0
- package/session/components/SessionCard.d.ts.map +1 -0
- package/session/components/SessionCard.js +57 -0
- package/session/components/SessionCard.js.map +1 -0
- package/session/hooks/useAgentSessionList.d.ts +21 -0
- package/session/hooks/useAgentSessionList.d.ts.map +1 -0
- package/session/hooks/useAgentSessionList.js +90 -0
- package/session/hooks/useAgentSessionList.js.map +1 -0
- package/session/index.d.ts +7 -0
- package/session/index.d.ts.map +1 -0
- package/session/index.js +6 -0
- package/session/index.js.map +1 -0
- package/skill/hooks/useSkillSearch.d.ts +25 -0
- package/skill/hooks/useSkillSearch.d.ts.map +1 -0
- package/skill/hooks/useSkillSearch.js +57 -0
- package/skill/hooks/useSkillSearch.js.map +1 -0
- package/skill/index.d.ts +3 -0
- package/skill/index.d.ts.map +1 -0
- package/skill/index.js +3 -0
- package/skill/index.js.map +1 -0
- package/src/agent/components/AgentCard.tsx +125 -0
- package/src/agent/components/AgentOverview.tsx +209 -0
- package/src/agent/components/AgentPicker.tsx +255 -0
- package/src/agent/hooks/useAgentSearch.ts +94 -0
- package/src/agent/index.ts +17 -0
- package/src/agent-execution/components/ApprovalControls.tsx +99 -0
- package/src/agent-execution/components/ExecutionStatus.tsx +33 -0
- package/src/agent-execution/components/ExecutionStream.tsx +148 -0
- package/src/agent-execution/components/MessageEntry.tsx +125 -0
- package/src/agent-execution/components/MessageInput.tsx +70 -0
- package/src/agent-execution/components/OutputBlock.tsx +43 -0
- package/src/agent-execution/components/SubAgentCard.tsx +138 -0
- package/src/agent-execution/components/ToolCallCard.tsx +153 -0
- package/src/agent-execution/helpers.ts +193 -0
- package/src/agent-execution/hooks/useAgentExecution.ts +147 -0
- package/src/agent-execution/hooks/useApproval.ts +56 -0
- package/src/agent-execution/index.ts +46 -0
- package/src/catalog/components/ResourceSearchCard.tsx +137 -0
- package/src/catalog/index.ts +6 -0
- package/src/catalog/internal/time.ts +40 -0
- package/src/context.ts +15 -0
- package/src/hooks.ts +32 -0
- package/src/index.ts +6 -0
- package/src/internal/badge.tsx +52 -0
- package/src/internal/button.tsx +60 -0
- package/src/internal/collapsible.tsx +21 -0
- package/src/internal/section.tsx +18 -0
- package/src/internal/textarea.tsx +23 -0
- package/src/mcp-server/hooks/useMcpServerSearch.ts +79 -0
- package/src/mcp-server/index.ts +6 -0
- package/src/provider.tsx +73 -0
- package/src/session/components/AgentSessionHistory.tsx +109 -0
- package/src/session/components/SessionCard.tsx +113 -0
- package/src/session/hooks/useAgentSessionList.ts +117 -0
- package/src/session/index.ts +13 -0
- package/src/skill/hooks/useSkillSearch.ts +79 -0
- package/src/skill/index.ts +6 -0
- package/src/styles.css +72 -0
- 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
|
+
}
|