@stigmer/react 0.2.3 → 0.3.1
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/composer/ComposerToolbar.d.ts +5 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +6 -3
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +17 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +10 -3
- package/composer/SessionComposer.js.map +1 -1
- package/execution/MessageEntry.d.ts +3 -1
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +30 -1
- package/execution/MessageEntry.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/models/HarnessSelector.d.ts +41 -0
- package/models/HarnessSelector.d.ts.map +1 -0
- package/models/HarnessSelector.js +74 -0
- package/models/HarnessSelector.js.map +1 -0
- package/models/ModelSelector.d.ts +26 -16
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +128 -48
- package/models/ModelSelector.js.map +1 -1
- package/models/__tests__/HarnessSelector.test.d.ts +2 -0
- package/models/__tests__/HarnessSelector.test.d.ts.map +1 -0
- package/models/__tests__/HarnessSelector.test.js +160 -0
- package/models/__tests__/HarnessSelector.test.js.map +1 -0
- package/models/__tests__/harness.test.d.ts +2 -0
- package/models/__tests__/harness.test.d.ts.map +1 -0
- package/models/__tests__/harness.test.js +50 -0
- package/models/__tests__/harness.test.js.map +1 -0
- package/models/__tests__/useModelRegistry.test.d.ts +2 -0
- package/models/__tests__/useModelRegistry.test.d.ts.map +1 -0
- package/models/__tests__/useModelRegistry.test.js +148 -0
- package/models/__tests__/useModelRegistry.test.js.map +1 -0
- package/models/harness.d.ts +21 -0
- package/models/harness.d.ts.map +1 -0
- package/models/harness.js +34 -0
- package/models/harness.js.map +1 -0
- package/models/index.d.ts +7 -2
- package/models/index.d.ts.map +1 -1
- package/models/index.js +3 -1
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +53 -13
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +51 -40
- package/models/registry.js.map +1 -1
- package/models/useModelRegistry.d.ts +39 -19
- package/models/useModelRegistry.d.ts.map +1 -1
- package/models/useModelRegistry.js +45 -23
- package/models/useModelRegistry.js.map +1 -1
- package/package.json +4 -4
- package/runner/RunnerListPanel.js +2 -1
- package/runner/RunnerListPanel.js.map +1 -1
- package/runner/__tests__/phase.test.js +6 -2
- package/runner/__tests__/phase.test.js.map +1 -1
- package/runner/phase.d.ts +9 -7
- package/runner/phase.d.ts.map +1 -1
- package/runner/phase.js +18 -12
- package/runner/phase.js.map +1 -1
- package/session/__tests__/useCreateSession.test.d.ts +2 -0
- package/session/__tests__/useCreateSession.test.d.ts.map +1 -0
- package/session/__tests__/useCreateSession.test.js +232 -0
- package/session/__tests__/useCreateSession.test.js.map +1 -0
- package/session/__tests__/useNewSessionFlow.test.d.ts +2 -0
- package/session/__tests__/useNewSessionFlow.test.d.ts.map +1 -0
- package/session/__tests__/useNewSessionFlow.test.js +199 -0
- package/session/__tests__/useNewSessionFlow.test.js.map +1 -0
- package/session/__tests__/useSessionConversation.test.js +37 -0
- package/session/__tests__/useSessionConversation.test.js.map +1 -1
- package/session/index.d.ts +1 -1
- package/session/index.d.ts.map +1 -1
- package/session/useCreateSession.d.ts +8 -0
- package/session/useCreateSession.d.ts.map +1 -1
- package/session/useCreateSession.js +2 -0
- package/session/useCreateSession.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +6 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +34 -8
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/usePersistedModel.d.ts +16 -1
- package/session/usePersistedModel.d.ts.map +1 -1
- package/session/usePersistedModel.js +15 -6
- package/session/usePersistedModel.js.map +1 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +6 -1
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +11 -0
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +11 -2
- package/session/useSessionPageFlow.js.map +1 -1
- package/src/composer/ComposerToolbar.tsx +24 -1
- package/src/composer/SessionComposer.tsx +35 -1
- package/src/execution/MessageEntry.tsx +134 -1
- package/src/index.ts +15 -1
- package/src/models/HarnessSelector.tsx +130 -0
- package/src/models/ModelSelector.tsx +285 -81
- package/src/models/__tests__/HarnessSelector.test.tsx +190 -0
- package/src/models/__tests__/harness.test.ts +66 -0
- package/src/models/__tests__/useModelRegistry.test.tsx +209 -0
- package/src/models/harness.ts +45 -0
- package/src/models/index.ts +7 -2
- package/src/models/registry.ts +122 -50
- package/src/models/useModelRegistry.ts +74 -24
- package/src/runner/RunnerListPanel.tsx +13 -5
- package/src/runner/__tests__/phase.test.ts +6 -2
- package/src/runner/phase.ts +18 -12
- package/src/session/__tests__/useCreateSession.test.tsx +296 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +258 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +53 -0
- package/src/session/index.ts +1 -1
- package/src/session/useCreateSession.ts +9 -0
- package/src/session/useNewSessionFlow.ts +46 -9
- package/src/session/usePersistedModel.ts +30 -6
- package/src/session/useSessionConversation.ts +6 -1
- package/src/session/useSessionPageFlow.ts +26 -2
- package/styles.css +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useState } from "react";
|
|
3
4
|
import Markdown from "react-markdown";
|
|
4
5
|
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
5
6
|
import { MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
@@ -20,9 +21,11 @@ export interface MessageEntryProps {
|
|
|
20
21
|
* - `MESSAGE_HUMAN` — plain text with muted background
|
|
21
22
|
* - `MESSAGE_AI` — markdown-rendered via `react-markdown` + `remark-gfm`,
|
|
22
23
|
* with a blinking cursor while streaming
|
|
24
|
+
* - `MESSAGE_THINKING` — collapsible thinking block with subdued styling,
|
|
25
|
+
* collapsed by default showing a brief summary
|
|
23
26
|
* - `MESSAGE_SYSTEM` — small muted text
|
|
24
27
|
* - `MESSAGE_TOOL` / `UNSPECIFIED` — renders nothing (tool results are
|
|
25
|
-
* consumed by {@link ToolCallGroup}
|
|
28
|
+
* consumed by {@link ToolCallGroup})
|
|
26
29
|
*
|
|
27
30
|
* Purely presentational — no data fetching, no state.
|
|
28
31
|
* All visual properties flow through `--stgm-*` tokens.
|
|
@@ -44,6 +47,14 @@ export function MessageEntry({ message, className }: MessageEntryProps) {
|
|
|
44
47
|
className={className}
|
|
45
48
|
/>
|
|
46
49
|
);
|
|
50
|
+
case MessageType.MESSAGE_THINKING:
|
|
51
|
+
return (
|
|
52
|
+
<ThinkingMessage
|
|
53
|
+
content={message.content}
|
|
54
|
+
isStreaming={message.isStreaming}
|
|
55
|
+
className={className}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
47
58
|
case MessageType.MESSAGE_SYSTEM:
|
|
48
59
|
return <SystemMessage content={message.content} className={className} />;
|
|
49
60
|
default:
|
|
@@ -103,6 +114,69 @@ function AiMessage({
|
|
|
103
114
|
);
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
const THINKING_PREVIEW_LENGTH = 80;
|
|
118
|
+
|
|
119
|
+
function ThinkingMessage({
|
|
120
|
+
content,
|
|
121
|
+
isStreaming,
|
|
122
|
+
className,
|
|
123
|
+
}: {
|
|
124
|
+
content: string;
|
|
125
|
+
isStreaming: boolean;
|
|
126
|
+
className?: string;
|
|
127
|
+
}) {
|
|
128
|
+
const [expanded, setExpanded] = useState(false);
|
|
129
|
+
const hasContent = content.trim().length > 0;
|
|
130
|
+
|
|
131
|
+
if (!hasContent && !isStreaming) return null;
|
|
132
|
+
|
|
133
|
+
const preview = content.length > THINKING_PREVIEW_LENGTH
|
|
134
|
+
? content.slice(0, THINKING_PREVIEW_LENGTH).trimEnd() + "..."
|
|
135
|
+
: content;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
role="article"
|
|
140
|
+
aria-label="Model thinking"
|
|
141
|
+
className={cn("px-4 py-1.5", className)}
|
|
142
|
+
>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
aria-expanded={expanded}
|
|
146
|
+
onClick={() => setExpanded((v) => !v)}
|
|
147
|
+
className={cn(
|
|
148
|
+
"flex items-center gap-1.5 text-xs text-muted-foreground transition-colors",
|
|
149
|
+
"hover:text-foreground cursor-pointer",
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<ThinkingIcon isStreaming={isStreaming} />
|
|
153
|
+
<span className="min-w-0 truncate">
|
|
154
|
+
{isStreaming && !hasContent
|
|
155
|
+
? "Thinking..."
|
|
156
|
+
: expanded
|
|
157
|
+
? "Thinking"
|
|
158
|
+
: preview}
|
|
159
|
+
</span>
|
|
160
|
+
{hasContent && <ChevronIcon expanded={expanded} />}
|
|
161
|
+
</button>
|
|
162
|
+
|
|
163
|
+
{expanded && hasContent && (
|
|
164
|
+
<div className="mt-1.5 border-l-2 border-muted-foreground/20 pl-3">
|
|
165
|
+
<p className="text-xs text-muted-foreground whitespace-pre-wrap leading-relaxed">
|
|
166
|
+
{content}
|
|
167
|
+
{isStreaming && (
|
|
168
|
+
<span
|
|
169
|
+
className="inline-block w-[2px] h-[0.8em] bg-muted-foreground align-text-bottom animate-pulse ml-0.5"
|
|
170
|
+
aria-hidden="true"
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
106
180
|
function SystemMessage({
|
|
107
181
|
content,
|
|
108
182
|
className,
|
|
@@ -121,3 +195,62 @@ function SystemMessage({
|
|
|
121
195
|
);
|
|
122
196
|
}
|
|
123
197
|
|
|
198
|
+
function ThinkingIcon({ isStreaming }: { isStreaming: boolean }) {
|
|
199
|
+
if (isStreaming) {
|
|
200
|
+
return (
|
|
201
|
+
<svg
|
|
202
|
+
width="12"
|
|
203
|
+
height="12"
|
|
204
|
+
viewBox="0 0 12 12"
|
|
205
|
+
fill="none"
|
|
206
|
+
stroke="currentColor"
|
|
207
|
+
strokeWidth="1.5"
|
|
208
|
+
className="shrink-0 animate-spin"
|
|
209
|
+
aria-hidden="true"
|
|
210
|
+
>
|
|
211
|
+
<path d="M6 1.5A4.5 4.5 0 1 1 1.5 6" strokeLinecap="round" />
|
|
212
|
+
</svg>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return (
|
|
216
|
+
<svg
|
|
217
|
+
width="12"
|
|
218
|
+
height="12"
|
|
219
|
+
viewBox="0 0 12 12"
|
|
220
|
+
fill="none"
|
|
221
|
+
stroke="currentColor"
|
|
222
|
+
strokeWidth="1.5"
|
|
223
|
+
strokeLinecap="round"
|
|
224
|
+
strokeLinejoin="round"
|
|
225
|
+
className="shrink-0"
|
|
226
|
+
aria-hidden="true"
|
|
227
|
+
>
|
|
228
|
+
<circle cx="6" cy="5" r="3.5" />
|
|
229
|
+
<path d="M4.5 9.5C4.5 8.5 5 8 6 8s1.5.5 1.5 1.5" />
|
|
230
|
+
<circle cx="5" cy="4.5" r="0.5" fill="currentColor" />
|
|
231
|
+
<circle cx="7" cy="4.5" r="0.5" fill="currentColor" />
|
|
232
|
+
</svg>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function ChevronIcon({ expanded }: { expanded: boolean }) {
|
|
237
|
+
return (
|
|
238
|
+
<svg
|
|
239
|
+
width="10"
|
|
240
|
+
height="10"
|
|
241
|
+
viewBox="0 0 10 10"
|
|
242
|
+
fill="none"
|
|
243
|
+
stroke="currentColor"
|
|
244
|
+
strokeWidth="1.5"
|
|
245
|
+
strokeLinecap="round"
|
|
246
|
+
strokeLinejoin="round"
|
|
247
|
+
className={cn(
|
|
248
|
+
"shrink-0 transition-transform duration-150",
|
|
249
|
+
expanded && "rotate-90",
|
|
250
|
+
)}
|
|
251
|
+
aria-hidden="true"
|
|
252
|
+
>
|
|
253
|
+
<path d="M3.5 2L6.5 5L3.5 8" />
|
|
254
|
+
</svg>
|
|
255
|
+
);
|
|
256
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,19 +18,32 @@ export {
|
|
|
18
18
|
export { type DeploymentMode, isResourceAvailable, ApiResourceKind } from "@stigmer/sdk";
|
|
19
19
|
export { CloudFeatureNotice, type CloudFeatureNoticeProps } from "./internal/CloudFeatureNotice";
|
|
20
20
|
|
|
21
|
-
// Models — data hook, styled
|
|
21
|
+
// Models — data hook, styled components, and registry data
|
|
22
22
|
export {
|
|
23
23
|
MODEL_REGISTRY,
|
|
24
24
|
DEFAULT_MODEL_ID,
|
|
25
|
+
DEFAULT_CURSOR_MODEL_ID,
|
|
26
|
+
DISABLED_PROVIDERS,
|
|
27
|
+
modelKey,
|
|
28
|
+
parseModelKey,
|
|
25
29
|
useModelRegistry,
|
|
26
30
|
ModelSelector,
|
|
31
|
+
HarnessSelector,
|
|
32
|
+
DEFAULT_HARNESS,
|
|
33
|
+
HARNESS_LABELS,
|
|
34
|
+
toProtoHarness,
|
|
35
|
+
fromProtoHarness,
|
|
27
36
|
} from "./models";
|
|
28
37
|
export type {
|
|
29
38
|
ModelInfo,
|
|
39
|
+
ParsedModelKey,
|
|
30
40
|
Provider,
|
|
31
41
|
CostTier,
|
|
32
42
|
UseModelRegistryReturn,
|
|
43
|
+
UseModelRegistryOptions,
|
|
33
44
|
ModelSelectorProps,
|
|
45
|
+
HarnessSelectorProps,
|
|
46
|
+
HarnessOption,
|
|
34
47
|
} from "./models";
|
|
35
48
|
|
|
36
49
|
// Workspace — behavior hooks and styled components
|
|
@@ -92,6 +105,7 @@ export type {
|
|
|
92
105
|
UseNewSessionFlowReturn,
|
|
93
106
|
UseSessionPageFlowOptions,
|
|
94
107
|
UseSessionPageFlowReturn,
|
|
108
|
+
UsePersistedModelOptions,
|
|
95
109
|
UsePersistedModelReturn,
|
|
96
110
|
UseEditSessionPrepReturn,
|
|
97
111
|
DraftResourceType,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useRef, type KeyboardEvent } from "react";
|
|
4
|
+
import { HARNESS_LABELS, type HarnessOption } from "./harness";
|
|
5
|
+
|
|
6
|
+
const OPTIONS: readonly HarnessOption[] = ["native", "cursor"];
|
|
7
|
+
|
|
8
|
+
/** Props for {@link HarnessSelector}. */
|
|
9
|
+
export interface HarnessSelectorProps {
|
|
10
|
+
/** Currently selected harness. */
|
|
11
|
+
readonly value: HarnessOption;
|
|
12
|
+
/** Called when the user picks a different harness. */
|
|
13
|
+
readonly onValueChange: (harness: HarnessOption) => void;
|
|
14
|
+
/** Additional CSS class names for the root container. */
|
|
15
|
+
readonly className?: string;
|
|
16
|
+
/** When true, disables the selector. */
|
|
17
|
+
readonly disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compact segmented control for choosing the session execution engine.
|
|
22
|
+
*
|
|
23
|
+
* Renders two mutually exclusive options — "Stigmer" (native) and
|
|
24
|
+
* "Cursor" (premium) — as adjacent pill segments. The Cursor segment
|
|
25
|
+
* carries a subtle premium tier indicator.
|
|
26
|
+
*
|
|
27
|
+
* Built as a `radiogroup` with full arrow-key navigation and ARIA
|
|
28
|
+
* semantics. All visual properties flow through `--stgm-*` tokens.
|
|
29
|
+
*
|
|
30
|
+
* Platform builders who need different rendering use
|
|
31
|
+
* {@link HarnessOption} and {@link HARNESS_LABELS} directly.
|
|
32
|
+
*
|
|
33
|
+
* @deprecated Use {@link ModelSelector} in unified mode (without the
|
|
34
|
+
* `harness` prop) instead. The unified model picker embeds an engine
|
|
35
|
+
* tag on each model row, eliminating the need for a separate harness
|
|
36
|
+
* control. This component is kept for backward compatibility.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function LauncherToolbar() {
|
|
41
|
+
* const [harness, setHarness] = useState<HarnessOption>("native");
|
|
42
|
+
*
|
|
43
|
+
* return <HarnessSelector value={harness} onValueChange={setHarness} />;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function HarnessSelector({
|
|
48
|
+
value,
|
|
49
|
+
onValueChange,
|
|
50
|
+
className,
|
|
51
|
+
disabled,
|
|
52
|
+
}: HarnessSelectorProps) {
|
|
53
|
+
const groupRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
|
|
55
|
+
const handleKeyDown = useCallback(
|
|
56
|
+
(e: KeyboardEvent<HTMLDivElement>) => {
|
|
57
|
+
if (disabled) return;
|
|
58
|
+
|
|
59
|
+
const idx = OPTIONS.indexOf(value);
|
|
60
|
+
let next: number | undefined;
|
|
61
|
+
|
|
62
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
63
|
+
next = (idx + 1) % OPTIONS.length;
|
|
64
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
65
|
+
next = (idx - 1 + OPTIONS.length) % OPTIONS.length;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (next !== undefined) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
onValueChange(OPTIONS[next]);
|
|
71
|
+
const buttons = groupRef.current?.querySelectorAll<HTMLButtonElement>("[role=radio]");
|
|
72
|
+
buttons?.[next]?.focus();
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[value, onValueChange, disabled],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
ref={groupRef}
|
|
81
|
+
role="radiogroup"
|
|
82
|
+
aria-label="Execution engine"
|
|
83
|
+
onKeyDown={handleKeyDown}
|
|
84
|
+
className={[
|
|
85
|
+
"inline-flex items-center rounded-md border border-border bg-background p-0.5",
|
|
86
|
+
disabled ? "pointer-events-none opacity-50" : undefined,
|
|
87
|
+
className,
|
|
88
|
+
]
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join(" ")}
|
|
91
|
+
>
|
|
92
|
+
{OPTIONS.map((option) => {
|
|
93
|
+
const isActive = value === option;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
key={option}
|
|
98
|
+
type="button"
|
|
99
|
+
role="radio"
|
|
100
|
+
aria-checked={isActive}
|
|
101
|
+
aria-label={HARNESS_LABELS[option]}
|
|
102
|
+
tabIndex={isActive ? 0 : -1}
|
|
103
|
+
disabled={disabled}
|
|
104
|
+
onClick={() => {
|
|
105
|
+
if (!isActive) onValueChange(option);
|
|
106
|
+
}}
|
|
107
|
+
className={[
|
|
108
|
+
"inline-flex items-center gap-1 rounded-[5px] px-2 py-1 text-xs transition-colors",
|
|
109
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
110
|
+
"disabled:pointer-events-none",
|
|
111
|
+
isActive
|
|
112
|
+
? "bg-accent font-medium text-foreground shadow-sm"
|
|
113
|
+
: "text-muted-foreground hover:text-foreground",
|
|
114
|
+
].join(" ")}
|
|
115
|
+
>
|
|
116
|
+
{HARNESS_LABELS[option]}
|
|
117
|
+
{option === "cursor" && (
|
|
118
|
+
<span
|
|
119
|
+
aria-label="premium"
|
|
120
|
+
className="text-[0.6rem] text-muted-foreground"
|
|
121
|
+
>
|
|
122
|
+
$$$
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
</button>
|
|
126
|
+
);
|
|
127
|
+
})}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|