@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,209 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
5
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
6
|
+
import {
|
|
7
|
+
Bot,
|
|
8
|
+
Globe,
|
|
9
|
+
Users,
|
|
10
|
+
Server,
|
|
11
|
+
FileCode2,
|
|
12
|
+
Wrench,
|
|
13
|
+
ChevronDown,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
|
|
16
|
+
import { cn } from "@stigmer/theme";
|
|
17
|
+
|
|
18
|
+
import { Badge } from "../../internal/badge";
|
|
19
|
+
import {
|
|
20
|
+
Collapsible,
|
|
21
|
+
CollapsibleTrigger,
|
|
22
|
+
CollapsibleContent,
|
|
23
|
+
} from "../../internal/collapsible";
|
|
24
|
+
import { Section } from "../../internal/section";
|
|
25
|
+
|
|
26
|
+
const INSTRUCTIONS_COLLAPSE_THRESHOLD = 300;
|
|
27
|
+
|
|
28
|
+
export interface AgentOverviewProps {
|
|
29
|
+
agent: Agent;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function AgentOverview({ agent, className }: AgentOverviewProps) {
|
|
34
|
+
const meta = agent.metadata;
|
|
35
|
+
const spec = agent.spec;
|
|
36
|
+
const isPublic = meta?.visibility === ApiResourceVisibility.visibility_public;
|
|
37
|
+
const qualifiedSlug = meta?.org ? `${meta.org}/${meta.slug}` : meta?.slug;
|
|
38
|
+
const hasLongInstructions =
|
|
39
|
+
(spec?.instructions?.length ?? 0) > INSTRUCTIONS_COLLAPSE_THRESHOLD;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className={cn("space-y-8", className)}>
|
|
43
|
+
<div className="space-y-2">
|
|
44
|
+
<div className="flex items-center gap-3">
|
|
45
|
+
{spec?.iconUrl ? (
|
|
46
|
+
<img
|
|
47
|
+
src={spec.iconUrl}
|
|
48
|
+
alt=""
|
|
49
|
+
className="size-10 rounded-lg object-cover"
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<div className="bg-muted flex size-10 items-center justify-center rounded-lg">
|
|
53
|
+
<Bot className="text-muted-foreground size-5" />
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
<div>
|
|
57
|
+
<h2 className="text-xl font-semibold">{meta?.name}</h2>
|
|
58
|
+
<p className="text-muted-foreground font-mono text-xs">
|
|
59
|
+
{qualifiedSlug}
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
{spec?.description && (
|
|
64
|
+
<p className="text-muted-foreground max-w-prose text-sm">
|
|
65
|
+
{spec.description}
|
|
66
|
+
</p>
|
|
67
|
+
)}
|
|
68
|
+
<div className="flex items-center gap-2">
|
|
69
|
+
{isPublic ? (
|
|
70
|
+
<Badge variant="outline" className="gap-1">
|
|
71
|
+
<Globe className="size-3" />
|
|
72
|
+
Public
|
|
73
|
+
</Badge>
|
|
74
|
+
) : (
|
|
75
|
+
<Badge variant="secondary" className="gap-1">
|
|
76
|
+
<Users className="size-3" />
|
|
77
|
+
Private
|
|
78
|
+
</Badge>
|
|
79
|
+
)}
|
|
80
|
+
{meta?.tags?.map((tag: string) => (
|
|
81
|
+
<Badge key={tag} variant="secondary">
|
|
82
|
+
{tag}
|
|
83
|
+
</Badge>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{spec?.instructions && (
|
|
89
|
+
<Section title="Instructions">
|
|
90
|
+
{hasLongInstructions ? (
|
|
91
|
+
<CollapsibleInstructions instructions={spec.instructions} />
|
|
92
|
+
) : (
|
|
93
|
+
<pre className="bg-muted rounded-lg p-4 font-mono text-xs leading-relaxed whitespace-pre-wrap">
|
|
94
|
+
{spec.instructions}
|
|
95
|
+
</pre>
|
|
96
|
+
)}
|
|
97
|
+
</Section>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{spec?.mcpServerUsages && spec.mcpServerUsages.length > 0 && (
|
|
101
|
+
<Section title="MCP Servers">
|
|
102
|
+
<div className="space-y-3">
|
|
103
|
+
{spec.mcpServerUsages.map((usage, i) => {
|
|
104
|
+
const ref = usage.mcpServerRef;
|
|
105
|
+
const slug = ref
|
|
106
|
+
? ref.org
|
|
107
|
+
? `${ref.org}/${ref.slug}`
|
|
108
|
+
: ref.slug
|
|
109
|
+
: `server-${i}`;
|
|
110
|
+
return (
|
|
111
|
+
<div key={slug} className="rounded-lg border p-3 text-sm">
|
|
112
|
+
<div className="flex items-center gap-2">
|
|
113
|
+
<Server className="text-muted-foreground size-4 shrink-0" />
|
|
114
|
+
<span className="font-mono font-medium">{slug}</span>
|
|
115
|
+
</div>
|
|
116
|
+
{usage.enabledTools.length > 0 && (
|
|
117
|
+
<div className="mt-2 flex flex-wrap gap-1">
|
|
118
|
+
{usage.enabledTools.map((tool) => (
|
|
119
|
+
<Badge
|
|
120
|
+
key={tool}
|
|
121
|
+
variant="secondary"
|
|
122
|
+
className="text-[10px]"
|
|
123
|
+
>
|
|
124
|
+
<Wrench className="mr-0.5 size-2.5" />
|
|
125
|
+
{tool}
|
|
126
|
+
</Badge>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
{usage.toolApprovalOverrides.length > 0 && (
|
|
131
|
+
<div className="text-muted-foreground mt-2 text-xs">
|
|
132
|
+
{usage.toolApprovalOverrides.length} approval override
|
|
133
|
+
{usage.toolApprovalOverrides.length !== 1 ? "s" : ""}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
})}
|
|
139
|
+
</div>
|
|
140
|
+
</Section>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{spec?.skillRefs && spec.skillRefs.length > 0 && (
|
|
144
|
+
<Section title="Skills">
|
|
145
|
+
<div className="space-y-2">
|
|
146
|
+
{spec.skillRefs.map((ref, i) => {
|
|
147
|
+
const slug = ref.org ? `${ref.org}/${ref.slug}` : ref.slug;
|
|
148
|
+
return (
|
|
149
|
+
<div
|
|
150
|
+
key={slug || i}
|
|
151
|
+
className="flex items-center gap-2 rounded-lg border p-3 text-sm"
|
|
152
|
+
>
|
|
153
|
+
<FileCode2 className="text-muted-foreground size-4 shrink-0" />
|
|
154
|
+
<span className="font-mono font-medium">{slug}</span>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
})}
|
|
158
|
+
</div>
|
|
159
|
+
</Section>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{spec?.subAgents && spec.subAgents.length > 0 && (
|
|
163
|
+
<Section title="Sub-Agents">
|
|
164
|
+
<div className="space-y-3">
|
|
165
|
+
{spec.subAgents.map((sub) => (
|
|
166
|
+
<div key={sub.name} className="rounded-lg border p-3 text-sm">
|
|
167
|
+
<div className="flex items-center gap-2">
|
|
168
|
+
<Bot className="text-muted-foreground size-4 shrink-0" />
|
|
169
|
+
<span className="font-medium">{sub.name}</span>
|
|
170
|
+
{sub.modelOverride && (
|
|
171
|
+
<Badge variant="outline" className="text-[10px]">
|
|
172
|
+
{sub.modelOverride}
|
|
173
|
+
</Badge>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
{sub.description && (
|
|
177
|
+
<p className="text-muted-foreground mt-1 text-xs">
|
|
178
|
+
{sub.description}
|
|
179
|
+
</p>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
</Section>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function CollapsibleInstructions({ instructions }: { instructions: string }) {
|
|
191
|
+
const [open, setOpen] = useState(false);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<Collapsible open={open} onOpenChange={setOpen}>
|
|
195
|
+
<pre className="bg-muted rounded-lg p-4 font-mono text-xs leading-relaxed whitespace-pre-wrap">
|
|
196
|
+
{open
|
|
197
|
+
? instructions
|
|
198
|
+
: instructions.slice(0, INSTRUCTIONS_COLLAPSE_THRESHOLD) + "..."}
|
|
199
|
+
</pre>
|
|
200
|
+
<CollapsibleContent />
|
|
201
|
+
<CollapsibleTrigger className="text-muted-foreground hover:text-foreground mt-2 flex items-center gap-1 text-xs">
|
|
202
|
+
<ChevronDown
|
|
203
|
+
className={cn("size-3.5 transition-transform", open && "rotate-180")}
|
|
204
|
+
/>
|
|
205
|
+
{open ? "Show less" : "Show full instructions"}
|
|
206
|
+
</CollapsibleTrigger>
|
|
207
|
+
</Collapsible>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useId,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { Bot, Search, X, Loader2 } from "lucide-react";
|
|
11
|
+
import { cn } from "@stigmer/theme";
|
|
12
|
+
import { useAgentSearch, type AgentSearchResult } from "../hooks/useAgentSearch";
|
|
13
|
+
|
|
14
|
+
export interface SelectedAgent {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
qualifiedSlug: string;
|
|
18
|
+
org: string;
|
|
19
|
+
description: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AgentPickerProps {
|
|
23
|
+
org: string;
|
|
24
|
+
onSelect: (agent: SelectedAgent) => void;
|
|
25
|
+
onClear: () => void;
|
|
26
|
+
selected: SelectedAgent | null;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function AgentPicker({
|
|
32
|
+
org,
|
|
33
|
+
onSelect,
|
|
34
|
+
onClear,
|
|
35
|
+
selected,
|
|
36
|
+
disabled = false,
|
|
37
|
+
className,
|
|
38
|
+
}: AgentPickerProps) {
|
|
39
|
+
const instanceId = useId();
|
|
40
|
+
const listboxId = `${instanceId}-listbox`;
|
|
41
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
42
|
+
|
|
43
|
+
const { query, setQuery, results, isLoading, error } = useAgentSearch({ org });
|
|
44
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
45
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
46
|
+
|
|
47
|
+
const showResults = isOpen && !selected;
|
|
48
|
+
|
|
49
|
+
const handleSelect = useCallback(
|
|
50
|
+
(index: number) => {
|
|
51
|
+
const result = results[index];
|
|
52
|
+
if (!result) return;
|
|
53
|
+
onSelect({
|
|
54
|
+
id: result.id,
|
|
55
|
+
name: result.name,
|
|
56
|
+
qualifiedSlug: result.qualifiedSlug,
|
|
57
|
+
org: result.org,
|
|
58
|
+
description: result.description,
|
|
59
|
+
});
|
|
60
|
+
setIsOpen(false);
|
|
61
|
+
setActiveIndex(-1);
|
|
62
|
+
},
|
|
63
|
+
[results, onSelect],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const handleClear = useCallback(() => {
|
|
67
|
+
onClear();
|
|
68
|
+
setQuery("");
|
|
69
|
+
setActiveIndex(-1);
|
|
70
|
+
setIsOpen(true);
|
|
71
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
72
|
+
}, [onClear, setQuery]);
|
|
73
|
+
|
|
74
|
+
const handleKeyDown = useCallback(
|
|
75
|
+
(e: KeyboardEvent<HTMLInputElement>) => {
|
|
76
|
+
if (!showResults) return;
|
|
77
|
+
|
|
78
|
+
switch (e.key) {
|
|
79
|
+
case "ArrowDown": {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
setActiveIndex((prev) => (prev < results.length - 1 ? prev + 1 : 0));
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "ArrowUp": {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
setActiveIndex((prev) => (prev > 0 ? prev - 1 : results.length - 1));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "Enter": {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
if (activeIndex >= 0 && activeIndex < results.length) {
|
|
92
|
+
handleSelect(activeIndex);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "Escape": {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
setIsOpen(false);
|
|
99
|
+
setActiveIndex(-1);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[showResults, results.length, activeIndex, handleSelect],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (selected) {
|
|
108
|
+
return (
|
|
109
|
+
<div className={cn("stgm-agent-picker space-y-1", className)}>
|
|
110
|
+
<label className="text-muted-foreground text-sm font-medium">
|
|
111
|
+
Agent
|
|
112
|
+
</label>
|
|
113
|
+
<div className="bg-muted/50 flex items-center gap-3 rounded-lg border px-3 py-2.5">
|
|
114
|
+
<div className="bg-primary/10 flex size-8 shrink-0 items-center justify-center rounded-md">
|
|
115
|
+
<Bot className="text-primary size-4" />
|
|
116
|
+
</div>
|
|
117
|
+
<div className="min-w-0 flex-1">
|
|
118
|
+
<p className="truncate text-sm font-medium">{selected.name}</p>
|
|
119
|
+
<p className="text-muted-foreground truncate font-mono text-xs">
|
|
120
|
+
{selected.qualifiedSlug}
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
onClick={handleClear}
|
|
126
|
+
disabled={disabled}
|
|
127
|
+
aria-label="Change agent"
|
|
128
|
+
className={cn(
|
|
129
|
+
"inline-flex size-7 shrink-0 items-center justify-center rounded-md",
|
|
130
|
+
"text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
131
|
+
"transition-colors focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
132
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
<X className="size-3.5" />
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const activeOptionId =
|
|
143
|
+
activeIndex >= 0 ? `${instanceId}-option-${activeIndex}` : undefined;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className={cn("stgm-agent-picker space-y-1", className)}>
|
|
147
|
+
<label
|
|
148
|
+
htmlFor={`${instanceId}-input`}
|
|
149
|
+
className="text-muted-foreground text-sm font-medium"
|
|
150
|
+
>
|
|
151
|
+
Agent
|
|
152
|
+
</label>
|
|
153
|
+
|
|
154
|
+
<div className="relative">
|
|
155
|
+
<div className="relative">
|
|
156
|
+
<Search className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2" />
|
|
157
|
+
<input
|
|
158
|
+
ref={inputRef}
|
|
159
|
+
id={`${instanceId}-input`}
|
|
160
|
+
type="text"
|
|
161
|
+
role="combobox"
|
|
162
|
+
aria-expanded={showResults}
|
|
163
|
+
aria-controls={listboxId}
|
|
164
|
+
aria-activedescendant={activeOptionId}
|
|
165
|
+
aria-autocomplete="list"
|
|
166
|
+
aria-label="Search agents"
|
|
167
|
+
value={query}
|
|
168
|
+
disabled={disabled}
|
|
169
|
+
placeholder="Search agents..."
|
|
170
|
+
className={cn(
|
|
171
|
+
"bg-background w-full rounded-lg border py-2.5 pr-9 pl-9 text-sm",
|
|
172
|
+
"placeholder:text-muted-foreground",
|
|
173
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
174
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
175
|
+
)}
|
|
176
|
+
onChange={(e) => {
|
|
177
|
+
setQuery(e.target.value);
|
|
178
|
+
setActiveIndex(-1);
|
|
179
|
+
if (!isOpen) setIsOpen(true);
|
|
180
|
+
}}
|
|
181
|
+
onFocus={() => setIsOpen(true)}
|
|
182
|
+
onBlur={() => {
|
|
183
|
+
setTimeout(() => setIsOpen(false), 150);
|
|
184
|
+
}}
|
|
185
|
+
onKeyDown={handleKeyDown}
|
|
186
|
+
/>
|
|
187
|
+
{isLoading && (
|
|
188
|
+
<Loader2 className="text-muted-foreground absolute top-1/2 right-3 size-4 -translate-y-1/2 animate-spin" />
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{showResults && (
|
|
193
|
+
<ul
|
|
194
|
+
id={listboxId}
|
|
195
|
+
role="listbox"
|
|
196
|
+
aria-label="Agent search results"
|
|
197
|
+
className={cn(
|
|
198
|
+
"bg-popover absolute z-popover mt-1 max-h-64 w-full overflow-y-auto rounded-lg border shadow-md",
|
|
199
|
+
results.length === 0 && !isLoading && "p-3",
|
|
200
|
+
)}
|
|
201
|
+
>
|
|
202
|
+
{results.length === 0 && !isLoading && (
|
|
203
|
+
<li
|
|
204
|
+
className="text-muted-foreground text-center text-sm"
|
|
205
|
+
role="presentation"
|
|
206
|
+
>
|
|
207
|
+
{error
|
|
208
|
+
? error
|
|
209
|
+
: query
|
|
210
|
+
? "No agents found"
|
|
211
|
+
: "No agents available"}
|
|
212
|
+
</li>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
{results.map((result, index) => (
|
|
216
|
+
<li
|
|
217
|
+
key={result.id}
|
|
218
|
+
id={`${instanceId}-option-${index}`}
|
|
219
|
+
role="option"
|
|
220
|
+
aria-selected={index === activeIndex}
|
|
221
|
+
className={cn(
|
|
222
|
+
"flex cursor-pointer items-start gap-3 px-3 py-2.5",
|
|
223
|
+
"transition-colors",
|
|
224
|
+
index === activeIndex
|
|
225
|
+
? "bg-accent text-accent-foreground"
|
|
226
|
+
: "hover:bg-accent/50",
|
|
227
|
+
)}
|
|
228
|
+
onMouseDown={(e) => {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
}}
|
|
231
|
+
onClick={() => handleSelect(index)}
|
|
232
|
+
onMouseEnter={() => setActiveIndex(index)}
|
|
233
|
+
>
|
|
234
|
+
<div className="bg-primary/10 mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md">
|
|
235
|
+
<Bot className="text-primary size-3.5" />
|
|
236
|
+
</div>
|
|
237
|
+
<div className="min-w-0 flex-1">
|
|
238
|
+
<p className="truncate text-sm font-medium">{result.name}</p>
|
|
239
|
+
<p className="text-muted-foreground truncate font-mono text-xs">
|
|
240
|
+
{result.qualifiedSlug}
|
|
241
|
+
</p>
|
|
242
|
+
{result.description && (
|
|
243
|
+
<p className="text-muted-foreground/70 mt-0.5 line-clamp-1 text-xs">
|
|
244
|
+
{result.description}
|
|
245
|
+
</p>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
</li>
|
|
249
|
+
))}
|
|
250
|
+
</ul>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useStigmer } from "../../hooks";
|
|
5
|
+
import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
|
|
6
|
+
|
|
7
|
+
const DEBOUNCE_MS = 300;
|
|
8
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
9
|
+
|
|
10
|
+
export interface AgentSearchResult {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
qualifiedSlug: string;
|
|
14
|
+
org: string;
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UseAgentSearchOptions {
|
|
19
|
+
org: string;
|
|
20
|
+
debounceMs?: number;
|
|
21
|
+
pageSize?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseAgentSearchReturn {
|
|
25
|
+
query: string;
|
|
26
|
+
setQuery: (query: string) => void;
|
|
27
|
+
results: AgentSearchResult[];
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
error: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toAgentSearchResult(r: SearchResult): AgentSearchResult {
|
|
33
|
+
return {
|
|
34
|
+
id: r.id,
|
|
35
|
+
name: r.name,
|
|
36
|
+
qualifiedSlug: r.qualifiedSlug,
|
|
37
|
+
org: r.org,
|
|
38
|
+
description: r.description,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Debounced agent search scoped to a specific organization.
|
|
44
|
+
*
|
|
45
|
+
* On mount fetches all accessible agents (empty query). Typing fires at most
|
|
46
|
+
* one request per debounce interval. Stale responses from out-of-order
|
|
47
|
+
* requests are discarded.
|
|
48
|
+
*/
|
|
49
|
+
export function useAgentSearch(options: UseAgentSearchOptions): UseAgentSearchReturn {
|
|
50
|
+
const { org, debounceMs = DEBOUNCE_MS, pageSize = DEFAULT_PAGE_SIZE } = options;
|
|
51
|
+
const stigmer = useStigmer();
|
|
52
|
+
|
|
53
|
+
const [query, setQuery] = useState("");
|
|
54
|
+
const [results, setResults] = useState<AgentSearchResult[]>([]);
|
|
55
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
56
|
+
const [error, setError] = useState<string | null>(null);
|
|
57
|
+
|
|
58
|
+
const latestRequestId = useRef(0);
|
|
59
|
+
const debouncedQuery = useDebouncedValue(query, debounceMs);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const requestId = ++latestRequestId.current;
|
|
63
|
+
setIsLoading(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
|
|
66
|
+
stigmer.agent
|
|
67
|
+
.list({ query: debouncedQuery, org, page: { num: 1, size: pageSize } })
|
|
68
|
+
.then((response) => {
|
|
69
|
+
if (requestId !== latestRequestId.current) return;
|
|
70
|
+
setResults(response.entries.map(toAgentSearchResult));
|
|
71
|
+
})
|
|
72
|
+
.catch((err: unknown) => {
|
|
73
|
+
if (requestId !== latestRequestId.current) return;
|
|
74
|
+
setError(err instanceof Error ? err.message : "Search failed");
|
|
75
|
+
})
|
|
76
|
+
.finally(() => {
|
|
77
|
+
if (requestId !== latestRequestId.current) return;
|
|
78
|
+
setIsLoading(false);
|
|
79
|
+
});
|
|
80
|
+
}, [debouncedQuery, org, pageSize, stigmer]);
|
|
81
|
+
|
|
82
|
+
return { query, setQuery, results, isLoading, error };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
86
|
+
const [debounced, setDebounced] = useState(value);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const timer = setTimeout(() => setDebounced(value), delayMs);
|
|
90
|
+
return () => clearTimeout(timer);
|
|
91
|
+
}, [value, delayMs]);
|
|
92
|
+
|
|
93
|
+
return debounced;
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Hooks
|
|
2
|
+
export { useAgentSearch } from "./hooks/useAgentSearch";
|
|
3
|
+
export type {
|
|
4
|
+
UseAgentSearchOptions,
|
|
5
|
+
UseAgentSearchReturn,
|
|
6
|
+
AgentSearchResult,
|
|
7
|
+
} from "./hooks/useAgentSearch";
|
|
8
|
+
|
|
9
|
+
// Components
|
|
10
|
+
export { AgentCard } from "./components/AgentCard";
|
|
11
|
+
export type { AgentCardProps } from "./components/AgentCard";
|
|
12
|
+
|
|
13
|
+
export { AgentOverview } from "./components/AgentOverview";
|
|
14
|
+
export type { AgentOverviewProps } from "./components/AgentOverview";
|
|
15
|
+
|
|
16
|
+
export { AgentPicker } from "./components/AgentPicker";
|
|
17
|
+
export type { AgentPickerProps, SelectedAgent } from "./components/AgentPicker";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { Button } from "../../internal/button";
|
|
5
|
+
import { Textarea } from "../../internal/textarea";
|
|
6
|
+
import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
7
|
+
import { cn } from "@stigmer/theme";
|
|
8
|
+
import { Check, X, SkipForward, Loader2 } from "lucide-react";
|
|
9
|
+
|
|
10
|
+
interface ApprovalControlsProps {
|
|
11
|
+
approvalMessage: string;
|
|
12
|
+
onSubmit: (action: ApprovalAction, comment?: string) => Promise<void>;
|
|
13
|
+
isSubmitting: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ApprovalControls({
|
|
18
|
+
approvalMessage,
|
|
19
|
+
onSubmit,
|
|
20
|
+
isSubmitting,
|
|
21
|
+
className,
|
|
22
|
+
}: ApprovalControlsProps) {
|
|
23
|
+
const [showComment, setShowComment] = useState(false);
|
|
24
|
+
const [comment, setComment] = useState("");
|
|
25
|
+
|
|
26
|
+
const handleAction = useCallback(
|
|
27
|
+
async (action: ApprovalAction) => {
|
|
28
|
+
await onSubmit(action, comment || undefined);
|
|
29
|
+
setComment("");
|
|
30
|
+
setShowComment(false);
|
|
31
|
+
},
|
|
32
|
+
[onSubmit, comment],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cn(
|
|
38
|
+
"border-primary/30 bg-primary/5 space-y-3 rounded-lg border-2 p-3",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
<p className="text-sm font-medium">{approvalMessage}</p>
|
|
43
|
+
|
|
44
|
+
{showComment && (
|
|
45
|
+
<Textarea
|
|
46
|
+
value={comment}
|
|
47
|
+
onChange={(e) => setComment(e.target.value)}
|
|
48
|
+
placeholder="Optional comment..."
|
|
49
|
+
className="min-h-10 text-sm"
|
|
50
|
+
disabled={isSubmitting}
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<Button
|
|
56
|
+
size="sm"
|
|
57
|
+
onClick={() => handleAction(ApprovalAction.APPROVE)}
|
|
58
|
+
disabled={isSubmitting}
|
|
59
|
+
>
|
|
60
|
+
{isSubmitting ? (
|
|
61
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
62
|
+
) : (
|
|
63
|
+
<Check className="size-3.5" />
|
|
64
|
+
)}
|
|
65
|
+
Approve
|
|
66
|
+
</Button>
|
|
67
|
+
<Button
|
|
68
|
+
size="sm"
|
|
69
|
+
variant="secondary"
|
|
70
|
+
onClick={() => handleAction(ApprovalAction.SKIP)}
|
|
71
|
+
disabled={isSubmitting}
|
|
72
|
+
>
|
|
73
|
+
<SkipForward className="size-3.5" />
|
|
74
|
+
Skip
|
|
75
|
+
</Button>
|
|
76
|
+
<Button
|
|
77
|
+
size="sm"
|
|
78
|
+
variant="destructive"
|
|
79
|
+
onClick={() => handleAction(ApprovalAction.REJECT)}
|
|
80
|
+
disabled={isSubmitting}
|
|
81
|
+
>
|
|
82
|
+
<X className="size-3.5" />
|
|
83
|
+
Reject
|
|
84
|
+
</Button>
|
|
85
|
+
{!showComment && (
|
|
86
|
+
<Button
|
|
87
|
+
size="sm"
|
|
88
|
+
variant="ghost"
|
|
89
|
+
onClick={() => setShowComment(true)}
|
|
90
|
+
disabled={isSubmitting}
|
|
91
|
+
className="text-muted-foreground ml-auto text-xs"
|
|
92
|
+
>
|
|
93
|
+
Add comment
|
|
94
|
+
</Button>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|