@stigmer/react 3.5.3 → 3.7.0
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/agent/AgentPicker.d.ts +10 -1
- package/agent/AgentPicker.d.ts.map +1 -1
- package/agent/AgentPicker.js +2 -2
- package/agent/AgentPicker.js.map +1 -1
- package/billing/CreditLedgerTable.d.ts.map +1 -1
- package/billing/CreditLedgerTable.js +2 -7
- package/billing/CreditLedgerTable.js.map +1 -1
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +22 -5
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelTemplatesDialog.d.ts +53 -0
- package/channel/ChannelTemplatesDialog.d.ts.map +1 -0
- package/channel/ChannelTemplatesDialog.js +150 -0
- package/channel/ChannelTemplatesDialog.js.map +1 -0
- package/channel/index.d.ts +8 -0
- package/channel/index.d.ts.map +1 -1
- package/channel/index.js +4 -0
- package/channel/index.js.map +1 -1
- package/channel/providers.d.ts +12 -0
- package/channel/providers.d.ts.map +1 -1
- package/channel/providers.js +14 -2
- package/channel/providers.js.map +1 -1
- package/channel/templatePresentation.d.ts +37 -0
- package/channel/templatePresentation.d.ts.map +1 -0
- package/channel/templatePresentation.js +69 -0
- package/channel/templatePresentation.js.map +1 -0
- package/channel/useChannelTemplateList.d.ts +64 -0
- package/channel/useChannelTemplateList.d.ts.map +1 -0
- package/channel/useChannelTemplateList.js +69 -0
- package/channel/useChannelTemplateList.js.map +1 -0
- package/channel/useChannelTemplateReadiness.d.ts +53 -0
- package/channel/useChannelTemplateReadiness.d.ts.map +1 -0
- package/channel/useChannelTemplateReadiness.js +42 -0
- package/channel/useChannelTemplateReadiness.js.map +1 -0
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -2
- package/composer/SessionComposer.js.map +1 -1
- package/dashboard/useDashboardFailedRuns.d.ts.map +1 -1
- package/dashboard/useDashboardFailedRuns.js +2 -0
- package/dashboard/useDashboardFailedRuns.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +14 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +4 -2
- package/index.d.ts.map +1 -1
- package/index.js +10 -1
- package/index.js.map +1 -1
- package/internal/Pagination.d.ts +15 -0
- package/internal/Pagination.d.ts.map +1 -0
- package/internal/Pagination.js +20 -0
- package/internal/Pagination.js.map +1 -0
- package/library/useExportResource.d.ts +4 -3
- package/library/useExportResource.d.ts.map +1 -1
- package/library/useExportResource.js +1 -1
- package/library/useExportResource.js.map +1 -1
- package/models/ModelSelector.d.ts +26 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +27 -6
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +10 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +3 -0
- package/models/registry.js.map +1 -1
- package/models/service-tier.d.ts +27 -0
- package/models/service-tier.d.ts.map +1 -0
- package/models/service-tier.js +34 -0
- package/models/service-tier.js.map +1 -0
- package/organization/OrgProvider.d.ts +11 -4
- package/organization/OrgProvider.d.ts.map +1 -1
- package/organization/OrgProvider.js +28 -4
- package/organization/OrgProvider.js.map +1 -1
- package/package.json +4 -4
- package/resource-detail/useDeleteResource.d.ts +1 -1
- package/resource-detail/useDeleteResource.d.ts.map +1 -1
- package/resource-detail/useDeleteResource.js +5 -0
- package/resource-detail/useDeleteResource.js.map +1 -1
- package/resource-workbench/components/ResourceWorkbench.d.ts +10 -1
- package/resource-workbench/components/ResourceWorkbench.d.ts.map +1 -1
- package/resource-workbench/components/ResourceWorkbench.js +6 -2
- package/resource-workbench/components/ResourceWorkbench.js.map +1 -1
- package/resource-workbench/hooks/useResourceCollection.d.ts +8 -0
- package/resource-workbench/hooks/useResourceCollection.d.ts.map +1 -1
- package/resource-workbench/hooks/useResourceCollection.js +2 -2
- package/resource-workbench/hooks/useResourceCollection.js.map +1 -1
- package/schedule/CadenceField.d.ts +31 -0
- package/schedule/CadenceField.d.ts.map +1 -0
- package/schedule/CadenceField.js +116 -0
- package/schedule/CadenceField.js.map +1 -0
- package/schedule/ScheduleDetailView.d.ts +81 -0
- package/schedule/ScheduleDetailView.d.ts.map +1 -0
- package/schedule/ScheduleDetailView.js +648 -0
- package/schedule/ScheduleDetailView.js.map +1 -0
- package/schedule/ScheduleForm.d.ts +46 -0
- package/schedule/ScheduleForm.d.ts.map +1 -0
- package/schedule/ScheduleForm.js +214 -0
- package/schedule/ScheduleForm.js.map +1 -0
- package/schedule/ScheduleRowActions.d.ts +20 -0
- package/schedule/ScheduleRowActions.d.ts.map +1 -0
- package/schedule/ScheduleRowActions.js +54 -0
- package/schedule/ScheduleRowActions.js.map +1 -0
- package/schedule/ScheduleRunsTable.d.ts +72 -0
- package/schedule/ScheduleRunsTable.d.ts.map +1 -0
- package/schedule/ScheduleRunsTable.js +161 -0
- package/schedule/ScheduleRunsTable.js.map +1 -0
- package/schedule/TimeZoneField.d.ts +32 -0
- package/schedule/TimeZoneField.d.ts.map +1 -0
- package/schedule/TimeZoneField.js +104 -0
- package/schedule/TimeZoneField.js.map +1 -0
- package/schedule/cadence.d.ts +95 -0
- package/schedule/cadence.d.ts.map +1 -0
- package/schedule/cadence.js +242 -0
- package/schedule/cadence.js.map +1 -0
- package/schedule/index.d.ts +37 -0
- package/schedule/index.d.ts.map +1 -0
- package/schedule/index.js +19 -0
- package/schedule/index.js.map +1 -0
- package/schedule/scheduleColumns.d.ts +26 -0
- package/schedule/scheduleColumns.d.ts.map +1 -0
- package/schedule/scheduleColumns.js +77 -0
- package/schedule/scheduleColumns.js.map +1 -0
- package/schedule/scheduleListFn.d.ts +50 -0
- package/schedule/scheduleListFn.d.ts.map +1 -0
- package/schedule/scheduleListFn.js +65 -0
- package/schedule/scheduleListFn.js.map +1 -0
- package/schedule/scheduleState.d.ts +43 -0
- package/schedule/scheduleState.d.ts.map +1 -0
- package/schedule/scheduleState.js +67 -0
- package/schedule/scheduleState.js.map +1 -0
- package/schedule/useCreateSchedule.d.ts +57 -0
- package/schedule/useCreateSchedule.d.ts.map +1 -0
- package/schedule/useCreateSchedule.js +61 -0
- package/schedule/useCreateSchedule.js.map +1 -0
- package/schedule/useResumeSchedule.d.ts +30 -0
- package/schedule/useResumeSchedule.d.ts.map +1 -0
- package/schedule/useResumeSchedule.js +45 -0
- package/schedule/useResumeSchedule.js.map +1 -0
- package/schedule/useSchedule.d.ts +39 -0
- package/schedule/useSchedule.d.ts.map +1 -0
- package/schedule/useSchedule.js +44 -0
- package/schedule/useSchedule.js.map +1 -0
- package/schedule/useScheduleCount.d.ts +38 -0
- package/schedule/useScheduleCount.d.ts.map +1 -0
- package/schedule/useScheduleCount.js +30 -0
- package/schedule/useScheduleCount.js.map +1 -0
- package/schedule/useScheduleList.d.ts +48 -0
- package/schedule/useScheduleList.d.ts.map +1 -0
- package/schedule/useScheduleList.js +43 -0
- package/schedule/useScheduleList.js.map +1 -0
- package/schedule/useScheduleRuns.d.ts +44 -0
- package/schedule/useScheduleRuns.d.ts.map +1 -0
- package/schedule/useScheduleRuns.js +48 -0
- package/schedule/useScheduleRuns.js.map +1 -0
- package/schedule/useSetScheduleEnabled.d.ts +31 -0
- package/schedule/useSetScheduleEnabled.d.ts.map +1 -0
- package/schedule/useSetScheduleEnabled.js +53 -0
- package/schedule/useSetScheduleEnabled.js.map +1 -0
- package/schedule/useTriggerSchedule.d.ts +41 -0
- package/schedule/useTriggerSchedule.d.ts.map +1 -0
- package/schedule/useTriggerSchedule.js +62 -0
- package/schedule/useTriggerSchedule.js.map +1 -0
- package/schedule/useUpdateScheduleSpec.d.ts +49 -0
- package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
- package/schedule/useUpdateScheduleSpec.js +70 -0
- package/schedule/useUpdateScheduleSpec.js.map +1 -0
- package/search/useResourceCount.d.ts +2 -1
- package/search/useResourceCount.d.ts.map +1 -1
- package/search/useResourceCount.js +6 -1
- package/search/useResourceCount.js.map +1 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +1 -0
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +11 -0
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +1 -0
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/src/agent/AgentPicker.tsx +13 -1
- package/src/billing/CreditLedgerTable.tsx +3 -42
- package/src/channel/AgentChannelsPanel.tsx +43 -1
- package/src/channel/ChannelTemplatesDialog.tsx +433 -0
- package/src/channel/__tests__/AgentChannelsPanel.test.tsx +73 -0
- package/src/channel/__tests__/ChannelTemplatesDialog.test.tsx +292 -0
- package/src/channel/__tests__/providers.test.ts +9 -0
- package/src/channel/__tests__/templatePresentation.test.ts +96 -0
- package/src/channel/__tests__/useChannelTemplateList.test.tsx +216 -0
- package/src/channel/__tests__/useChannelTemplateReadiness.test.tsx +124 -0
- package/src/channel/index.ts +15 -0
- package/src/channel/providers.ts +26 -2
- package/src/channel/templatePresentation.ts +82 -0
- package/src/channel/useChannelTemplateList.ts +123 -0
- package/src/channel/useChannelTemplateReadiness.ts +69 -0
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +24 -1
- package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +156 -0
- package/src/dashboard/__tests__/useDashboardFailedRuns.test.tsx +106 -0
- package/src/dashboard/useDashboardFailedRuns.ts +2 -0
- package/src/execution/useCreateAgentExecution.ts +18 -0
- package/src/index.ts +70 -0
- package/src/internal/Pagination.tsx +72 -0
- package/src/library/useExportResource.ts +4 -3
- package/src/models/ModelSelector.tsx +100 -3
- package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +172 -0
- package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +16 -0
- package/src/models/service-tier.ts +49 -0
- package/src/organization/OrgProvider.tsx +29 -4
- package/src/organization/__tests__/OrgProvider.orgSwitch.test.tsx +89 -0
- package/src/resource-detail/useDeleteResource.ts +11 -1
- package/src/resource-workbench/__tests__/ResourceWorkbench.test.tsx +112 -0
- package/src/resource-workbench/components/ResourceWorkbench.tsx +32 -15
- package/src/resource-workbench/hooks/useResourceCollection.ts +11 -1
- package/src/schedule/CadenceField.tsx +365 -0
- package/src/schedule/ScheduleDetailView.tsx +1651 -0
- package/src/schedule/ScheduleForm.tsx +615 -0
- package/src/schedule/ScheduleRowActions.tsx +124 -0
- package/src/schedule/ScheduleRunsTable.tsx +424 -0
- package/src/schedule/TimeZoneField.tsx +199 -0
- package/src/schedule/__tests__/ScheduleDetailView.test.tsx +683 -0
- package/src/schedule/__tests__/cadence.test.ts +278 -0
- package/src/schedule/__tests__/scheduleColumnsRowActions.test.tsx +176 -0
- package/src/schedule/__tests__/scheduleCreation.test.tsx +537 -0
- package/src/schedule/__tests__/scheduleHooks.test.tsx +399 -0
- package/src/schedule/__tests__/scheduleListFn.test.ts +128 -0
- package/src/schedule/__tests__/scheduleState.test.ts +133 -0
- package/src/schedule/cadence.ts +298 -0
- package/src/schedule/index.ts +69 -0
- package/src/schedule/scheduleColumns.tsx +118 -0
- package/src/schedule/scheduleListFn.ts +108 -0
- package/src/schedule/scheduleState.ts +96 -0
- package/src/schedule/useCreateSchedule.ts +88 -0
- package/src/schedule/useResumeSchedule.ts +67 -0
- package/src/schedule/useSchedule.ts +67 -0
- package/src/schedule/useScheduleCount.ts +57 -0
- package/src/schedule/useScheduleList.ts +83 -0
- package/src/schedule/useScheduleRuns.ts +93 -0
- package/src/schedule/useSetScheduleEnabled.ts +84 -0
- package/src/schedule/useTriggerSchedule.ts +90 -0
- package/src/schedule/useUpdateScheduleSpec.ts +108 -0
- package/src/search/__tests__/useResourceCount.test.tsx +89 -0
- package/src/search/useResourceCount.ts +8 -2
- package/src/session/useNewSessionFlow.ts +1 -0
- package/src/session/useSessionConversation.ts +12 -0
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/workflow/WorkflowDashboard.tsx +1 -0
- package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
- package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
- package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
- package/src/workflow/starter-workflow-yaml.ts +2 -1
- package/src/workflow/useWorkflowExecutionList.ts +13 -2
- package/styles.css +1 -1
- package/workflow/CanvasTransitionEdge.d.ts +1 -1
- package/workflow/WorkflowDashboard.d.ts.map +1 -1
- package/workflow/WorkflowDashboard.js +1 -0
- package/workflow/WorkflowDashboard.js.map +1 -1
- package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
- package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
- package/workflow/inspector/forms/AgentCallForm.js +87 -9
- package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.js +31 -5
- package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
- package/workflow/starter-workflow-yaml.d.ts +1 -1
- package/workflow/starter-workflow-yaml.d.ts.map +1 -1
- package/workflow/starter-workflow-yaml.js +2 -1
- package/workflow/starter-workflow-yaml.js.map +1 -1
- package/workflow/useWorkflowExecutionList.d.ts +10 -0
- package/workflow/useWorkflowExecutionList.d.ts.map +1 -1
- package/workflow/useWorkflowExecutionList.js +3 -2
- package/workflow/useWorkflowExecutionList.js.map +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach, beforeAll } from "vitest";
|
|
2
|
+
import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
+
import { type ReactNode } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
import { StigmerContext } from "../../context";
|
|
6
|
+
import { ModelRegistryContext } from "../../models/ModelRegistryContext";
|
|
7
|
+
import type { ModelInfo } from "../../models/registry";
|
|
8
|
+
import { SessionComposer } from "../SessionComposer";
|
|
9
|
+
|
|
10
|
+
// Portal into document.body so the popover mounts under happy-dom, and shim
|
|
11
|
+
// ResizeObserver for Base UI's positioner (the scheduleCreation.test.tsx
|
|
12
|
+
// pattern).
|
|
13
|
+
vi.mock("../../portal-container", () => ({
|
|
14
|
+
useStigmerPortalContainer: () => document.body,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
if (!("ResizeObserver" in globalThis)) {
|
|
19
|
+
(globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
|
|
20
|
+
class {
|
|
21
|
+
observe() {}
|
|
22
|
+
unobserve() {}
|
|
23
|
+
disconnect() {}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* End-to-end composer wiring for the service tier (stigmer/stigmer#357):
|
|
30
|
+
* the toolbar's ModelSelector toggle must reach composer state, and an
|
|
31
|
+
* active fast selection must ride the submit context to execution create.
|
|
32
|
+
* This seam (SessionComposer → ComposerToolbar props → submit context) is
|
|
33
|
+
* exactly where the feature can silently die while every layer on either
|
|
34
|
+
* side unit-tests green.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const FAST_CAPABLE: ModelInfo = {
|
|
38
|
+
modelId: "composer-2.5",
|
|
39
|
+
provider: "cursor",
|
|
40
|
+
displayName: "Composer 2.5",
|
|
41
|
+
shortDescription: "Fast agentic model",
|
|
42
|
+
speedTier: "fastest",
|
|
43
|
+
costTier: "economy",
|
|
44
|
+
harness: "cursor",
|
|
45
|
+
featured: true,
|
|
46
|
+
serviceTiers: ["fast"],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function createMinimalStigmerMock(): Stigmer {
|
|
50
|
+
return {
|
|
51
|
+
agentExecution: { uploadAttachment: vi.fn() },
|
|
52
|
+
environment: { getPersonal: vi.fn().mockResolvedValue(null) },
|
|
53
|
+
baseUrl: "http://localhost:8080",
|
|
54
|
+
getAuthCredential: vi.fn().mockResolvedValue("test-token"),
|
|
55
|
+
config: {
|
|
56
|
+
baseUrl: "http://localhost:8080",
|
|
57
|
+
getAccessToken: vi.fn().mockResolvedValue(""),
|
|
58
|
+
},
|
|
59
|
+
} as unknown as Stigmer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createWrapper(client: Stigmer) {
|
|
63
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
64
|
+
return (
|
|
65
|
+
<StigmerContext.Provider value={client}>
|
|
66
|
+
<ModelRegistryContext.Provider
|
|
67
|
+
value={{
|
|
68
|
+
models: [FAST_CAPABLE],
|
|
69
|
+
isLoading: false,
|
|
70
|
+
error: null,
|
|
71
|
+
refetch: vi.fn(),
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</ModelRegistryContext.Provider>
|
|
76
|
+
</StigmerContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderComposer() {
|
|
82
|
+
const client = createMinimalStigmerMock();
|
|
83
|
+
const onSubmit = vi.fn();
|
|
84
|
+
|
|
85
|
+
const result = render(
|
|
86
|
+
<SessionComposer
|
|
87
|
+
onSubmit={onSubmit}
|
|
88
|
+
harness="cursor"
|
|
89
|
+
defaultModelId="composer-2.5"
|
|
90
|
+
/>,
|
|
91
|
+
{ wrapper: createWrapper(client) },
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return { ...result, onSubmit };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function submitMessage(message: string) {
|
|
98
|
+
const textarea = screen.getByRole("textbox");
|
|
99
|
+
fireEvent.change(textarea, { target: { value: message } });
|
|
100
|
+
fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Open the model selector popover (Base UI portals mount asynchronously in
|
|
105
|
+
* jsdom — the switch is awaited) and flip the fast-tier toggle.
|
|
106
|
+
*/
|
|
107
|
+
async function toggleFastTier() {
|
|
108
|
+
fireEvent.click(screen.getByRole("button", { name: /Composer 2\.5/ }));
|
|
109
|
+
const toggle = await screen.findByRole("switch", { name: "Fast tier" });
|
|
110
|
+
fireEvent.click(toggle);
|
|
111
|
+
fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
afterEach(cleanup);
|
|
115
|
+
|
|
116
|
+
describe("SessionComposer — service tier submit contract", () => {
|
|
117
|
+
it("renders the tier toggle through the toolbar for a fast-capable model", async () => {
|
|
118
|
+
renderComposer();
|
|
119
|
+
|
|
120
|
+
fireEvent.click(screen.getByRole("button", { name: /Composer 2\.5/ }));
|
|
121
|
+
expect(await screen.findByRole("switch", { name: "Fast tier" })).toBeTruthy();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("carries serviceTier 'fast' on the submit context after toggling", async () => {
|
|
125
|
+
const { onSubmit } = renderComposer();
|
|
126
|
+
|
|
127
|
+
await toggleFastTier();
|
|
128
|
+
submitMessage("Run it fast");
|
|
129
|
+
|
|
130
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
131
|
+
const context = onSubmit.mock.calls[0][2];
|
|
132
|
+
expect(context?.serviceTier).toBe("fast");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("leaves serviceTier undefined when the toggle is untouched", async () => {
|
|
136
|
+
const { onSubmit } = renderComposer();
|
|
137
|
+
|
|
138
|
+
submitMessage("Run it normally");
|
|
139
|
+
|
|
140
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
141
|
+
const context = onSubmit.mock.calls[0][2];
|
|
142
|
+
expect(context?.serviceTier).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("leaves serviceTier undefined after toggling fast on and back off", async () => {
|
|
146
|
+
const { onSubmit } = renderComposer();
|
|
147
|
+
|
|
148
|
+
await toggleFastTier();
|
|
149
|
+
await toggleFastTier();
|
|
150
|
+
submitMessage("Changed my mind");
|
|
151
|
+
|
|
152
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
153
|
+
const context = onSubmit.mock.calls[0][2];
|
|
154
|
+
expect(context?.serviceTier).toBeUndefined();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { ExecutionPhase as AgentPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import { ExecutionPhase as WorkflowPhase } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
|
|
6
|
+
import { StigmerContext } from "../../context";
|
|
7
|
+
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
8
|
+
import { useDashboardFailedRuns } from "../useDashboardFailedRuns";
|
|
9
|
+
|
|
10
|
+
function createMockStigmer(overrides: {
|
|
11
|
+
agentList?: (...args: unknown[]) => Promise<unknown>;
|
|
12
|
+
workflowList?: (...args: unknown[]) => Promise<unknown>;
|
|
13
|
+
} = {}) {
|
|
14
|
+
return {
|
|
15
|
+
agentExecution: {
|
|
16
|
+
list: overrides.agentList ?? vi.fn().mockResolvedValue({ entries: [] }),
|
|
17
|
+
},
|
|
18
|
+
workflowExecution: {
|
|
19
|
+
list: overrides.workflowList ?? vi.fn().mockResolvedValue({ entries: [] }),
|
|
20
|
+
},
|
|
21
|
+
} as never;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function wrapper(client: unknown) {
|
|
25
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
26
|
+
return (
|
|
27
|
+
<FetchCacheContext.Provider value={null}>
|
|
28
|
+
<StigmerContext.Provider value={client as never}>
|
|
29
|
+
{children}
|
|
30
|
+
</StigmerContext.Provider>
|
|
31
|
+
</FetchCacheContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("useDashboardFailedRuns", () => {
|
|
37
|
+
it("scopes both list requests to the active org and the FAILED phase", async () => {
|
|
38
|
+
const agentList = vi.fn().mockResolvedValue({ entries: [] });
|
|
39
|
+
const workflowList = vi.fn().mockResolvedValue({ entries: [] });
|
|
40
|
+
const client = createMockStigmer({ agentList, workflowList });
|
|
41
|
+
|
|
42
|
+
const { result } = renderHook(() => useDashboardFailedRuns("acme"), {
|
|
43
|
+
wrapper: wrapper(client),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
47
|
+
|
|
48
|
+
// Regression guard: the pre-fix hook used org only as a fetch gate and
|
|
49
|
+
// sent no org on the wire, so a brand-new org's dashboard showed other
|
|
50
|
+
// orgs' failures.
|
|
51
|
+
const agentRequest = agentList.mock.calls[0][0];
|
|
52
|
+
expect(agentRequest.org).toBe("acme");
|
|
53
|
+
expect(agentRequest.phase).toBe(AgentPhase.EXECUTION_FAILED);
|
|
54
|
+
|
|
55
|
+
const workflowRequest = workflowList.mock.calls[0][0];
|
|
56
|
+
expect(workflowRequest.org).toBe("acme");
|
|
57
|
+
expect(workflowRequest.phase).toBe(WorkflowPhase.EXECUTION_FAILED);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("does not fetch until an org is available", async () => {
|
|
61
|
+
const agentList = vi.fn().mockResolvedValue({ entries: [] });
|
|
62
|
+
const workflowList = vi.fn().mockResolvedValue({ entries: [] });
|
|
63
|
+
const client = createMockStigmer({ agentList, workflowList });
|
|
64
|
+
|
|
65
|
+
renderHook(() => useDashboardFailedRuns(null), {
|
|
66
|
+
wrapper: wrapper(client),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Give any wrongly-armed fetch a tick to fire.
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
71
|
+
|
|
72
|
+
expect(agentList).not.toHaveBeenCalled();
|
|
73
|
+
expect(workflowList).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("interleaves agent and workflow failures newest-first, capped at 5", async () => {
|
|
77
|
+
const ts = (secs: number) => ({ seconds: BigInt(secs), nanos: 0 });
|
|
78
|
+
const agentEntries = [1, 3, 5].map((n) => ({
|
|
79
|
+
metadata: { id: `aex_${n}`, name: `agent ${n}` },
|
|
80
|
+
spec: { agentId: `agt_${n}` },
|
|
81
|
+
status: { error: "boom", audit: { specAudit: { createdAt: ts(n) } } },
|
|
82
|
+
}));
|
|
83
|
+
const workflowEntries = [2, 4, 6].map((n) => ({
|
|
84
|
+
metadata: { id: `wex_${n}`, name: `workflow ${n}`, slug: `wf-${n}` },
|
|
85
|
+
status: { error: "boom", audit: { specAudit: { createdAt: ts(n) } } },
|
|
86
|
+
}));
|
|
87
|
+
const client = createMockStigmer({
|
|
88
|
+
agentList: vi.fn().mockResolvedValue({ entries: agentEntries }),
|
|
89
|
+
workflowList: vi.fn().mockResolvedValue({ entries: workflowEntries }),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const { result } = renderHook(() => useDashboardFailedRuns("acme"), {
|
|
93
|
+
wrapper: wrapper(client),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await waitFor(() => expect(result.current.failedRuns).toHaveLength(5));
|
|
97
|
+
|
|
98
|
+
expect(result.current.failedRuns.map((run) => run.id)).toEqual([
|
|
99
|
+
"wex_6",
|
|
100
|
+
"aex_5",
|
|
101
|
+
"wex_4",
|
|
102
|
+
"aex_3",
|
|
103
|
+
"wex_2",
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -48,6 +48,7 @@ export function useDashboardFailedRuns(
|
|
|
48
48
|
create(ListAgentExecutionsRequestSchema, {
|
|
49
49
|
pageSize: PAGE_SIZE,
|
|
50
50
|
phase: AgentPhase.EXECUTION_FAILED,
|
|
51
|
+
org: orgVal,
|
|
51
52
|
}),
|
|
52
53
|
);
|
|
53
54
|
return [...resp.entries] as readonly AgentExecution[];
|
|
@@ -64,6 +65,7 @@ export function useDashboardFailedRuns(
|
|
|
64
65
|
create(ListWorkflowExecutionsRequestSchema, {
|
|
65
66
|
pageSize: PAGE_SIZE,
|
|
66
67
|
phase: WorkflowPhase.EXECUTION_FAILED,
|
|
68
|
+
org: orgVal,
|
|
67
69
|
}),
|
|
68
70
|
);
|
|
69
71
|
return [...resp.entries] as readonly WorkflowExecution[];
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { useStigmer } from "../hooks.js";
|
|
14
14
|
import { toError } from "../internal/toError.js";
|
|
15
15
|
import { toProtoInteractionMode } from "../composer/interaction-mode.js";
|
|
16
|
+
import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
|
|
16
17
|
import { toProtoHarness, type HarnessOption } from "../models/harness.js";
|
|
17
18
|
import {
|
|
18
19
|
toProtoExecutionTarget,
|
|
@@ -123,6 +124,19 @@ export interface SharedAgentExecutionFields {
|
|
|
123
124
|
* Maps to `ExecutionConfig.interaction_mode` in the proto.
|
|
124
125
|
*/
|
|
125
126
|
readonly interactionMode?: "agent" | "plan";
|
|
127
|
+
/**
|
|
128
|
+
* Service tier for this execution's model calls (stigmer/stigmer#357).
|
|
129
|
+
*
|
|
130
|
+
* - `"standard"` (default): the model's base-priced configuration,
|
|
131
|
+
* requested explicitly — never the provider account default.
|
|
132
|
+
* - `"fast"`: the provider's fast variant at fast rates. Valid only for
|
|
133
|
+
* models whose registry entry prices a fast variant — the backend
|
|
134
|
+
* refuses the create otherwise, so gate the option on
|
|
135
|
+
* `ModelInfo.serviceTiers`.
|
|
136
|
+
*
|
|
137
|
+
* Maps to `ExecutionConfig.service_tier` in the proto.
|
|
138
|
+
*/
|
|
139
|
+
readonly serviceTier?: ServiceTierOption;
|
|
126
140
|
/**
|
|
127
141
|
* Marks this execution as a Build-from-plan turn: the user approved a
|
|
128
142
|
* plan from a prior Plan-mode execution and asked the agent to implement
|
|
@@ -302,6 +316,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
|
|
|
302
316
|
const hasConfig =
|
|
303
317
|
input.modelName ||
|
|
304
318
|
input.interactionMode ||
|
|
319
|
+
input.serviceTier ||
|
|
305
320
|
input.structuredOutputSchema ||
|
|
306
321
|
input.buildFromPlan;
|
|
307
322
|
const executionConfig = hasConfig
|
|
@@ -310,6 +325,9 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
|
|
|
310
325
|
...(input.interactionMode
|
|
311
326
|
? { interactionMode: toProtoInteractionMode(input.interactionMode) }
|
|
312
327
|
: {}),
|
|
328
|
+
...(input.serviceTier
|
|
329
|
+
? { serviceTier: toProtoServiceTier(input.serviceTier) }
|
|
330
|
+
: {}),
|
|
313
331
|
...(input.structuredOutputSchema
|
|
314
332
|
? { structuredOutputSchema: input.structuredOutputSchema }
|
|
315
333
|
: {}),
|
package/src/index.ts
CHANGED
|
@@ -770,6 +770,66 @@ export type {
|
|
|
770
770
|
DatastoreDetailViewProps,
|
|
771
771
|
} from "./datastore/index.js";
|
|
772
772
|
|
|
773
|
+
// Schedule — the disabled-vs-paused state derivation, the direct-query
|
|
774
|
+
// list core + workbench adapter (schedules are not search-backed), data
|
|
775
|
+
// hooks (get by reference, paginated list, count), behavior hooks
|
|
776
|
+
// (create via apply, resume, trigger once, owner enable/disable and
|
|
777
|
+
// per-field spec edits via lossless manifest apply), the cadence model
|
|
778
|
+
// (preset ⇄ cron, no cron parsing — recognition of builder-emitted
|
|
779
|
+
// shapes only), the cadence builder, the creation form, the tabbed
|
|
780
|
+
// detail view with inline editing, and the paginated run-history table
|
|
781
|
+
export {
|
|
782
|
+
deriveScheduleState,
|
|
783
|
+
formatNextFire,
|
|
784
|
+
createScheduleListFn,
|
|
785
|
+
listSchedulesPage,
|
|
786
|
+
useSchedule,
|
|
787
|
+
useScheduleList,
|
|
788
|
+
useScheduleCount,
|
|
789
|
+
useCreateSchedule,
|
|
790
|
+
useResumeSchedule,
|
|
791
|
+
useTriggerSchedule,
|
|
792
|
+
useSetScheduleEnabled,
|
|
793
|
+
useUpdateScheduleSpec,
|
|
794
|
+
createScheduleColumns,
|
|
795
|
+
cadenceToCron,
|
|
796
|
+
cronToCadence,
|
|
797
|
+
describeCadence,
|
|
798
|
+
validateCron,
|
|
799
|
+
validateTimeZone,
|
|
800
|
+
WEEKDAY_LABELS,
|
|
801
|
+
CadenceField,
|
|
802
|
+
ScheduleForm,
|
|
803
|
+
ScheduleRowActions,
|
|
804
|
+
ScheduleDetailView,
|
|
805
|
+
ScheduleIcon,
|
|
806
|
+
ScheduleRunsTable,
|
|
807
|
+
} from "./schedule/index.js";
|
|
808
|
+
export type {
|
|
809
|
+
ScheduleState,
|
|
810
|
+
ScheduleStateInfo,
|
|
811
|
+
ScheduleListClient,
|
|
812
|
+
SchedulePage,
|
|
813
|
+
UseScheduleReturn,
|
|
814
|
+
UseScheduleListOptions,
|
|
815
|
+
UseScheduleListReturn,
|
|
816
|
+
UseScheduleCountOptions,
|
|
817
|
+
UseScheduleCountReturn,
|
|
818
|
+
UseCreateScheduleReturn,
|
|
819
|
+
UseResumeScheduleReturn,
|
|
820
|
+
UseTriggerScheduleReturn,
|
|
821
|
+
UseSetScheduleEnabledReturn,
|
|
822
|
+
CadencePreset,
|
|
823
|
+
CadenceKind,
|
|
824
|
+
CadenceFieldProps,
|
|
825
|
+
ScheduleFormProps,
|
|
826
|
+
ScheduleColumnsOptions,
|
|
827
|
+
ScheduleRowActionsProps,
|
|
828
|
+
ScheduleDetailViewProps,
|
|
829
|
+
ScheduleRunsTableProps,
|
|
830
|
+
UseUpdateScheduleSpecReturn,
|
|
831
|
+
} from "./schedule/index.js";
|
|
832
|
+
|
|
773
833
|
// Environment — data hooks, list hook, personal convenience hook, secret reveal, variable management, env var form, system env vars, and styled components
|
|
774
834
|
export {
|
|
775
835
|
useEnvironment,
|
|
@@ -1259,13 +1319,18 @@ export {
|
|
|
1259
1319
|
useConnectSlackChannel,
|
|
1260
1320
|
useInstallChannel,
|
|
1261
1321
|
useChannelToolReadiness,
|
|
1322
|
+
useChannelTemplateReadiness,
|
|
1262
1323
|
useChannelSessions,
|
|
1324
|
+
useChannelTemplateList,
|
|
1325
|
+
templateStatusPhase,
|
|
1326
|
+
splitTemplateBody,
|
|
1263
1327
|
AgentChannelsPanel,
|
|
1264
1328
|
ConnectSlackDialog,
|
|
1265
1329
|
ConnectWhatsAppDialog,
|
|
1266
1330
|
ChannelCredentialsDialog,
|
|
1267
1331
|
ChannelToolCredentials,
|
|
1268
1332
|
ChannelConversationsDialog,
|
|
1333
|
+
ChannelTemplatesDialog,
|
|
1269
1334
|
} from "./channel/index.js";
|
|
1270
1335
|
export type {
|
|
1271
1336
|
UseAgentChannelListReturn,
|
|
@@ -1277,13 +1342,18 @@ export type {
|
|
|
1277
1342
|
SlackConnectPhase,
|
|
1278
1343
|
UseInstallChannelReturn,
|
|
1279
1344
|
InstallChannelPhase,
|
|
1345
|
+
ChannelTemplateReadiness,
|
|
1280
1346
|
UseChannelSessionsReturn,
|
|
1347
|
+
UseChannelTemplateListOptions,
|
|
1348
|
+
UseChannelTemplateListReturn,
|
|
1349
|
+
TemplateBodySegment,
|
|
1281
1350
|
AgentChannelsPanelProps,
|
|
1282
1351
|
ConnectSlackDialogProps,
|
|
1283
1352
|
ConnectWhatsAppDialogProps,
|
|
1284
1353
|
ChannelCredentialsDialogProps,
|
|
1285
1354
|
ChannelToolCredentialsProps,
|
|
1286
1355
|
ChannelConversationsDialogProps,
|
|
1356
|
+
ChannelTemplatesDialogProps,
|
|
1287
1357
|
} from "./channel/index.js";
|
|
1288
1358
|
|
|
1289
1359
|
// Error — structured error display with classification, retry, and contextual guidance
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared prev/next pagination control for paginated SDK tables.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from `CreditLedgerTable` (its original, file-private home)
|
|
7
|
+
* when `ScheduleRunsTable` became the second consumer. Deliberately
|
|
8
|
+
* minimal — Previous / "Page N of M" / Next — because every paginated
|
|
9
|
+
* surface in the SDK so far wants exactly this shape; page-number
|
|
10
|
+
* jumping earns its complexity only when a consumer actually needs it.
|
|
11
|
+
*
|
|
12
|
+
* Internal (not barrel-exported): the public contract is each table
|
|
13
|
+
* component, not this control.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { cn } from "@stigmer/theme";
|
|
17
|
+
|
|
18
|
+
/** Props for {@link Pagination}. */
|
|
19
|
+
export interface PaginationProps {
|
|
20
|
+
/** Current page number, 1-indexed. */
|
|
21
|
+
readonly pageNum: number;
|
|
22
|
+
/** Total number of pages. Render the control only when > 1. */
|
|
23
|
+
readonly totalPages: number;
|
|
24
|
+
/** Called with the new page number when the user navigates. */
|
|
25
|
+
readonly onPageChange: (page: number) => void;
|
|
26
|
+
/** Accessible label naming what is being paginated (e.g. "Run history pagination"). */
|
|
27
|
+
readonly ariaLabel: string;
|
|
28
|
+
/** Additional CSS classes for the root container. */
|
|
29
|
+
readonly className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const buttonClasses = cn(
|
|
33
|
+
"rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors",
|
|
34
|
+
"hover:bg-accent hover:text-foreground",
|
|
35
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export function Pagination({
|
|
39
|
+
pageNum,
|
|
40
|
+
totalPages,
|
|
41
|
+
onPageChange,
|
|
42
|
+
ariaLabel,
|
|
43
|
+
className,
|
|
44
|
+
}: PaginationProps) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
className={cn("flex items-center justify-between", className)}
|
|
48
|
+
role="navigation"
|
|
49
|
+
aria-label={ariaLabel}
|
|
50
|
+
>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
disabled={pageNum <= 1}
|
|
54
|
+
onClick={() => onPageChange(pageNum - 1)}
|
|
55
|
+
className={buttonClasses}
|
|
56
|
+
>
|
|
57
|
+
Previous
|
|
58
|
+
</button>
|
|
59
|
+
<span className="text-xs tabular-nums text-muted-foreground">
|
|
60
|
+
Page {pageNum} of {totalPages}
|
|
61
|
+
</span>
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
disabled={pageNum >= totalPages}
|
|
65
|
+
onClick={() => onPageChange(pageNum + 1)}
|
|
66
|
+
className={buttonClasses}
|
|
67
|
+
>
|
|
68
|
+
Next
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -5,6 +5,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
5
5
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
6
6
|
import type { Datastore } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
7
7
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
8
|
+
import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
|
|
8
9
|
import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
9
10
|
import { serializeManifest } from "@stigmer/sdk";
|
|
10
11
|
import { toast } from "../feedback/toast.js";
|
|
@@ -17,9 +18,9 @@ import { serializeWorkflowYaml } from "../workflow/serialize-workflow-yaml.js";
|
|
|
17
18
|
/** Options for {@link useExportResource}. */
|
|
18
19
|
export interface UseExportResourceOptions {
|
|
19
20
|
/** The resource kind — determines which serializer is used. */
|
|
20
|
-
readonly kind: "Agent" | "McpServer" | "Workflow" | "Datastore";
|
|
21
|
+
readonly kind: "Agent" | "McpServer" | "Workflow" | "Datastore" | "Schedule";
|
|
21
22
|
/** The proto resource to export, or `null` when not yet loaded. */
|
|
22
|
-
readonly resource: Agent | McpServer | Workflow | Datastore | null;
|
|
23
|
+
readonly resource: Agent | McpServer | Workflow | Datastore | Schedule | null;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/** Return value of {@link useExportResource}. */
|
|
@@ -44,7 +45,7 @@ export interface UseExportResourceReturn {
|
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* Headless export hook for Stigmer resources (Agent, McpServer,
|
|
47
|
-
* Workflow, Datastore).
|
|
48
|
+
* Workflow, Datastore, Schedule).
|
|
48
49
|
*
|
|
49
50
|
* Serializes the resource into YAML and JSON formats and provides
|
|
50
51
|
* stable callbacks for copying to clipboard or triggering a file
|
|
@@ -7,6 +7,7 @@ import { useStigmerPortalContainer } from "../portal-container.js";
|
|
|
7
7
|
import { useModelRegistry } from "./useModelRegistry.js";
|
|
8
8
|
import type { ModelInfo, CostTier, SpeedTier } from "./registry.js";
|
|
9
9
|
import { HARNESS_META, HARNESS_OPTIONS, type HarnessOption } from "./harness.js";
|
|
10
|
+
import { FAST_SERVICE_TIER, type ServiceTierOption } from "./service-tier.js";
|
|
10
11
|
|
|
11
12
|
const COST_TIER_LABEL: Record<CostTier, string> = {
|
|
12
13
|
economy: "$",
|
|
@@ -61,6 +62,31 @@ export interface ModelSelectorProps {
|
|
|
61
62
|
readonly className?: string;
|
|
62
63
|
/** When true, disables the selector. */
|
|
63
64
|
readonly disabled?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Trigger label shown while `value` is empty, instead of falling back
|
|
67
|
+
* to the registry's default model. Lets a form distinguish "nothing
|
|
68
|
+
* pinned — the platform default applies" from an actual selection
|
|
69
|
+
* (the schedule form's contract: an unset model inherits the
|
|
70
|
+
* surface's platform default). The harness prefix is hidden while
|
|
71
|
+
* the placeholder shows, since no engine is pinned either.
|
|
72
|
+
*/
|
|
73
|
+
readonly placeholderLabel?: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Current service tier for the selected model (stigmer/stigmer#357).
|
|
77
|
+
* Defaults to "standard". Only meaningful together with
|
|
78
|
+
* {@link onServiceTierChange}.
|
|
79
|
+
*/
|
|
80
|
+
readonly serviceTier?: ServiceTierOption;
|
|
81
|
+
/**
|
|
82
|
+
* Called when the user toggles the fast tier. Providing this enables the
|
|
83
|
+
* tier toggle, which renders ONLY when the selected model prices a fast
|
|
84
|
+
* variant ({@link ModelInfo.serviceTiers}) — no dead controls. Selecting
|
|
85
|
+
* a model without a fast tier while "fast" is active resets the tier to
|
|
86
|
+
* "standard" through this callback, so an unsupported combination can
|
|
87
|
+
* never be submitted.
|
|
88
|
+
*/
|
|
89
|
+
readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
|
|
64
90
|
|
|
65
91
|
/**
|
|
66
92
|
* @deprecated Use {@link onHarnessChange} instead.
|
|
@@ -104,6 +130,9 @@ export function ModelSelector({
|
|
|
104
130
|
compact = false,
|
|
105
131
|
className,
|
|
106
132
|
disabled,
|
|
133
|
+
placeholderLabel,
|
|
134
|
+
serviceTier = "standard",
|
|
135
|
+
onServiceTierChange,
|
|
107
136
|
}: ModelSelectorProps) {
|
|
108
137
|
const portalContainer = useStigmerPortalContainer();
|
|
109
138
|
|
|
@@ -216,9 +245,19 @@ export function ModelSelector({
|
|
|
216
245
|
const selectModel = useCallback(
|
|
217
246
|
(model: ModelInfo) => {
|
|
218
247
|
onValueChange(model.modelId);
|
|
248
|
+
// Fail-safe: the fast tier is per-model; carrying it onto a model
|
|
249
|
+
// that does not price it would be refused at create time (#357), so
|
|
250
|
+
// reset here instead of surfacing a dead selection.
|
|
251
|
+
if (
|
|
252
|
+
serviceTier === "fast"
|
|
253
|
+
&& onServiceTierChange
|
|
254
|
+
&& !model.serviceTiers.includes(FAST_SERVICE_TIER)
|
|
255
|
+
) {
|
|
256
|
+
onServiceTierChange("standard");
|
|
257
|
+
}
|
|
219
258
|
setOpen(false);
|
|
220
259
|
},
|
|
221
|
-
[onValueChange],
|
|
260
|
+
[onValueChange, serviceTier, onServiceTierChange],
|
|
222
261
|
);
|
|
223
262
|
|
|
224
263
|
const scrollHighlightIntoView = useCallback((idx: number) => {
|
|
@@ -265,8 +304,23 @@ export function ModelSelector({
|
|
|
265
304
|
|
|
266
305
|
const showShowAllButton = !isSearching && !showAll && featuredModels.length > 0 && featuredModels.length < models.length;
|
|
267
306
|
|
|
268
|
-
|
|
269
|
-
|
|
307
|
+
// The tier toggle exists only where it applies: a consumer opted in AND
|
|
308
|
+
// the selected model prices a fast variant (recognition over recall —
|
|
309
|
+
// never a dead control on models with no tier choice).
|
|
310
|
+
const showServiceTierToggle =
|
|
311
|
+
onServiceTierChange !== undefined
|
|
312
|
+
&& selectedModel !== undefined
|
|
313
|
+
&& selectedModel.serviceTiers.includes(FAST_SERVICE_TIER);
|
|
314
|
+
const fastActive = serviceTier === "fast";
|
|
315
|
+
|
|
316
|
+
const usingPlaceholder = !value && placeholderLabel !== undefined;
|
|
317
|
+
const triggerLabel = usingPlaceholder
|
|
318
|
+
? placeholderLabel
|
|
319
|
+
: (selectedModel?.displayName ?? "Select model");
|
|
320
|
+
const triggerHarness =
|
|
321
|
+
!isHarnessLocked && !usingPlaceholder
|
|
322
|
+
? HARNESS_META[activeHarness].label
|
|
323
|
+
: undefined;
|
|
270
324
|
|
|
271
325
|
return (
|
|
272
326
|
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
@@ -288,6 +342,16 @@ export function ModelSelector({
|
|
|
288
342
|
<span className="shrink-0 text-border" aria-hidden>·</span>
|
|
289
343
|
)}
|
|
290
344
|
<span className="truncate">{triggerLabel}</span>
|
|
345
|
+
{showServiceTierToggle && fastActive && (
|
|
346
|
+
<span
|
|
347
|
+
className={cn(
|
|
348
|
+
"shrink-0 rounded-sm border border-border px-1 py-px",
|
|
349
|
+
"text-[0.6rem] font-medium uppercase tracking-wider text-muted-foreground",
|
|
350
|
+
)}
|
|
351
|
+
>
|
|
352
|
+
Fast
|
|
353
|
+
</span>
|
|
354
|
+
)}
|
|
291
355
|
<ChevronIcon />
|
|
292
356
|
</Popover.Trigger>
|
|
293
357
|
|
|
@@ -471,6 +535,39 @@ export function ModelSelector({
|
|
|
471
535
|
</button>
|
|
472
536
|
)}
|
|
473
537
|
</div>
|
|
538
|
+
|
|
539
|
+
{/* Service tier toggle — only for models that price a fast variant (#357) */}
|
|
540
|
+
{showServiceTierToggle && (
|
|
541
|
+
<div className="flex items-center justify-between border-t border-border px-3 py-2">
|
|
542
|
+
<div className="flex flex-col">
|
|
543
|
+
<span className="text-xs text-foreground">Fast tier</span>
|
|
544
|
+
<span className="text-[0.65rem] text-muted-foreground">
|
|
545
|
+
Faster responses at higher per-token rates
|
|
546
|
+
</span>
|
|
547
|
+
</div>
|
|
548
|
+
<button
|
|
549
|
+
type="button"
|
|
550
|
+
role="switch"
|
|
551
|
+
aria-checked={fastActive}
|
|
552
|
+
aria-label="Fast tier"
|
|
553
|
+
className={cn(
|
|
554
|
+
"relative h-4.5 w-8 shrink-0 rounded-full transition-colors cursor-pointer",
|
|
555
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
556
|
+
fastActive ? "bg-primary" : "bg-muted",
|
|
557
|
+
)}
|
|
558
|
+
onClick={() =>
|
|
559
|
+
onServiceTierChange?.(fastActive ? "standard" : "fast")
|
|
560
|
+
}
|
|
561
|
+
>
|
|
562
|
+
<span
|
|
563
|
+
className={cn(
|
|
564
|
+
"absolute top-0.5 size-3.5 rounded-full bg-background shadow-sm transition-transform",
|
|
565
|
+
fastActive ? "translate-x-4" : "translate-x-0.5",
|
|
566
|
+
)}
|
|
567
|
+
/>
|
|
568
|
+
</button>
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
474
571
|
</Popover.Popup>
|
|
475
572
|
</Popover.Positioner>
|
|
476
573
|
</Popover.Portal>
|