@shipfox/client-agent 5.0.0 → 6.0.2

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.
Files changed (168) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +57 -0
  3. package/dist/components/available-provider-card.d.ts +2 -2
  4. package/dist/components/available-provider-card.d.ts.map +1 -1
  5. package/dist/components/available-provider-card.js.map +1 -1
  6. package/dist/components/available-providers-grid.d.ts +2 -2
  7. package/dist/components/available-providers-grid.d.ts.map +1 -1
  8. package/dist/components/available-providers-grid.js +1 -1
  9. package/dist/components/available-providers-grid.js.map +1 -1
  10. package/dist/components/change-default-model-form.d.ts +3 -4
  11. package/dist/components/change-default-model-form.d.ts.map +1 -1
  12. package/dist/components/change-default-model-form.js +2 -4
  13. package/dist/components/change-default-model-form.js.map +1 -1
  14. package/dist/components/custom-model-provider-api-options.d.ts +3 -3
  15. package/dist/components/custom-model-provider-api-options.d.ts.map +1 -1
  16. package/dist/components/custom-model-provider-api-options.js.map +1 -1
  17. package/dist/components/custom-model-provider-form.d.ts +2 -2
  18. package/dist/components/custom-model-provider-form.d.ts.map +1 -1
  19. package/dist/components/custom-model-provider-form.js +12 -10
  20. package/dist/components/custom-model-provider-form.js.map +1 -1
  21. package/dist/components/custom-model-provider-form.stories.d.ts.map +1 -1
  22. package/dist/components/custom-model-provider-form.stories.js +11 -11
  23. package/dist/components/custom-model-provider-form.stories.js.map +1 -1
  24. package/dist/components/custom-model-provider-payload.d.ts +9 -9
  25. package/dist/components/custom-model-provider-payload.d.ts.map +1 -1
  26. package/dist/components/custom-model-provider-payload.js +42 -26
  27. package/dist/components/custom-model-provider-payload.js.map +1 -1
  28. package/dist/components/custom-model-provider-slug.d.ts +1 -1
  29. package/dist/components/custom-model-provider-slug.d.ts.map +1 -1
  30. package/dist/components/custom-model-provider-slug.js +3 -3
  31. package/dist/components/custom-model-provider-slug.js.map +1 -1
  32. package/dist/components/default-model-field.d.ts +2 -2
  33. package/dist/components/default-model-field.d.ts.map +1 -1
  34. package/dist/components/default-model-field.js +1 -1
  35. package/dist/components/default-model-field.js.map +1 -1
  36. package/dist/components/harness-availability.d.ts +4 -7
  37. package/dist/components/harness-availability.d.ts.map +1 -1
  38. package/dist/components/harness-availability.js +2 -11
  39. package/dist/components/harness-availability.js.map +1 -1
  40. package/dist/components/harnesses-section.d.ts.map +1 -1
  41. package/dist/components/harnesses-section.js +4 -6
  42. package/dist/components/harnesses-section.js.map +1 -1
  43. package/dist/components/model-provider-usage-modal.d.ts +2 -2
  44. package/dist/components/model-provider-usage-modal.d.ts.map +1 -1
  45. package/dist/components/model-provider-usage-modal.js +4 -4
  46. package/dist/components/model-provider-usage-modal.js.map +1 -1
  47. package/dist/components/model-provider-usage-modal.stories.d.ts.map +1 -1
  48. package/dist/components/model-provider-usage-modal.stories.js +14 -16
  49. package/dist/components/model-provider-usage-modal.stories.js.map +1 -1
  50. package/dist/components/model-provider-usage-target.d.ts +5 -5
  51. package/dist/components/model-provider-usage-target.d.ts.map +1 -1
  52. package/dist/components/model-provider-usage-target.js +10 -8
  53. package/dist/components/model-provider-usage-target.js.map +1 -1
  54. package/dist/components/model-providers-section.d.ts.map +1 -1
  55. package/dist/components/model-providers-section.js +112 -94
  56. package/dist/components/model-providers-section.js.map +1 -1
  57. package/dist/components/model-providers-section.stories.js +8 -4
  58. package/dist/components/model-providers-section.stories.js.map +1 -1
  59. package/dist/components/test-and-save-form.d.ts +3 -3
  60. package/dist/components/test-and-save-form.d.ts.map +1 -1
  61. package/dist/components/test-and-save-form.js +7 -7
  62. package/dist/components/test-and-save-form.js.map +1 -1
  63. package/dist/core/harness-policy.d.ts +11 -0
  64. package/dist/core/harness-policy.d.ts.map +1 -0
  65. package/dist/core/harness-policy.js +71 -0
  66. package/dist/core/harness-policy.js.map +1 -0
  67. package/dist/core/model-provider-management-reducer.d.ts +51 -0
  68. package/dist/core/model-provider-management-reducer.d.ts.map +1 -0
  69. package/dist/core/model-provider-management-reducer.js +43 -0
  70. package/dist/core/model-provider-management-reducer.js.map +1 -0
  71. package/dist/core/models.d.ts +120 -0
  72. package/dist/core/models.d.ts.map +1 -0
  73. package/dist/core/models.js +3 -0
  74. package/dist/core/models.js.map +1 -0
  75. package/dist/core/onboarding-reducer.d.ts +36 -0
  76. package/dist/core/onboarding-reducer.d.ts.map +1 -0
  77. package/dist/core/onboarding-reducer.js +54 -0
  78. package/dist/core/onboarding-reducer.js.map +1 -0
  79. package/dist/core/provider-policy.d.ts +9 -0
  80. package/dist/core/provider-policy.d.ts.map +1 -0
  81. package/dist/core/provider-policy.js +37 -0
  82. package/dist/core/provider-policy.js.map +1 -0
  83. package/dist/feature.d.ts +7 -0
  84. package/dist/feature.d.ts.map +1 -1
  85. package/dist/feature.js +10 -9
  86. package/dist/feature.js.map +1 -1
  87. package/dist/hooks/api/model-provider-command-mapper.d.ts +11 -0
  88. package/dist/hooks/api/model-provider-command-mapper.d.ts.map +1 -0
  89. package/dist/hooks/api/model-provider-command-mapper.js +144 -0
  90. package/dist/hooks/api/model-provider-command-mapper.js.map +1 -0
  91. package/dist/hooks/api/model-provider-mapper.d.ts +11 -0
  92. package/dist/hooks/api/model-provider-mapper.d.ts.map +1 -0
  93. package/dist/hooks/api/model-provider-mapper.js +94 -0
  94. package/dist/hooks/api/model-provider-mapper.js.map +1 -0
  95. package/dist/hooks/api/model-providers.d.ts +60 -295
  96. package/dist/hooks/api/model-providers.d.ts.map +1 -1
  97. package/dist/hooks/api/model-providers.js +51 -37
  98. package/dist/hooks/api/model-providers.js.map +1 -1
  99. package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -1
  100. package/dist/pages/model-provider-onboarding-page.js +57 -45
  101. package/dist/pages/model-provider-onboarding-page.js.map +1 -1
  102. package/dist/routes/inputs.d.ts +4 -0
  103. package/dist/routes/inputs.d.ts.map +1 -0
  104. package/dist/routes/inputs.js +12 -0
  105. package/dist/routes/inputs.js.map +1 -0
  106. package/dist/routes/model-provider.d.ts.map +1 -1
  107. package/dist/routes/model-provider.js +4 -5
  108. package/dist/routes/model-provider.js.map +1 -1
  109. package/dist/state/model-provider-onboarding.d.ts.map +1 -1
  110. package/dist/state/model-provider-onboarding.js +9 -18
  111. package/dist/state/model-provider-onboarding.js.map +1 -1
  112. package/dist/tsconfig.test.tsbuildinfo +1 -1
  113. package/package.json +9 -10
  114. package/src/components/available-provider-card.tsx +2 -2
  115. package/src/components/available-providers-grid.tsx +3 -3
  116. package/src/components/change-default-model-form.tsx +5 -6
  117. package/src/components/custom-model-provider-api-options.ts +3 -3
  118. package/src/components/custom-model-provider-form.stories.tsx +13 -13
  119. package/src/components/custom-model-provider-form.tsx +19 -12
  120. package/src/components/custom-model-provider-payload.test.ts +22 -22
  121. package/src/components/custom-model-provider-payload.ts +69 -54
  122. package/src/components/custom-model-provider-slug.test.ts +1 -1
  123. package/src/components/custom-model-provider-slug.ts +6 -3
  124. package/src/components/default-model-field.tsx +5 -5
  125. package/src/components/harness-availability.test.ts +13 -10
  126. package/src/components/harness-availability.ts +4 -28
  127. package/src/components/harnesses-section.tsx +7 -11
  128. package/src/components/model-provider-usage-modal.stories.tsx +18 -23
  129. package/src/components/model-provider-usage-modal.test.tsx +35 -7
  130. package/src/components/model-provider-usage-modal.tsx +12 -16
  131. package/src/components/model-provider-usage-target.ts +15 -18
  132. package/src/components/model-providers-section.stories.tsx +4 -1
  133. package/src/components/model-providers-section.tsx +112 -125
  134. package/src/components/test-and-save-form.tsx +13 -17
  135. package/src/core/harness-policy.test.ts +15 -0
  136. package/src/core/harness-policy.ts +83 -0
  137. package/src/core/model-provider-management-reducer.test.ts +59 -0
  138. package/src/core/model-provider-management-reducer.ts +72 -0
  139. package/src/core/models.ts +143 -0
  140. package/src/core/onboarding-reducer.test.ts +44 -0
  141. package/src/core/onboarding-reducer.ts +64 -0
  142. package/src/core/provider-policy.ts +53 -0
  143. package/src/feature.ts +12 -10
  144. package/src/hooks/api/model-provider-command-mapper.ts +122 -0
  145. package/src/hooks/api/model-provider-mapper.test.ts +27 -0
  146. package/src/hooks/api/model-provider-mapper.ts +120 -0
  147. package/src/hooks/api/model-providers.test.ts +15 -15
  148. package/src/hooks/api/model-providers.ts +146 -81
  149. package/src/pages/model-provider-onboarding-page.tsx +62 -56
  150. package/src/routes/inputs.test.ts +11 -0
  151. package/src/routes/inputs.ts +9 -0
  152. package/src/routes/model-provider.tsx +4 -3
  153. package/src/state/model-provider-onboarding.test.ts +4 -4
  154. package/src/state/model-provider-onboarding.ts +13 -23
  155. package/test/fixtures/model-providers.ts +1 -1
  156. package/tsconfig.build.tsbuildinfo +1 -1
  157. package/vercel.json +5 -0
  158. package/dist/components/provider-search.d.ts +0 -4
  159. package/dist/components/provider-search.d.ts.map +0 -1
  160. package/dist/components/provider-search.js +0 -13
  161. package/dist/components/provider-search.js.map +0 -1
  162. package/dist/components/supported-model-provider-catalog-entry.d.ts +0 -8
  163. package/dist/components/supported-model-provider-catalog-entry.d.ts.map +0 -1
  164. package/dist/components/supported-model-provider-catalog-entry.js +0 -9
  165. package/dist/components/supported-model-provider-catalog-entry.js.map +0 -1
  166. package/src/components/provider-search.test.ts +0 -45
  167. package/src/components/provider-search.ts +0 -18
  168. package/src/components/supported-model-provider-catalog-entry.ts +0 -19
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { DEFAULT_HARNESS, harnessSupportsProvider, listHarnessDescriptors } from '@shipfox/api-agent-dto';
3
2
  import { QueryLoadError } from '@shipfox/client-ui';
4
3
  import { Button } from '@shipfox/react-ui/button';
5
4
  import { Callout } from '@shipfox/react-ui/callout';
@@ -10,32 +9,32 @@ import { Skeleton } from '@shipfox/react-ui/skeleton';
10
9
  import { toast } from '@shipfox/react-ui/toast';
11
10
  import { Header, Text } from '@shipfox/react-ui/typography';
12
11
  import { cn } from '@shipfox/react-ui/utils';
13
- import { useEffect, useMemo, useState } from 'react';
12
+ import { useEffect, useMemo, useReducer } from 'react';
14
13
  import { AvailableProvidersGrid, PROVIDER_GRID_CLASS } from '#components/available-providers-grid.js';
15
14
  import { modelProviderConfigErrorToFormError } from '#components/form-errors.js';
16
15
  import { ModelProviderTestAndSaveForm } from '#components/test-and-save-form.js';
16
+ import { DEFAULT_HARNESS, harnessSupportsProvider, listHarnesses } from '#core/harness-policy.js';
17
+ import { initialOnboardingState, onboardingReducer } from '#core/onboarding-reducer.js';
18
+ import { isSupportedProvider } from '#core/provider-policy.js';
17
19
  import { useModelProviderCatalogQuery, useSetDefaultHarnessMutation } from '#hooks/api/model-providers.js';
18
20
  import { dismissModelProviderOnboarding } from '#state/model-provider-onboarding.js';
19
21
  const SURFACE_CLASS = 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
20
22
  export function ModelProviderOnboardingPage({ workspaceId, onSkip, onConfigured }) {
21
23
  const catalogQuery = useModelProviderCatalogQuery();
22
24
  const setDefaultHarness = useSetDefaultHarnessMutation();
23
- const [selectedHarness, setSelectedHarness] = useState(null);
24
- const [selectedEntry, setSelectedEntry] = useState(null);
25
- const [defaultHarnessError, setDefaultHarnessError] = useState();
26
- const [isSettingDefaultHarness, setIsSettingDefaultHarness] = useState(false);
27
- const supportedProviders = catalogQuery.data?.providers.filter((provider)=>provider.support_status === 'supported') ?? [];
25
+ const [onboarding, dispatch] = useReducer(onboardingReducer, initialOnboardingState);
26
+ const supportedProviders = catalogQuery.data?.providers.filter(isSupportedProvider) ?? [];
28
27
  const filteredProviders = useMemo(()=>{
29
- if (selectedHarness === null) return [];
30
- return supportedProviders.filter((provider)=>harnessSupportsProvider(selectedHarness, provider.id));
28
+ if (onboarding.step === 'choose-harness') return [];
29
+ return supportedProviders.filter((provider)=>harnessSupportsProvider(onboarding.harnessId, provider.id));
31
30
  }, [
32
- selectedHarness,
31
+ onboarding,
33
32
  supportedProviders
34
33
  ]);
35
34
  useEffect(()=>{
36
- document.getElementById(selectedHarness === null ? 'model-provider-harness-step' : 'model-provider-provider-step')?.focus();
35
+ document.getElementById(onboarding.step === 'choose-harness' ? 'model-provider-harness-step' : 'model-provider-provider-step')?.focus();
37
36
  }, [
38
- selectedHarness
37
+ onboarding.step
39
38
  ]);
40
39
  function handleSkip() {
41
40
  try {
@@ -46,32 +45,39 @@ export function ModelProviderOnboardingPage({ workspaceId, onSkip, onConfigured
46
45
  onSkip();
47
46
  }
48
47
  async function handleProviderSaved() {
49
- if (selectedEntry === null || selectedHarness === null) return;
50
- setDefaultHarnessError(undefined);
51
- if (selectedHarness === DEFAULT_HARNESS) {
52
- toast.success(`${selectedEntry.label} saved`);
48
+ if (onboarding.step !== 'configure-provider' && onboarding.step !== 'saving-default-harness') return;
49
+ const provider = onboarding.provider;
50
+ if (onboarding.step === 'configure-provider') dispatch({
51
+ type: 'provider-saved'
52
+ });
53
+ if (onboarding.harnessId === DEFAULT_HARNESS) {
54
+ toast.success(`${provider.label} saved`);
55
+ dispatch({
56
+ type: 'default-harness-saved'
57
+ });
53
58
  onConfigured();
54
59
  return;
55
60
  }
56
- setIsSettingDefaultHarness(true);
57
61
  try {
58
62
  await setDefaultHarness.mutateAsync({
59
63
  workspaceId,
60
- body: {
61
- harness_id: selectedHarness
62
- }
64
+ harnessId: onboarding.harnessId
65
+ });
66
+ toast.success(`${provider.label} saved`);
67
+ dispatch({
68
+ type: 'default-harness-saved'
63
69
  });
64
- toast.success(`${selectedEntry.label} saved`);
65
70
  onConfigured();
66
71
  } catch (error) {
67
72
  const mapped = modelProviderConfigErrorToFormError(error);
68
- setDefaultHarnessError(mapped.message || 'Could not save default harness. Try again.');
69
- } finally{
70
- setIsSettingDefaultHarness(false);
73
+ dispatch({
74
+ type: 'default-harness-failed',
75
+ message: mapped.message || 'Could not save default harness. Try again.'
76
+ });
71
77
  }
72
78
  }
73
- const selectedHarnessDescriptor = selectedHarness === null ? null : listHarnessDescriptors().find((harness)=>harness.id === selectedHarness);
74
- const headingId = selectedHarness === null ? 'model-provider-harness-step' : 'model-provider-provider-step';
79
+ const selectedHarnessDescriptor = onboarding.step === 'choose-harness' ? null : listHarnesses().find((harness)=>harness.id === onboarding.harnessId);
80
+ const headingId = onboarding.step === 'choose-harness' ? 'model-provider-harness-step' : 'model-provider-provider-step';
75
81
  return /*#__PURE__*/ _jsxs("div", {
76
82
  className: "mx-auto flex w-full max-w-[640px] flex-col gap-20",
77
83
  children: [
@@ -107,15 +113,20 @@ export function ModelProviderOnboardingPage({ workspaceId, onSkip, onConfigured
107
113
  /*#__PURE__*/ _jsx("section", {
108
114
  "aria-labelledby": headingId,
109
115
  className: "flex flex-col gap-16",
110
- children: selectedHarness === null ? /*#__PURE__*/ _jsx(HarnessPicker, {
111
- onSelect: setSelectedHarness
116
+ children: onboarding.step === 'choose-harness' ? /*#__PURE__*/ _jsx(HarnessPicker, {
117
+ onSelect: (harnessId)=>dispatch({
118
+ type: 'harness-selected',
119
+ harnessId
120
+ })
112
121
  }) : /*#__PURE__*/ _jsxs(_Fragment, {
113
122
  children: [
114
123
  /*#__PURE__*/ _jsx("div", {
115
124
  children: /*#__PURE__*/ _jsx(Button, {
116
125
  type: "button",
117
126
  variant: "transparent",
118
- onClick: ()=>setSelectedHarness(null),
127
+ onClick: ()=>dispatch({
128
+ type: 'back'
129
+ }),
119
130
  children: "Back"
120
131
  })
121
132
  }),
@@ -123,37 +134,38 @@ export function ModelProviderOnboardingPage({ workspaceId, onSkip, onConfigured
123
134
  catalogQuery: catalogQuery,
124
135
  supportedProviders: filteredProviders,
125
136
  onSelect: (entry)=>{
126
- setDefaultHarnessError(undefined);
127
- setSelectedEntry(entry);
137
+ dispatch({
138
+ type: 'provider-selected',
139
+ provider: entry
140
+ });
128
141
  }
129
- }, selectedHarness)
142
+ }, onboarding.harnessId)
130
143
  ]
131
144
  })
132
145
  }),
133
146
  /*#__PURE__*/ _jsx(Modal, {
134
- open: selectedEntry !== null,
147
+ open: onboarding.step === 'configure-provider' || onboarding.step === 'saving-default-harness',
135
148
  onOpenChange: (open)=>{
136
- if (!open && !isSettingDefaultHarness) {
137
- setSelectedEntry(null);
138
- setDefaultHarnessError(undefined);
139
- }
149
+ if (!open && onboarding.step === 'configure-provider') dispatch({
150
+ type: 'back'
151
+ });
140
152
  },
141
153
  children: /*#__PURE__*/ _jsxs(ModalContent, {
142
154
  "aria-describedby": undefined,
143
155
  children: [
144
156
  /*#__PURE__*/ _jsx(ModalTitle, {
145
157
  className: "sr-only",
146
- children: selectedEntry ? `Configure ${selectedEntry.label}` : ''
158
+ children: onboarding.step === 'configure-provider' || onboarding.step === 'saving-default-harness' ? `Configure ${onboarding.provider.label}` : ''
147
159
  }),
148
160
  /*#__PURE__*/ _jsx(ModalHeader, {
149
161
  children: /*#__PURE__*/ _jsx(Text, {
150
162
  size: "lg",
151
163
  "aria-hidden": "true",
152
164
  className: "overflow-ellipsis overflow-hidden whitespace-nowrap",
153
- children: selectedEntry ? `Configure ${selectedEntry.label}` : ''
165
+ children: onboarding.step === 'configure-provider' || onboarding.step === 'saving-default-harness' ? `Configure ${onboarding.provider.label}` : ''
154
166
  })
155
167
  }),
156
- isSettingDefaultHarness ? /*#__PURE__*/ _jsx("div", {
168
+ onboarding.step === 'saving-default-harness' ? /*#__PURE__*/ _jsx("div", {
157
169
  role: "status",
158
170
  className: "px-20 pb-8",
159
171
  children: /*#__PURE__*/ _jsx(Text, {
@@ -162,7 +174,7 @@ export function ModelProviderOnboardingPage({ workspaceId, onSkip, onConfigured
162
174
  children: "Saving harness default..."
163
175
  })
164
176
  }) : null,
165
- defaultHarnessError ? /*#__PURE__*/ _jsx("div", {
177
+ onboarding.step === 'saving-default-harness' && onboarding.error ? /*#__PURE__*/ _jsx("div", {
166
178
  className: "px-20 pb-8",
167
179
  children: /*#__PURE__*/ _jsx(Callout, {
168
180
  role: "alert",
@@ -177,15 +189,15 @@ export function ModelProviderOnboardingPage({ workspaceId, onSkip, onConfigured
177
189
  }),
178
190
  /*#__PURE__*/ _jsx(Text, {
179
191
  size: "sm",
180
- children: defaultHarnessError
192
+ children: onboarding.error
181
193
  })
182
194
  ]
183
195
  })
184
196
  })
185
197
  }) : null,
186
- selectedEntry ? /*#__PURE__*/ _jsx(ModelProviderTestAndSaveForm, {
198
+ onboarding.step === 'configure-provider' || onboarding.step === 'saving-default-harness' ? /*#__PURE__*/ _jsx(ModelProviderTestAndSaveForm, {
187
199
  workspaceId: workspaceId,
188
- entry: selectedEntry,
200
+ entry: onboarding.provider,
189
201
  setAsDefaultOnSave: true,
190
202
  onSaved: ()=>{
191
203
  void handleProviderSaved();
@@ -201,7 +213,7 @@ function HarnessPicker({ onSelect }) {
201
213
  return /*#__PURE__*/ _jsx("ul", {
202
214
  className: PROVIDER_GRID_CLASS,
203
215
  "aria-label": "Agent harnesses",
204
- children: listHarnessDescriptors().map((harness)=>/*#__PURE__*/ _jsx(HarnessCard, {
216
+ children: listHarnesses().map((harness)=>/*#__PURE__*/ _jsx(HarnessCard, {
205
217
  harness: harness,
206
218
  onChoose: ()=>onSelect(harness.id)
207
219
  }, harness.id))
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/model-provider-onboarding-page.tsx"],"sourcesContent":["import {\n DEFAULT_HARNESS,\n type Harness,\n type HarnessDescriptor,\n harnessSupportsProvider,\n listHarnessDescriptors,\n type ModelProviderCatalogEntryDto,\n} from '@shipfox/api-agent-dto';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Modal, ModalContent, ModalHeader, ModalTitle} from '@shipfox/react-ui/modal';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useEffect, useMemo, useState} from 'react';\nimport {AvailableProvidersGrid, PROVIDER_GRID_CLASS} from '#components/available-providers-grid.js';\nimport {modelProviderConfigErrorToFormError} from '#components/form-errors.js';\nimport {ModelProviderTestAndSaveForm} from '#components/test-and-save-form.js';\nimport {\n useModelProviderCatalogQuery,\n useSetDefaultHarnessMutation,\n} from '#hooks/api/model-providers.js';\nimport {dismissModelProviderOnboarding} from '#state/model-provider-onboarding.js';\n\nconst SURFACE_CLASS =\n 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';\n\nexport function ModelProviderOnboardingPage({\n workspaceId,\n onSkip,\n onConfigured,\n}: {\n workspaceId: string;\n onSkip: () => void;\n onConfigured: () => void;\n}) {\n const catalogQuery = useModelProviderCatalogQuery();\n const setDefaultHarness = useSetDefaultHarnessMutation();\n const [selectedHarness, setSelectedHarness] = useState<Harness | null>(null);\n const [selectedEntry, setSelectedEntry] = useState<ModelProviderCatalogEntryDto | null>(null);\n const [defaultHarnessError, setDefaultHarnessError] = useState<string | undefined>();\n const [isSettingDefaultHarness, setIsSettingDefaultHarness] = useState(false);\n const supportedProviders =\n catalogQuery.data?.providers.filter((provider) => provider.support_status === 'supported') ??\n [];\n const filteredProviders = useMemo(() => {\n if (selectedHarness === null) return [];\n return supportedProviders.filter((provider) =>\n harnessSupportsProvider(selectedHarness, provider.id),\n );\n }, [selectedHarness, supportedProviders]);\n\n useEffect(() => {\n document\n .getElementById(\n selectedHarness === null ? 'model-provider-harness-step' : 'model-provider-provider-step',\n )\n ?.focus();\n }, [selectedHarness]);\n\n function handleSkip() {\n try {\n dismissModelProviderOnboarding(workspaceId);\n } catch {\n // Skip is best-effort persistence; access to the product must not depend on storage.\n }\n onSkip();\n }\n\n async function handleProviderSaved() {\n if (selectedEntry === null || selectedHarness === null) return;\n\n setDefaultHarnessError(undefined);\n if (selectedHarness === DEFAULT_HARNESS) {\n toast.success(`${selectedEntry.label} saved`);\n onConfigured();\n return;\n }\n\n setIsSettingDefaultHarness(true);\n try {\n await setDefaultHarness.mutateAsync({\n workspaceId,\n body: {harness_id: selectedHarness},\n });\n toast.success(`${selectedEntry.label} saved`);\n onConfigured();\n } catch (error) {\n const mapped = modelProviderConfigErrorToFormError(error);\n setDefaultHarnessError(mapped.message || 'Could not save default harness. Try again.');\n } finally {\n setIsSettingDefaultHarness(false);\n }\n }\n\n const selectedHarnessDescriptor =\n selectedHarness === null\n ? null\n : listHarnessDescriptors().find((harness) => harness.id === selectedHarness);\n const headingId =\n selectedHarness === null ? 'model-provider-harness-step' : 'model-provider-provider-step';\n\n return (\n <div className=\"mx-auto flex w-full max-w-[640px] flex-col gap-20\">\n <header className=\"flex flex-col gap-12\">\n <div className=\"flex items-start justify-between gap-16 max-[520px]:flex-col max-[520px]:gap-8\">\n <Header id={headingId} variant=\"h1\" tabIndex={-1} className=\"outline-none\">\n {selectedHarnessDescriptor\n ? `Configure ${selectedHarnessDescriptor.label}`\n : 'Choose agent harness'}\n </Header>\n <Button type=\"button\" variant=\"secondary\" onClick={handleSkip} className=\"shrink-0\">\n Skip for now\n </Button>\n </div>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n {selectedHarnessDescriptor\n ? `${selectedHarnessDescriptor.label} needs a compatible model provider. Add your own API key, or go back to choose a different harness.`\n : 'A harness runs agent steps. Choose the engine you want first, then connect a compatible model provider.'}\n </Text>\n </header>\n\n <section aria-labelledby={headingId} className=\"flex flex-col gap-16\">\n {selectedHarness === null ? (\n <HarnessPicker onSelect={setSelectedHarness} />\n ) : (\n <>\n <div>\n <Button type=\"button\" variant=\"transparent\" onClick={() => setSelectedHarness(null)}>\n Back\n </Button>\n </div>\n <ModelProviderPicker\n key={selectedHarness}\n catalogQuery={catalogQuery}\n supportedProviders={filteredProviders}\n onSelect={(entry) => {\n setDefaultHarnessError(undefined);\n setSelectedEntry(entry);\n }}\n />\n </>\n )}\n </section>\n\n <Modal\n open={selectedEntry !== null}\n onOpenChange={(open) => {\n if (!open && !isSettingDefaultHarness) {\n setSelectedEntry(null);\n setDefaultHarnessError(undefined);\n }\n }}\n >\n <ModalContent aria-describedby={undefined}>\n <ModalTitle className=\"sr-only\">\n {selectedEntry ? `Configure ${selectedEntry.label}` : ''}\n </ModalTitle>\n <ModalHeader>\n <Text\n size=\"lg\"\n aria-hidden=\"true\"\n className=\"overflow-ellipsis overflow-hidden whitespace-nowrap\"\n >\n {selectedEntry ? `Configure ${selectedEntry.label}` : ''}\n </Text>\n </ModalHeader>\n {isSettingDefaultHarness ? (\n <div role=\"status\" className=\"px-20 pb-8\">\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Saving harness default...\n </Text>\n </div>\n ) : null}\n {defaultHarnessError ? (\n <div className=\"px-20 pb-8\">\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\" bold>\n Could not save default harness\n </Text>\n <Text size=\"sm\">{defaultHarnessError}</Text>\n </div>\n </Callout>\n </div>\n ) : null}\n {selectedEntry ? (\n <ModelProviderTestAndSaveForm\n workspaceId={workspaceId}\n entry={selectedEntry}\n setAsDefaultOnSave\n onSaved={() => {\n void handleProviderSaved();\n }}\n />\n ) : null}\n </ModalContent>\n </Modal>\n </div>\n );\n}\n\nfunction HarnessPicker({onSelect}: {onSelect: (harness: Harness) => void}) {\n return (\n <ul className={PROVIDER_GRID_CLASS} aria-label=\"Agent harnesses\">\n {listHarnessDescriptors().map((harness) => (\n <HarnessCard key={harness.id} harness={harness} onChoose={() => onSelect(harness.id)} />\n ))}\n </ul>\n );\n}\n\nfunction HarnessCard({harness, onChoose}: {harness: HarnessDescriptor; onChoose: () => void}) {\n return (\n <li>\n <button\n type=\"button\"\n className={cn(\n 'group block w-full cursor-pointer p-16 text-left outline-none transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus',\n SURFACE_CLASS,\n )}\n aria-label={`Choose ${harness.label}`}\n onClick={onChoose}\n >\n <div className=\"flex min-w-0 items-center justify-between gap-12\">\n <div className=\"flex min-w-0 flex-col gap-4\">\n <Text size=\"md\" bold className=\"min-w-0 truncate\">\n {harness.label}\n </Text>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {harness.description}\n </Text>\n </div>\n <div className=\"flex shrink-0 items-center gap-4 text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive\">\n <Text size=\"sm\">Choose</Text>\n <Icon name=\"chevronRight\" className=\"size-16\" />\n </div>\n </div>\n </button>\n </li>\n );\n}\n\nfunction ModelProviderPicker({\n catalogQuery,\n supportedProviders,\n onSelect,\n}: {\n catalogQuery: ReturnType<typeof useModelProviderCatalogQuery>;\n supportedProviders: ModelProviderCatalogEntryDto[];\n onSelect: (entry: ModelProviderCatalogEntryDto) => void;\n}) {\n if (catalogQuery.isPending) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Loading model providers\">\n <ModelProviderGridSkeleton />\n </div>\n );\n }\n\n if (catalogQuery.isError && catalogQuery.data === undefined) {\n return <QueryLoadError query={catalogQuery} subject=\"model provider catalog\" />;\n }\n\n if (catalogQuery.data !== undefined && supportedProviders.length === 0) {\n return (\n <EmptyState\n icon=\"componentLine\"\n title=\"No providers available to configure\"\n description=\"Skip for now and add one later from workspace settings.\"\n />\n );\n }\n\n return <AvailableProvidersGrid entries={supportedProviders} onSelect={onSelect} />;\n}\n\nfunction ModelProviderGridSkeleton() {\n return (\n <div className={PROVIDER_GRID_CLASS}>\n {[0, 1, 2, 3].map((card) => (\n <Skeleton key={card} className=\"h-136 w-full\" />\n ))}\n </div>\n );\n}\n"],"names":["DEFAULT_HARNESS","harnessSupportsProvider","listHarnessDescriptors","QueryLoadError","Button","Callout","EmptyState","Icon","Modal","ModalContent","ModalHeader","ModalTitle","Skeleton","toast","Header","Text","cn","useEffect","useMemo","useState","AvailableProvidersGrid","PROVIDER_GRID_CLASS","modelProviderConfigErrorToFormError","ModelProviderTestAndSaveForm","useModelProviderCatalogQuery","useSetDefaultHarnessMutation","dismissModelProviderOnboarding","SURFACE_CLASS","ModelProviderOnboardingPage","workspaceId","onSkip","onConfigured","catalogQuery","setDefaultHarness","selectedHarness","setSelectedHarness","selectedEntry","setSelectedEntry","defaultHarnessError","setDefaultHarnessError","isSettingDefaultHarness","setIsSettingDefaultHarness","supportedProviders","data","providers","filter","provider","support_status","filteredProviders","id","document","getElementById","focus","handleSkip","handleProviderSaved","undefined","success","label","mutateAsync","body","harness_id","error","mapped","message","selectedHarnessDescriptor","find","harness","headingId","div","className","header","variant","tabIndex","type","onClick","size","section","aria-labelledby","HarnessPicker","onSelect","ModelProviderPicker","entry","open","onOpenChange","aria-describedby","aria-hidden","role","bold","setAsDefaultOnSave","onSaved","ul","aria-label","map","HarnessCard","onChoose","li","button","description","name","isPending","aria-busy","ModelProviderGridSkeleton","isError","query","subject","length","icon","title","entries","card"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/model-provider-onboarding-page.tsx"],"sourcesContent":["import {QueryLoadError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Modal, ModalContent, ModalHeader, ModalTitle} from '@shipfox/react-ui/modal';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useEffect, useMemo, useReducer} from 'react';\nimport {AvailableProvidersGrid, PROVIDER_GRID_CLASS} from '#components/available-providers-grid.js';\nimport {modelProviderConfigErrorToFormError} from '#components/form-errors.js';\nimport {ModelProviderTestAndSaveForm} from '#components/test-and-save-form.js';\nimport {DEFAULT_HARNESS, harnessSupportsProvider, listHarnesses} from '#core/harness-policy.js';\nimport type {HarnessDescriptor, HarnessId, SupportedProvider} from '#core/models.js';\nimport {initialOnboardingState, onboardingReducer} from '#core/onboarding-reducer.js';\nimport {isSupportedProvider} from '#core/provider-policy.js';\nimport {\n useModelProviderCatalogQuery,\n useSetDefaultHarnessMutation,\n} from '#hooks/api/model-providers.js';\nimport {dismissModelProviderOnboarding} from '#state/model-provider-onboarding.js';\n\nconst SURFACE_CLASS =\n 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';\n\nexport function ModelProviderOnboardingPage({\n workspaceId,\n onSkip,\n onConfigured,\n}: {\n workspaceId: string;\n onSkip: () => void;\n onConfigured: () => void;\n}) {\n const catalogQuery = useModelProviderCatalogQuery();\n const setDefaultHarness = useSetDefaultHarnessMutation();\n const [onboarding, dispatch] = useReducer(onboardingReducer, initialOnboardingState);\n const supportedProviders = catalogQuery.data?.providers.filter(isSupportedProvider) ?? [];\n const filteredProviders = useMemo(() => {\n if (onboarding.step === 'choose-harness') return [];\n return supportedProviders.filter((provider) =>\n harnessSupportsProvider(onboarding.harnessId, provider.id),\n );\n }, [onboarding, supportedProviders]);\n\n useEffect(() => {\n document\n .getElementById(\n onboarding.step === 'choose-harness'\n ? 'model-provider-harness-step'\n : 'model-provider-provider-step',\n )\n ?.focus();\n }, [onboarding.step]);\n\n function handleSkip() {\n try {\n dismissModelProviderOnboarding(workspaceId);\n } catch {\n // Skip is best-effort persistence; access to the product must not depend on storage.\n }\n onSkip();\n }\n\n async function handleProviderSaved() {\n if (onboarding.step !== 'configure-provider' && onboarding.step !== 'saving-default-harness')\n return;\n\n const provider = onboarding.provider;\n if (onboarding.step === 'configure-provider') dispatch({type: 'provider-saved'});\n if (onboarding.harnessId === DEFAULT_HARNESS) {\n toast.success(`${provider.label} saved`);\n dispatch({type: 'default-harness-saved'});\n onConfigured();\n return;\n }\n\n try {\n await setDefaultHarness.mutateAsync({\n workspaceId,\n harnessId: onboarding.harnessId,\n });\n toast.success(`${provider.label} saved`);\n dispatch({type: 'default-harness-saved'});\n onConfigured();\n } catch (error) {\n const mapped = modelProviderConfigErrorToFormError(error);\n dispatch({\n type: 'default-harness-failed',\n message: mapped.message || 'Could not save default harness. Try again.',\n });\n }\n }\n\n const selectedHarnessDescriptor =\n onboarding.step === 'choose-harness'\n ? null\n : listHarnesses().find((harness) => harness.id === onboarding.harnessId);\n const headingId =\n onboarding.step === 'choose-harness'\n ? 'model-provider-harness-step'\n : 'model-provider-provider-step';\n\n return (\n <div className=\"mx-auto flex w-full max-w-[640px] flex-col gap-20\">\n <header className=\"flex flex-col gap-12\">\n <div className=\"flex items-start justify-between gap-16 max-[520px]:flex-col max-[520px]:gap-8\">\n <Header id={headingId} variant=\"h1\" tabIndex={-1} className=\"outline-none\">\n {selectedHarnessDescriptor\n ? `Configure ${selectedHarnessDescriptor.label}`\n : 'Choose agent harness'}\n </Header>\n <Button type=\"button\" variant=\"secondary\" onClick={handleSkip} className=\"shrink-0\">\n Skip for now\n </Button>\n </div>\n <Text size=\"md\" className=\"text-foreground-neutral-muted\">\n {selectedHarnessDescriptor\n ? `${selectedHarnessDescriptor.label} needs a compatible model provider. Add your own API key, or go back to choose a different harness.`\n : 'A harness runs agent steps. Choose the engine you want first, then connect a compatible model provider.'}\n </Text>\n </header>\n\n <section aria-labelledby={headingId} className=\"flex flex-col gap-16\">\n {onboarding.step === 'choose-harness' ? (\n <HarnessPicker\n onSelect={(harnessId) => dispatch({type: 'harness-selected', harnessId})}\n />\n ) : (\n <>\n <div>\n <Button type=\"button\" variant=\"transparent\" onClick={() => dispatch({type: 'back'})}>\n Back\n </Button>\n </div>\n <ModelProviderPicker\n key={onboarding.harnessId}\n catalogQuery={catalogQuery}\n supportedProviders={filteredProviders}\n onSelect={(entry) => {\n dispatch({type: 'provider-selected', provider: entry});\n }}\n />\n </>\n )}\n </section>\n\n <Modal\n open={\n onboarding.step === 'configure-provider' || onboarding.step === 'saving-default-harness'\n }\n onOpenChange={(open) => {\n if (!open && onboarding.step === 'configure-provider') dispatch({type: 'back'});\n }}\n >\n <ModalContent aria-describedby={undefined}>\n <ModalTitle className=\"sr-only\">\n {onboarding.step === 'configure-provider' ||\n onboarding.step === 'saving-default-harness'\n ? `Configure ${onboarding.provider.label}`\n : ''}\n </ModalTitle>\n <ModalHeader>\n <Text\n size=\"lg\"\n aria-hidden=\"true\"\n className=\"overflow-ellipsis overflow-hidden whitespace-nowrap\"\n >\n {onboarding.step === 'configure-provider' ||\n onboarding.step === 'saving-default-harness'\n ? `Configure ${onboarding.provider.label}`\n : ''}\n </Text>\n </ModalHeader>\n {onboarding.step === 'saving-default-harness' ? (\n <div role=\"status\" className=\"px-20 pb-8\">\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Saving harness default...\n </Text>\n </div>\n ) : null}\n {onboarding.step === 'saving-default-harness' && onboarding.error ? (\n <div className=\"px-20 pb-8\">\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\" bold>\n Could not save default harness\n </Text>\n <Text size=\"sm\">{onboarding.error}</Text>\n </div>\n </Callout>\n </div>\n ) : null}\n {onboarding.step === 'configure-provider' ||\n onboarding.step === 'saving-default-harness' ? (\n <ModelProviderTestAndSaveForm\n workspaceId={workspaceId}\n entry={onboarding.provider}\n setAsDefaultOnSave\n onSaved={() => {\n void handleProviderSaved();\n }}\n />\n ) : null}\n </ModalContent>\n </Modal>\n </div>\n );\n}\n\nfunction HarnessPicker({onSelect}: {onSelect: (harness: HarnessId) => void}) {\n return (\n <ul className={PROVIDER_GRID_CLASS} aria-label=\"Agent harnesses\">\n {listHarnesses().map((harness) => (\n <HarnessCard key={harness.id} harness={harness} onChoose={() => onSelect(harness.id)} />\n ))}\n </ul>\n );\n}\n\nfunction HarnessCard({harness, onChoose}: {harness: HarnessDescriptor; onChoose: () => void}) {\n return (\n <li>\n <button\n type=\"button\"\n className={cn(\n 'group block w-full cursor-pointer p-16 text-left outline-none transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus',\n SURFACE_CLASS,\n )}\n aria-label={`Choose ${harness.label}`}\n onClick={onChoose}\n >\n <div className=\"flex min-w-0 items-center justify-between gap-12\">\n <div className=\"flex min-w-0 flex-col gap-4\">\n <Text size=\"md\" bold className=\"min-w-0 truncate\">\n {harness.label}\n </Text>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {harness.description}\n </Text>\n </div>\n <div className=\"flex shrink-0 items-center gap-4 text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive\">\n <Text size=\"sm\">Choose</Text>\n <Icon name=\"chevronRight\" className=\"size-16\" />\n </div>\n </div>\n </button>\n </li>\n );\n}\n\nfunction ModelProviderPicker({\n catalogQuery,\n supportedProviders,\n onSelect,\n}: {\n catalogQuery: ReturnType<typeof useModelProviderCatalogQuery>;\n supportedProviders: SupportedProvider[];\n onSelect: (entry: SupportedProvider) => void;\n}) {\n if (catalogQuery.isPending) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Loading model providers\">\n <ModelProviderGridSkeleton />\n </div>\n );\n }\n\n if (catalogQuery.isError && catalogQuery.data === undefined) {\n return <QueryLoadError query={catalogQuery} subject=\"model provider catalog\" />;\n }\n\n if (catalogQuery.data !== undefined && supportedProviders.length === 0) {\n return (\n <EmptyState\n icon=\"componentLine\"\n title=\"No providers available to configure\"\n description=\"Skip for now and add one later from workspace settings.\"\n />\n );\n }\n\n return <AvailableProvidersGrid entries={supportedProviders} onSelect={onSelect} />;\n}\n\nfunction ModelProviderGridSkeleton() {\n return (\n <div className={PROVIDER_GRID_CLASS}>\n {[0, 1, 2, 3].map((card) => (\n <Skeleton key={card} className=\"h-136 w-full\" />\n ))}\n </div>\n );\n}\n"],"names":["QueryLoadError","Button","Callout","EmptyState","Icon","Modal","ModalContent","ModalHeader","ModalTitle","Skeleton","toast","Header","Text","cn","useEffect","useMemo","useReducer","AvailableProvidersGrid","PROVIDER_GRID_CLASS","modelProviderConfigErrorToFormError","ModelProviderTestAndSaveForm","DEFAULT_HARNESS","harnessSupportsProvider","listHarnesses","initialOnboardingState","onboardingReducer","isSupportedProvider","useModelProviderCatalogQuery","useSetDefaultHarnessMutation","dismissModelProviderOnboarding","SURFACE_CLASS","ModelProviderOnboardingPage","workspaceId","onSkip","onConfigured","catalogQuery","setDefaultHarness","onboarding","dispatch","supportedProviders","data","providers","filter","filteredProviders","step","provider","harnessId","id","document","getElementById","focus","handleSkip","handleProviderSaved","type","success","label","mutateAsync","error","mapped","message","selectedHarnessDescriptor","find","harness","headingId","div","className","header","variant","tabIndex","onClick","size","section","aria-labelledby","HarnessPicker","onSelect","ModelProviderPicker","entry","open","onOpenChange","aria-describedby","undefined","aria-hidden","role","bold","setAsDefaultOnSave","onSaved","ul","aria-label","map","HarnessCard","onChoose","li","button","description","name","isPending","aria-busy","ModelProviderGridSkeleton","isError","query","subject","length","icon","title","entries","card"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function modelProviderRouteParams(input: Record<string, unknown>): {
2
+ wid: string;
3
+ };
4
+ //# sourceMappingURL=inputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../src/routes/inputs.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CAItF"}
@@ -0,0 +1,12 @@
1
+ export function modelProviderRouteParams(input) {
2
+ const wid = stringParam(input.wid);
3
+ if (!wid) throw new Error('Model provider route is missing the workspace path parameter.');
4
+ return {
5
+ wid
6
+ };
7
+ }
8
+ function stringParam(value) {
9
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
10
+ }
11
+
12
+ //# sourceMappingURL=inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/inputs.ts"],"sourcesContent":["export function modelProviderRouteParams(input: Record<string, unknown>): {wid: string} {\n const wid = stringParam(input.wid);\n if (!wid) throw new Error('Model provider route is missing the workspace path parameter.');\n return {wid};\n}\n\nfunction stringParam(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n"],"names":["modelProviderRouteParams","input","wid","stringParam","Error","value","length","undefined"],"mappings":"AAAA,OAAO,SAASA,yBAAyBC,KAA8B;IACrE,MAAMC,MAAMC,YAAYF,MAAMC,GAAG;IACjC,IAAI,CAACA,KAAK,MAAM,IAAIE,MAAM;IAC1B,OAAO;QAACF;IAAG;AACb;AAEA,SAASC,YAAYE,KAAc;IACjC,OAAO,OAAOA,UAAU,YAAYA,MAAMC,MAAM,GAAG,IAAID,QAAQE;AACjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"model-provider.d.ts","sourceRoot":"","sources":["../../src/routes/model-provider.tsx"],"names":[],"mappings":";;;AAIA,wBAeG"}
1
+ {"version":3,"file":"model-provider.d.ts","sourceRoot":"","sources":["../../src/routes/model-provider.tsx"],"names":[],"mappings":";;;AAKA,wBAeG"}
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { defineRoute } from '@shipfox/client-shell/runtime';
3
- import { useNavigate, useParams } from '@tanstack/react-router';
2
+ import { defineRoute, useRouteParams } from '@shipfox/client-shell/runtime';
3
+ import { useNavigate } from '@tanstack/react-router';
4
4
  import { ModelProviderOnboardingPage } from '#pages/model-provider-onboarding-page.js';
5
+ import { modelProviderRouteParams } from './inputs.js';
5
6
  export default defineRoute({
6
7
  component: ()=>{
7
- const { wid } = useParams({
8
- strict: false
9
- });
8
+ const { wid } = useRouteParams(modelProviderRouteParams);
10
9
  const navigate = useNavigate();
11
10
  const goToProjectCreation = ()=>{
12
11
  void navigate({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/model-provider.tsx"],"sourcesContent":["import {defineRoute} from '@shipfox/client-shell/runtime';\nimport {useNavigate, useParams} from '@tanstack/react-router';\nimport {ModelProviderOnboardingPage} from '#pages/model-provider-onboarding-page.js';\n\nexport default defineRoute({\n component: () => {\n const {wid} = useParams({strict: false}) as {wid: string};\n const navigate = useNavigate();\n const goToProjectCreation = () => {\n void navigate({to: '/workspaces/$wid/projects/new', params: {wid}, replace: true});\n };\n return (\n <ModelProviderOnboardingPage\n workspaceId={wid}\n onSkip={goToProjectCreation}\n onConfigured={goToProjectCreation}\n />\n );\n },\n});\n"],"names":["defineRoute","useNavigate","useParams","ModelProviderOnboardingPage","component","wid","strict","navigate","goToProjectCreation","to","params","replace","workspaceId","onSkip","onConfigured"],"mappings":";AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,WAAW,EAAEC,SAAS,QAAO,yBAAyB;AAC9D,SAAQC,2BAA2B,QAAO,2CAA2C;AAErF,eAAeH,YAAY;IACzBI,WAAW;QACT,MAAM,EAACC,GAAG,EAAC,GAAGH,UAAU;YAACI,QAAQ;QAAK;QACtC,MAAMC,WAAWN;QACjB,MAAMO,sBAAsB;YAC1B,KAAKD,SAAS;gBAACE,IAAI;gBAAiCC,QAAQ;oBAACL;gBAAG;gBAAGM,SAAS;YAAI;QAClF;QACA,qBACE,KAACR;YACCS,aAAaP;YACbQ,QAAQL;YACRM,cAAcN;;IAGpB;AACF,GAAG"}
1
+ {"version":3,"sources":["../../src/routes/model-provider.tsx"],"sourcesContent":["import {defineRoute, useRouteParams} from '@shipfox/client-shell/runtime';\nimport {useNavigate} from '@tanstack/react-router';\nimport {ModelProviderOnboardingPage} from '#pages/model-provider-onboarding-page.js';\nimport {modelProviderRouteParams} from './inputs.js';\n\nexport default defineRoute({\n component: () => {\n const {wid} = useRouteParams(modelProviderRouteParams);\n const navigate = useNavigate();\n const goToProjectCreation = () => {\n void navigate({to: '/workspaces/$wid/projects/new', params: {wid}, replace: true});\n };\n return (\n <ModelProviderOnboardingPage\n workspaceId={wid}\n onSkip={goToProjectCreation}\n onConfigured={goToProjectCreation}\n />\n );\n },\n});\n"],"names":["defineRoute","useRouteParams","useNavigate","ModelProviderOnboardingPage","modelProviderRouteParams","component","wid","navigate","goToProjectCreation","to","params","replace","workspaceId","onSkip","onConfigured"],"mappings":";AAAA,SAAQA,WAAW,EAAEC,cAAc,QAAO,gCAAgC;AAC1E,SAAQC,WAAW,QAAO,yBAAyB;AACnD,SAAQC,2BAA2B,QAAO,2CAA2C;AACrF,SAAQC,wBAAwB,QAAO,cAAc;AAErD,eAAeJ,YAAY;IACzBK,WAAW;QACT,MAAM,EAACC,GAAG,EAAC,GAAGL,eAAeG;QAC7B,MAAMG,WAAWL;QACjB,MAAMM,sBAAsB;YAC1B,KAAKD,SAAS;gBAACE,IAAI;gBAAiCC,QAAQ;oBAACJ;gBAAG;gBAAGK,SAAS;YAAI;QAClF;QACA,qBACE,KAACR;YACCS,aAAaN;YACbO,QAAQL;YACRM,cAAcN;;IAGpB;AACF,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"model-provider-onboarding.d.ts","sourceRoot":"","sources":["../../src/state/model-provider-onboarding.ts"],"names":[],"mappings":"AAmBA,wBAAgB,kCAAkC,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE/E;AAED,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAIxE"}
1
+ {"version":3,"file":"model-provider-onboarding.d.ts","sourceRoot":"","sources":["../../src/state/model-provider-onboarding.ts"],"names":[],"mappings":"AAgBA,wBAAgB,kCAAkC,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE/E;AAED,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAExE"}
@@ -1,32 +1,23 @@
1
1
  import { createTypedBrowserStorage, localStorageOrUndefined } from '@shipfox/client-ui';
2
- const dismissedStorage = createTypedBrowserStorage(localStorageOrUndefined, {
2
+ const dismissedStorageKey = {
3
3
  key: 'shipfox.modelProviderOnboardingDismissed',
4
4
  lifetime: 'persistent',
5
5
  principalScope: 'workspace',
6
6
  serialize: (dismissed)=>JSON.stringify(dismissed),
7
7
  parse: (raw)=>{
8
- try {
9
- const parsed = JSON.parse(raw);
10
- return isDismissedMap(parsed) ? parsed : undefined;
11
- } catch {
12
- return undefined;
13
- }
8
+ return raw === 'true' ? true : raw === 'false' ? false : undefined;
14
9
  }
15
- });
10
+ };
16
11
  export function isModelProviderOnboardingDismissed(workspaceId) {
17
- return Boolean(readDismissedMap()[workspaceId]);
12
+ return dismissedStorage(workspaceId).read() === true;
18
13
  }
19
14
  export function dismissModelProviderOnboarding(workspaceId) {
20
- const dismissed = readDismissedMap();
21
- dismissed[workspaceId] = true;
22
- dismissedStorage.write(dismissed);
15
+ dismissedStorage(workspaceId).write(true);
23
16
  }
24
- function readDismissedMap() {
25
- return dismissedStorage.read() ?? {};
26
- }
27
- function isDismissedMap(value) {
28
- if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
29
- return Object.values(value).every((entry)=>entry === true);
17
+ function dismissedStorage(workspaceId) {
18
+ return createTypedBrowserStorage(localStorageOrUndefined, dismissedStorageKey, {
19
+ workspaceId
20
+ });
30
21
  }
31
22
 
32
23
  //# sourceMappingURL=model-provider-onboarding.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/state/model-provider-onboarding.ts"],"sourcesContent":["import {createTypedBrowserStorage, localStorageOrUndefined} from '@shipfox/client-ui';\n\ntype DismissedMap = Record<string, true>;\n\nconst dismissedStorage = createTypedBrowserStorage(localStorageOrUndefined, {\n key: 'shipfox.modelProviderOnboardingDismissed',\n lifetime: 'persistent',\n principalScope: 'workspace',\n serialize: (dismissed: DismissedMap) => JSON.stringify(dismissed),\n parse: (raw) => {\n try {\n const parsed: unknown = JSON.parse(raw);\n return isDismissedMap(parsed) ? parsed : undefined;\n } catch {\n return undefined;\n }\n },\n});\n\nexport function isModelProviderOnboardingDismissed(workspaceId: string): boolean {\n return Boolean(readDismissedMap()[workspaceId]);\n}\n\nexport function dismissModelProviderOnboarding(workspaceId: string): void {\n const dismissed = readDismissedMap();\n dismissed[workspaceId] = true;\n dismissedStorage.write(dismissed);\n}\n\nfunction readDismissedMap(): DismissedMap {\n return dismissedStorage.read() ?? {};\n}\n\nfunction isDismissedMap(value: unknown): value is DismissedMap {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n return Object.values(value).every((entry) => entry === true);\n}\n"],"names":["createTypedBrowserStorage","localStorageOrUndefined","dismissedStorage","key","lifetime","principalScope","serialize","dismissed","JSON","stringify","parse","raw","parsed","isDismissedMap","undefined","isModelProviderOnboardingDismissed","workspaceId","Boolean","readDismissedMap","dismissModelProviderOnboarding","write","read","value","Array","isArray","Object","values","every","entry"],"mappings":"AAAA,SAAQA,yBAAyB,EAAEC,uBAAuB,QAAO,qBAAqB;AAItF,MAAMC,mBAAmBF,0BAA0BC,yBAAyB;IAC1EE,KAAK;IACLC,UAAU;IACVC,gBAAgB;IAChBC,WAAW,CAACC,YAA4BC,KAAKC,SAAS,CAACF;IACvDG,OAAO,CAACC;QACN,IAAI;YACF,MAAMC,SAAkBJ,KAAKE,KAAK,CAACC;YACnC,OAAOE,eAAeD,UAAUA,SAASE;QAC3C,EAAE,OAAM;YACN,OAAOA;QACT;IACF;AACF;AAEA,OAAO,SAASC,mCAAmCC,WAAmB;IACpE,OAAOC,QAAQC,kBAAkB,CAACF,YAAY;AAChD;AAEA,OAAO,SAASG,+BAA+BH,WAAmB;IAChE,MAAMT,YAAYW;IAClBX,SAAS,CAACS,YAAY,GAAG;IACzBd,iBAAiBkB,KAAK,CAACb;AACzB;AAEA,SAASW;IACP,OAAOhB,iBAAiBmB,IAAI,MAAM,CAAC;AACrC;AAEA,SAASR,eAAeS,KAAc;IACpC,IAAI,OAAOA,UAAU,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,QAAQ,OAAO;IAChF,OAAOG,OAAOC,MAAM,CAACJ,OAAOK,KAAK,CAAC,CAACC,QAAUA,UAAU;AACzD"}
1
+ {"version":3,"sources":["../../src/state/model-provider-onboarding.ts"],"sourcesContent":["import {\n type BrowserStorageKey,\n createTypedBrowserStorage,\n localStorageOrUndefined,\n} from '@shipfox/client-ui';\n\nconst dismissedStorageKey = {\n key: 'shipfox.modelProviderOnboardingDismissed',\n lifetime: 'persistent',\n principalScope: 'workspace',\n serialize: (dismissed: boolean) => JSON.stringify(dismissed),\n parse: (raw) => {\n return raw === 'true' ? true : raw === 'false' ? false : undefined;\n },\n} satisfies BrowserStorageKey<boolean>;\n\nexport function isModelProviderOnboardingDismissed(workspaceId: string): boolean {\n return dismissedStorage(workspaceId).read() === true;\n}\n\nexport function dismissModelProviderOnboarding(workspaceId: string): void {\n dismissedStorage(workspaceId).write(true);\n}\n\nfunction dismissedStorage(workspaceId: string) {\n return createTypedBrowserStorage(localStorageOrUndefined, dismissedStorageKey, {workspaceId});\n}\n"],"names":["createTypedBrowserStorage","localStorageOrUndefined","dismissedStorageKey","key","lifetime","principalScope","serialize","dismissed","JSON","stringify","parse","raw","undefined","isModelProviderOnboardingDismissed","workspaceId","dismissedStorage","read","dismissModelProviderOnboarding","write"],"mappings":"AAAA,SAEEA,yBAAyB,EACzBC,uBAAuB,QAClB,qBAAqB;AAE5B,MAAMC,sBAAsB;IAC1BC,KAAK;IACLC,UAAU;IACVC,gBAAgB;IAChBC,WAAW,CAACC,YAAuBC,KAAKC,SAAS,CAACF;IAClDG,OAAO,CAACC;QACN,OAAOA,QAAQ,SAAS,OAAOA,QAAQ,UAAU,QAAQC;IAC3D;AACF;AAEA,OAAO,SAASC,mCAAmCC,WAAmB;IACpE,OAAOC,iBAAiBD,aAAaE,IAAI,OAAO;AAClD;AAEA,OAAO,SAASC,+BAA+BH,WAAmB;IAChEC,iBAAiBD,aAAaI,KAAK,CAAC;AACtC;AAEA,SAASH,iBAAiBD,WAAmB;IAC3C,OAAOd,0BAA0BC,yBAAyBC,qBAAqB;QAACY;IAAW;AAC7F"}