@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,137 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
|
|
3
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
4
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
5
|
+
import { Bot, FileCode2, Server, Globe, Box } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@stigmer/theme";
|
|
8
|
+
|
|
9
|
+
import { Badge } from "../../internal/badge";
|
|
10
|
+
import { formatRelativeTime, toDate } from "../internal/time";
|
|
11
|
+
|
|
12
|
+
const KIND_ICON: Record<number, ReactNode> = {
|
|
13
|
+
[ApiResourceKind.agent]: <Bot className="text-muted-foreground size-4" />,
|
|
14
|
+
[ApiResourceKind.skill]: <FileCode2 className="text-muted-foreground size-4" />,
|
|
15
|
+
[ApiResourceKind.mcp_server]: <Server className="text-muted-foreground size-4" />,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const FALLBACK_ICON = <Box className="text-muted-foreground size-4" />;
|
|
19
|
+
|
|
20
|
+
export interface ResourceSearchCardProps {
|
|
21
|
+
/** The search result to display. */
|
|
22
|
+
result: SearchResult;
|
|
23
|
+
/** When provided, the card renders as an `<a>` element with this URL. */
|
|
24
|
+
href?: string;
|
|
25
|
+
/** Click handler for SPA navigation or custom actions. */
|
|
26
|
+
onClick?: () => void;
|
|
27
|
+
/** Override the auto-detected icon derived from `result.kind`. */
|
|
28
|
+
icon?: ReactNode;
|
|
29
|
+
/** Additional CSS class names on the root element. */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A card component for displaying any Stigmer resource search result
|
|
35
|
+
* (agent, skill, MCP server) in a grid catalog layout.
|
|
36
|
+
*
|
|
37
|
+
* Auto-selects the icon based on `result.kind`. Renders as an `<a>` tag
|
|
38
|
+
* when `href` is provided, otherwise as a `<div>`.
|
|
39
|
+
*/
|
|
40
|
+
export function ResourceSearchCard({
|
|
41
|
+
result,
|
|
42
|
+
href,
|
|
43
|
+
onClick,
|
|
44
|
+
icon,
|
|
45
|
+
className,
|
|
46
|
+
}: ResourceSearchCardProps) {
|
|
47
|
+
const isPublic =
|
|
48
|
+
result.visibility === ApiResourceVisibility.visibility_public;
|
|
49
|
+
|
|
50
|
+
const resolvedIcon = icon ?? KIND_ICON[result.kind] ?? FALLBACK_ICON;
|
|
51
|
+
|
|
52
|
+
const Tag = href ? "a" : "div";
|
|
53
|
+
const interactive = !!(onClick || href);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Tag
|
|
57
|
+
href={href}
|
|
58
|
+
onClick={onClick}
|
|
59
|
+
role={interactive ? "button" : undefined}
|
|
60
|
+
tabIndex={interactive ? 0 : undefined}
|
|
61
|
+
onKeyDown={
|
|
62
|
+
interactive
|
|
63
|
+
? (e: React.KeyboardEvent) => {
|
|
64
|
+
if (onClick && (e.key === "Enter" || e.key === " ")) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
onClick();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
: undefined
|
|
70
|
+
}
|
|
71
|
+
className={cn(
|
|
72
|
+
"bg-card text-card-foreground flex flex-col rounded-xl border p-4",
|
|
73
|
+
interactive &&
|
|
74
|
+
"hover:bg-accent/50 focus-visible:ring-ring cursor-pointer transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{/* Header: icon + name + visibility */}
|
|
79
|
+
<div className="flex items-start gap-3">
|
|
80
|
+
<div className="bg-muted flex size-9 shrink-0 items-center justify-center rounded-lg">
|
|
81
|
+
{resolvedIcon}
|
|
82
|
+
</div>
|
|
83
|
+
<div className="min-w-0 flex-1">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<span className="truncate text-sm font-semibold">
|
|
86
|
+
{result.name}
|
|
87
|
+
</span>
|
|
88
|
+
{isPublic && (
|
|
89
|
+
<Badge variant="outline" className="shrink-0 text-[10px]">
|
|
90
|
+
<Globe className="size-2.5" />
|
|
91
|
+
Public
|
|
92
|
+
</Badge>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
<p className="text-muted-foreground truncate font-mono text-[11px]">
|
|
96
|
+
{result.qualifiedSlug}
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Description */}
|
|
102
|
+
{result.description && (
|
|
103
|
+
<p className="text-muted-foreground mt-2 line-clamp-2 text-xs leading-relaxed">
|
|
104
|
+
{result.description}
|
|
105
|
+
</p>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Footer: tags + timestamp */}
|
|
109
|
+
<div className="mt-3 flex items-end justify-between gap-2">
|
|
110
|
+
{result.tags.length > 0 ? (
|
|
111
|
+
<div className="flex min-w-0 flex-wrap gap-1">
|
|
112
|
+
{result.tags.slice(0, 3).map((tag) => (
|
|
113
|
+
<Badge key={tag} variant="secondary" className="text-[10px]">
|
|
114
|
+
{tag}
|
|
115
|
+
</Badge>
|
|
116
|
+
))}
|
|
117
|
+
{result.tags.length > 3 && (
|
|
118
|
+
<span className="text-muted-foreground self-center text-[10px]">
|
|
119
|
+
+{result.tags.length - 3}
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
) : (
|
|
124
|
+
<div />
|
|
125
|
+
)}
|
|
126
|
+
{result.createdAt && (
|
|
127
|
+
<time
|
|
128
|
+
dateTime={toDate(result.createdAt)?.toISOString() ?? ""}
|
|
129
|
+
className="text-muted-foreground shrink-0 text-[10px]"
|
|
130
|
+
>
|
|
131
|
+
{formatRelativeTime(result.createdAt)}
|
|
132
|
+
</time>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</Tag>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Timestamp } from "@bufbuild/protobuf/wkt";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert a protobuf Timestamp to a JS Date.
|
|
5
|
+
* Returns null if the timestamp is undefined or has no seconds.
|
|
6
|
+
*/
|
|
7
|
+
export function toDate(ts: Timestamp | undefined): Date | null {
|
|
8
|
+
if (!ts) return null;
|
|
9
|
+
const seconds = Number(ts.seconds);
|
|
10
|
+
if (!seconds && seconds !== 0) return null;
|
|
11
|
+
return new Date(seconds * 1000 + Math.floor(ts.nanos / 1_000_000));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format a protobuf Timestamp as a human-readable relative time string.
|
|
16
|
+
*
|
|
17
|
+
* Examples: "just now", "2m ago", "3h ago", "yesterday", "Mar 12"
|
|
18
|
+
*/
|
|
19
|
+
export function formatRelativeTime(ts: Timestamp | undefined): string {
|
|
20
|
+
const date = toDate(ts);
|
|
21
|
+
if (!date) return "";
|
|
22
|
+
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const diffMs = now - date.getTime();
|
|
25
|
+
|
|
26
|
+
if (diffMs < 0) return "just now";
|
|
27
|
+
if (diffMs < 60_000) return "just now";
|
|
28
|
+
|
|
29
|
+
const minutes = Math.floor(diffMs / 60_000);
|
|
30
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
31
|
+
|
|
32
|
+
const hours = Math.floor(minutes / 60);
|
|
33
|
+
if (hours < 24) return `${hours}h ago`;
|
|
34
|
+
|
|
35
|
+
const days = Math.floor(hours / 24);
|
|
36
|
+
if (days === 1) return "yesterday";
|
|
37
|
+
if (days < 7) return `${days}d ago`;
|
|
38
|
+
|
|
39
|
+
return date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
|
40
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* React context for the Stigmer SDK client instance.
|
|
8
|
+
*
|
|
9
|
+
* Separated from the provider component to prevent circular imports:
|
|
10
|
+
* both the provider and consumer hooks import from this file.
|
|
11
|
+
*
|
|
12
|
+
* The context value is `null` when no provider is mounted. Consumer hooks
|
|
13
|
+
* throw in this case to surface wiring mistakes during development.
|
|
14
|
+
*/
|
|
15
|
+
export const StigmerContext = createContext<Stigmer | null>(null);
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
import { StigmerContext } from "./context";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Access the {@link Stigmer} SDK client from the nearest
|
|
9
|
+
* {@link StigmerProvider}.
|
|
10
|
+
*
|
|
11
|
+
* Throws if called outside a provider — this surfaces wiring mistakes
|
|
12
|
+
* immediately during development rather than producing silent `null`
|
|
13
|
+
* failures at runtime.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function AgentDetail({ id }: { id: string }) {
|
|
18
|
+
* const stigmer = useStigmer();
|
|
19
|
+
* const agent = await stigmer.agent.get(id);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function useStigmer(): Stigmer {
|
|
24
|
+
const client = useContext(StigmerContext);
|
|
25
|
+
if (!client) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"useStigmer must be used within <StigmerProvider>. " +
|
|
28
|
+
"Wrap your component tree with <StigmerProvider client={stigmerClient}>.",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return client;
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function Badge({
|
|
31
|
+
className,
|
|
32
|
+
variant = "default",
|
|
33
|
+
render,
|
|
34
|
+
...props
|
|
35
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
36
|
+
return useRender({
|
|
37
|
+
defaultTagName: "span",
|
|
38
|
+
props: mergeProps<"span">(
|
|
39
|
+
{
|
|
40
|
+
className: cn(badgeVariants({ variant }), className),
|
|
41
|
+
},
|
|
42
|
+
props,
|
|
43
|
+
),
|
|
44
|
+
render,
|
|
45
|
+
state: {
|
|
46
|
+
slot: "badge",
|
|
47
|
+
variant,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@stigmer/theme";
|
|
7
|
+
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
14
|
+
outline:
|
|
15
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
18
|
+
ghost:
|
|
19
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
20
|
+
destructive:
|
|
21
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default:
|
|
26
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
27
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
28
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
29
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
30
|
+
icon: "size-8",
|
|
31
|
+
"icon-xs":
|
|
32
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
33
|
+
"icon-sm":
|
|
34
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
35
|
+
"icon-lg": "size-9",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
size: "default",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function Button({
|
|
46
|
+
className,
|
|
47
|
+
variant = "default",
|
|
48
|
+
size = "default",
|
|
49
|
+
...props
|
|
50
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
51
|
+
return (
|
|
52
|
+
<ButtonPrimitive
|
|
53
|
+
data-slot="button"
|
|
54
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
|
|
4
|
+
|
|
5
|
+
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
|
|
6
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface SectionProps {
|
|
2
|
+
title: string;
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function Section({ title, children }: SectionProps) {
|
|
7
|
+
return (
|
|
8
|
+
<section>
|
|
9
|
+
<h3 className="text-muted-foreground mb-3 text-sm font-semibold tracking-wider uppercase">
|
|
10
|
+
{title}
|
|
11
|
+
</h3>
|
|
12
|
+
{children}
|
|
13
|
+
</section>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { Section };
|
|
18
|
+
export type { SectionProps };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@stigmer/theme";
|
|
4
|
+
|
|
5
|
+
const Textarea = React.forwardRef<
|
|
6
|
+
HTMLTextAreaElement,
|
|
7
|
+
React.ComponentProps<"textarea">
|
|
8
|
+
>(({ className, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
ref={ref}
|
|
12
|
+
data-slot="textarea"
|
|
13
|
+
className={cn(
|
|
14
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 disabled:bg-input/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 flex field-sizing-content min-h-16 w-full rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors outline-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
Textarea.displayName = "Textarea";
|
|
22
|
+
|
|
23
|
+
export { Textarea };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { 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 UseMcpServerSearchOptions {
|
|
11
|
+
org: string;
|
|
12
|
+
debounceMs?: number;
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseMcpServerSearchReturn {
|
|
17
|
+
query: string;
|
|
18
|
+
setQuery: (query: string) => void;
|
|
19
|
+
results: SearchResult[];
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Debounced MCP server search scoped to a specific organization.
|
|
26
|
+
*
|
|
27
|
+
* On mount fetches all accessible MCP servers (empty query). Typing fires at
|
|
28
|
+
* most one request per debounce interval. Stale responses from out-of-order
|
|
29
|
+
* requests are discarded.
|
|
30
|
+
*
|
|
31
|
+
* Returns full {@link SearchResult} objects suitable for use with
|
|
32
|
+
* `ResourceSearchCard`.
|
|
33
|
+
*/
|
|
34
|
+
export function useMcpServerSearch(options: UseMcpServerSearchOptions): UseMcpServerSearchReturn {
|
|
35
|
+
const { org, debounceMs = DEBOUNCE_MS, pageSize = DEFAULT_PAGE_SIZE } = options;
|
|
36
|
+
const stigmer = useStigmer();
|
|
37
|
+
|
|
38
|
+
const [query, setQuery] = useState("");
|
|
39
|
+
const [results, setResults] = useState<SearchResult[]>([]);
|
|
40
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
41
|
+
const [error, setError] = useState<string | null>(null);
|
|
42
|
+
|
|
43
|
+
const latestRequestId = useRef(0);
|
|
44
|
+
const debouncedQuery = useDebouncedValue(query, debounceMs);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const requestId = ++latestRequestId.current;
|
|
48
|
+
setIsLoading(true);
|
|
49
|
+
setError(null);
|
|
50
|
+
|
|
51
|
+
stigmer.mcpServer
|
|
52
|
+
.list({ query: debouncedQuery, org, page: { num: 1, size: pageSize } })
|
|
53
|
+
.then((response) => {
|
|
54
|
+
if (requestId !== latestRequestId.current) return;
|
|
55
|
+
setResults(response.entries);
|
|
56
|
+
})
|
|
57
|
+
.catch((err: unknown) => {
|
|
58
|
+
if (requestId !== latestRequestId.current) return;
|
|
59
|
+
setError(err instanceof Error ? err.message : "Search failed");
|
|
60
|
+
})
|
|
61
|
+
.finally(() => {
|
|
62
|
+
if (requestId !== latestRequestId.current) return;
|
|
63
|
+
setIsLoading(false);
|
|
64
|
+
});
|
|
65
|
+
}, [debouncedQuery, org, pageSize, stigmer]);
|
|
66
|
+
|
|
67
|
+
return { query, setQuery, results, isLoading, error };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
71
|
+
const [debounced, setDebounced] = useState(value);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const timer = setTimeout(() => setDebounced(value), delayMs);
|
|
75
|
+
return () => clearTimeout(timer);
|
|
76
|
+
}, [value, delayMs]);
|
|
77
|
+
|
|
78
|
+
return debounced;
|
|
79
|
+
}
|
package/src/provider.tsx
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
import { cn, resolvePresetClass } from "@stigmer/theme";
|
|
6
|
+
import type { ThemePresetId } from "@stigmer/theme";
|
|
7
|
+
import { StigmerContext } from "./context";
|
|
8
|
+
|
|
9
|
+
export interface StigmerProviderProps {
|
|
10
|
+
/** A configured {@link Stigmer} client instance. */
|
|
11
|
+
readonly client: Stigmer;
|
|
12
|
+
readonly children: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Built-in theme preset to apply.
|
|
15
|
+
*
|
|
16
|
+
* Maps to a CSS class on the scoping container so the preset's
|
|
17
|
+
* design tokens take effect for all descendant Stigmer components.
|
|
18
|
+
* Omit (or pass `"default"`) to use the base Stigmer palette.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <StigmerProvider client={client} preset="corporate">
|
|
23
|
+
* <ChatWidget />
|
|
24
|
+
* </StigmerProvider>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
readonly preset?: ThemePresetId;
|
|
28
|
+
/**
|
|
29
|
+
* Additional CSS class names applied to the scoping container element.
|
|
30
|
+
* The container always includes the `stgm` class for style isolation.
|
|
31
|
+
*/
|
|
32
|
+
readonly className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* React provider that distributes a {@link Stigmer} SDK client to
|
|
37
|
+
* descendant components via {@link StigmerContext}.
|
|
38
|
+
*
|
|
39
|
+
* Renders a `<div class="stgm">` container that scopes Stigmer's
|
|
40
|
+
* CSS reset and design tokens. External consumers importing
|
|
41
|
+
* `@stigmer/react/styles.css` get isolated styles that do not
|
|
42
|
+
* leak into the host application.
|
|
43
|
+
*
|
|
44
|
+
* Pass {@link StigmerProviderProps.preset | preset} to apply a
|
|
45
|
+
* built-in theme, or use {@link StigmerProviderProps.className | className}
|
|
46
|
+
* for custom styling.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const client = useMemo(
|
|
51
|
+
* () => new Stigmer({ baseUrl, getAccessToken }),
|
|
52
|
+
* [getAccessToken],
|
|
53
|
+
* );
|
|
54
|
+
*
|
|
55
|
+
* <StigmerProvider client={client} preset="fintech">
|
|
56
|
+
* <App />
|
|
57
|
+
* </StigmerProvider>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function StigmerProvider({
|
|
61
|
+
client,
|
|
62
|
+
children,
|
|
63
|
+
preset,
|
|
64
|
+
className,
|
|
65
|
+
}: StigmerProviderProps) {
|
|
66
|
+
const presetClass = preset ? resolvePresetClass(preset) : "";
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<StigmerContext.Provider value={client}>
|
|
70
|
+
<div className={cn("stgm", presetClass, className)}>{children}</div>
|
|
71
|
+
</StigmerContext.Provider>
|
|
72
|
+
);
|
|
73
|
+
}
|