@stigmer/react 0.0.92 → 0.0.93
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/README.md +57 -12
- package/agent/AgentDetailView.js +7 -7
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/AgentPicker.js +3 -3
- package/agent/AgentPicker.js.map +1 -1
- package/api-key/ApiKeyCreatedAlert.js +1 -1
- package/api-key/ApiKeyCreatedAlert.js.map +1 -1
- package/api-key/ApiKeyListPanel.js +4 -4
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/api-key/CreateApiKeyForm.js +1 -1
- package/api-key/CreateApiKeyForm.js.map +1 -1
- package/attachment/AttachmentChipList.js +2 -2
- package/attachment/AttachmentChipList.js.map +1 -1
- package/color-mode.d.ts +48 -0
- package/color-mode.d.ts.map +1 -0
- package/color-mode.js +53 -0
- package/color-mode.js.map +1 -0
- package/composer/ComposerToolbar.d.ts +6 -2
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +5 -3
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/ConfigureMenu.js +3 -3
- package/composer/ConfigureMenu.js.map +1 -1
- package/composer/ContextChip.js +1 -1
- package/composer/ContextChip.js.map +1 -1
- package/composer/ContextPopover.js +1 -1
- package/composer/ContextPopover.js.map +1 -1
- package/composer/SessionComposer.d.ts +24 -3
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +5 -4
- package/composer/SessionComposer.js.map +1 -1
- package/environment/CreateEnvironmentForm.js +1 -1
- package/environment/CreateEnvironmentForm.js.map +1 -1
- package/environment/EnvVarForm.js +2 -2
- package/environment/EnvVarForm.js.map +1 -1
- package/environment/EnvironmentListPanel.js +2 -2
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/environment/EnvironmentVariableEditor.js +6 -6
- package/environment/EnvironmentVariableEditor.js.map +1 -1
- package/error/ErrorMessage.js +1 -1
- package/error/ErrorMessage.js.map +1 -1
- package/execution/ApprovalCard.js +4 -4
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ArtifactCard.js +1 -1
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.js +4 -4
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/FollowUpInput.js +1 -1
- package/execution/FollowUpInput.js.map +1 -1
- package/execution/McpToolDetail.js +4 -4
- package/execution/McpToolDetail.js.map +1 -1
- package/execution/MessageEntry.js +1 -1
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.js +1 -1
- package/execution/MessageThread.js.map +1 -1
- package/execution/SessionVariablesInput.js +4 -4
- package/execution/SessionVariablesInput.js.map +1 -1
- package/execution/SubAgentSection.js +2 -2
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/ToolCallDetail.js +2 -2
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallGroup.js +1 -1
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.js +2 -2
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/WriteBackCard.js +3 -3
- package/execution/WriteBackCard.js.map +1 -1
- package/execution/tool-rendering-primitives.js +3 -3
- package/execution/tool-rendering-primitives.js.map +1 -1
- package/github/GitHubRepoPicker.js +5 -5
- package/github/GitHubRepoPicker.js.map +1 -1
- package/iam-policy/GrantAccessForm.js +1 -1
- package/iam-policy/GrantAccessForm.js.map +1 -1
- package/iam-policy/OrgMembersPanel.js +5 -5
- package/iam-policy/OrgMembersPanel.js.map +1 -1
- package/identity-provider/CreateIdentityProviderForm.js +3 -3
- package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +5 -5
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/identity-provider/IdentityProviderListPanel.js +3 -3
- package/identity-provider/IdentityProviderListPanel.js.map +1 -1
- package/identity-provider/IdentityProviderWizard.js +7 -7
- package/identity-provider/IdentityProviderWizard.js.map +1 -1
- package/identity-provider/ProviderPicker.js +2 -2
- package/identity-provider/ProviderPicker.js.map +1 -1
- package/index.d.ts +8 -4
- package/index.d.ts.map +1 -1
- package/index.js +7 -3
- package/index.js.map +1 -1
- package/internal/CloudFeatureNotice.js +1 -1
- package/internal/CloudFeatureNotice.js.map +1 -1
- package/internal/Tabs.js +1 -1
- package/internal/Tabs.js.map +1 -1
- package/internal/markdown-components.js +2 -2
- package/internal/markdown-components.js.map +1 -1
- package/invitation/InvitationCreatedAlert.js +1 -1
- package/invitation/InvitationCreatedAlert.js.map +1 -1
- package/invitation/InvitationManager.js +5 -5
- package/invitation/InvitationManager.js.map +1 -1
- package/invitation/InvitationRedemption.js +4 -4
- package/invitation/InvitationRedemption.js.map +1 -1
- package/library/ResourceCountCard.js +1 -1
- package/library/ResourceCountCard.js.map +1 -1
- package/library/ResourceListView.js +5 -5
- package/library/ResourceListView.js.map +1 -1
- package/mcp-server/McpServerConfigPanel.js +5 -5
- package/mcp-server/McpServerConfigPanel.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +4 -4
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.js +14 -14
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.js +4 -4
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/McpToolSelector.js +3 -3
- package/mcp-server/McpToolSelector.js.map +1 -1
- package/mcp-server/OAuthAppForm.js +1 -1
- package/mcp-server/OAuthAppForm.js.map +1 -1
- package/models/ModelSelector.js +1 -1
- package/models/ModelSelector.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.js +1 -1
- package/oauth-app/CreateOAuthAppForm.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +3 -3
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/oauth-app/OAuthAppListPanel.js +2 -2
- package/oauth-app/OAuthAppListPanel.js.map +1 -1
- package/organization/CreateOrganizationForm.js +1 -1
- package/organization/CreateOrganizationForm.js.map +1 -1
- package/organization/OrgProfilePanel.js +3 -3
- package/organization/OrgProfilePanel.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/CreatePlatformClientForm.js +2 -2
- package/platform-client/CreatePlatformClientForm.js.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +4 -4
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/platform-client/PlatformClientListPanel.js +3 -3
- package/platform-client/PlatformClientListPanel.js.map +1 -1
- package/platform-client/PlatformClientSecretAlert.js +2 -2
- package/platform-client/PlatformClientSecretAlert.js.map +1 -1
- package/provider.d.ts +39 -2
- package/provider.d.ts.map +1 -1
- package/provider.js +11 -3
- package/provider.js.map +1 -1
- package/runner/RunnerListPanel.d.ts +65 -0
- package/runner/RunnerListPanel.d.ts.map +1 -0
- package/runner/RunnerListPanel.js +237 -0
- package/runner/RunnerListPanel.js.map +1 -0
- package/runner/RunnerPicker.d.ts +54 -0
- package/runner/RunnerPicker.d.ts.map +1 -0
- package/runner/RunnerPicker.js +133 -0
- package/runner/RunnerPicker.js.map +1 -0
- package/runner/__tests__/useDeleteRunner.test.d.ts +2 -0
- package/runner/__tests__/useDeleteRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useDeleteRunner.test.js +108 -0
- package/runner/__tests__/useDeleteRunner.test.js.map +1 -0
- package/runner/__tests__/useLaunchLocalRunner.test.d.ts +2 -0
- package/runner/__tests__/useLaunchLocalRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useLaunchLocalRunner.test.js +143 -0
- package/runner/__tests__/useLaunchLocalRunner.test.js.map +1 -0
- package/runner/__tests__/useStopRunner.test.d.ts +2 -0
- package/runner/__tests__/useStopRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useStopRunner.test.js +114 -0
- package/runner/__tests__/useStopRunner.test.js.map +1 -0
- package/runner/index.d.ts +14 -0
- package/runner/index.d.ts.map +1 -0
- package/runner/index.js +8 -0
- package/runner/index.js.map +1 -0
- package/runner/phase.d.ts +30 -0
- package/runner/phase.d.ts.map +1 -0
- package/runner/phase.js +58 -0
- package/runner/phase.js.map +1 -0
- package/runner/useDeleteRunner.d.ts +36 -0
- package/runner/useDeleteRunner.d.ts.map +1 -0
- package/runner/useDeleteRunner.js +42 -0
- package/runner/useDeleteRunner.js.map +1 -0
- package/runner/useLaunchLocalRunner.d.ts +84 -0
- package/runner/useLaunchLocalRunner.d.ts.map +1 -0
- package/runner/useLaunchLocalRunner.js +75 -0
- package/runner/useLaunchLocalRunner.js.map +1 -0
- package/runner/useRunnerList.d.ts +49 -0
- package/runner/useRunnerList.d.ts.map +1 -0
- package/runner/useRunnerList.js +70 -0
- package/runner/useRunnerList.js.map +1 -0
- package/runner/useStopRunner.d.ts +53 -0
- package/runner/useStopRunner.d.ts.map +1 -0
- package/runner/useStopRunner.js +50 -0
- package/runner/useStopRunner.js.map +1 -0
- package/session/draft.d.ts +53 -0
- package/session/draft.d.ts.map +1 -0
- package/session/draft.js +45 -0
- package/session/draft.js.map +1 -0
- package/session/index.d.ts +10 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +5 -0
- package/session/index.js.map +1 -1
- package/session/useCreateSession.d.ts +8 -0
- package/session/useCreateSession.d.ts.map +1 -1
- package/session/useCreateSession.js +1 -0
- package/session/useCreateSession.js.map +1 -1
- package/session/useEditSessionPrep.d.ts +26 -0
- package/session/useEditSessionPrep.d.ts.map +1 -0
- package/session/useEditSessionPrep.js +83 -0
- package/session/useEditSessionPrep.js.map +1 -0
- package/session/useNewSessionFlow.d.ts +110 -0
- package/session/useNewSessionFlow.d.ts.map +1 -0
- package/session/useNewSessionFlow.js +184 -0
- package/session/useNewSessionFlow.js.map +1 -0
- package/session/usePersistedModel.d.ts +18 -0
- package/session/usePersistedModel.d.ts.map +1 -0
- package/session/usePersistedModel.js +31 -0
- package/session/usePersistedModel.js.map +1 -0
- package/session/useSessionPageFlow.d.ts +104 -0
- package/session/useSessionPageFlow.d.ts.map +1 -0
- package/session/useSessionPageFlow.js +172 -0
- package/session/useSessionPageFlow.js.map +1 -0
- package/skill/SkillDetailView.js +3 -3
- package/skill/SkillDetailView.js.map +1 -1
- package/skill/SkillPicker.js +3 -3
- package/skill/SkillPicker.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +8 -8
- package/src/agent/AgentPicker.tsx +3 -3
- package/src/api-key/ApiKeyCreatedAlert.tsx +2 -2
- package/src/api-key/ApiKeyListPanel.tsx +6 -6
- package/src/api-key/CreateApiKeyForm.tsx +2 -2
- package/src/attachment/AttachmentChipList.tsx +3 -3
- package/src/color-mode.ts +75 -0
- package/src/composer/ComposerToolbar.tsx +29 -7
- package/src/composer/ConfigureMenu.tsx +6 -6
- package/src/composer/ContextChip.tsx +1 -1
- package/src/composer/ContextPopover.tsx +2 -2
- package/src/composer/SessionComposer.tsx +34 -5
- package/src/environment/CreateEnvironmentForm.tsx +3 -3
- package/src/environment/EnvVarForm.tsx +6 -6
- package/src/environment/EnvironmentListPanel.tsx +3 -3
- package/src/environment/EnvironmentVariableEditor.tsx +7 -7
- package/src/error/ErrorMessage.tsx +5 -5
- package/src/execution/ApprovalCard.tsx +5 -5
- package/src/execution/ArtifactCard.tsx +2 -2
- package/src/execution/ArtifactPreviewModal.tsx +4 -4
- package/src/execution/FollowUpInput.tsx +2 -2
- package/src/execution/McpToolDetail.tsx +4 -4
- package/src/execution/MessageEntry.tsx +1 -1
- package/src/execution/MessageThread.tsx +1 -1
- package/src/execution/SessionVariablesInput.tsx +7 -7
- package/src/execution/SubAgentSection.tsx +5 -5
- package/src/execution/ToolCallDetail.tsx +2 -2
- package/src/execution/ToolCallGroup.tsx +3 -3
- package/src/execution/ToolCallItem.tsx +4 -4
- package/src/execution/WriteBackCard.tsx +5 -5
- package/src/execution/tool-rendering-primitives.tsx +5 -5
- package/src/github/GitHubRepoPicker.tsx +5 -5
- package/src/iam-policy/GrantAccessForm.tsx +2 -2
- package/src/iam-policy/OrgMembersPanel.tsx +11 -11
- package/src/identity-provider/CreateIdentityProviderForm.tsx +4 -4
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +7 -7
- package/src/identity-provider/IdentityProviderListPanel.tsx +7 -7
- package/src/identity-provider/IdentityProviderWizard.tsx +8 -8
- package/src/identity-provider/ProviderPicker.tsx +2 -2
- package/src/index.ts +46 -7
- package/src/internal/CloudFeatureNotice.tsx +1 -1
- package/src/internal/Tabs.tsx +1 -1
- package/src/internal/markdown-components.tsx +2 -2
- package/src/invitation/InvitationCreatedAlert.tsx +2 -2
- package/src/invitation/InvitationManager.tsx +9 -9
- package/src/invitation/InvitationRedemption.tsx +11 -11
- package/src/library/ResourceCountCard.tsx +1 -1
- package/src/library/ResourceListView.tsx +7 -7
- package/src/mcp-server/McpServerConfigPanel.tsx +7 -7
- package/src/mcp-server/McpServerConnectDialog.tsx +5 -5
- package/src/mcp-server/McpServerDetailView.tsx +19 -19
- package/src/mcp-server/McpServerPicker.tsx +6 -6
- package/src/mcp-server/McpToolSelector.tsx +4 -4
- package/src/mcp-server/OAuthAppForm.tsx +3 -3
- package/src/models/ModelSelector.tsx +1 -1
- package/src/oauth-app/CreateOAuthAppForm.tsx +3 -3
- package/src/oauth-app/OAuthAppDetailPanel.tsx +7 -7
- package/src/oauth-app/OAuthAppListPanel.tsx +3 -3
- package/src/organization/CreateOrganizationForm.tsx +3 -3
- package/src/organization/OrgProfilePanel.tsx +4 -5
- package/src/platform-client/CreatePlatformClientForm.tsx +6 -6
- package/src/platform-client/PlatformClientDetailPanel.tsx +19 -19
- package/src/platform-client/PlatformClientListPanel.tsx +7 -7
- package/src/platform-client/PlatformClientSecretAlert.tsx +2 -2
- package/src/provider.tsx +52 -2
- package/src/runner/RunnerListPanel.tsx +725 -0
- package/src/runner/RunnerPicker.tsx +319 -0
- package/src/runner/__tests__/useDeleteRunner.test.tsx +150 -0
- package/src/runner/__tests__/useLaunchLocalRunner.test.tsx +223 -0
- package/src/runner/__tests__/useStopRunner.test.tsx +154 -0
- package/src/runner/index.ts +34 -0
- package/src/runner/phase.ts +62 -0
- package/src/runner/useDeleteRunner.ts +67 -0
- package/src/runner/useLaunchLocalRunner.ts +139 -0
- package/src/runner/useRunnerList.ts +114 -0
- package/src/runner/useStopRunner.ts +92 -0
- package/src/session/draft.ts +82 -0
- package/src/session/index.ts +28 -0
- package/src/session/useCreateSession.ts +9 -0
- package/src/session/useEditSessionPrep.ts +111 -0
- package/src/session/useNewSessionFlow.ts +283 -0
- package/src/session/usePersistedModel.ts +41 -0
- package/src/session/useSessionPageFlow.ts +280 -0
- package/src/skill/SkillDetailView.tsx +5 -5
- package/src/skill/SkillPicker.tsx +3 -3
- package/src/styles.css +25 -1
- package/src/usage/OrgUsagePanel.tsx +4 -4
- package/src/workspace/WorkspaceEditor.tsx +78 -66
- package/src/workspace/index.ts +0 -8
- package/styles.css +1 -1
- package/usage/OrgUsagePanel.js +2 -2
- package/usage/OrgUsagePanel.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts +28 -3
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +24 -25
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/index.d.ts +0 -4
- package/workspace/index.d.ts.map +1 -1
- package/workspace/index.js +0 -2
- package/workspace/index.js.map +1 -1
- package/src/workspace/FolderBrowser.tsx +0 -579
- package/src/workspace/useFolderListing.ts +0 -164
- package/workspace/FolderBrowser.d.ts +0 -37
- package/workspace/FolderBrowser.d.ts.map +0 -1
- package/workspace/FolderBrowser.js +0 -188
- package/workspace/FolderBrowser.js.map +0 -1
- package/workspace/useFolderListing.d.ts +0 -73
- package/workspace/useFolderListing.d.ts.map +0 -1
- package/workspace/useFolderListing.js +0 -110
- package/workspace/useFolderListing.js.map +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ResourceRef } from "@stigmer/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resource types that can be created or edited via a draft session.
|
|
5
|
+
*
|
|
6
|
+
* Each type maps to a Stigmer system agent that guides the user
|
|
7
|
+
* through the creation/editing flow in a conversational session.
|
|
8
|
+
*/
|
|
9
|
+
export type DraftResourceType = "agent" | "skill" | "mcp-server";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parsed draft session parameters.
|
|
13
|
+
*
|
|
14
|
+
* When `editRef` is present, the session is an edit session (modify an
|
|
15
|
+
* existing resource). When absent, it's a create session (new resource).
|
|
16
|
+
*/
|
|
17
|
+
export interface DraftParams {
|
|
18
|
+
/** The kind of resource this draft session will create or edit. */
|
|
19
|
+
readonly draftType: DraftResourceType;
|
|
20
|
+
/**
|
|
21
|
+
* When present, identifies the existing resource to edit.
|
|
22
|
+
* Absent for create-mode sessions.
|
|
23
|
+
*/
|
|
24
|
+
readonly editRef?: {
|
|
25
|
+
/** Organization that owns the resource. */
|
|
26
|
+
readonly org: string;
|
|
27
|
+
/** URL-friendly identifier of the resource. */
|
|
28
|
+
readonly slug: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* System agents responsible for creating each resource type.
|
|
34
|
+
*
|
|
35
|
+
* Keys match {@link DraftResourceType}; values are {@link ResourceRef}
|
|
36
|
+
* objects pointing to Stigmer's built-in creator agents.
|
|
37
|
+
*/
|
|
38
|
+
export const CREATOR_AGENTS: Record<DraftResourceType, ResourceRef> = {
|
|
39
|
+
agent: { org: "stigmer", slug: "agent-creator" },
|
|
40
|
+
skill: { org: "stigmer", slug: "skill-creator" },
|
|
41
|
+
"mcp-server": { org: "stigmer", slug: "mcp-server-creator" },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const VALID_TYPES = new Set<string>(Object.keys(CREATOR_AGENTS));
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate a string as a recognized {@link DraftResourceType}.
|
|
48
|
+
*
|
|
49
|
+
* Returns the validated type or `null` if unrecognized.
|
|
50
|
+
*/
|
|
51
|
+
export function parseDraftType(value: string | null): DraftResourceType | null {
|
|
52
|
+
if (value !== null && VALID_TYPES.has(value)) {
|
|
53
|
+
return value as DraftResourceType;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parse draft session parameters from standard `URLSearchParams`.
|
|
60
|
+
*
|
|
61
|
+
* Returns `null` when no valid `draft` param is present. When `editOrg`
|
|
62
|
+
* and `editSlug` are both present, the returned object includes an
|
|
63
|
+
* `editRef` indicating this is an edit session rather than a create session.
|
|
64
|
+
*
|
|
65
|
+
* Works identically with Next.js `useSearchParams()` or react-router's
|
|
66
|
+
* `useSearchParams()` — both return standard `URLSearchParams`.
|
|
67
|
+
*/
|
|
68
|
+
export function parseDraftParams(
|
|
69
|
+
searchParams: URLSearchParams,
|
|
70
|
+
): DraftParams | null {
|
|
71
|
+
const draftType = parseDraftType(searchParams.get("draft"));
|
|
72
|
+
if (!draftType) return null;
|
|
73
|
+
|
|
74
|
+
const editOrg = searchParams.get("editOrg");
|
|
75
|
+
const editSlug = searchParams.get("editSlug");
|
|
76
|
+
|
|
77
|
+
if (editOrg && editSlug) {
|
|
78
|
+
return { draftType, editRef: { org: editOrg, slug: editSlug } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { draftType };
|
|
82
|
+
}
|
package/src/session/index.ts
CHANGED
|
@@ -48,6 +48,34 @@ export type {
|
|
|
48
48
|
export { useAgentRefFromSession } from "./useAgentRefFromSession";
|
|
49
49
|
export type { UseAgentRefFromSessionReturn } from "./useAgentRefFromSession";
|
|
50
50
|
|
|
51
|
+
export { useNewSessionFlow } from "./useNewSessionFlow";
|
|
52
|
+
export type {
|
|
53
|
+
UseNewSessionFlowOptions,
|
|
54
|
+
UseNewSessionFlowReturn,
|
|
55
|
+
} from "./useNewSessionFlow";
|
|
56
|
+
|
|
57
|
+
export { useSessionPageFlow } from "./useSessionPageFlow";
|
|
58
|
+
export type {
|
|
59
|
+
UseSessionPageFlowOptions,
|
|
60
|
+
UseSessionPageFlowReturn,
|
|
61
|
+
} from "./useSessionPageFlow";
|
|
62
|
+
|
|
63
|
+
export { usePersistedModel } from "./usePersistedModel";
|
|
64
|
+
export type { UsePersistedModelReturn } from "./usePersistedModel";
|
|
65
|
+
|
|
66
|
+
export { useEditSessionPrep } from "./useEditSessionPrep";
|
|
67
|
+
export type { UseEditSessionPrepReturn } from "./useEditSessionPrep";
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
CREATOR_AGENTS,
|
|
71
|
+
parseDraftType,
|
|
72
|
+
parseDraftParams,
|
|
73
|
+
} from "./draft";
|
|
74
|
+
export type {
|
|
75
|
+
DraftResourceType,
|
|
76
|
+
DraftParams,
|
|
77
|
+
} from "./draft";
|
|
78
|
+
|
|
51
79
|
export { groupSessionsByTime } from "./group-sessions";
|
|
52
80
|
export type { SessionGroup } from "./group-sessions";
|
|
53
81
|
|
|
@@ -22,6 +22,14 @@ export interface SharedSessionFields {
|
|
|
22
22
|
readonly mcpServerUsages?: McpServerUsageInput[];
|
|
23
23
|
/** Skill references to enable for executions in this session. */
|
|
24
24
|
readonly skillRefs?: ResourceRef[];
|
|
25
|
+
/**
|
|
26
|
+
* Runner to bind this session to.
|
|
27
|
+
*
|
|
28
|
+
* When set, all executions in the session are routed to this runner's
|
|
29
|
+
* task queue. When omitted, the backend auto-selects a runner (session
|
|
30
|
+
* auto-bind in OSS, cloud auto-provisioning in Cloud).
|
|
31
|
+
*/
|
|
32
|
+
readonly runnerId?: string;
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
/**
|
|
@@ -147,6 +155,7 @@ export function useCreateSession(): UseCreateSessionReturn {
|
|
|
147
155
|
workspaceEntries: input.workspaceEntries,
|
|
148
156
|
mcpServerUsages: input.mcpServerUsages,
|
|
149
157
|
skillRefs: input.skillRefs,
|
|
158
|
+
runnerId: input.runnerId,
|
|
150
159
|
agentInstanceId: resolvedInstanceId,
|
|
151
160
|
});
|
|
152
161
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import { GetArtifactRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
6
|
+
import { useStigmer } from "../hooks";
|
|
7
|
+
import { useAgent } from "../agent";
|
|
8
|
+
import { useMcpServer } from "../mcp-server";
|
|
9
|
+
import { useSkill } from "../skill";
|
|
10
|
+
import { serializeAgentYaml, serializeMcpServerYaml } from "../library";
|
|
11
|
+
import type { DraftResourceType } from "./draft";
|
|
12
|
+
|
|
13
|
+
/** Return value of {@link useEditSessionPrep}. */
|
|
14
|
+
export interface UseEditSessionPrepReturn {
|
|
15
|
+
/** Files ready to be passed as `initialAttachments` to SessionComposer. */
|
|
16
|
+
readonly files: File[] | undefined;
|
|
17
|
+
/** Non-null when resource fetch or serialization failed. */
|
|
18
|
+
readonly error: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Prepares initial attachment files for an edit-mode draft session.
|
|
23
|
+
*
|
|
24
|
+
* Given a resource type and an org/slug reference, this hook fetches the
|
|
25
|
+
* existing resource and serializes it into a `File` object suitable for
|
|
26
|
+
* the `initialAttachments` prop of `SessionComposer`:
|
|
27
|
+
*
|
|
28
|
+
* - **agent** / **mcp-server**: serialized to YAML
|
|
29
|
+
* - **skill**: downloaded as a ZIP package
|
|
30
|
+
*
|
|
31
|
+
* Pass `null` for `draftType` or `editRef` to skip preparation (used
|
|
32
|
+
* when the session is in create-mode rather than edit-mode).
|
|
33
|
+
*/
|
|
34
|
+
export function useEditSessionPrep(
|
|
35
|
+
draftType: DraftResourceType | null,
|
|
36
|
+
editRef: { readonly org: string; readonly slug: string } | null,
|
|
37
|
+
): UseEditSessionPrepReturn {
|
|
38
|
+
const stigmer = useStigmer();
|
|
39
|
+
const editOrg = editRef?.org ?? null;
|
|
40
|
+
const editSlug = editRef?.slug ?? null;
|
|
41
|
+
|
|
42
|
+
const { agent: editAgent } = useAgent(
|
|
43
|
+
draftType === "agent" ? editOrg : null,
|
|
44
|
+
draftType === "agent" ? editSlug : null,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const { mcpServer: editMcpServer } = useMcpServer(
|
|
48
|
+
draftType === "mcp-server" ? editOrg : null,
|
|
49
|
+
draftType === "mcp-server" ? editSlug : null,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const { skill: editSkill } = useSkill(
|
|
53
|
+
draftType === "skill" ? editOrg : null,
|
|
54
|
+
draftType === "skill" ? editSlug : null,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const [files, setFiles] = useState<File[] | undefined>(undefined);
|
|
58
|
+
const [error, setError] = useState<string | null>(null);
|
|
59
|
+
const built = useRef(false);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (built.current) return;
|
|
63
|
+
|
|
64
|
+
if (draftType === "agent" && editAgent) {
|
|
65
|
+
built.current = true;
|
|
66
|
+
try {
|
|
67
|
+
const yaml = serializeAgentYaml(editAgent);
|
|
68
|
+
const slug = editAgent.metadata?.slug ?? "agent";
|
|
69
|
+
setFiles([new File([yaml], `${slug}.yaml`, { type: "text/yaml" })]);
|
|
70
|
+
} catch {
|
|
71
|
+
setError("Failed to serialize agent for editing");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (draftType === "mcp-server" && editMcpServer) {
|
|
76
|
+
built.current = true;
|
|
77
|
+
try {
|
|
78
|
+
const yaml = serializeMcpServerYaml(editMcpServer);
|
|
79
|
+
const slug = editMcpServer.metadata?.slug ?? "mcp-server";
|
|
80
|
+
setFiles([new File([yaml], `${slug}.yaml`, { type: "text/yaml" })]);
|
|
81
|
+
} catch {
|
|
82
|
+
setError("Failed to serialize MCP server for editing");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, [draftType, editAgent, editMcpServer]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (built.current) return;
|
|
89
|
+
if (draftType !== "skill" || !editSkill) return;
|
|
90
|
+
|
|
91
|
+
const storageKey = editSkill.status?.artifactStorageKey;
|
|
92
|
+
if (!storageKey) return;
|
|
93
|
+
|
|
94
|
+
built.current = true;
|
|
95
|
+
const slug = editSkill.metadata?.slug ?? "skill";
|
|
96
|
+
|
|
97
|
+
stigmer.skill
|
|
98
|
+
.getArtifact(create(GetArtifactRequestSchema, { artifactStorageKey: storageKey }))
|
|
99
|
+
.then((resp) => {
|
|
100
|
+
const buf = new ArrayBuffer(resp.artifact.byteLength);
|
|
101
|
+
new Uint8Array(buf).set(resp.artifact);
|
|
102
|
+
const blob = new Blob([buf], { type: "application/zip" });
|
|
103
|
+
setFiles([new File([blob], `${slug}.zip`, { type: "application/zip" })]);
|
|
104
|
+
})
|
|
105
|
+
.catch(() => {
|
|
106
|
+
setError("Failed to download skill package for editing");
|
|
107
|
+
});
|
|
108
|
+
}, [draftType, editSkill, stigmer]);
|
|
109
|
+
|
|
110
|
+
return { files, error };
|
|
111
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { getUserMessage, type McpServerUsageInput, type ResourceRef } from "@stigmer/sdk";
|
|
5
|
+
import type { AgentResolution } from "../agent";
|
|
6
|
+
import { useDefaultAgent } from "../agent";
|
|
7
|
+
import { useModelRegistry } from "../models";
|
|
8
|
+
import { useWorkspaceEntries, type UseWorkspaceEntriesReturn } from "../workspace";
|
|
9
|
+
import { useSessionVariables, type UseSessionVariablesReturn } from "../execution/useSessionVariables";
|
|
10
|
+
import type { SessionComposerSubmitContext } from "../composer";
|
|
11
|
+
import { useCreateSession } from "./useCreateSession";
|
|
12
|
+
import { useCreateAgentExecution } from "../execution/useCreateAgentExecution";
|
|
13
|
+
|
|
14
|
+
const STORAGE_KEY_MODEL = "stigmer:session:model";
|
|
15
|
+
|
|
16
|
+
/** Options for {@link useNewSessionFlow}. */
|
|
17
|
+
export interface UseNewSessionFlowOptions {
|
|
18
|
+
/** Organization slug. Required for session and execution creation. */
|
|
19
|
+
readonly org: string;
|
|
20
|
+
/**
|
|
21
|
+
* Called after a session and its first execution are created successfully.
|
|
22
|
+
* The consumer is responsible for navigation (e.g. `router.push`).
|
|
23
|
+
*/
|
|
24
|
+
readonly onSessionCreated: (sessionId: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Called when an error occurs during session creation.
|
|
27
|
+
* The consumer can use this for toast notifications or other UI feedback.
|
|
28
|
+
* If not provided, errors are still available via {@link UseNewSessionFlowReturn.submitError}.
|
|
29
|
+
*/
|
|
30
|
+
readonly onError?: (message: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Return value of {@link useNewSessionFlow}. */
|
|
34
|
+
export interface UseNewSessionFlowReturn {
|
|
35
|
+
/** Currently selected model ID (persisted to localStorage). */
|
|
36
|
+
readonly modelId: string | undefined;
|
|
37
|
+
/** Update the selected model. Automatically persists to localStorage. */
|
|
38
|
+
readonly setModelId: (id: string) => void;
|
|
39
|
+
|
|
40
|
+
/** Currently selected agent reference, or `null` for the default agent. */
|
|
41
|
+
readonly agentRef: ResourceRef | null;
|
|
42
|
+
/** Update the selected agent reference. */
|
|
43
|
+
readonly setAgentRef: (ref: ResourceRef | null) => void;
|
|
44
|
+
|
|
45
|
+
/** Current agent resolution state (saved instance vs direct reference). */
|
|
46
|
+
readonly resolution: AgentResolution | null;
|
|
47
|
+
/** Update the agent resolution. */
|
|
48
|
+
readonly setResolution: (r: AgentResolution | null) => void;
|
|
49
|
+
|
|
50
|
+
/** Active MCP server configurations. */
|
|
51
|
+
readonly mcpServerUsages: McpServerUsageInput[];
|
|
52
|
+
/** Update MCP server configurations. */
|
|
53
|
+
readonly setMcpServerUsages: (usages: McpServerUsageInput[]) => void;
|
|
54
|
+
|
|
55
|
+
/** Active skill references. */
|
|
56
|
+
readonly skillRefs: ResourceRef[];
|
|
57
|
+
/** Update skill references. */
|
|
58
|
+
readonly setSkillRefs: (refs: ResourceRef[]) => void;
|
|
59
|
+
|
|
60
|
+
/** Selected runner ID, or `null` for auto-selection. */
|
|
61
|
+
readonly runnerId: string | null;
|
|
62
|
+
/** Update the selected runner. */
|
|
63
|
+
readonly setRunnerId: (id: string | null) => void;
|
|
64
|
+
|
|
65
|
+
/** Workspace entries manager (git repos and local paths). */
|
|
66
|
+
readonly workspace: UseWorkspaceEntriesReturn;
|
|
67
|
+
/** Session variables (per-execution secrets) manager. */
|
|
68
|
+
readonly sessionVariables: UseSessionVariablesReturn;
|
|
69
|
+
|
|
70
|
+
/** `true` while the create session + execution flow is in flight. */
|
|
71
|
+
readonly isSubmitting: boolean;
|
|
72
|
+
/** Human-readable error from the last failed submission, or `null`. */
|
|
73
|
+
readonly submitError: string | null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a session with the first execution.
|
|
77
|
+
*
|
|
78
|
+
* Composes all managed state (agent, workspace, MCP servers, skills,
|
|
79
|
+
* runner, model, session variables) into the session and execution
|
|
80
|
+
* creation RPCs, then calls `onSessionCreated` on success.
|
|
81
|
+
*
|
|
82
|
+
* The `model` parameter overrides `modelId` for this submission only
|
|
83
|
+
* (used when SessionComposer passes a per-message model selection).
|
|
84
|
+
*/
|
|
85
|
+
readonly submit: (
|
|
86
|
+
message: string,
|
|
87
|
+
model?: string,
|
|
88
|
+
context?: SessionComposerSubmitContext,
|
|
89
|
+
) => Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Orchestrates the "create a new session" flow.
|
|
94
|
+
*
|
|
95
|
+
* Manages all the state required to configure and submit a new session:
|
|
96
|
+
* model selection (with localStorage persistence), agent resolution,
|
|
97
|
+
* MCP server/skill/runner selection, workspace entries, and session
|
|
98
|
+
* variables. On submission, creates the session and its first execution
|
|
99
|
+
* in sequence, then notifies the consumer via `onSessionCreated`.
|
|
100
|
+
*
|
|
101
|
+
* This hook is framework-agnostic — it works identically in Next.js,
|
|
102
|
+
* Vite, Tauri, or any React environment. Navigation, toast notifications,
|
|
103
|
+
* and draft-mode logic are the consumer's responsibility.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* const flow = useNewSessionFlow({
|
|
108
|
+
* org: "acme",
|
|
109
|
+
* onSessionCreated: (id) => navigate(`/sessions/${id}`),
|
|
110
|
+
* onError: (msg) => toast.error(msg),
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* <SessionComposer
|
|
114
|
+
* onSubmit={flow.submit}
|
|
115
|
+
* isSubmitting={flow.isSubmitting}
|
|
116
|
+
* org="acme"
|
|
117
|
+
* workspace={flow.workspace}
|
|
118
|
+
* agentRef={flow.agentRef}
|
|
119
|
+
* onAgentRefChange={flow.setAgentRef}
|
|
120
|
+
* onAgentResolutionChange={flow.setResolution}
|
|
121
|
+
* mcpServerUsages={flow.mcpServerUsages}
|
|
122
|
+
* onMcpServerUsagesChange={flow.setMcpServerUsages}
|
|
123
|
+
* skillRefs={flow.skillRefs}
|
|
124
|
+
* onSkillRefsChange={flow.setSkillRefs}
|
|
125
|
+
* runnerId={flow.runnerId}
|
|
126
|
+
* onRunnerIdChange={flow.setRunnerId}
|
|
127
|
+
* sessionVariables={flow.sessionVariables}
|
|
128
|
+
* defaultModelId={flow.modelId}
|
|
129
|
+
* onModelChange={flow.setModelId}
|
|
130
|
+
* />
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export function useNewSessionFlow(
|
|
134
|
+
options: UseNewSessionFlowOptions,
|
|
135
|
+
): UseNewSessionFlowReturn {
|
|
136
|
+
const { org, onSessionCreated, onError } = options;
|
|
137
|
+
|
|
138
|
+
const { getModel } = useModelRegistry();
|
|
139
|
+
const { create: createSession } = useCreateSession();
|
|
140
|
+
const { create: createExecution } = useCreateAgentExecution();
|
|
141
|
+
const { agent: defaultAgent } = useDefaultAgent(org);
|
|
142
|
+
const workspace = useWorkspaceEntries();
|
|
143
|
+
const sessionVariables = useSessionVariables();
|
|
144
|
+
|
|
145
|
+
const [modelId, setModelId] = useState<string | undefined>(undefined);
|
|
146
|
+
const [agentRef, setAgentRef] = useState<ResourceRef | null>(null);
|
|
147
|
+
const [resolution, setResolution] = useState<AgentResolution | null>(null);
|
|
148
|
+
const [mcpServerUsages, setMcpServerUsages] = useState<McpServerUsageInput[]>([]);
|
|
149
|
+
const [skillRefs, setSkillRefs] = useState<ResourceRef[]>([]);
|
|
150
|
+
const [runnerId, setRunnerId] = useState<string | null>(null);
|
|
151
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
152
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
153
|
+
|
|
154
|
+
const validModelId = modelId && getModel(modelId) ? modelId : undefined;
|
|
155
|
+
|
|
156
|
+
// Restore persisted model on mount
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const stored = localStorage.getItem(STORAGE_KEY_MODEL);
|
|
159
|
+
if (stored && getModel(stored)) {
|
|
160
|
+
setModelId(stored);
|
|
161
|
+
}
|
|
162
|
+
}, [getModel]);
|
|
163
|
+
|
|
164
|
+
// Persist model on change
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (modelId) {
|
|
167
|
+
localStorage.setItem(STORAGE_KEY_MODEL, modelId);
|
|
168
|
+
}
|
|
169
|
+
}, [modelId]);
|
|
170
|
+
|
|
171
|
+
const submit = useCallback(
|
|
172
|
+
async (
|
|
173
|
+
message: string,
|
|
174
|
+
selectedModel?: string,
|
|
175
|
+
context?: SessionComposerSubmitContext,
|
|
176
|
+
) => {
|
|
177
|
+
if (isSubmitting) return;
|
|
178
|
+
if (!org) {
|
|
179
|
+
const msg = "Select an organization before starting a session.";
|
|
180
|
+
setSubmitError(msg);
|
|
181
|
+
onError?.(msg);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setIsSubmitting(true);
|
|
186
|
+
setSubmitError(null);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const sessionFields = {
|
|
190
|
+
org,
|
|
191
|
+
workspaceEntries: workspace.hasEntries
|
|
192
|
+
? workspace.toInput()
|
|
193
|
+
: undefined,
|
|
194
|
+
mcpServerUsages: mcpServerUsages.length > 0 ? mcpServerUsages : undefined,
|
|
195
|
+
skillRefs: skillRefs.length > 0 ? skillRefs : undefined,
|
|
196
|
+
runnerId: runnerId ?? undefined,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const executionFields = {
|
|
200
|
+
org,
|
|
201
|
+
message,
|
|
202
|
+
modelName: selectedModel ?? validModelId,
|
|
203
|
+
runtimeEnv: context?.runtimeEnv,
|
|
204
|
+
attachments: context?.attachments,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
let sessionId: string;
|
|
208
|
+
|
|
209
|
+
if (agentRef && resolution) {
|
|
210
|
+
if (resolution.mode === "saved") {
|
|
211
|
+
({ sessionId } = await createSession({
|
|
212
|
+
...sessionFields,
|
|
213
|
+
agentInstanceId: resolution.instanceId,
|
|
214
|
+
}));
|
|
215
|
+
} else {
|
|
216
|
+
({ sessionId } = await createSession({
|
|
217
|
+
...sessionFields,
|
|
218
|
+
agentRef,
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
const defaultInstanceId = defaultAgent?.status?.defaultInstanceId;
|
|
223
|
+
if (!defaultInstanceId) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
"No default agent available. Select an agent to start a session.",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
({ sessionId } = await createSession({
|
|
229
|
+
...sessionFields,
|
|
230
|
+
agentInstanceId: defaultInstanceId,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await createExecution({ ...executionFields, sessionId });
|
|
235
|
+
sessionVariables.clear();
|
|
236
|
+
onSessionCreated(sessionId);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
const detail = getUserMessage(err, "Failed to start session");
|
|
239
|
+
setSubmitError(detail);
|
|
240
|
+
onError?.(detail);
|
|
241
|
+
} finally {
|
|
242
|
+
setIsSubmitting(false);
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
[
|
|
246
|
+
isSubmitting,
|
|
247
|
+
org,
|
|
248
|
+
validModelId,
|
|
249
|
+
workspace,
|
|
250
|
+
mcpServerUsages,
|
|
251
|
+
skillRefs,
|
|
252
|
+
runnerId,
|
|
253
|
+
agentRef,
|
|
254
|
+
resolution,
|
|
255
|
+
defaultAgent,
|
|
256
|
+
createSession,
|
|
257
|
+
createExecution,
|
|
258
|
+
sessionVariables,
|
|
259
|
+
onSessionCreated,
|
|
260
|
+
onError,
|
|
261
|
+
],
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
modelId: validModelId,
|
|
266
|
+
setModelId,
|
|
267
|
+
agentRef,
|
|
268
|
+
setAgentRef,
|
|
269
|
+
resolution,
|
|
270
|
+
setResolution,
|
|
271
|
+
mcpServerUsages,
|
|
272
|
+
setMcpServerUsages,
|
|
273
|
+
skillRefs,
|
|
274
|
+
setSkillRefs,
|
|
275
|
+
runnerId,
|
|
276
|
+
setRunnerId,
|
|
277
|
+
workspace,
|
|
278
|
+
sessionVariables,
|
|
279
|
+
isSubmitting,
|
|
280
|
+
submitError,
|
|
281
|
+
submit,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useModelRegistry } from "../models";
|
|
5
|
+
|
|
6
|
+
const STORAGE_KEY_MODEL = "stigmer:session:model";
|
|
7
|
+
|
|
8
|
+
/** Return value of {@link usePersistedModel}. */
|
|
9
|
+
export type UsePersistedModelReturn = readonly [
|
|
10
|
+
modelId: string | undefined,
|
|
11
|
+
setModelId: (id: string) => void,
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Model selection with localStorage persistence.
|
|
16
|
+
*
|
|
17
|
+
* Restores the last selected model on mount and validates it against the
|
|
18
|
+
* model registry. Invalid or removed models are silently ignored (returns
|
|
19
|
+
* `undefined`). On change, persists the new selection to localStorage so
|
|
20
|
+
* it survives page reloads and navigation.
|
|
21
|
+
*
|
|
22
|
+
* Used by both the session launcher (new session) and session page
|
|
23
|
+
* (follow-up messages) to maintain a consistent model preference.
|
|
24
|
+
*/
|
|
25
|
+
export function usePersistedModel(): UsePersistedModelReturn {
|
|
26
|
+
const { getModel } = useModelRegistry();
|
|
27
|
+
|
|
28
|
+
const [modelId, setModelId] = useState<string | undefined>(() => {
|
|
29
|
+
if (typeof window === "undefined") return undefined;
|
|
30
|
+
return localStorage.getItem(STORAGE_KEY_MODEL) ?? undefined;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (modelId) {
|
|
35
|
+
localStorage.setItem(STORAGE_KEY_MODEL, modelId);
|
|
36
|
+
}
|
|
37
|
+
}, [modelId]);
|
|
38
|
+
|
|
39
|
+
const validModelId = modelId && getModel(modelId) ? modelId : undefined;
|
|
40
|
+
return [validModelId, setModelId] as const;
|
|
41
|
+
}
|