@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,615 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState, type FormEvent } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import {
|
|
6
|
+
getUserMessage,
|
|
7
|
+
type ResourceRef,
|
|
8
|
+
type RunConfigInput,
|
|
9
|
+
} from "@stigmer/sdk";
|
|
10
|
+
import type { Schedule } from "@stigmer/protos/ai/stigmer/agentic/schedule/v1/api_pb";
|
|
11
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
12
|
+
import { Popover } from "@base-ui/react/popover";
|
|
13
|
+
import { AgentPicker } from "../agent/AgentPicker.js";
|
|
14
|
+
import { EnvironmentPicker } from "../environment/EnvironmentPicker.js";
|
|
15
|
+
import { useGitHubConnection } from "../github/useGitHubConnection.js";
|
|
16
|
+
import { ModelSelector } from "../models/ModelSelector.js";
|
|
17
|
+
import { toProtoHarness, type HarnessOption } from "../models/harness.js";
|
|
18
|
+
import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
|
|
19
|
+
import { Switch } from "../switch/Switch.js";
|
|
20
|
+
import { useStigmerPortalContainer } from "../portal-container.js";
|
|
21
|
+
import { useWorkspaceEntries } from "../workspace/useWorkspaceEntries.js";
|
|
22
|
+
import { WorkspaceEditor } from "../workspace/WorkspaceEditor.js";
|
|
23
|
+
import { CadenceField } from "./CadenceField.js";
|
|
24
|
+
import { TimeZoneField, browserTimeZone } from "./TimeZoneField.js";
|
|
25
|
+
import { useCreateSchedule } from "./useCreateSchedule.js";
|
|
26
|
+
import { cadenceToCron, validateCron, type CadencePreset } from "./cadence.js";
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Public API
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/** Props for {@link ScheduleForm}. */
|
|
33
|
+
export interface ScheduleFormProps {
|
|
34
|
+
/**
|
|
35
|
+
* Organization slug. Schedules are org-local: the server requires the
|
|
36
|
+
* target agent to live in this same org, so the agent picker is
|
|
37
|
+
* locked to org scope.
|
|
38
|
+
*/
|
|
39
|
+
readonly org: string;
|
|
40
|
+
/** Fired with the created schedule after a successful submit. */
|
|
41
|
+
readonly onComplete?: (schedule: Schedule) => void;
|
|
42
|
+
/** Fired when the user cancels. */
|
|
43
|
+
readonly onCancel?: () => void;
|
|
44
|
+
/** Additional CSS class names for the root form element. */
|
|
45
|
+
readonly className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** invocation.proto pins AgentInvocation.message to 8192 characters. */
|
|
49
|
+
const MESSAGE_MAX_LEN = 8192;
|
|
50
|
+
/** Show the remaining-characters counter once within this margin. */
|
|
51
|
+
const MESSAGE_COUNTER_THRESHOLD = 500;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Single-page form for creating a {@link Schedule}.
|
|
55
|
+
*
|
|
56
|
+
* The form speaks the composer's vocabulary (DD-018 D-5): a schedule is
|
|
57
|
+
* a saved run — which agent, what message, what workspace, which
|
|
58
|
+
* engine and model, what budget — plus a cadence. It reuses the
|
|
59
|
+
* composer's own pickers ({@link ModelSelector}, {@link WorkspaceEditor})
|
|
60
|
+
* so the interactive and scheduled surfaces cannot drift, minus only
|
|
61
|
+
* what unattended makes impossible (no local folders, no attachments,
|
|
62
|
+
* no approval-mode choice).
|
|
63
|
+
*
|
|
64
|
+
* Enabled defaults to OFF, deliberately: it matches the proto/YAML
|
|
65
|
+
* default (an omitted `enabled` is false) and the platform's
|
|
66
|
+
* recommended workflow — create staged-disabled, validate with
|
|
67
|
+
* "Run now" on the detail page, then enable.
|
|
68
|
+
*
|
|
69
|
+
* Creation only. Editing an existing schedule must not flow through
|
|
70
|
+
* this form's curated input — see the note on {@link useCreateSchedule}.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* <ScheduleForm
|
|
75
|
+
* org="acme"
|
|
76
|
+
* onComplete={(s) => navigate(`/library/schedules/${s.metadata?.org}/${s.metadata?.slug}`)}
|
|
77
|
+
* onCancel={() => navigate("/library/schedules")}
|
|
78
|
+
* />
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function ScheduleForm({
|
|
82
|
+
org,
|
|
83
|
+
onComplete,
|
|
84
|
+
onCancel,
|
|
85
|
+
className,
|
|
86
|
+
}: ScheduleFormProps) {
|
|
87
|
+
const { create, isCreating, error, clearError } = useCreateSchedule();
|
|
88
|
+
|
|
89
|
+
const [name, setName] = useState("");
|
|
90
|
+
const [agentRef, setAgentRef] = useState<ResourceRef | null>(null);
|
|
91
|
+
const [agentName, setAgentName] = useState<string | null>(null);
|
|
92
|
+
const [message, setMessage] = useState("");
|
|
93
|
+
const [cadence, setCadence] = useState<CadencePreset>({
|
|
94
|
+
kind: "daily",
|
|
95
|
+
hour: 9,
|
|
96
|
+
minute: 0,
|
|
97
|
+
});
|
|
98
|
+
const [timeZone, setTimeZone] = useState(browserTimeZone);
|
|
99
|
+
const [enabled, setEnabled] = useState(false);
|
|
100
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
101
|
+
const [environmentRefs, setEnvironmentRefs] = useState<ResourceRef[]>([]);
|
|
102
|
+
|
|
103
|
+
// Engine & model are one atomic choice: picking a model pins BOTH the
|
|
104
|
+
// harness and the model (the registry scopes models per harness, so a
|
|
105
|
+
// model without its harness is meaningless). Empty model = nothing
|
|
106
|
+
// pinned = the platform defaults apply to both. Browsing harnesses in
|
|
107
|
+
// the popover without picking a model pins nothing.
|
|
108
|
+
const [modelName, setModelName] = useState("");
|
|
109
|
+
const [modelHarness, setModelHarness] = useState<HarnessOption>("cursor");
|
|
110
|
+
// Service tier rides the model choice (#357): the ModelSelector renders
|
|
111
|
+
// the fast toggle only for models whose registry entry prices a fast
|
|
112
|
+
// variant, and resets it when the user switches to one that does not.
|
|
113
|
+
// No model pinned = no tier pinned (fast requires a model, and the form's
|
|
114
|
+
// reset button clears both).
|
|
115
|
+
const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
|
|
116
|
+
|
|
117
|
+
const [budgetUsd, setBudgetUsd] = useState("");
|
|
118
|
+
|
|
119
|
+
// Workspace the fresh per-fire session clones — git sources only
|
|
120
|
+
// (write-time validated server-side: no client is connected at fire
|
|
121
|
+
// time to serve a local folder). The GitHub repo picker lights up
|
|
122
|
+
// when the user already has a connection; otherwise the editor's
|
|
123
|
+
// manual URL+branch input applies, with no OAuth flow forced into
|
|
124
|
+
// this form.
|
|
125
|
+
const workspace = useWorkspaceEntries();
|
|
126
|
+
const gitHub = useGitHubConnection(org);
|
|
127
|
+
|
|
128
|
+
const trimmedName = name.trim();
|
|
129
|
+
const trimmedMessage = message.trim();
|
|
130
|
+
const cron = cadenceToCron(cadence).trim();
|
|
131
|
+
const cronValid = cron !== "" && validateCron(cron) === null;
|
|
132
|
+
|
|
133
|
+
const canSubmit =
|
|
134
|
+
trimmedName !== "" &&
|
|
135
|
+
agentRef !== null &&
|
|
136
|
+
trimmedMessage !== "" &&
|
|
137
|
+
cronValid &&
|
|
138
|
+
!isCreating;
|
|
139
|
+
|
|
140
|
+
const handleAgentChange = useCallback((ref: ResourceRef | null) => {
|
|
141
|
+
setAgentRef(ref);
|
|
142
|
+
if (ref === null) setAgentName(null);
|
|
143
|
+
setPickerOpen(false);
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
const handleSubmit = useCallback(
|
|
147
|
+
async (e: FormEvent) => {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
if (!canSubmit || !agentRef) return;
|
|
150
|
+
|
|
151
|
+
clearError();
|
|
152
|
+
const runConfig = buildRunConfig(modelName, budgetUsd, serviceTier);
|
|
153
|
+
try {
|
|
154
|
+
const schedule = await create({
|
|
155
|
+
name: trimmedName,
|
|
156
|
+
org,
|
|
157
|
+
cron,
|
|
158
|
+
timeZone,
|
|
159
|
+
enabled,
|
|
160
|
+
agent: {
|
|
161
|
+
agentRef,
|
|
162
|
+
message: trimmedMessage,
|
|
163
|
+
// The harness travels with the model it was picked under;
|
|
164
|
+
// no model means no harness — the platform defaults apply.
|
|
165
|
+
...(modelName !== ""
|
|
166
|
+
? { harness: toProtoHarness(modelHarness) }
|
|
167
|
+
: {}),
|
|
168
|
+
...(workspace.hasEntries
|
|
169
|
+
? { workspaceEntries: workspace.toInput() }
|
|
170
|
+
: {}),
|
|
171
|
+
...(environmentRefs.length > 0 ? { environmentRefs } : {}),
|
|
172
|
+
...(runConfig ? { runConfig } : {}),
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
onComplete?.(schedule);
|
|
176
|
+
} catch {
|
|
177
|
+
// error state is managed by useCreateSchedule
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
[
|
|
181
|
+
canSubmit,
|
|
182
|
+
agentRef,
|
|
183
|
+
clearError,
|
|
184
|
+
create,
|
|
185
|
+
trimmedName,
|
|
186
|
+
org,
|
|
187
|
+
cron,
|
|
188
|
+
timeZone,
|
|
189
|
+
enabled,
|
|
190
|
+
trimmedMessage,
|
|
191
|
+
environmentRefs,
|
|
192
|
+
modelName,
|
|
193
|
+
modelHarness,
|
|
194
|
+
serviceTier,
|
|
195
|
+
budgetUsd,
|
|
196
|
+
workspace,
|
|
197
|
+
onComplete,
|
|
198
|
+
],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const remaining = MESSAGE_MAX_LEN - message.length;
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<form
|
|
205
|
+
onSubmit={handleSubmit}
|
|
206
|
+
className={cn("max-w-2xl space-y-5", className)}
|
|
207
|
+
aria-label="New schedule"
|
|
208
|
+
>
|
|
209
|
+
{/* Name */}
|
|
210
|
+
<div className="space-y-1">
|
|
211
|
+
<label htmlFor="stgm-new-schedule-name" className={labelClasses}>
|
|
212
|
+
Name
|
|
213
|
+
</label>
|
|
214
|
+
<input
|
|
215
|
+
id="stgm-new-schedule-name"
|
|
216
|
+
type="text"
|
|
217
|
+
value={name}
|
|
218
|
+
onChange={(e) => setName(e.target.value)}
|
|
219
|
+
placeholder="e.g. daily-fee-reminders"
|
|
220
|
+
disabled={isCreating}
|
|
221
|
+
autoFocus
|
|
222
|
+
required
|
|
223
|
+
className={inputClasses}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{/* Target agent */}
|
|
228
|
+
<div className="space-y-1">
|
|
229
|
+
<span id="stgm-new-schedule-agent-label" className={labelClasses}>
|
|
230
|
+
Agent to run
|
|
231
|
+
</span>
|
|
232
|
+
<Popover.Root open={pickerOpen} onOpenChange={setPickerOpen}>
|
|
233
|
+
<Popover.Trigger
|
|
234
|
+
disabled={isCreating}
|
|
235
|
+
aria-labelledby="stgm-new-schedule-agent-label"
|
|
236
|
+
className={cn(
|
|
237
|
+
"flex w-full items-center justify-between rounded-md border border-input bg-background px-2.5 py-1.5 text-left text-xs",
|
|
238
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
239
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
240
|
+
agentRef ? "text-foreground" : "text-muted-foreground",
|
|
241
|
+
)}
|
|
242
|
+
>
|
|
243
|
+
<span className="truncate">
|
|
244
|
+
{agentRef
|
|
245
|
+
? (agentName ?? `${agentRef.org}/${agentRef.slug}`)
|
|
246
|
+
: "Choose an agent…"}
|
|
247
|
+
</span>
|
|
248
|
+
<ChevronIcon />
|
|
249
|
+
</Popover.Trigger>
|
|
250
|
+
<AgentPickerPopup
|
|
251
|
+
org={org}
|
|
252
|
+
value={agentRef}
|
|
253
|
+
onChange={handleAgentChange}
|
|
254
|
+
onDisplayNameResolved={(_key, resolvedName) =>
|
|
255
|
+
setAgentName(resolvedName)
|
|
256
|
+
}
|
|
257
|
+
disabled={isCreating}
|
|
258
|
+
/>
|
|
259
|
+
</Popover.Root>
|
|
260
|
+
<p className={hintClasses}>
|
|
261
|
+
Each fire runs this agent unattended in a fresh session. Only
|
|
262
|
+
agents in <span className="font-medium">{org}</span> can be
|
|
263
|
+
scheduled.
|
|
264
|
+
</p>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Message */}
|
|
268
|
+
<div className="space-y-1">
|
|
269
|
+
<label htmlFor="stgm-new-schedule-message" className={labelClasses}>
|
|
270
|
+
Message
|
|
271
|
+
</label>
|
|
272
|
+
<textarea
|
|
273
|
+
id="stgm-new-schedule-message"
|
|
274
|
+
value={message}
|
|
275
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
276
|
+
placeholder="The instruction the agent receives on every fire — write it for a run with no human present."
|
|
277
|
+
disabled={isCreating}
|
|
278
|
+
required
|
|
279
|
+
rows={5}
|
|
280
|
+
maxLength={MESSAGE_MAX_LEN}
|
|
281
|
+
className={cn(inputClasses, "resize-y")}
|
|
282
|
+
/>
|
|
283
|
+
{remaining <= MESSAGE_COUNTER_THRESHOLD && (
|
|
284
|
+
<p className={hintClasses}>{remaining} characters left</p>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
{/* Workspace — what each fire's fresh session operates on
|
|
289
|
+
(DD-018 D-4). Git sources only; the server refuses local
|
|
290
|
+
folders at write time because no client is connected when a
|
|
291
|
+
schedule fires. */}
|
|
292
|
+
<div className="space-y-1">
|
|
293
|
+
<span className={labelClasses}>
|
|
294
|
+
Workspace{" "}
|
|
295
|
+
<span className="font-normal text-muted-foreground">(optional)</span>
|
|
296
|
+
</span>
|
|
297
|
+
<WorkspaceEditor
|
|
298
|
+
workspace={workspace}
|
|
299
|
+
gitHubConnection={gitHub.isConnected ? gitHub : undefined}
|
|
300
|
+
enableGitHub
|
|
301
|
+
enableLocal={false}
|
|
302
|
+
disabled={isCreating}
|
|
303
|
+
/>
|
|
304
|
+
<p className={hintClasses}>
|
|
305
|
+
Each run clones these repositories fresh. Private repositories need
|
|
306
|
+
a <code className="font-mono">GITHUB_TOKEN</code> in one of this
|
|
307
|
+
schedule’s environments; public ones need nothing.
|
|
308
|
+
</p>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Environments — how a tool-using agent becomes schedulable
|
|
312
|
+
(DD-017 D-2). Only org-shared environments resolve for a
|
|
313
|
+
schedule fire, so the picker is filtered to visibility_org —
|
|
314
|
+
the same credential surface a channel binding uses. */}
|
|
315
|
+
<div className="space-y-1">
|
|
316
|
+
<span id="stgm-new-schedule-env-label" className={labelClasses}>
|
|
317
|
+
Environments <span className="font-normal text-muted-foreground">(optional)</span>
|
|
318
|
+
</span>
|
|
319
|
+
<EnvironmentPicker
|
|
320
|
+
org={org}
|
|
321
|
+
value={environmentRefs}
|
|
322
|
+
onChange={setEnvironmentRefs}
|
|
323
|
+
disabled={isCreating}
|
|
324
|
+
filterEnvironment={(env) =>
|
|
325
|
+
env.metadata?.visibility === ApiResourceVisibility.visibility_org
|
|
326
|
+
}
|
|
327
|
+
/>
|
|
328
|
+
<p className={hintClasses}>
|
|
329
|
+
Bind org-shared credentials (for example an MCP server’s secret)
|
|
330
|
+
so the agent’s tools work on an unattended fire. Without this, an
|
|
331
|
+
agent whose tools need credentials will be refused every run.
|
|
332
|
+
</p>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
{/* Cadence */}
|
|
336
|
+
<div className="space-y-1">
|
|
337
|
+
<span className={labelClasses}>Runs</span>
|
|
338
|
+
<CadenceField
|
|
339
|
+
value={cadence}
|
|
340
|
+
onChange={setCadence}
|
|
341
|
+
timeZone={timeZone}
|
|
342
|
+
disabled={isCreating}
|
|
343
|
+
/>
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
{/* Engine & model — the composer's own picker (DD-018 D-5),
|
|
347
|
+
replacing the free-text model box whose typos surfaced as
|
|
348
|
+
fire-time failures. Nothing is pinned until a model is picked. */}
|
|
349
|
+
<div className="space-y-1">
|
|
350
|
+
<span className={labelClasses}>
|
|
351
|
+
Engine & model{" "}
|
|
352
|
+
<span className="font-normal text-muted-foreground">(optional)</span>
|
|
353
|
+
</span>
|
|
354
|
+
<div className="flex items-center gap-2">
|
|
355
|
+
<ModelSelector
|
|
356
|
+
value={modelName}
|
|
357
|
+
onValueChange={setModelName}
|
|
358
|
+
initialHarness={modelHarness}
|
|
359
|
+
onHarnessChange={setModelHarness}
|
|
360
|
+
serviceTier={serviceTier}
|
|
361
|
+
onServiceTierChange={setServiceTier}
|
|
362
|
+
placeholderLabel="Platform default"
|
|
363
|
+
disabled={isCreating}
|
|
364
|
+
/>
|
|
365
|
+
{modelName !== "" && (
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
onClick={() => {
|
|
369
|
+
setModelName("");
|
|
370
|
+
setServiceTier("standard");
|
|
371
|
+
}}
|
|
372
|
+
disabled={isCreating}
|
|
373
|
+
className={cn(
|
|
374
|
+
"rounded-md px-2 py-1 text-[0.65rem] text-muted-foreground",
|
|
375
|
+
"hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
376
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
377
|
+
)}
|
|
378
|
+
>
|
|
379
|
+
Reset to platform default
|
|
380
|
+
</button>
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
<p className={hintClasses}>
|
|
384
|
+
Runs use the platform’s default engine and model unless you
|
|
385
|
+
pick one here. Picking a model pins the engine it belongs to.
|
|
386
|
+
</p>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{/* Budget — the one run bound that matters for an unattended
|
|
390
|
+
surface: nobody is watching a 3 AM fire (DD-018 D-5). Clamped
|
|
391
|
+
by the platform profile; tool-round bounds stay API-only. */}
|
|
392
|
+
<div className="space-y-1">
|
|
393
|
+
<label htmlFor="stgm-new-schedule-budget" className={labelClasses}>
|
|
394
|
+
Budget per run (USD){" "}
|
|
395
|
+
<span className="font-normal text-muted-foreground">(optional)</span>
|
|
396
|
+
</label>
|
|
397
|
+
<input
|
|
398
|
+
id="stgm-new-schedule-budget"
|
|
399
|
+
type="number"
|
|
400
|
+
min="0"
|
|
401
|
+
step="any"
|
|
402
|
+
value={budgetUsd}
|
|
403
|
+
onChange={(e) => setBudgetUsd(e.target.value)}
|
|
404
|
+
placeholder="platform default"
|
|
405
|
+
disabled={isCreating}
|
|
406
|
+
className={cn(inputClasses, "sm:max-w-48")}
|
|
407
|
+
/>
|
|
408
|
+
<p className={hintClasses}>
|
|
409
|
+
Each run stops when it reaches this spend. You can lower the
|
|
410
|
+
platform’s per-run cap, never raise it past the
|
|
411
|
+
platform’s ceiling.
|
|
412
|
+
</p>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{/* Time zone */}
|
|
416
|
+
<div className="space-y-1">
|
|
417
|
+
<label htmlFor="stgm-new-schedule-tz" className={labelClasses}>
|
|
418
|
+
Time zone
|
|
419
|
+
</label>
|
|
420
|
+
<TimeZoneField
|
|
421
|
+
id="stgm-new-schedule-tz"
|
|
422
|
+
value={timeZone}
|
|
423
|
+
onChange={setTimeZone}
|
|
424
|
+
disabled={isCreating}
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
{/* Enabled */}
|
|
429
|
+
<div className="flex items-start gap-3">
|
|
430
|
+
<Switch
|
|
431
|
+
id="stgm-new-schedule-enabled"
|
|
432
|
+
checked={enabled}
|
|
433
|
+
onCheckedChange={setEnabled}
|
|
434
|
+
disabled={isCreating}
|
|
435
|
+
aria-labelledby="stgm-new-schedule-enabled-label"
|
|
436
|
+
/>
|
|
437
|
+
<div className="space-y-0.5">
|
|
438
|
+
<label
|
|
439
|
+
id="stgm-new-schedule-enabled-label"
|
|
440
|
+
htmlFor="stgm-new-schedule-enabled"
|
|
441
|
+
className={labelClasses}
|
|
442
|
+
>
|
|
443
|
+
Enabled
|
|
444
|
+
</label>
|
|
445
|
+
<p className={hintClasses}>
|
|
446
|
+
Leave off to create the schedule staged: validate it with
|
|
447
|
+
“Run now” on the schedule page, then enable it there.
|
|
448
|
+
</p>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
{error && (
|
|
453
|
+
<p className="text-xs text-destructive" role="alert">
|
|
454
|
+
{getUserMessage(error)}
|
|
455
|
+
</p>
|
|
456
|
+
)}
|
|
457
|
+
|
|
458
|
+
<div className="flex items-center gap-2">
|
|
459
|
+
<button
|
|
460
|
+
type="submit"
|
|
461
|
+
disabled={!canSubmit}
|
|
462
|
+
className={cn(
|
|
463
|
+
"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium",
|
|
464
|
+
"bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
465
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
466
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
467
|
+
)}
|
|
468
|
+
>
|
|
469
|
+
{isCreating && <SpinnerIcon />}
|
|
470
|
+
Create schedule
|
|
471
|
+
</button>
|
|
472
|
+
|
|
473
|
+
{onCancel && (
|
|
474
|
+
<button
|
|
475
|
+
type="button"
|
|
476
|
+
onClick={onCancel}
|
|
477
|
+
disabled={isCreating}
|
|
478
|
+
className={cn(
|
|
479
|
+
"rounded-md px-3 py-1.5 text-sm",
|
|
480
|
+
"text-muted-foreground hover:bg-accent-hover hover:text-foreground",
|
|
481
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
482
|
+
)}
|
|
483
|
+
>
|
|
484
|
+
Cancel
|
|
485
|
+
</button>
|
|
486
|
+
)}
|
|
487
|
+
</div>
|
|
488
|
+
</form>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
// Private helpers
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
const labelClasses = "block text-xs font-medium text-foreground";
|
|
497
|
+
const hintClasses = "text-[0.65rem] text-muted-foreground";
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Assemble a {@link RunConfigInput} from the model choice and budget, or
|
|
501
|
+
* `undefined` when both are unset — an all-empty run_config carries no
|
|
502
|
+
* meaning, and omitting it keeps the schedule on the platform defaults
|
|
503
|
+
* (the server's own "empty = inherit" contract, DD-017 D-3 as carried
|
|
504
|
+
* into DD-018 D-2). Non-numeric or negative budgets are dropped rather
|
|
505
|
+
* than sent; the proto's `gte = 0` constraint would reject them anyway,
|
|
506
|
+
* and a blank field must not become a zero override. `max_tool_rounds`
|
|
507
|
+
* is deliberately not collected here — an implementation knob, not a
|
|
508
|
+
* user concept; API-reachable for operators (DD-018 D-5).
|
|
509
|
+
*/
|
|
510
|
+
function buildRunConfig(
|
|
511
|
+
modelName: string,
|
|
512
|
+
budgetUsd: string,
|
|
513
|
+
serviceTier: ServiceTierOption,
|
|
514
|
+
): RunConfigInput | undefined {
|
|
515
|
+
const model = modelName.trim();
|
|
516
|
+
const cost = Number.parseFloat(budgetUsd);
|
|
517
|
+
|
|
518
|
+
const config: RunConfigInput = {};
|
|
519
|
+
if (model !== "") config.modelName = model;
|
|
520
|
+
if (Number.isFinite(cost) && cost > 0) config.maxCostUsd = cost;
|
|
521
|
+
// Carried only when the user actively chose fast AND pinned a model
|
|
522
|
+
// (fast is a per-model price; the server refuses it without one). An
|
|
523
|
+
// untouched toggle stays absent — unspecified-vs-explicit is a
|
|
524
|
+
// load-bearing ledger distinction (#357).
|
|
525
|
+
if (serviceTier === "fast" && model !== "") {
|
|
526
|
+
config.serviceTier = toProtoServiceTier(serviceTier);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
530
|
+
}
|
|
531
|
+
const inputClasses = cn(
|
|
532
|
+
"w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
|
|
533
|
+
"placeholder:text-muted-foreground",
|
|
534
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
535
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
function AgentPickerPopup({
|
|
539
|
+
org,
|
|
540
|
+
value,
|
|
541
|
+
onChange,
|
|
542
|
+
onDisplayNameResolved,
|
|
543
|
+
disabled,
|
|
544
|
+
}: {
|
|
545
|
+
readonly org: string;
|
|
546
|
+
readonly value: ResourceRef | null;
|
|
547
|
+
readonly onChange: (ref: ResourceRef | null) => void;
|
|
548
|
+
readonly onDisplayNameResolved: (key: string, name: string) => void;
|
|
549
|
+
readonly disabled?: boolean;
|
|
550
|
+
}) {
|
|
551
|
+
const portalContainer = useStigmerPortalContainer();
|
|
552
|
+
return (
|
|
553
|
+
<Popover.Portal container={portalContainer}>
|
|
554
|
+
<Popover.Positioner sideOffset={4} align="start">
|
|
555
|
+
<Popover.Popup
|
|
556
|
+
className={cn(
|
|
557
|
+
"z-popover overflow-x-hidden overflow-y-auto rounded-lg border border-border",
|
|
558
|
+
"bg-popover p-3 text-popover-foreground shadow-md",
|
|
559
|
+
"max-h-[60vh]",
|
|
560
|
+
)}
|
|
561
|
+
>
|
|
562
|
+
<AgentPicker
|
|
563
|
+
org={org}
|
|
564
|
+
value={value}
|
|
565
|
+
onChange={onChange}
|
|
566
|
+
onDisplayNameResolved={onDisplayNameResolved}
|
|
567
|
+
// Schedules are org-local: the server rejects a target
|
|
568
|
+
// agent outside the schedule's org, so cross-org browsing
|
|
569
|
+
// could only offer choices that fail at submit.
|
|
570
|
+
scope="org"
|
|
571
|
+
showScopeToggle={false}
|
|
572
|
+
disabled={disabled}
|
|
573
|
+
/>
|
|
574
|
+
</Popover.Popup>
|
|
575
|
+
</Popover.Positioner>
|
|
576
|
+
</Popover.Portal>
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function ChevronIcon() {
|
|
581
|
+
return (
|
|
582
|
+
<svg
|
|
583
|
+
width="12"
|
|
584
|
+
height="12"
|
|
585
|
+
viewBox="0 0 16 16"
|
|
586
|
+
fill="none"
|
|
587
|
+
stroke="currentColor"
|
|
588
|
+
strokeWidth="1.5"
|
|
589
|
+
strokeLinecap="round"
|
|
590
|
+
strokeLinejoin="round"
|
|
591
|
+
className="shrink-0 text-muted-foreground"
|
|
592
|
+
aria-hidden="true"
|
|
593
|
+
>
|
|
594
|
+
<path d="M4 6l4 4 4-4" />
|
|
595
|
+
</svg>
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function SpinnerIcon() {
|
|
600
|
+
return (
|
|
601
|
+
<svg
|
|
602
|
+
width="14"
|
|
603
|
+
height="14"
|
|
604
|
+
viewBox="0 0 16 16"
|
|
605
|
+
fill="none"
|
|
606
|
+
stroke="currentColor"
|
|
607
|
+
strokeWidth="2"
|
|
608
|
+
strokeLinecap="round"
|
|
609
|
+
className="animate-spin"
|
|
610
|
+
aria-hidden="true"
|
|
611
|
+
>
|
|
612
|
+
<path d="M8 2a6 6 0 1 0 6 6" />
|
|
613
|
+
</svg>
|
|
614
|
+
);
|
|
615
|
+
}
|