@shipfox/client-agent 14.0.1 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +21 -0
- package/dist/components/add-custom-provider-card.js +3 -3
- package/dist/components/add-custom-provider-card.js.map +1 -1
- package/dist/components/available-provider-card.js +3 -3
- package/dist/components/available-provider-card.js.map +1 -1
- package/dist/components/available-providers-grid.d.ts +1 -1
- package/dist/components/available-providers-grid.d.ts.map +1 -1
- package/dist/components/available-providers-grid.js +2 -2
- package/dist/components/available-providers-grid.js.map +1 -1
- package/dist/components/change-default-model-form.js +3 -3
- package/dist/components/change-default-model-form.js.map +1 -1
- package/dist/components/custom-model-provider-form.js +18 -19
- package/dist/components/custom-model-provider-form.js.map +1 -1
- package/dist/components/custom-model-provider-form.stories.d.ts.map +1 -1
- package/dist/components/default-model-field.js +1 -1
- package/dist/components/default-model-field.js.map +1 -1
- package/dist/components/harnesses-section.js +7 -7
- package/dist/components/harnesses-section.js.map +1 -1
- package/dist/components/harnesses-section.stories.d.ts.map +1 -1
- package/dist/components/model-provider-usage-modal.js +8 -8
- package/dist/components/model-provider-usage-modal.js.map +1 -1
- package/dist/components/model-provider-usage-modal.stories.d.ts.map +1 -1
- package/dist/components/model-providers-section.js +20 -20
- package/dist/components/model-providers-section.js.map +1 -1
- package/dist/components/model-providers-section.stories.d.ts.map +1 -1
- package/dist/components/test-and-save-form.js +3 -3
- package/dist/components/test-and-save-form.js.map +1 -1
- package/dist/feature.d.ts +8 -8
- package/dist/feature.d.ts.map +1 -1
- package/dist/hooks/api/model-providers.d.ts +1 -1
- package/dist/hooks/api/model-providers.d.ts.map +1 -1
- package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -1
- package/dist/pages/model-provider-onboarding-page.js +51 -43
- package/dist/pages/model-provider-onboarding-page.js.map +1 -1
- package/dist/pages/model-provider-onboarding-page.stories.d.ts.map +1 -1
- package/dist/routes/agents-settings.d.ts.map +1 -1
- package/dist/routes/agents-settings.js +1 -1
- package/dist/routes/agents-settings.js.map +1 -1
- package/dist/routes/model-provider.d.ts.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/components/add-custom-provider-card.tsx +3 -3
- package/src/components/available-provider-card.tsx +3 -3
- package/src/components/available-providers-grid.tsx +2 -2
- package/src/components/change-default-model-form.tsx +3 -3
- package/src/components/custom-model-provider-form.tsx +23 -20
- package/src/components/default-model-field.tsx +1 -1
- package/src/components/harnesses-section.tsx +7 -7
- package/src/components/model-provider-usage-modal.tsx +8 -8
- package/src/components/model-providers-section.tsx +20 -20
- package/src/components/test-and-save-form.tsx +3 -3
- package/src/pages/model-provider-onboarding-page.tsx +38 -34
- package/src/routes/agents-settings.tsx +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/custom-model-provider-form.tsx"],"sourcesContent":["import {Badge} from '@shipfox/react-ui/badge';\nimport {Button, IconButton} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Collapsible, CollapsibleContent, CollapsibleTrigger} from '@shipfox/react-ui/collapsible';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {ModalBody, ModalFooter} from '@shipfox/react-ui/modal';\nimport {Switch} from '@shipfox/react-ui/switch';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport type {ReactNode, RefObject} from 'react';\nimport {useEffect, useRef, useState} from 'react';\nimport type {CustomProviderConfig, ProviderApi} from '#core/models.js';\nimport {\n useCreateCustomModelProviderMutation,\n useDiscoverCustomModelProviderModelsBySlugMutation,\n useDiscoverCustomModelProviderModelsMutation,\n useModelProviderCatalogQuery,\n useUpdateCustomModelProviderMutation,\n} from '#hooks/api/model-providers.js';\nimport {MODEL_PROVIDER_API_OPTIONS} from './custom-model-provider-api-options.js';\nimport {\n buildCreateCustomModelProviderBody,\n buildDiscoverModelsBody,\n buildDiscoverModelsBySlugBody,\n buildUpdateCustomModelProviderBody,\n type CustomModelProviderFormValues,\n type CustomModelProviderHeaderFormValue,\n type CustomModelProviderModelFormValue,\n createCustomModelProviderFormValues,\n createFormRowId,\n editCustomModelProviderFormValues,\n} from './custom-model-provider-payload.js';\nimport {\n customModelProviderSlugError,\n deriveCustomModelProviderSlug,\n} from './custom-model-provider-slug.js';\nimport {FormFieldSelect} from './default-model-field.js';\nimport {modelProviderConfigErrorField} from './form-errors.js';\n\nexport const CUSTOM_MODEL_PROVIDER_FORM_ID = 'custom-model-provider-form';\n\nexport function CustomModelProviderForm({\n workspaceId,\n existingConfig,\n onSaved,\n}: {\n workspaceId: string;\n existingConfig?: CustomProviderConfig | undefined;\n onSaved: () => void;\n}) {\n const createMutation = useCreateCustomModelProviderMutation();\n const updateMutation = useUpdateCustomModelProviderMutation();\n const discoverMutation = useDiscoverCustomModelProviderModelsMutation();\n const catalogQuery = useModelProviderCatalogQuery();\n const reservedProviderIds = catalogQuery.data?.providers.map((provider) => provider.id) ?? [];\n const discoverBySlugMutation = useDiscoverCustomModelProviderModelsBySlugMutation();\n const [slugEdited, setSlugEdited] = useState(existingConfig !== undefined);\n const [formError, setFormError] = useState<string | undefined>();\n const [discoveryStatus, setDiscoveryStatus] = useState<string | undefined>();\n const [removedDefaultNotice, setRemovedDefaultNotice] = useState(false);\n const mountedRef = useRef(true);\n const form = useForm({\n defaultValues: existingConfig\n ? editCustomModelProviderFormValues(existingConfig)\n : createCustomModelProviderFormValues(),\n onSubmit: async ({value}) => {\n setFormError(undefined);\n\n try {\n if (existingConfig) {\n await updateMutation.mutateAsync({\n workspaceId,\n providerId: existingConfig.providerId,\n command: buildUpdateCustomModelProviderBody(existingConfig, value),\n });\n } else {\n await createMutation.mutateAsync({\n workspaceId,\n command: buildCreateCustomModelProviderBody(value),\n });\n }\n onSaved();\n } catch (error) {\n applyServerError(error);\n }\n },\n });\n\n const isPending =\n createMutation.isPending ||\n updateMutation.isPending ||\n discoverMutation.isPending ||\n discoverBySlugMutation.isPending;\n\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n async function handleDiscoverModels(values: CustomModelProviderFormValues) {\n setDiscoveryStatus(undefined);\n try {\n const response = existingConfig\n ? await discoverBySlugMutation.mutateAsync({\n workspaceId,\n providerId: existingConfig.providerId,\n command: buildDiscoverModelsBySlugBody(existingConfig, values),\n })\n : await discoverMutation.mutateAsync({\n workspaceId,\n command: buildDiscoverModelsBody(values),\n });\n if (!mountedRef.current) return;\n const existingIds = new Set(values.models.map((model) => model.id.trim()).filter(Boolean));\n const discovered = response.filter((model) => !existingIds.has(model.id));\n form.setFieldValue('models', [\n ...values.models,\n ...discovered.map((model) => ({\n client_id: createFormRowId(),\n id: model.id,\n label: model.label,\n context_window: '',\n max_output_tokens: '',\n input_image: false,\n reasoning: false,\n })),\n ]);\n setDiscoveryStatus(\n discovered.length > 0\n ? `Discovered ${discovered.length} models.`\n : 'No models found - that is normal for some servers. Add models manually.',\n );\n } catch (error) {\n if (!mountedRef.current) return;\n applyServerError(error);\n }\n }\n\n function applyServerError(error: unknown) {\n const mapped = modelProviderConfigErrorField(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n return;\n }\n setFormError(mapped.message);\n }\n\n return (\n <>\n <ModalBody className=\"min-h-0 flex-1 gap-0 overflow-y-auto overflow-x-clip scrollbar\">\n <form\n id={CUSTOM_MODEL_PROVIDER_FORM_ID}\n className=\"flex w-full flex-col gap-24\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Subscribe selector={(state) => state.values}>\n {(values) => (\n <>\n <FormGroup title=\"Provider\">\n <div className=\"grid grid-cols-2 gap-12 max-[640px]:grid-cols-1\">\n <form.Field\n name=\"display_name\"\n validators={{\n onBlur: ({value}) =>\n value.trim() ? undefined : 'Display name is required.',\n onSubmit: ({value}) =>\n value.trim() ? undefined : 'Display name is required.',\n }}\n >\n {(field) => (\n <FormField\n label=\"Display name\"\n id=\"custom-provider-display-name\"\n error={fieldError(field)}\n >\n <FormFieldInput\n value={field.state.value}\n onChange={(event) => {\n const displayName = event.target.value;\n field.handleChange(displayName);\n if (!slugEdited && !existingConfig) {\n form.setFieldValue(\n 'slug',\n deriveCustomModelProviderSlug(displayName),\n );\n }\n }}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: ({value}) =>\n existingConfig\n ? undefined\n : customModelProviderSlugError(value, reservedProviderIds),\n onSubmit: ({value}) =>\n existingConfig\n ? undefined\n : customModelProviderSlugError(value, reservedProviderIds),\n }}\n >\n {(field) => (\n <FormField\n label=\"Provider ID\"\n id=\"custom-provider-slug\"\n error={fieldError(field)}\n description=\"Use this ID in workflow files.\"\n >\n <FormFieldInput\n className=\"font-code\"\n value={field.state.value}\n disabled={existingConfig !== undefined}\n onChange={(event) => {\n setSlugEdited(event.target.value.trim() !== '');\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </div>\n\n <div className=\"grid grid-cols-2 gap-12 max-[640px]:grid-cols-1\">\n <form.Field name=\"api\">\n {(field) => (\n <FormField label=\"Protocol\" id=\"custom-provider-api\">\n <FormFieldSelect\n value={field.state.value}\n onChange={(event) =>\n field.handleChange(event.target.value as ProviderApi)\n }\n onBlur={field.handleBlur}\n >\n {MODEL_PROVIDER_API_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </FormFieldSelect>\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"base_url\"\n validators={{\n onBlur: ({value}) =>\n canParseUrl(value)\n ? undefined\n : 'Enter a full URL, like https://api.openai.com/v1',\n onSubmit: ({value}) =>\n canParseUrl(value)\n ? undefined\n : 'Enter a full URL, like https://api.openai.com/v1',\n }}\n >\n {(field) => (\n <FormField\n label=\"Base URL\"\n id=\"custom-provider-base-url\"\n error={fieldError(field)}\n >\n <FormFieldInput\n className=\"font-code\"\n placeholder=\"https://api.openai.com/v1\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </div>\n\n <form.Field name=\"api_key\">\n {(field) => (\n <FormField\n label=\"API key\"\n id=\"custom-provider-api-key\"\n description={\n existingConfig\n ? 'Leave blank to keep the current key.'\n : 'Leave blank when the endpoint does not require a key.'\n }\n >\n <FormFieldInput\n type=\"password\"\n autoComplete=\"off\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </FormGroup>\n\n <FormGroup title=\"Headers\">\n <form.Field name=\"headers\">\n {(field) => (\n <HeaderRows\n rows={field.state.value}\n onChange={(headers) => field.handleChange(headers)}\n />\n )}\n </form.Field>\n </FormGroup>\n\n <FormGroup title=\"Models\">\n <div className=\"flex flex-wrap items-center justify-between gap-8\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconLeft=\"downloadLine\"\n disabled={!canParseUrl(values.base_url)}\n isLoading={discoverMutation.isPending || discoverBySlugMutation.isPending}\n onClick={() => {\n void handleDiscoverModels(values);\n }}\n >\n Fetch models\n </Button>\n {discoveryStatus ? (\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\" role=\"status\">\n {discoveryStatus}\n </Text>\n ) : null}\n </div>\n\n <form.Field\n name=\"models\"\n validators={{\n onSubmit: ({value}) =>\n value.some((model) => model.id.trim() && model.label.trim())\n ? undefined\n : 'Add at least one model.',\n }}\n >\n {(field) => (\n <>\n <ModelRows\n rows={field.state.value}\n defaultModel={values.default_model}\n onDefaultRemoved={() => setRemovedDefaultNotice(true)}\n onChange={(models) => field.handleChange(models)}\n onDefaultChange={(defaultModel) => {\n setRemovedDefaultNotice(false);\n form.setFieldValue('default_model', defaultModel);\n }}\n />\n {fieldError(field) ? (\n <Text size=\"sm\" className=\"text-foreground-highlight-error\">\n {fieldError(field)}\n </Text>\n ) : null}\n </>\n )}\n </form.Field>\n\n {removedDefaultNotice ? (\n <Callout role=\"alert\" type=\"warning\">\n <Text size=\"sm\">\n Default model removed - choose a new default or keep none.\n </Text>\n </Callout>\n ) : null}\n <form.Field\n name=\"default_model\"\n validators={{\n onBlur: ({value}) => validateDefaultModel(value, values.models),\n onSubmit: ({value}) => validateDefaultModel(value, values.models),\n }}\n >\n {(field) => (\n <FormField\n label=\"Default model\"\n id=\"custom-provider-default-model\"\n error={fieldError(field)}\n >\n <FormFieldSelect\n value={field.state.value}\n onChange={(event) => {\n setRemovedDefaultNotice(false);\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n >\n <option value=\"\">No default</option>\n {values.models\n .filter((model) => model.id.trim())\n .map((model) => (\n <option key={model.client_id} value={model.id}>\n {model.label.trim() || model.id}\n </option>\n ))}\n </FormFieldSelect>\n </FormField>\n )}\n </form.Field>\n </FormGroup>\n </>\n )}\n </form.Subscribe>\n </form>\n {formError ? (\n <Callout role=\"alert\" type=\"error\" className=\"mt-16\">\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\" bold>\n Could not save provider\n </Text>\n <Text size=\"sm\">{formError}</Text>\n </div>\n </Callout>\n ) : null}\n </ModalBody>\n <ModalFooter>\n <Text size=\"sm\" className=\"mr-auto text-foreground-neutral-muted\">\n Saving runs a live test against the endpoint.\n </Text>\n <Button type=\"submit\" form={CUSTOM_MODEL_PROVIDER_FORM_ID} isLoading={isPending}>\n Test & save\n </Button>\n </ModalFooter>\n </>\n );\n}\n\nfunction FormGroup({title, children}: {title: string; children: ReactNode}) {\n return (\n <section className=\"flex flex-col gap-16 border-t border-border-neutral-base pt-16 first:border-t-0 first:pt-0\">\n <Text size=\"xs\" className=\"font-medium uppercase text-foreground-neutral-muted\">\n {title}\n </Text>\n {children}\n </section>\n );\n}\n\nfunction HeaderRows({\n rows,\n onChange,\n}: {\n rows: CustomModelProviderHeaderFormValue[];\n onChange: (rows: CustomModelProviderHeaderFormValue[]) => void;\n}) {\n const addButtonRef = useRef<HTMLButtonElement>(null);\n\n return (\n <div className=\"flex flex-col gap-10\">\n {rows.map((row, index) => (\n <div\n key={row.client_id}\n className=\"grid grid-cols-[1fr_1fr_auto_auto] items-end gap-x-8 gap-y-4 max-[760px]:grid-cols-1\"\n >\n <FormField label=\"Name\" id={`custom-header-name-${row.client_id}`}>\n <FormFieldInput\n className=\"font-code\"\n value={row.name}\n onChange={(event) =>\n updateRow(rows, index, {...row, name: event.target.value}, onChange)\n }\n />\n </FormField>\n <FormField label=\"Value\" id={`custom-header-value-${row.client_id}`}>\n <FormFieldInput\n type={row.secret ? 'password' : 'text'}\n placeholder={row.hasStoredValue ? 'Unchanged' : undefined}\n value={row.value}\n onChange={(event) =>\n updateRow(rows, index, {...row, value: event.target.value}, onChange)\n }\n />\n </FormField>\n <div className=\"flex h-32 items-center gap-8 text-sm text-foreground-neutral-muted\">\n <Switch\n size=\"sm\"\n aria-label={`Mark header ${index + 1} as secret`}\n checked={row.secret}\n onCheckedChange={(checked) =>\n updateRow(rows, index, {...row, secret: checked}, onChange)\n }\n />\n Secret\n </div>\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant=\"transparent\"\n icon=\"deleteBinLine\"\n aria-label=\"Remove header\"\n onClick={() => {\n onChange(rows.filter((_, rowIndex) => rowIndex !== index));\n focusAfterRender(addButtonRef);\n }}\n />\n </div>\n ))}\n <Button\n ref={addButtonRef}\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconLeft=\"addLine\"\n onClick={() => {\n const clientId = createFormRowId();\n onChange([\n ...rows,\n {\n client_id: clientId,\n name: '',\n value: '',\n secret: false,\n hasStoredValue: false,\n storedName: '',\n },\n ]);\n focusAfterRender(`custom-header-name-${clientId}`);\n }}\n >\n Add header\n </Button>\n </div>\n );\n}\n\nfunction ModelRows({\n rows,\n defaultModel,\n onChange,\n onDefaultChange,\n onDefaultRemoved,\n}: {\n rows: CustomModelProviderModelFormValue[];\n defaultModel: string;\n onChange: (rows: CustomModelProviderModelFormValue[]) => void;\n onDefaultChange: (value: string) => void;\n onDefaultRemoved: () => void;\n}) {\n const addButtonRef = useRef<HTMLButtonElement>(null);\n\n return (\n <div className=\"flex flex-col gap-10\">\n {rows.map((row, index) => (\n <div key={row.client_id} className=\"rounded-8 border border-border-neutral-base p-12\">\n <div className=\"grid grid-cols-[1fr_1fr_auto] items-end gap-8 max-[760px]:grid-cols-1\">\n <FormField label=\"Model id\" id={`custom-model-id-${row.client_id}`}>\n <FormFieldInput\n className=\"font-code\"\n value={row.id}\n onChange={(event) =>\n updateRow(rows, index, {...row, id: event.target.value}, onChange)\n }\n />\n </FormField>\n <FormField label=\"Label\" id={`custom-model-label-${row.client_id}`}>\n <FormFieldInput\n value={row.label}\n onChange={(event) =>\n updateRow(rows, index, {...row, label: event.target.value}, onChange)\n }\n />\n </FormField>\n <div className=\"flex items-center gap-8\">\n {defaultModel && row.id === defaultModel ? (\n <Badge variant=\"neutral\">Default</Badge>\n ) : null}\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant=\"transparent\"\n icon=\"deleteBinLine\"\n aria-label=\"Remove model\"\n onClick={() => {\n const nextRows = rows.filter((_, rowIndex) => rowIndex !== index);\n onChange(nextRows);\n focusAfterRender(addButtonRef);\n if (row.id === defaultModel) {\n onDefaultChange('');\n onDefaultRemoved();\n }\n }}\n />\n </div>\n </div>\n <Collapsible>\n <CollapsibleTrigger asChild>\n <Button type=\"button\" size=\"sm\" variant=\"transparentMuted\" className=\"mt-8\">\n Defaults: 128k context, 16k output, text-only\n </Button>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"mt-8 grid grid-cols-2 gap-8 max-[640px]:grid-cols-1\">\n <FormField label=\"Context window\" id={`custom-model-context-${row.client_id}`}>\n <FormFieldInput\n type=\"number\"\n inputMode=\"numeric\"\n placeholder=\"128000\"\n value={row.context_window}\n onChange={(event) =>\n updateRow(rows, index, {...row, context_window: event.target.value}, onChange)\n }\n />\n </FormField>\n <FormField label=\"Max output tokens\" id={`custom-model-output-${row.client_id}`}>\n <FormFieldInput\n type=\"number\"\n inputMode=\"numeric\"\n placeholder=\"16384\"\n value={row.max_output_tokens}\n onChange={(event) =>\n updateRow(\n rows,\n index,\n {...row, max_output_tokens: event.target.value},\n onChange,\n )\n }\n />\n </FormField>\n <div className=\"flex h-32 items-center gap-8 text-sm text-foreground-neutral-muted\">\n <Switch\n size=\"sm\"\n aria-label={`Enable image input for model ${index + 1}`}\n checked={row.input_image}\n onCheckedChange={(checked) =>\n updateRow(rows, index, {...row, input_image: checked}, onChange)\n }\n />\n Image input\n </div>\n <div className=\"flex h-32 items-center gap-8 text-sm text-foreground-neutral-muted\">\n <Switch\n size=\"sm\"\n aria-label={`Enable reasoning for model ${index + 1}`}\n checked={row.reasoning}\n onCheckedChange={(checked) =>\n updateRow(rows, index, {...row, reasoning: checked}, onChange)\n }\n />\n Reasoning\n </div>\n </div>\n </CollapsibleContent>\n </Collapsible>\n </div>\n ))}\n <Button\n ref={addButtonRef}\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconLeft=\"addLine\"\n onClick={() => {\n const clientId = createFormRowId();\n onChange([\n ...rows,\n {\n client_id: clientId,\n id: '',\n label: '',\n context_window: '',\n max_output_tokens: '',\n input_image: false,\n reasoning: false,\n },\n ]);\n focusAfterRender(`custom-model-id-${clientId}`);\n }}\n >\n Add model\n </Button>\n </div>\n );\n}\n\nfunction updateRow<T>(rows: T[], index: number, row: T, onChange: (rows: T[]) => void) {\n onChange(rows.map((candidate, rowIndex) => (rowIndex === index ? row : candidate)));\n}\n\nfunction focusAfterRender(target: string | RefObject<HTMLElement | null>): void {\n requestAnimationFrame(() => {\n const element = typeof target === 'string' ? document.getElementById(target) : target.current;\n element?.focus();\n });\n}\n\nfunction validateDefaultModel(\n value: string,\n models: CustomModelProviderModelFormValue[],\n): string | undefined {\n return value.trim() && !models.some((model) => model.id.trim() === value.trim())\n ? 'Choose a model from the models list.'\n : undefined;\n}\n\nfunction canParseUrl(value: string): boolean {\n try {\n new URL(value);\n return true;\n } catch {\n return false;\n }\n}\n"],"names":["Badge","Button","IconButton","Callout","Collapsible","CollapsibleContent","CollapsibleTrigger","FormField","FormFieldInput","fieldError","ModalBody","ModalFooter","Switch","Text","useForm","useEffect","useRef","useState","useCreateCustomModelProviderMutation","useDiscoverCustomModelProviderModelsBySlugMutation","useDiscoverCustomModelProviderModelsMutation","useModelProviderCatalogQuery","useUpdateCustomModelProviderMutation","MODEL_PROVIDER_API_OPTIONS","buildCreateCustomModelProviderBody","buildDiscoverModelsBody","buildDiscoverModelsBySlugBody","buildUpdateCustomModelProviderBody","createCustomModelProviderFormValues","createFormRowId","editCustomModelProviderFormValues","customModelProviderSlugError","deriveCustomModelProviderSlug","FormFieldSelect","modelProviderConfigErrorField","CUSTOM_MODEL_PROVIDER_FORM_ID","CustomModelProviderForm","workspaceId","existingConfig","onSaved","createMutation","updateMutation","discoverMutation","catalogQuery","reservedProviderIds","data","providers","map","provider","id","discoverBySlugMutation","slugEdited","setSlugEdited","undefined","formError","setFormError","discoveryStatus","setDiscoveryStatus","removedDefaultNotice","setRemovedDefaultNotice","mountedRef","form","defaultValues","onSubmit","value","mutateAsync","providerId","command","error","applyServerError","isPending","current","handleDiscoverModels","values","response","existingIds","Set","models","model","trim","filter","Boolean","discovered","has","setFieldValue","client_id","label","context_window","max_output_tokens","input_image","reasoning","length","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","className","noValidate","event","preventDefault","stopPropagation","handleSubmit","Subscribe","selector","state","FormGroup","title","div","Field","name","validators","onBlur","onChange","displayName","target","handleChange","handleBlur","description","disabled","option","canParseUrl","placeholder","type","autoComplete","HeaderRows","rows","headers","size","variant","iconLeft","base_url","isLoading","onClick","role","some","ModelRows","defaultModel","default_model","onDefaultRemoved","onDefaultChange","validateDefaultModel","bold","children","section","addButtonRef","row","index","updateRow","secret","hasStoredValue","aria-label","checked","onCheckedChange","icon","_","rowIndex","focusAfterRender","ref","clientId","storedName","nextRows","asChild","inputMode","candidate","requestAnimationFrame","element","document","getElementById","focus","URL"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/custom-model-provider-form.tsx"],"sourcesContent":["import {Badge} from '@shipfox/react-ui/badge';\nimport {Button, IconButton} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Collapsible, CollapsibleContent, CollapsibleTrigger} from '@shipfox/react-ui/collapsible';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {ModalBody, ModalFooter} from '@shipfox/react-ui/modal';\nimport {Switch} from '@shipfox/react-ui/switch';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport type {ReactNode, RefObject} from 'react';\nimport {useEffect, useRef, useState} from 'react';\nimport type {CustomProviderConfig, ProviderApi} from '#core/models.js';\nimport {\n useCreateCustomModelProviderMutation,\n useDiscoverCustomModelProviderModelsBySlugMutation,\n useDiscoverCustomModelProviderModelsMutation,\n useModelProviderCatalogQuery,\n useUpdateCustomModelProviderMutation,\n} from '#hooks/api/model-providers.js';\nimport {MODEL_PROVIDER_API_OPTIONS} from './custom-model-provider-api-options.js';\nimport {\n buildCreateCustomModelProviderBody,\n buildDiscoverModelsBody,\n buildDiscoverModelsBySlugBody,\n buildUpdateCustomModelProviderBody,\n type CustomModelProviderFormValues,\n type CustomModelProviderHeaderFormValue,\n type CustomModelProviderModelFormValue,\n createCustomModelProviderFormValues,\n createFormRowId,\n editCustomModelProviderFormValues,\n} from './custom-model-provider-payload.js';\nimport {\n customModelProviderSlugError,\n deriveCustomModelProviderSlug,\n} from './custom-model-provider-slug.js';\nimport {FormFieldSelect} from './default-model-field.js';\nimport {modelProviderConfigErrorField} from './form-errors.js';\n\nexport const CUSTOM_MODEL_PROVIDER_FORM_ID = 'custom-model-provider-form';\n\nexport function CustomModelProviderForm({\n workspaceId,\n existingConfig,\n onSaved,\n}: {\n workspaceId: string;\n existingConfig?: CustomProviderConfig | undefined;\n onSaved: () => void;\n}) {\n const createMutation = useCreateCustomModelProviderMutation();\n const updateMutation = useUpdateCustomModelProviderMutation();\n const discoverMutation = useDiscoverCustomModelProviderModelsMutation();\n const catalogQuery = useModelProviderCatalogQuery();\n const reservedProviderIds = catalogQuery.data?.providers.map((provider) => provider.id) ?? [];\n const discoverBySlugMutation = useDiscoverCustomModelProviderModelsBySlugMutation();\n const [slugEdited, setSlugEdited] = useState(existingConfig !== undefined);\n const [formError, setFormError] = useState<string | undefined>();\n const [discoveryStatus, setDiscoveryStatus] = useState<string | undefined>();\n const [removedDefaultNotice, setRemovedDefaultNotice] = useState(false);\n const mountedRef = useRef(true);\n const form = useForm({\n defaultValues: existingConfig\n ? editCustomModelProviderFormValues(existingConfig)\n : createCustomModelProviderFormValues(),\n onSubmit: async ({value}) => {\n setFormError(undefined);\n\n try {\n if (existingConfig) {\n await updateMutation.mutateAsync({\n workspaceId,\n providerId: existingConfig.providerId,\n command: buildUpdateCustomModelProviderBody(existingConfig, value),\n });\n } else {\n await createMutation.mutateAsync({\n workspaceId,\n command: buildCreateCustomModelProviderBody(value),\n });\n }\n onSaved();\n } catch (error) {\n applyServerError(error);\n }\n },\n });\n\n const isPending =\n createMutation.isPending ||\n updateMutation.isPending ||\n discoverMutation.isPending ||\n discoverBySlugMutation.isPending;\n\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n async function handleDiscoverModels(values: CustomModelProviderFormValues) {\n setDiscoveryStatus(undefined);\n try {\n const response = existingConfig\n ? await discoverBySlugMutation.mutateAsync({\n workspaceId,\n providerId: existingConfig.providerId,\n command: buildDiscoverModelsBySlugBody(existingConfig, values),\n })\n : await discoverMutation.mutateAsync({\n workspaceId,\n command: buildDiscoverModelsBody(values),\n });\n if (!mountedRef.current) return;\n const existingIds = new Set(values.models.map((model) => model.id.trim()).filter(Boolean));\n const discovered = response.filter((model) => !existingIds.has(model.id));\n form.setFieldValue('models', [\n ...values.models,\n ...discovered.map((model) => ({\n client_id: createFormRowId(),\n id: model.id,\n label: model.label,\n context_window: '',\n max_output_tokens: '',\n input_image: false,\n reasoning: false,\n })),\n ]);\n setDiscoveryStatus(\n discovered.length > 0\n ? `Discovered ${discovered.length} models.`\n : 'No models found - that is normal for some servers. Add models manually.',\n );\n } catch (error) {\n if (!mountedRef.current) return;\n applyServerError(error);\n }\n }\n\n function applyServerError(error: unknown) {\n const mapped = modelProviderConfigErrorField(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n return;\n }\n setFormError(mapped.message);\n }\n\n return (\n <>\n <ModalBody className=\"min-h-0 flex-1 gap-group overflow-y-auto overflow-x-clip scrollbar\">\n <form\n id={CUSTOM_MODEL_PROVIDER_FORM_ID}\n className=\"flex w-full flex-col gap-section\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Subscribe selector={(state) => state.values}>\n {(values) => (\n <>\n <FormGroup title=\"Provider\">\n <div className=\"grid grid-cols-2 gap-cluster max-[640px]:grid-cols-1\">\n <form.Field\n name=\"display_name\"\n validators={{\n onBlur: ({value}) =>\n value.trim() ? undefined : 'Display name is required.',\n onSubmit: ({value}) =>\n value.trim() ? undefined : 'Display name is required.',\n }}\n >\n {(field) => (\n <FormField\n label=\"Display name\"\n id=\"custom-provider-display-name\"\n error={fieldError(field)}\n >\n <FormFieldInput\n value={field.state.value}\n onChange={(event) => {\n const displayName = event.target.value;\n field.handleChange(displayName);\n if (!slugEdited && !existingConfig) {\n form.setFieldValue(\n 'slug',\n deriveCustomModelProviderSlug(displayName),\n );\n }\n }}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: ({value}) =>\n existingConfig\n ? undefined\n : customModelProviderSlugError(value, reservedProviderIds),\n onSubmit: ({value}) =>\n existingConfig\n ? undefined\n : customModelProviderSlugError(value, reservedProviderIds),\n }}\n >\n {(field) => (\n <FormField\n label=\"Provider ID\"\n id=\"custom-provider-slug\"\n error={fieldError(field)}\n description=\"Use this ID in workflow files.\"\n >\n <FormFieldInput\n className=\"font-code\"\n value={field.state.value}\n disabled={existingConfig !== undefined}\n onChange={(event) => {\n setSlugEdited(event.target.value.trim() !== '');\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </div>\n\n <div className=\"grid grid-cols-2 gap-cluster max-[640px]:grid-cols-1\">\n <form.Field name=\"api\">\n {(field) => (\n <FormField label=\"Protocol\" id=\"custom-provider-api\">\n <FormFieldSelect\n value={field.state.value}\n onChange={(event) =>\n field.handleChange(event.target.value as ProviderApi)\n }\n onBlur={field.handleBlur}\n >\n {MODEL_PROVIDER_API_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </FormFieldSelect>\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"base_url\"\n validators={{\n onBlur: ({value}) =>\n canParseUrl(value)\n ? undefined\n : 'Enter a full URL, like https://api.openai.com/v1',\n onSubmit: ({value}) =>\n canParseUrl(value)\n ? undefined\n : 'Enter a full URL, like https://api.openai.com/v1',\n }}\n >\n {(field) => (\n <FormField\n label=\"Base URL\"\n id=\"custom-provider-base-url\"\n error={fieldError(field)}\n >\n <FormFieldInput\n className=\"font-code\"\n placeholder=\"https://api.openai.com/v1\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </div>\n\n <form.Field name=\"api_key\">\n {(field) => (\n <FormField\n label=\"API key\"\n id=\"custom-provider-api-key\"\n description={\n existingConfig\n ? 'Leave blank to keep the current key.'\n : 'Leave blank when the endpoint does not require a key.'\n }\n >\n <FormFieldInput\n type=\"password\"\n autoComplete=\"off\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </FormGroup>\n\n <FormGroup title=\"Headers\">\n <form.Field name=\"headers\">\n {(field) => (\n <HeaderRows\n rows={field.state.value}\n onChange={(headers) => field.handleChange(headers)}\n />\n )}\n </form.Field>\n </FormGroup>\n\n <FormGroup title=\"Models\">\n <div className=\"flex flex-wrap items-center justify-between gap-inline\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconLeft=\"downloadLine\"\n disabled={!canParseUrl(values.base_url)}\n isLoading={discoverMutation.isPending || discoverBySlugMutation.isPending}\n onClick={() => {\n void handleDiscoverModels(values);\n }}\n >\n Fetch models\n </Button>\n {discoveryStatus ? (\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\" role=\"status\">\n {discoveryStatus}\n </Text>\n ) : null}\n </div>\n\n <form.Field\n name=\"models\"\n validators={{\n onSubmit: ({value}) =>\n value.some((model) => model.id.trim() && model.label.trim())\n ? undefined\n : 'Add at least one model.',\n }}\n >\n {(field) => (\n <>\n <ModelRows\n rows={field.state.value}\n defaultModel={values.default_model}\n onDefaultRemoved={() => setRemovedDefaultNotice(true)}\n onChange={(models) => field.handleChange(models)}\n onDefaultChange={(defaultModel) => {\n setRemovedDefaultNotice(false);\n form.setFieldValue('default_model', defaultModel);\n }}\n />\n {fieldError(field) ? (\n <Text size=\"sm\" className=\"text-foreground-highlight-error\">\n {fieldError(field)}\n </Text>\n ) : null}\n </>\n )}\n </form.Field>\n\n {removedDefaultNotice ? (\n <Callout role=\"alert\" type=\"warning\">\n <Text size=\"sm\">\n Default model removed - choose a new default or keep none.\n </Text>\n </Callout>\n ) : null}\n <form.Field\n name=\"default_model\"\n validators={{\n onBlur: ({value}) => validateDefaultModel(value, values.models),\n onSubmit: ({value}) => validateDefaultModel(value, values.models),\n }}\n >\n {(field) => (\n <FormField\n label=\"Default model\"\n id=\"custom-provider-default-model\"\n error={fieldError(field)}\n >\n <FormFieldSelect\n value={field.state.value}\n onChange={(event) => {\n setRemovedDefaultNotice(false);\n field.handleChange(event.target.value);\n }}\n onBlur={field.handleBlur}\n >\n <option value=\"\">No default</option>\n {values.models\n .filter((model) => model.id.trim())\n .map((model) => (\n <option key={model.client_id} value={model.id}>\n {model.label.trim() || model.id}\n </option>\n ))}\n </FormFieldSelect>\n </FormField>\n )}\n </form.Field>\n </FormGroup>\n </>\n )}\n </form.Subscribe>\n </form>\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-inline\">\n <Text size=\"sm\" bold>\n Could not save provider\n </Text>\n <Text size=\"sm\">{formError}</Text>\n </div>\n </Callout>\n ) : null}\n </ModalBody>\n <ModalFooter>\n <Text size=\"sm\" className=\"mr-auto text-foreground-neutral-muted\">\n Saving runs a live test against the endpoint.\n </Text>\n <Button type=\"submit\" form={CUSTOM_MODEL_PROVIDER_FORM_ID} isLoading={isPending}>\n Test & save\n </Button>\n </ModalFooter>\n </>\n );\n}\n\nfunction FormGroup({title, children}: {title: string; children: ReactNode}) {\n return (\n <section className=\"flex flex-col gap-group border-t border-border-neutral-base pt-[16px] first:border-t-0 first:pt-0\">\n <Text size=\"xs\" className=\"font-medium uppercase text-foreground-neutral-muted\">\n {title}\n </Text>\n {children}\n </section>\n );\n}\n\nfunction HeaderRows({\n rows,\n onChange,\n}: {\n rows: CustomModelProviderHeaderFormValue[];\n onChange: (rows: CustomModelProviderHeaderFormValue[]) => void;\n}) {\n const addButtonRef = useRef<HTMLButtonElement>(null);\n\n return (\n <div className=\"flex flex-col gap-inline\">\n {rows.map((row, index) => (\n <div\n key={row.client_id}\n className=\"grid grid-cols-[1fr_1fr_auto_auto] items-end gap-x-inline gap-y-tight max-[760px]:grid-cols-1\"\n >\n <FormField label=\"Name\" id={`custom-header-name-${row.client_id}`}>\n <FormFieldInput\n className=\"font-code\"\n value={row.name}\n onChange={(event) =>\n updateRow(rows, index, {...row, name: event.target.value}, onChange)\n }\n />\n </FormField>\n <FormField label=\"Value\" id={`custom-header-value-${row.client_id}`}>\n <FormFieldInput\n type={row.secret ? 'password' : 'text'}\n placeholder={row.hasStoredValue ? 'Unchanged' : undefined}\n value={row.value}\n onChange={(event) =>\n updateRow(rows, index, {...row, value: event.target.value}, onChange)\n }\n />\n </FormField>\n <div className=\"flex h-32 items-center gap-inline text-sm text-foreground-neutral-muted\">\n <Switch\n size=\"sm\"\n aria-label={`Mark header ${index + 1} as secret`}\n checked={row.secret}\n onCheckedChange={(checked) =>\n updateRow(rows, index, {...row, secret: checked}, onChange)\n }\n />\n Secret\n </div>\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant=\"transparent\"\n icon=\"deleteBinLine\"\n aria-label=\"Remove header\"\n onClick={() => {\n onChange(rows.filter((_, rowIndex) => rowIndex !== index));\n focusAfterRender(addButtonRef);\n }}\n />\n </div>\n ))}\n <Button\n ref={addButtonRef}\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconLeft=\"addLine\"\n onClick={() => {\n const clientId = createFormRowId();\n onChange([\n ...rows,\n {\n client_id: clientId,\n name: '',\n value: '',\n secret: false,\n hasStoredValue: false,\n storedName: '',\n },\n ]);\n focusAfterRender(`custom-header-name-${clientId}`);\n }}\n >\n Add header\n </Button>\n </div>\n );\n}\n\nfunction ModelRows({\n rows,\n defaultModel,\n onChange,\n onDefaultChange,\n onDefaultRemoved,\n}: {\n rows: CustomModelProviderModelFormValue[];\n defaultModel: string;\n onChange: (rows: CustomModelProviderModelFormValue[]) => void;\n onDefaultChange: (value: string) => void;\n onDefaultRemoved: () => void;\n}) {\n const addButtonRef = useRef<HTMLButtonElement>(null);\n\n return (\n <div className=\"flex flex-col gap-inline\">\n {rows.map((row, index) => (\n <div\n key={row.client_id}\n className=\"flex flex-col gap-inline rounded-8 border border-border-neutral-base p-panel-compact\"\n >\n <div className=\"grid grid-cols-[1fr_1fr_auto] items-end gap-inline max-[760px]:grid-cols-1\">\n <FormField label=\"Model id\" id={`custom-model-id-${row.client_id}`}>\n <FormFieldInput\n className=\"font-code\"\n value={row.id}\n onChange={(event) =>\n updateRow(rows, index, {...row, id: event.target.value}, onChange)\n }\n />\n </FormField>\n <FormField label=\"Label\" id={`custom-model-label-${row.client_id}`}>\n <FormFieldInput\n value={row.label}\n onChange={(event) =>\n updateRow(rows, index, {...row, label: event.target.value}, onChange)\n }\n />\n </FormField>\n <div className=\"flex items-center gap-inline\">\n {defaultModel && row.id === defaultModel ? (\n <Badge variant=\"neutral\">Default</Badge>\n ) : null}\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant=\"transparent\"\n icon=\"deleteBinLine\"\n aria-label=\"Remove model\"\n onClick={() => {\n const nextRows = rows.filter((_, rowIndex) => rowIndex !== index);\n onChange(nextRows);\n focusAfterRender(addButtonRef);\n if (row.id === defaultModel) {\n onDefaultChange('');\n onDefaultRemoved();\n }\n }}\n />\n </div>\n </div>\n <Collapsible className=\"flex flex-col\">\n <CollapsibleTrigger asChild>\n <Button type=\"button\" size=\"sm\" variant=\"transparentMuted\">\n Defaults: 128k context, 16k output, text-only\n </Button>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"grid grid-cols-2 gap-inline pt-[8px] max-[640px]:grid-cols-1\">\n <FormField label=\"Context window\" id={`custom-model-context-${row.client_id}`}>\n <FormFieldInput\n type=\"number\"\n inputMode=\"numeric\"\n placeholder=\"128000\"\n value={row.context_window}\n onChange={(event) =>\n updateRow(rows, index, {...row, context_window: event.target.value}, onChange)\n }\n />\n </FormField>\n <FormField label=\"Max output tokens\" id={`custom-model-output-${row.client_id}`}>\n <FormFieldInput\n type=\"number\"\n inputMode=\"numeric\"\n placeholder=\"16384\"\n value={row.max_output_tokens}\n onChange={(event) =>\n updateRow(\n rows,\n index,\n {...row, max_output_tokens: event.target.value},\n onChange,\n )\n }\n />\n </FormField>\n <div className=\"flex h-32 items-center gap-inline text-sm text-foreground-neutral-muted\">\n <Switch\n size=\"sm\"\n aria-label={`Enable image input for model ${index + 1}`}\n checked={row.input_image}\n onCheckedChange={(checked) =>\n updateRow(rows, index, {...row, input_image: checked}, onChange)\n }\n />\n Image input\n </div>\n <div className=\"flex h-32 items-center gap-inline text-sm text-foreground-neutral-muted\">\n <Switch\n size=\"sm\"\n aria-label={`Enable reasoning for model ${index + 1}`}\n checked={row.reasoning}\n onCheckedChange={(checked) =>\n updateRow(rows, index, {...row, reasoning: checked}, onChange)\n }\n />\n Reasoning\n </div>\n </div>\n </CollapsibleContent>\n </Collapsible>\n </div>\n ))}\n <Button\n ref={addButtonRef}\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconLeft=\"addLine\"\n onClick={() => {\n const clientId = createFormRowId();\n onChange([\n ...rows,\n {\n client_id: clientId,\n id: '',\n label: '',\n context_window: '',\n max_output_tokens: '',\n input_image: false,\n reasoning: false,\n },\n ]);\n focusAfterRender(`custom-model-id-${clientId}`);\n }}\n >\n Add model\n </Button>\n </div>\n );\n}\n\nfunction updateRow<T>(rows: T[], index: number, row: T, onChange: (rows: T[]) => void) {\n onChange(rows.map((candidate, rowIndex) => (rowIndex === index ? row : candidate)));\n}\n\nfunction focusAfterRender(target: string | RefObject<HTMLElement | null>): void {\n requestAnimationFrame(() => {\n const element = typeof target === 'string' ? document.getElementById(target) : target.current;\n element?.focus();\n });\n}\n\nfunction validateDefaultModel(\n value: string,\n models: CustomModelProviderModelFormValue[],\n): string | undefined {\n return value.trim() && !models.some((model) => model.id.trim() === value.trim())\n ? 'Choose a model from the models list.'\n : undefined;\n}\n\nfunction canParseUrl(value: string): boolean {\n try {\n new URL(value);\n return true;\n } catch {\n return false;\n }\n}\n"],"names":["Badge","Button","IconButton","Callout","Collapsible","CollapsibleContent","CollapsibleTrigger","FormField","FormFieldInput","fieldError","ModalBody","ModalFooter","Switch","Text","useForm","useEffect","useRef","useState","useCreateCustomModelProviderMutation","useDiscoverCustomModelProviderModelsBySlugMutation","useDiscoverCustomModelProviderModelsMutation","useModelProviderCatalogQuery","useUpdateCustomModelProviderMutation","MODEL_PROVIDER_API_OPTIONS","buildCreateCustomModelProviderBody","buildDiscoverModelsBody","buildDiscoverModelsBySlugBody","buildUpdateCustomModelProviderBody","createCustomModelProviderFormValues","createFormRowId","editCustomModelProviderFormValues","customModelProviderSlugError","deriveCustomModelProviderSlug","FormFieldSelect","modelProviderConfigErrorField","CUSTOM_MODEL_PROVIDER_FORM_ID","CustomModelProviderForm","workspaceId","existingConfig","onSaved","createMutation","updateMutation","discoverMutation","catalogQuery","reservedProviderIds","data","providers","map","provider","id","discoverBySlugMutation","slugEdited","setSlugEdited","undefined","formError","setFormError","discoveryStatus","setDiscoveryStatus","removedDefaultNotice","setRemovedDefaultNotice","mountedRef","form","defaultValues","onSubmit","value","mutateAsync","providerId","command","error","applyServerError","isPending","current","handleDiscoverModels","values","response","existingIds","Set","models","model","trim","filter","Boolean","discovered","has","setFieldValue","client_id","label","context_window","max_output_tokens","input_image","reasoning","length","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","className","noValidate","event","preventDefault","stopPropagation","handleSubmit","Subscribe","selector","state","FormGroup","title","div","Field","name","validators","onBlur","onChange","displayName","target","handleChange","handleBlur","description","disabled","option","canParseUrl","placeholder","type","autoComplete","HeaderRows","rows","headers","size","variant","iconLeft","base_url","isLoading","onClick","role","some","ModelRows","defaultModel","default_model","onDefaultRemoved","onDefaultChange","validateDefaultModel","bold","children","section","addButtonRef","row","index","updateRow","secret","hasStoredValue","aria-label","checked","onCheckedChange","icon","_","rowIndex","focusAfterRender","ref","clientId","storedName","nextRows","asChild","inputMode","candidate","requestAnimationFrame","element","document","getElementById","focus","URL"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-model-provider-form.stories.d.ts","sourceRoot":"","sources":["../../src/components/custom-model-provider-form.stories.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAMrD,UAAU,iCAAiC;IACzC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;CACzB;AAED,iBAAS,4BAA4B,CAAC,EAAC,IAAI,EAAC,EAAE,iCAAiC,+BAiC9E;AAED,QAAA,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"custom-model-provider-form.stories.d.ts","sourceRoot":"","sources":["../../src/components/custom-model-provider-form.stories.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAMrD,UAAU,iCAAiC;IACzC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;CACzB;AAED,iBAAS,4BAA4B,CAAC,EAAC,IAAI,EAAC,EAAE,iCAAiC,+BAiC9E;AAED,QAAA,MAAM,IAAI;;;;QAGK,MAAM;;;;;CAEgC,CAAC;eAEvC,IAAI;AACnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC"}
|
|
@@ -51,7 +51,7 @@ function defaultModelDescription(entry, selectedModel) {
|
|
|
51
51
|
export function FormFieldSelect({ className, ...props }) {
|
|
52
52
|
const wiring = useFormField();
|
|
53
53
|
return /*#__PURE__*/ _jsx("select", {
|
|
54
|
-
className: cn('w-full min-w-0 rounded-6 bg-background-field-base px-
|
|
54
|
+
className: cn('w-full min-w-0 rounded-6 bg-background-field-base px-tight py-[6px] text-sm leading-20 text-foreground-neutral-base shadow-button-neutral outline-none transition-[color,box-shadow]', 'hover:bg-background-field-hover focus-visible:shadow-border-interactive-with-active disabled:cursor-not-allowed disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled', 'aria-invalid:shadow-border-error', className),
|
|
55
55
|
...props,
|
|
56
56
|
...wiring
|
|
57
57
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/default-model-field.tsx"],"sourcesContent":["import {FormField, useFormField} from '@shipfox/react-ui/form-field';\nimport {cn} from '@shipfox/react-ui/utils';\nimport type {ComponentProps, ReactNode} from 'react';\nimport type {SupportedProvider} from '#core/models.js';\n\nexport const LATEST_MODEL_VALUE = '__latest__';\n\nexport function DefaultModelField({\n entry,\n value,\n error,\n children,\n onChange,\n onBlur,\n}: {\n entry: SupportedProvider;\n value: string;\n error: string | undefined;\n children?: ReactNode;\n onChange: ComponentProps<'select'>['onChange'];\n onBlur: ComponentProps<'select'>['onBlur'];\n}) {\n return (\n <FormField\n label=\"Default model\"\n id={`model-provider-${entry.id}-default-model`}\n error={error}\n description={defaultModelDescription(entry, value)}\n >\n <FormFieldSelect value={value} onChange={onChange} onBlur={onBlur}>\n <option value={LATEST_MODEL_VALUE}>Latest</option>\n {entry.models.map((model) => (\n <option key={model.id} value={model.id}>\n {model.label}\n </option>\n ))}\n </FormFieldSelect>\n {children}\n </FormField>\n );\n}\n\nexport function defaultModelFormValue(defaultModel: string | null | undefined): string {\n return defaultModel ?? LATEST_MODEL_VALUE;\n}\n\nexport function selectedModelForCredentialsPayload(selectedModel: string): string | null {\n return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();\n}\n\nexport function selectedModelForModelPayload(selectedModel: string): string | null {\n return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();\n}\n\nfunction defaultModelLabel(entry: SupportedProvider): string {\n const defaultModel = entry.defaultModel ?? entry.models[0]?.id ?? '';\n return entry.models.find((model) => model.id === defaultModel)?.label ?? defaultModel;\n}\n\nfunction defaultModelDescription(\n entry: SupportedProvider,\n selectedModel: string | undefined,\n): string | undefined {\n if (selectedModel === LATEST_MODEL_VALUE) {\n return `Latest follows the provider catalog default. Currently resolves to ${defaultModelLabel(entry)}.`;\n }\n if (!selectedModel) return undefined;\n return selectedModel;\n}\n\nexport function FormFieldSelect({className, ...props}: ComponentProps<'select'>) {\n const wiring = useFormField();\n return (\n <select\n className={cn(\n 'w-full min-w-0 rounded-6 bg-background-field-base px-
|
|
1
|
+
{"version":3,"sources":["../../src/components/default-model-field.tsx"],"sourcesContent":["import {FormField, useFormField} from '@shipfox/react-ui/form-field';\nimport {cn} from '@shipfox/react-ui/utils';\nimport type {ComponentProps, ReactNode} from 'react';\nimport type {SupportedProvider} from '#core/models.js';\n\nexport const LATEST_MODEL_VALUE = '__latest__';\n\nexport function DefaultModelField({\n entry,\n value,\n error,\n children,\n onChange,\n onBlur,\n}: {\n entry: SupportedProvider;\n value: string;\n error: string | undefined;\n children?: ReactNode;\n onChange: ComponentProps<'select'>['onChange'];\n onBlur: ComponentProps<'select'>['onBlur'];\n}) {\n return (\n <FormField\n label=\"Default model\"\n id={`model-provider-${entry.id}-default-model`}\n error={error}\n description={defaultModelDescription(entry, value)}\n >\n <FormFieldSelect value={value} onChange={onChange} onBlur={onBlur}>\n <option value={LATEST_MODEL_VALUE}>Latest</option>\n {entry.models.map((model) => (\n <option key={model.id} value={model.id}>\n {model.label}\n </option>\n ))}\n </FormFieldSelect>\n {children}\n </FormField>\n );\n}\n\nexport function defaultModelFormValue(defaultModel: string | null | undefined): string {\n return defaultModel ?? LATEST_MODEL_VALUE;\n}\n\nexport function selectedModelForCredentialsPayload(selectedModel: string): string | null {\n return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();\n}\n\nexport function selectedModelForModelPayload(selectedModel: string): string | null {\n return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();\n}\n\nfunction defaultModelLabel(entry: SupportedProvider): string {\n const defaultModel = entry.defaultModel ?? entry.models[0]?.id ?? '';\n return entry.models.find((model) => model.id === defaultModel)?.label ?? defaultModel;\n}\n\nfunction defaultModelDescription(\n entry: SupportedProvider,\n selectedModel: string | undefined,\n): string | undefined {\n if (selectedModel === LATEST_MODEL_VALUE) {\n return `Latest follows the provider catalog default. Currently resolves to ${defaultModelLabel(entry)}.`;\n }\n if (!selectedModel) return undefined;\n return selectedModel;\n}\n\nexport function FormFieldSelect({className, ...props}: ComponentProps<'select'>) {\n const wiring = useFormField();\n return (\n <select\n className={cn(\n 'w-full min-w-0 rounded-6 bg-background-field-base px-tight py-[6px] text-sm leading-20 text-foreground-neutral-base shadow-button-neutral outline-none transition-[color,box-shadow]',\n 'hover:bg-background-field-hover focus-visible:shadow-border-interactive-with-active disabled:cursor-not-allowed disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled',\n 'aria-invalid:shadow-border-error',\n className,\n )}\n {...props}\n {...wiring}\n />\n );\n}\n"],"names":["FormField","useFormField","cn","LATEST_MODEL_VALUE","DefaultModelField","entry","value","error","children","onChange","onBlur","label","id","description","defaultModelDescription","FormFieldSelect","option","models","map","model","defaultModelFormValue","defaultModel","selectedModelForCredentialsPayload","selectedModel","trim","selectedModelForModelPayload","defaultModelLabel","find","undefined","className","props","wiring","select"],"mappings":";AAAA,SAAQA,SAAS,EAAEC,YAAY,QAAO,+BAA+B;AACrE,SAAQC,EAAE,QAAO,0BAA0B;AAI3C,OAAO,MAAMC,qBAAqB,aAAa;AAE/C,OAAO,SAASC,kBAAkB,EAChCC,KAAK,EACLC,KAAK,EACLC,KAAK,EACLC,QAAQ,EACRC,QAAQ,EACRC,MAAM,EAQP;IACC,qBACE,MAACV;QACCW,OAAM;QACNC,IAAI,CAAC,eAAe,EAAEP,MAAMO,EAAE,CAAC,cAAc,CAAC;QAC9CL,OAAOA;QACPM,aAAaC,wBAAwBT,OAAOC;;0BAE5C,MAACS;gBAAgBT,OAAOA;gBAAOG,UAAUA;gBAAUC,QAAQA;;kCACzD,KAACM;wBAAOV,OAAOH;kCAAoB;;oBAClCE,MAAMY,MAAM,CAACC,GAAG,CAAC,CAACC,sBACjB,KAACH;4BAAsBV,OAAOa,MAAMP,EAAE;sCACnCO,MAAMR,KAAK;2BADDQ,MAAMP,EAAE;;;YAKxBJ;;;AAGP;AAEA,OAAO,SAASY,sBAAsBC,YAAuC;IAC3E,OAAOA,gBAAgBlB;AACzB;AAEA,OAAO,SAASmB,mCAAmCC,aAAqB;IACtE,OAAOA,kBAAkBpB,qBAAqB,OAAOoB,cAAcC,IAAI;AACzE;AAEA,OAAO,SAASC,6BAA6BF,aAAqB;IAChE,OAAOA,kBAAkBpB,qBAAqB,OAAOoB,cAAcC,IAAI;AACzE;AAEA,SAASE,kBAAkBrB,KAAwB;IACjD,MAAMgB,eAAehB,MAAMgB,YAAY,IAAIhB,MAAMY,MAAM,CAAC,EAAE,EAAEL,MAAM;IAClE,OAAOP,MAAMY,MAAM,CAACU,IAAI,CAAC,CAACR,QAAUA,MAAMP,EAAE,KAAKS,eAAeV,SAASU;AAC3E;AAEA,SAASP,wBACPT,KAAwB,EACxBkB,aAAiC;IAEjC,IAAIA,kBAAkBpB,oBAAoB;QACxC,OAAO,CAAC,mEAAmE,EAAEuB,kBAAkBrB,OAAO,CAAC,CAAC;IAC1G;IACA,IAAI,CAACkB,eAAe,OAAOK;IAC3B,OAAOL;AACT;AAEA,OAAO,SAASR,gBAAgB,EAACc,SAAS,EAAE,GAAGC,OAAgC;IAC7E,MAAMC,SAAS9B;IACf,qBACE,KAAC+B;QACCH,WAAW3B,GACT,wLACA,qMACA,oCACA2B;QAED,GAAGC,KAAK;QACR,GAAGC,MAAM;;AAGhB"}
|
|
@@ -66,11 +66,11 @@ export function WorkspaceHarnessesSection({ workspaceId }) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
return /*#__PURE__*/ _jsxs("section", {
|
|
69
|
-
className: "flex flex-col gap-
|
|
69
|
+
className: "flex flex-col gap-group",
|
|
70
70
|
"aria-label": "Harnesses",
|
|
71
71
|
children: [
|
|
72
72
|
/*#__PURE__*/ _jsxs("div", {
|
|
73
|
-
className: "flex flex-col gap-
|
|
73
|
+
className: "flex flex-col gap-tight",
|
|
74
74
|
children: [
|
|
75
75
|
/*#__PURE__*/ _jsx(Header, {
|
|
76
76
|
variant: "h3",
|
|
@@ -85,7 +85,7 @@ export function WorkspaceHarnessesSection({ workspaceId }) {
|
|
|
85
85
|
}),
|
|
86
86
|
configsQuery.isPending ? /*#__PURE__*/ _jsx(HarnessRowsSkeleton, {}) : null,
|
|
87
87
|
configsQuery.isError && configsQuery.data === undefined ? /*#__PURE__*/ _jsx("div", {
|
|
88
|
-
className: cn(SURFACE_CLASS, 'px-
|
|
88
|
+
className: cn(SURFACE_CLASS, 'px-row'),
|
|
89
89
|
children: /*#__PURE__*/ _jsx(QueryLoadError, {
|
|
90
90
|
query: configsQuery,
|
|
91
91
|
subject: "harnesses"
|
|
@@ -108,13 +108,13 @@ export function WorkspaceHarnessesSection({ workspaceId }) {
|
|
|
108
108
|
function HarnessRow({ harness, isDefault, isAvailable, isSettingDefault, defaultError, onSetDefault }) {
|
|
109
109
|
const unavailableCopy = harnessUnavailableCopy(isDefault);
|
|
110
110
|
return /*#__PURE__*/ _jsxs("li", {
|
|
111
|
-
className: "flex flex-col gap-
|
|
111
|
+
className: "flex flex-col gap-inline px-row py-row transition-colors hover:bg-background-components-hover",
|
|
112
112
|
children: [
|
|
113
113
|
/*#__PURE__*/ _jsxs("div", {
|
|
114
|
-
className: "flex items-center justify-between gap-
|
|
114
|
+
className: "flex items-center justify-between gap-cluster",
|
|
115
115
|
children: [
|
|
116
116
|
/*#__PURE__*/ _jsxs("div", {
|
|
117
|
-
className: "flex min-w-0 items-center gap-
|
|
117
|
+
className: "flex min-w-0 items-center gap-inline",
|
|
118
118
|
children: [
|
|
119
119
|
isDefault ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
120
120
|
children: [
|
|
@@ -228,7 +228,7 @@ function HarnessRowsSkeleton() {
|
|
|
228
228
|
0,
|
|
229
229
|
1
|
|
230
230
|
].map((row)=>/*#__PURE__*/ _jsxs("li", {
|
|
231
|
-
className: "flex items-center gap-
|
|
231
|
+
className: "flex items-center gap-cluster px-row py-row",
|
|
232
232
|
children: [
|
|
233
233
|
/*#__PURE__*/ _jsx(Skeleton, {
|
|
234
234
|
className: "size-16 shrink-0"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/harnesses-section.tsx"],"sourcesContent":["import {QueryLoadError} from '@shipfox/client-ui';\nimport {IconButton} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useRef, useState} from 'react';\nimport {DEFAULT_HARNESS, listHarnesses} from '#core/harness-policy.js';\nimport type {HarnessDescriptor, HarnessId} from '#core/models.js';\nimport {\n useModelProviderConfigsQuery,\n useSetDefaultHarnessMutation,\n} from '#hooks/api/model-providers.js';\nimport {modelProviderConfigErrorToFormError} from './form-errors.js';\nimport {isHarnessAvailable} from './harness-availability.js';\n\nconst SURFACE_CLASS =\n 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';\n\nexport function WorkspaceHarnessesSection({workspaceId}: {workspaceId: string}) {\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const setDefaultHarness = useSetDefaultHarnessMutation();\n const activeWorkspaceIdRef = useRef(workspaceId);\n const defaultRequestSeqRef = useRef(0);\n const activeDefaultRequestRef = useRef<{workspaceId: string; id: number} | null>(null);\n const [pendingDefaultHarness, setPendingDefaultHarness] = useState<{\n workspaceId: string;\n harnessId: HarnessId;\n } | null>(null);\n const [defaultError, setDefaultError] = useState<\n {workspaceId: string; harnessId: HarnessId; message: string} | undefined\n >();\n const configs = configsQuery.data?.configs ?? [];\n const defaultHarnessId = configsQuery.data?.defaultHarnessId ?? DEFAULT_HARNESS;\n activeWorkspaceIdRef.current = workspaceId;\n\n function isActiveDefaultRequest(request: {workspaceId: string; id: number}) {\n const activeRequest = activeDefaultRequestRef.current;\n return (\n activeWorkspaceIdRef.current === request.workspaceId &&\n activeRequest?.workspaceId === request.workspaceId &&\n activeRequest.id === request.id\n );\n }\n\n async function handleSetDefault(harness: HarnessDescriptor) {\n if (activeDefaultRequestRef.current?.workspaceId === workspaceId) return;\n\n const request = {workspaceId, id: defaultRequestSeqRef.current + 1};\n defaultRequestSeqRef.current = request.id;\n activeDefaultRequestRef.current = request;\n setPendingDefaultHarness({workspaceId, harnessId: harness.id});\n setDefaultError(undefined);\n try {\n await setDefaultHarness.mutateAsync({\n workspaceId,\n harnessId: harness.id,\n });\n if (!isActiveDefaultRequest(request)) return;\n toast.success(`${harness.label} is now the default harness`);\n } catch (error) {\n if (!isActiveDefaultRequest(request)) return;\n const mapped = modelProviderConfigErrorToFormError(error);\n setDefaultError({\n workspaceId,\n harnessId: harness.id,\n message: mapped.message || 'Could not save default harness. Try again.',\n });\n } finally {\n if (isActiveDefaultRequest(request)) {\n activeDefaultRequestRef.current = null;\n setPendingDefaultHarness(null);\n }\n }\n }\n\n return (\n <section className=\"flex flex-col gap-16\" aria-label=\"Harnesses\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Harnesses</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Harnesses available to run agent steps in this workspace.\n </Text>\n </div>\n\n {configsQuery.isPending ? <HarnessRowsSkeleton /> : null}\n\n {configsQuery.isError && configsQuery.data === undefined ? (\n <div className={cn(SURFACE_CLASS, 'px-16')}>\n <QueryLoadError query={configsQuery} subject=\"harnesses\" />\n </div>\n ) : null}\n\n {configsQuery.data !== undefined ? (\n <ul className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}>\n {listHarnesses().map((harness) => (\n <HarnessRow\n key={harness.id}\n harness={harness}\n isDefault={harness.id === defaultHarnessId}\n isAvailable={isHarnessAvailable(harness, configs)}\n isSettingDefault={pendingDefaultHarness?.workspaceId === workspaceId}\n defaultError={\n defaultError?.workspaceId === workspaceId && defaultError.harnessId === harness.id\n ? defaultError.message\n : undefined\n }\n onSetDefault={handleSetDefault}\n />\n ))}\n </ul>\n ) : null}\n </section>\n );\n}\n\nfunction HarnessRow({\n harness,\n isDefault,\n isAvailable,\n isSettingDefault,\n defaultError,\n onSetDefault,\n}: {\n harness: HarnessDescriptor;\n isDefault: boolean;\n isAvailable: boolean;\n isSettingDefault: boolean;\n defaultError: string | undefined;\n onSetDefault: (harness: HarnessDescriptor) => void;\n}) {\n const unavailableCopy = harnessUnavailableCopy(isDefault);\n\n return (\n <li className=\"flex flex-col gap-10 px-16 py-12 transition-colors hover:bg-background-components-hover\">\n <div className=\"flex items-center justify-between gap-12\">\n <div className=\"flex min-w-0 items-center gap-8\">\n {isDefault ? (\n <>\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex size-16 shrink-0 items-center justify-center\">\n <Icon\n name=\"starLine\"\n className=\"size-16 text-foreground-neutral-muted\"\n aria-hidden\n />\n </span>\n </TooltipTrigger>\n <TooltipContent>Default harness</TooltipContent>\n </Tooltip>\n <span className=\"sr-only\">Default harness</span>\n </>\n ) : null}\n <Text size=\"md\" bold className=\"truncate\">\n {harness.label}\n </Text>\n {!isAvailable ? (\n <>\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex size-16 shrink-0 items-center justify-center\">\n <Icon\n name=\"errorWarningLine\"\n className=\"size-16 text-foreground-warning-base\"\n aria-hidden\n />\n </span>\n </TooltipTrigger>\n <TooltipContent>{unavailableCopy}</TooltipContent>\n </Tooltip>\n <span className=\"sr-only\">{unavailableCopy}</span>\n </>\n ) : null}\n </div>\n {!isDefault ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton\n size=\"sm\"\n variant=\"transparent\"\n icon=\"more2Line\"\n disabled={isSettingDefault}\n aria-label={`Open ${harness.label} harness actions`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n icon=\"starLine\"\n disabled={isSettingDefault || !isAvailable}\n onSelect={() => {\n onSetDefault(harness);\n }}\n >\n Set as default\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : null}\n </div>\n {defaultError ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{defaultError}</Text>\n </Callout>\n ) : null}\n </li>\n );\n}\n\nfunction harnessUnavailableCopy(isDefault: boolean): string {\n const base = 'Configure a compatible model provider to use this harness.';\n if (!isDefault) return base;\n return `${base} The stored default harness cannot currently run in this workspace.`;\n}\n\nfunction HarnessRowsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading harnesses\"\n className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}\n >\n {[0, 1].map((row) => (\n <li key={row} className=\"flex items-center gap-12 px-16 py-12\">\n <Skeleton className=\"size-16 shrink-0\" />\n <Skeleton className=\"h-16 w-120\" />\n <Skeleton className=\"ml-auto size-28 shrink-0\" />\n </li>\n ))}\n </ul>\n );\n}\n"],"names":["QueryLoadError","IconButton","Callout","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","Icon","Skeleton","toast","Tooltip","TooltipContent","TooltipTrigger","Header","Text","cn","useRef","useState","DEFAULT_HARNESS","listHarnesses","useModelProviderConfigsQuery","useSetDefaultHarnessMutation","modelProviderConfigErrorToFormError","isHarnessAvailable","SURFACE_CLASS","WorkspaceHarnessesSection","workspaceId","configsQuery","setDefaultHarness","activeWorkspaceIdRef","defaultRequestSeqRef","activeDefaultRequestRef","pendingDefaultHarness","setPendingDefaultHarness","defaultError","setDefaultError","configs","data","defaultHarnessId","current","isActiveDefaultRequest","request","activeRequest","id","handleSetDefault","harness","harnessId","undefined","mutateAsync","success","label","error","mapped","message","section","className","aria-label","div","variant","size","isPending","HarnessRowsSkeleton","isError","query","subject","ul","map","HarnessRow","isDefault","isAvailable","isSettingDefault","onSetDefault","unavailableCopy","harnessUnavailableCopy","li","asChild","span","name","aria-hidden","bold","icon","disabled","align","onSelect","role","type","base","row"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/harnesses-section.tsx"],"sourcesContent":["import {QueryLoadError} from '@shipfox/client-ui';\nimport {IconButton} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useRef, useState} from 'react';\nimport {DEFAULT_HARNESS, listHarnesses} from '#core/harness-policy.js';\nimport type {HarnessDescriptor, HarnessId} from '#core/models.js';\nimport {\n useModelProviderConfigsQuery,\n useSetDefaultHarnessMutation,\n} from '#hooks/api/model-providers.js';\nimport {modelProviderConfigErrorToFormError} from './form-errors.js';\nimport {isHarnessAvailable} from './harness-availability.js';\n\nconst SURFACE_CLASS =\n 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';\n\nexport function WorkspaceHarnessesSection({workspaceId}: {workspaceId: string}) {\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const setDefaultHarness = useSetDefaultHarnessMutation();\n const activeWorkspaceIdRef = useRef(workspaceId);\n const defaultRequestSeqRef = useRef(0);\n const activeDefaultRequestRef = useRef<{workspaceId: string; id: number} | null>(null);\n const [pendingDefaultHarness, setPendingDefaultHarness] = useState<{\n workspaceId: string;\n harnessId: HarnessId;\n } | null>(null);\n const [defaultError, setDefaultError] = useState<\n {workspaceId: string; harnessId: HarnessId; message: string} | undefined\n >();\n const configs = configsQuery.data?.configs ?? [];\n const defaultHarnessId = configsQuery.data?.defaultHarnessId ?? DEFAULT_HARNESS;\n activeWorkspaceIdRef.current = workspaceId;\n\n function isActiveDefaultRequest(request: {workspaceId: string; id: number}) {\n const activeRequest = activeDefaultRequestRef.current;\n return (\n activeWorkspaceIdRef.current === request.workspaceId &&\n activeRequest?.workspaceId === request.workspaceId &&\n activeRequest.id === request.id\n );\n }\n\n async function handleSetDefault(harness: HarnessDescriptor) {\n if (activeDefaultRequestRef.current?.workspaceId === workspaceId) return;\n\n const request = {workspaceId, id: defaultRequestSeqRef.current + 1};\n defaultRequestSeqRef.current = request.id;\n activeDefaultRequestRef.current = request;\n setPendingDefaultHarness({workspaceId, harnessId: harness.id});\n setDefaultError(undefined);\n try {\n await setDefaultHarness.mutateAsync({\n workspaceId,\n harnessId: harness.id,\n });\n if (!isActiveDefaultRequest(request)) return;\n toast.success(`${harness.label} is now the default harness`);\n } catch (error) {\n if (!isActiveDefaultRequest(request)) return;\n const mapped = modelProviderConfigErrorToFormError(error);\n setDefaultError({\n workspaceId,\n harnessId: harness.id,\n message: mapped.message || 'Could not save default harness. Try again.',\n });\n } finally {\n if (isActiveDefaultRequest(request)) {\n activeDefaultRequestRef.current = null;\n setPendingDefaultHarness(null);\n }\n }\n }\n\n return (\n <section className=\"flex flex-col gap-group\" aria-label=\"Harnesses\">\n <div className=\"flex flex-col gap-tight\">\n <Header variant=\"h3\">Harnesses</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Harnesses available to run agent steps in this workspace.\n </Text>\n </div>\n\n {configsQuery.isPending ? <HarnessRowsSkeleton /> : null}\n\n {configsQuery.isError && configsQuery.data === undefined ? (\n <div className={cn(SURFACE_CLASS, 'px-row')}>\n <QueryLoadError query={configsQuery} subject=\"harnesses\" />\n </div>\n ) : null}\n\n {configsQuery.data !== undefined ? (\n <ul className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}>\n {listHarnesses().map((harness) => (\n <HarnessRow\n key={harness.id}\n harness={harness}\n isDefault={harness.id === defaultHarnessId}\n isAvailable={isHarnessAvailable(harness, configs)}\n isSettingDefault={pendingDefaultHarness?.workspaceId === workspaceId}\n defaultError={\n defaultError?.workspaceId === workspaceId && defaultError.harnessId === harness.id\n ? defaultError.message\n : undefined\n }\n onSetDefault={handleSetDefault}\n />\n ))}\n </ul>\n ) : null}\n </section>\n );\n}\n\nfunction HarnessRow({\n harness,\n isDefault,\n isAvailable,\n isSettingDefault,\n defaultError,\n onSetDefault,\n}: {\n harness: HarnessDescriptor;\n isDefault: boolean;\n isAvailable: boolean;\n isSettingDefault: boolean;\n defaultError: string | undefined;\n onSetDefault: (harness: HarnessDescriptor) => void;\n}) {\n const unavailableCopy = harnessUnavailableCopy(isDefault);\n\n return (\n <li className=\"flex flex-col gap-inline px-row py-row transition-colors hover:bg-background-components-hover\">\n <div className=\"flex items-center justify-between gap-cluster\">\n <div className=\"flex min-w-0 items-center gap-inline\">\n {isDefault ? (\n <>\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex size-16 shrink-0 items-center justify-center\">\n <Icon\n name=\"starLine\"\n className=\"size-16 text-foreground-neutral-muted\"\n aria-hidden\n />\n </span>\n </TooltipTrigger>\n <TooltipContent>Default harness</TooltipContent>\n </Tooltip>\n <span className=\"sr-only\">Default harness</span>\n </>\n ) : null}\n <Text size=\"md\" bold className=\"truncate\">\n {harness.label}\n </Text>\n {!isAvailable ? (\n <>\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex size-16 shrink-0 items-center justify-center\">\n <Icon\n name=\"errorWarningLine\"\n className=\"size-16 text-foreground-warning-base\"\n aria-hidden\n />\n </span>\n </TooltipTrigger>\n <TooltipContent>{unavailableCopy}</TooltipContent>\n </Tooltip>\n <span className=\"sr-only\">{unavailableCopy}</span>\n </>\n ) : null}\n </div>\n {!isDefault ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton\n size=\"sm\"\n variant=\"transparent\"\n icon=\"more2Line\"\n disabled={isSettingDefault}\n aria-label={`Open ${harness.label} harness actions`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n icon=\"starLine\"\n disabled={isSettingDefault || !isAvailable}\n onSelect={() => {\n onSetDefault(harness);\n }}\n >\n Set as default\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : null}\n </div>\n {defaultError ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{defaultError}</Text>\n </Callout>\n ) : null}\n </li>\n );\n}\n\nfunction harnessUnavailableCopy(isDefault: boolean): string {\n const base = 'Configure a compatible model provider to use this harness.';\n if (!isDefault) return base;\n return `${base} The stored default harness cannot currently run in this workspace.`;\n}\n\nfunction HarnessRowsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading harnesses\"\n className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}\n >\n {[0, 1].map((row) => (\n <li key={row} className=\"flex items-center gap-cluster px-row py-row\">\n <Skeleton className=\"size-16 shrink-0\" />\n <Skeleton className=\"h-16 w-120\" />\n <Skeleton className=\"ml-auto size-28 shrink-0\" />\n </li>\n ))}\n </ul>\n );\n}\n"],"names":["QueryLoadError","IconButton","Callout","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","Icon","Skeleton","toast","Tooltip","TooltipContent","TooltipTrigger","Header","Text","cn","useRef","useState","DEFAULT_HARNESS","listHarnesses","useModelProviderConfigsQuery","useSetDefaultHarnessMutation","modelProviderConfigErrorToFormError","isHarnessAvailable","SURFACE_CLASS","WorkspaceHarnessesSection","workspaceId","configsQuery","setDefaultHarness","activeWorkspaceIdRef","defaultRequestSeqRef","activeDefaultRequestRef","pendingDefaultHarness","setPendingDefaultHarness","defaultError","setDefaultError","configs","data","defaultHarnessId","current","isActiveDefaultRequest","request","activeRequest","id","handleSetDefault","harness","harnessId","undefined","mutateAsync","success","label","error","mapped","message","section","className","aria-label","div","variant","size","isPending","HarnessRowsSkeleton","isError","query","subject","ul","map","HarnessRow","isDefault","isAvailable","isSettingDefault","onSetDefault","unavailableCopy","harnessUnavailableCopy","li","asChild","span","name","aria-hidden","bold","icon","disabled","align","onSelect","role","type","base","row"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnesses-section.stories.d.ts","sourceRoot":"","sources":["../../src/components/harnesses-section.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAOrD,KAAK,QAAQ,GACT,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,4BAA4B,GAC5B,SAAS,GACT,OAAO,CAAC;AAEZ,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,iBAAS,cAAc,CAAC,EAAC,QAAQ,EAAC,EAAE,mBAAmB,+BAmBtD;AAED,QAAA,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"harnesses-section.stories.d.ts","sourceRoot":"","sources":["../../src/components/harnesses-section.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAOrD,KAAK,QAAQ,GACT,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,4BAA4B,GAC5B,SAAS,GACT,OAAO,CAAC;AAEZ,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,iBAAS,cAAc,CAAC,EAAC,QAAQ,EAAC,EAAE,mBAAmB,+BAmBtD;AAED,QAAA,MAAM,IAAI;;;;QAGK,MAAM;;;;;CAEkB,CAAC;eAEzB,IAAI;AACnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,KAEtC,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC"}
|
|
@@ -81,7 +81,7 @@ export function ModelProviderUsageModal({ target, initialModel, open, onOpenChan
|
|
|
81
81
|
}),
|
|
82
82
|
/*#__PURE__*/ _jsx(ModalHeader, {
|
|
83
83
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
84
|
-
className: "flex min-w-0 flex-col gap-
|
|
84
|
+
className: "flex min-w-0 flex-col gap-tight",
|
|
85
85
|
children: [
|
|
86
86
|
/*#__PURE__*/ _jsx(Text, {
|
|
87
87
|
size: "lg",
|
|
@@ -102,13 +102,13 @@ export function ModelProviderUsageModal({ target, initialModel, open, onOpenChan
|
|
|
102
102
|
/*#__PURE__*/ _jsx(ModalBody, {
|
|
103
103
|
className: "min-h-0 flex-1 gap-0 overflow-y-auto overflow-x-clip scrollbar",
|
|
104
104
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
105
|
-
className: "flex w-full flex-col gap-
|
|
105
|
+
className: "flex w-full flex-col gap-section",
|
|
106
106
|
children: [
|
|
107
107
|
compatibleIds.length === 1 ? /*#__PURE__*/ _jsxs("div", {
|
|
108
|
-
className: "flex flex-col gap-
|
|
108
|
+
className: "flex flex-col gap-cluster",
|
|
109
109
|
children: [
|
|
110
110
|
/*#__PURE__*/ _jsxs("div", {
|
|
111
|
-
className: "flex min-h-40 items-center justify-between gap-
|
|
111
|
+
className: "flex min-h-40 items-center justify-between gap-cluster rounded-8 border border-border-neutral-base px-row py-row",
|
|
112
112
|
children: [
|
|
113
113
|
/*#__PURE__*/ _jsx(Text, {
|
|
114
114
|
size: "sm",
|
|
@@ -136,7 +136,7 @@ export function ModelProviderUsageModal({ target, initialModel, open, onOpenChan
|
|
|
136
136
|
})
|
|
137
137
|
]
|
|
138
138
|
}) : /*#__PURE__*/ _jsxs("div", {
|
|
139
|
-
className: "flex flex-col gap-
|
|
139
|
+
className: "flex flex-col gap-cluster sm:flex-row sm:gap-cluster",
|
|
140
140
|
children: [
|
|
141
141
|
/*#__PURE__*/ _jsx(Combobox, {
|
|
142
142
|
"aria-label": "Harness",
|
|
@@ -165,7 +165,7 @@ export function ModelProviderUsageModal({ target, initialModel, open, onOpenChan
|
|
|
165
165
|
]
|
|
166
166
|
}),
|
|
167
167
|
/*#__PURE__*/ _jsxs("div", {
|
|
168
|
-
className: "flex flex-col gap-
|
|
168
|
+
className: "flex flex-col gap-inline",
|
|
169
169
|
children: [
|
|
170
170
|
/*#__PURE__*/ _jsxs(CodeBlock, {
|
|
171
171
|
data: data,
|
|
@@ -213,7 +213,7 @@ export function ModelProviderUsageModal({ target, initialModel, open, onOpenChan
|
|
|
213
213
|
]
|
|
214
214
|
}),
|
|
215
215
|
/*#__PURE__*/ _jsxs("div", {
|
|
216
|
-
className: "flex flex-col gap-
|
|
216
|
+
className: "flex flex-col gap-inline",
|
|
217
217
|
children: [
|
|
218
218
|
/*#__PURE__*/ _jsxs(Text, {
|
|
219
219
|
size: "sm",
|
|
@@ -296,7 +296,7 @@ function ModelProviderModelRow({ label, id }) {
|
|
|
296
296
|
children: /*#__PURE__*/ _jsxs("button", {
|
|
297
297
|
type: "button",
|
|
298
298
|
"aria-label": `Copy ${label} model id ${id}`,
|
|
299
|
-
className: "flex min-h-40 w-full min-w-0 flex-col items-start gap-
|
|
299
|
+
className: "flex min-h-40 w-full min-w-0 flex-col items-start gap-tight px-row py-row text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none sm:flex-row sm:items-center sm:gap-inline",
|
|
300
300
|
onClick: ()=>{
|
|
301
301
|
void handleCopy();
|
|
302
302
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/model-provider-usage-modal.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {\n CodeBlock,\n CodeBlockBody,\n CodeBlockContent,\n CodeBlockCopyButton,\n CodeBlockFilename,\n CodeBlockFiles,\n CodeBlockHeader,\n CodeBlockItem,\n} from '@shipfox/react-ui/code-block';\nimport {Combobox} from '@shipfox/react-ui/combobox';\nimport {useCopyToClipboard} from '@shipfox/react-ui/hooks';\nimport {\n Modal,\n ModalBody,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalTitle,\n} from '@shipfox/react-ui/modal';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {useEffect, useMemo, useRef, useState} from 'react';\nimport {DEFAULT_HARNESS, getHarness, listHarnesses} from '#core/harness-policy.js';\nimport type {HarnessId} from '#core/models.js';\nimport {buildAgentWorkflowExample} from './agent-workflow-example.js';\nimport {compatibleHarnessIds} from './harness-availability.js';\nimport type {ModelProviderUsageTarget} from './model-provider-usage-target.js';\n\ntype CopyState = 'idle' | 'copied' | 'failed';\n\nconst WORKFLOW_EXAMPLE_FILENAME = '.shipfox/workflows/agent.yml';\n\nexport function ModelProviderUsageModal({\n target,\n initialModel,\n open,\n onOpenChange,\n closeFocusTarget,\n workspaceDefaultHarnessId,\n}: {\n target: ModelProviderUsageTarget | null;\n initialModel?: string | null | undefined;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n closeFocusTarget?: HTMLElement | null | undefined;\n workspaceDefaultHarnessId?: HarnessId | null | undefined;\n}) {\n const [selectedModel, setSelectedModel] = useState('');\n const [selectedHarness, setSelectedHarness] = useState<HarnessId>(DEFAULT_HARNESS);\n\n useEffect(() => {\n if (!target) return;\n setSelectedModel(initialModel ?? target.defaultModel ?? target.models[0]?.id ?? '');\n }, [target, initialModel]);\n\n useEffect(() => {\n if (!target) return;\n const nextCompatibleHarnessIds = compatibleHarnessIds({\n isCustom: target.isCustom,\n providerId: target.id,\n });\n setSelectedHarness(selectInitialHarness(nextCompatibleHarnessIds, workspaceDefaultHarnessId));\n }, [target, workspaceDefaultHarnessId]);\n\n const compatibleIds = useMemo(\n () =>\n target\n ? compatibleHarnessIds({isCustom: target.isCustom, providerId: target.id})\n : ([] as HarnessId[]),\n [target],\n );\n const harnessOptions = useMemo(\n () =>\n compatibleIds.map((harnessId) => ({\n value: harnessId,\n label: getHarness(harnessId).label,\n })),\n [compatibleIds],\n );\n const modelOptions = useMemo(\n () => target?.models.map((model) => ({value: model.id, label: model.label})) ?? [],\n [target],\n );\n const example = target\n ? buildAgentWorkflowExample({\n harness: selectedHarness,\n providerId: target.id,\n model: selectedModel,\n })\n : null;\n const data =\n example === null\n ? []\n : [{language: 'yaml', filename: WORKFLOW_EXAMPLE_FILENAME, code: example.code}];\n\n function handleOpenChange(nextOpen: boolean) {\n onOpenChange(nextOpen);\n if (!nextOpen && closeFocusTarget) {\n window.setTimeout(() => closeFocusTarget.focus(), 0);\n }\n }\n\n return (\n <Modal open={open && target !== null} onOpenChange={handleOpenChange}>\n <ModalContent aria-describedby={undefined} className=\"max-h-[calc(100vh-32px)] max-w-[640px]\">\n <ModalTitle className=\"sr-only\">\n {target ? `Use ${target.label} in a workflow` : 'Use provider in a workflow'}\n </ModalTitle>\n <ModalHeader>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <Text size=\"lg\" aria-hidden=\"true\" className=\"truncate\">\n {target ? `Use ${target.label} in a workflow` : 'Use provider in a workflow'}\n </Text>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Reference this provider from a workflow agent step.\n </Text>\n </div>\n </ModalHeader>\n {target && example ? (\n <>\n <ModalBody className=\"min-h-0 flex-1 gap-0 overflow-y-auto overflow-x-clip scrollbar\">\n <div className=\"flex w-full flex-col gap-20\">\n {compatibleIds.length === 1 ? (\n <div className=\"flex flex-col gap-12\">\n <div className=\"flex min-h-40 items-center justify-between gap-12 rounded-8 border border-border-neutral-base px-12 py-8\">\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Harness\n </Text>\n <Code as=\"span\" variant=\"label\">\n {compatibleIds[0]}\n </Code>\n </div>\n <Combobox\n aria-label=\"Model\"\n options={modelOptions}\n value={selectedModel}\n onValueChange={(value) => {\n if (value) setSelectedModel(value);\n }}\n placeholder=\"Select model...\"\n searchPlaceholder=\"Search models...\"\n emptyState=\"No model found.\"\n className=\"w-full\"\n />\n </div>\n ) : (\n <div className=\"flex flex-col gap-12 sm:flex-row sm:gap-12\">\n <Combobox\n aria-label=\"Harness\"\n options={harnessOptions}\n value={selectedHarness}\n onValueChange={(value) => {\n if (isHarness(value)) setSelectedHarness(value);\n }}\n placeholder=\"Select harness...\"\n searchPlaceholder=\"Search harnesses...\"\n emptyState=\"No harness found.\"\n className=\"w-full sm:flex-1\"\n />\n <Combobox\n aria-label=\"Model\"\n options={modelOptions}\n value={selectedModel}\n onValueChange={(value) => {\n if (value) setSelectedModel(value);\n }}\n placeholder=\"Select model...\"\n searchPlaceholder=\"Search models...\"\n emptyState=\"No model found.\"\n className=\"w-full sm:flex-1\"\n />\n </div>\n )}\n\n <div className=\"flex flex-col gap-8\">\n <CodeBlock data={data} className=\"h-auto min-h-0 rounded-8\">\n <CodeBlockHeader>\n <CodeBlockFiles>\n {(item) => (\n <CodeBlockFilename value={item.filename}>\n {item.filename}\n </CodeBlockFilename>\n )}\n </CodeBlockFiles>\n <CodeBlockCopyButton />\n </CodeBlockHeader>\n <CodeBlockBody>\n {(item) => (\n <CodeBlockItem value={item.filename}>\n <CodeBlockContent\n language=\"yaml\"\n syntaxHighlighting\n highlightedLineRange={example.highlightedLineRange}\n >\n {item.code}\n </CodeBlockContent>\n </CodeBlockItem>\n )}\n </CodeBlockBody>\n </CodeBlock>\n\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Add this to a workflow file under{' '}\n <Code as=\"span\" variant=\"label\">\n .shipfox/workflows/\n </Code>{' '}\n in your repository to run it.\n </Text>\n </div>\n\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\" bold>\n Available models ({target.models.length})\n </Text>\n <ul className=\"rounded-8 border border-border-neutral-base\">\n {target.models.map((model) => (\n <ModelProviderModelRow key={model.id} label={model.label} id={model.id} />\n ))}\n </ul>\n </div>\n </div>\n </ModalBody>\n <ModalFooter>\n <Button type=\"button\" onClick={() => handleOpenChange(false)}>\n Done\n </Button>\n </ModalFooter>\n </>\n ) : null}\n </ModalContent>\n </Modal>\n );\n}\n\nfunction selectInitialHarness(\n compatibleIds: HarnessId[],\n workspaceDefaultHarnessId: HarnessId | null | undefined,\n): HarnessId {\n if (workspaceDefaultHarnessId && compatibleIds.includes(workspaceDefaultHarnessId)) {\n return workspaceDefaultHarnessId;\n }\n if (compatibleIds.includes(DEFAULT_HARNESS)) return DEFAULT_HARNESS;\n return compatibleIds[0] ?? DEFAULT_HARNESS;\n}\n\nfunction isHarness(value: string): value is HarnessId {\n return listHarnesses().some((descriptor) => descriptor.id === value);\n}\n\nfunction ModelProviderModelRow({label, id}: {label: string; id: string}) {\n const [copyState, setCopyState] = useState<CopyState>('idle');\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const {copy} = useCopyToClipboard({\n text: id,\n onCopy: () => {\n setTemporaryCopyState('copied');\n },\n });\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n };\n }, []);\n\n function setTemporaryCopyState(state: Exclude<CopyState, 'idle'>) {\n setCopyState(state);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n timeoutRef.current = setTimeout(() => setCopyState('idle'), 2500);\n }\n\n async function handleCopy() {\n try {\n await copy();\n } catch {\n setTemporaryCopyState('failed');\n }\n }\n\n return (\n <li className=\"border-b border-border-neutral-base last:border-b-0\">\n <Tooltip open={copyState === 'copied'}>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={`Copy ${label} model id ${id}`}\n className=\"flex min-h-40 w-full min-w-0 flex-col items-start gap-2 px-12 py-8 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none sm:flex-row sm:items-center sm:gap-8\"\n onClick={() => {\n void handleCopy();\n }}\n >\n <Text as=\"span\" size=\"sm\" bold className=\"max-w-full shrink-0 truncate sm:max-w-[48%]\">\n {label}\n </Text>\n <Code\n as=\"span\"\n variant=\"label\"\n className=\"min-w-0 max-w-full truncate text-left text-foreground-neutral-muted sm:flex-1 sm:text-right\"\n >\n {id}\n </Code>\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"top\" align=\"center\">\n Copied\n </TooltipContent>\n </Tooltip>\n {copyState === 'copied' ? (\n <span className=\"sr-only\" role=\"status\">\n Copied {label} id\n </span>\n ) : null}\n {copyState === 'failed' ? (\n <span className=\"sr-only\" role=\"status\">\n Could not copy {label} id\n </span>\n ) : null}\n </li>\n );\n}\n"],"names":["Button","CodeBlock","CodeBlockBody","CodeBlockContent","CodeBlockCopyButton","CodeBlockFilename","CodeBlockFiles","CodeBlockHeader","CodeBlockItem","Combobox","useCopyToClipboard","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","Tooltip","TooltipContent","TooltipTrigger","Code","Text","useEffect","useMemo","useRef","useState","DEFAULT_HARNESS","getHarness","listHarnesses","buildAgentWorkflowExample","compatibleHarnessIds","WORKFLOW_EXAMPLE_FILENAME","ModelProviderUsageModal","target","initialModel","open","onOpenChange","closeFocusTarget","workspaceDefaultHarnessId","selectedModel","setSelectedModel","selectedHarness","setSelectedHarness","defaultModel","models","id","nextCompatibleHarnessIds","isCustom","providerId","selectInitialHarness","compatibleIds","harnessOptions","map","harnessId","value","label","modelOptions","model","example","harness","data","language","filename","code","handleOpenChange","nextOpen","window","setTimeout","focus","aria-describedby","undefined","className","div","size","aria-hidden","length","as","variant","aria-label","options","onValueChange","placeholder","searchPlaceholder","emptyState","isHarness","item","syntaxHighlighting","highlightedLineRange","bold","ul","ModelProviderModelRow","type","onClick","includes","some","descriptor","copyState","setCopyState","timeoutRef","copy","text","onCopy","setTemporaryCopyState","current","clearTimeout","state","handleCopy","li","asChild","button","side","align","span","role"],"mappings":";AAAA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SACEC,SAAS,EACTC,aAAa,EACbC,gBAAgB,EAChBC,mBAAmB,EACnBC,iBAAiB,EACjBC,cAAc,EACdC,eAAe,EACfC,aAAa,QACR,+BAA+B;AACtC,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,kBAAkB,QAAO,0BAA0B;AAC3D,SACEC,KAAK,EACLC,SAAS,EACTC,YAAY,EACZC,WAAW,EACXC,WAAW,EACXC,UAAU,QACL,0BAA0B;AACjC,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AACxD,SAAQC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAC3D,SAAQC,eAAe,EAAEC,UAAU,EAAEC,aAAa,QAAO,0BAA0B;AAEnF,SAAQC,yBAAyB,QAAO,8BAA8B;AACtE,SAAQC,oBAAoB,QAAO,4BAA4B;AAK/D,MAAMC,4BAA4B;AAElC,OAAO,SAASC,wBAAwB,EACtCC,MAAM,EACNC,YAAY,EACZC,IAAI,EACJC,YAAY,EACZC,gBAAgB,EAChBC,yBAAyB,EAQ1B;IACC,MAAM,CAACC,eAAeC,iBAAiB,GAAGf,SAAS;IACnD,MAAM,CAACgB,iBAAiBC,mBAAmB,GAAGjB,SAAoBC;IAElEJ,UAAU;QACR,IAAI,CAACW,QAAQ;QACbO,iBAAiBN,gBAAgBD,OAAOU,YAAY,IAAIV,OAAOW,MAAM,CAAC,EAAE,EAAEC,MAAM;IAClF,GAAG;QAACZ;QAAQC;KAAa;IAEzBZ,UAAU;QACR,IAAI,CAACW,QAAQ;QACb,MAAMa,2BAA2BhB,qBAAqB;YACpDiB,UAAUd,OAAOc,QAAQ;YACzBC,YAAYf,OAAOY,EAAE;QACvB;QACAH,mBAAmBO,qBAAqBH,0BAA0BR;IACpE,GAAG;QAACL;QAAQK;KAA0B;IAEtC,MAAMY,gBAAgB3B,QACpB,IACEU,SACIH,qBAAqB;YAACiB,UAAUd,OAAOc,QAAQ;YAAEC,YAAYf,OAAOY,EAAE;QAAA,KACrE,EAAE,EACT;QAACZ;KAAO;IAEV,MAAMkB,iBAAiB5B,QACrB,IACE2B,cAAcE,GAAG,CAAC,CAACC,YAAe,CAAA;gBAChCC,OAAOD;gBACPE,OAAO5B,WAAW0B,WAAWE,KAAK;YACpC,CAAA,IACF;QAACL;KAAc;IAEjB,MAAMM,eAAejC,QACnB,IAAMU,QAAQW,OAAOQ,IAAI,CAACK,QAAW,CAAA;gBAACH,OAAOG,MAAMZ,EAAE;gBAAEU,OAAOE,MAAMF,KAAK;YAAA,CAAA,MAAO,EAAE,EAClF;QAACtB;KAAO;IAEV,MAAMyB,UAAUzB,SACZJ,0BAA0B;QACxB8B,SAASlB;QACTO,YAAYf,OAAOY,EAAE;QACrBY,OAAOlB;IACT,KACA;IACJ,MAAMqB,OACJF,YAAY,OACR,EAAE,GACF;QAAC;YAACG,UAAU;YAAQC,UAAU/B;YAA2BgC,MAAML,QAAQK,IAAI;QAAA;KAAE;IAEnF,SAASC,iBAAiBC,QAAiB;QACzC7B,aAAa6B;QACb,IAAI,CAACA,YAAY5B,kBAAkB;YACjC6B,OAAOC,UAAU,CAAC,IAAM9B,iBAAiB+B,KAAK,IAAI;QACpD;IACF;IAEA,qBACE,KAACzD;QAAMwB,MAAMA,QAAQF,WAAW;QAAMG,cAAc4B;kBAClD,cAAA,MAACnD;YAAawD,oBAAkBC;YAAWC,WAAU;;8BACnD,KAACvD;oBAAWuD,WAAU;8BACnBtC,SAAS,CAAC,IAAI,EAAEA,OAAOsB,KAAK,CAAC,cAAc,CAAC,GAAG;;8BAElD,KAACxC;8BACC,cAAA,MAACyD;wBAAID,WAAU;;0CACb,KAAClD;gCAAKoD,MAAK;gCAAKC,eAAY;gCAAOH,WAAU;0CAC1CtC,SAAS,CAAC,IAAI,EAAEA,OAAOsB,KAAK,CAAC,cAAc,CAAC,GAAG;;0CAElD,KAAClC;gCAAKoD,MAAK;gCAAKF,WAAU;0CAAgC;;;;;gBAK7DtC,UAAUyB,wBACT;;sCACE,KAAC9C;4BAAU2D,WAAU;sCACnB,cAAA,MAACC;gCAAID,WAAU;;oCACZrB,cAAcyB,MAAM,KAAK,kBACxB,MAACH;wCAAID,WAAU;;0DACb,MAACC;gDAAID,WAAU;;kEACb,KAAClD;wDAAKoD,MAAK;wDAAKF,WAAU;kEAAgC;;kEAG1D,KAACnD;wDAAKwD,IAAG;wDAAOC,SAAQ;kEACrB3B,aAAa,CAAC,EAAE;;;;0DAGrB,KAACzC;gDACCqE,cAAW;gDACXC,SAASvB;gDACTF,OAAOf;gDACPyC,eAAe,CAAC1B;oDACd,IAAIA,OAAOd,iBAAiBc;gDAC9B;gDACA2B,aAAY;gDACZC,mBAAkB;gDAClBC,YAAW;gDACXZ,WAAU;;;uDAId,MAACC;wCAAID,WAAU;;0DACb,KAAC9D;gDACCqE,cAAW;gDACXC,SAAS5B;gDACTG,OAAOb;gDACPuC,eAAe,CAAC1B;oDACd,IAAI8B,UAAU9B,QAAQZ,mBAAmBY;gDAC3C;gDACA2B,aAAY;gDACZC,mBAAkB;gDAClBC,YAAW;gDACXZ,WAAU;;0DAEZ,KAAC9D;gDACCqE,cAAW;gDACXC,SAASvB;gDACTF,OAAOf;gDACPyC,eAAe,CAAC1B;oDACd,IAAIA,OAAOd,iBAAiBc;gDAC9B;gDACA2B,aAAY;gDACZC,mBAAkB;gDAClBC,YAAW;gDACXZ,WAAU;;;;kDAKhB,MAACC;wCAAID,WAAU;;0DACb,MAACtE;gDAAU2D,MAAMA;gDAAMW,WAAU;;kEAC/B,MAAChE;;0EACC,KAACD;0EACE,CAAC+E,qBACA,KAAChF;wEAAkBiD,OAAO+B,KAAKvB,QAAQ;kFACpCuB,KAAKvB,QAAQ;;;0EAIpB,KAAC1D;;;kEAEH,KAACF;kEACE,CAACmF,qBACA,KAAC7E;gEAAc8C,OAAO+B,KAAKvB,QAAQ;0EACjC,cAAA,KAAC3D;oEACC0D,UAAS;oEACTyB,kBAAkB;oEAClBC,sBAAsB7B,QAAQ6B,oBAAoB;8EAEjDF,KAAKtB,IAAI;;;;;;0DAOpB,MAAC1C;gDAAKoD,MAAK;gDAAKF,WAAU;;oDAAgC;oDACtB;kEAClC,KAACnD;wDAAKwD,IAAG;wDAAOC,SAAQ;kEAAQ;;oDAExB;oDAAI;;;;;kDAKhB,MAACL;wCAAID,WAAU;;0DACb,MAAClD;gDAAKoD,MAAK;gDAAKe,IAAI;;oDAAC;oDACAvD,OAAOW,MAAM,CAAC+B,MAAM;oDAAC;;;0DAE1C,KAACc;gDAAGlB,WAAU;0DACXtC,OAAOW,MAAM,CAACQ,GAAG,CAAC,CAACK,sBAClB,KAACiC;wDAAqCnC,OAAOE,MAAMF,KAAK;wDAAEV,IAAIY,MAAMZ,EAAE;uDAA1CY,MAAMZ,EAAE;;;;;;;sCAM9C,KAAC/B;sCACC,cAAA,KAACd;gCAAO2F,MAAK;gCAASC,SAAS,IAAM5B,iBAAiB;0CAAQ;;;;qBAKhE;;;;AAIZ;AAEA,SAASf,qBACPC,aAA0B,EAC1BZ,yBAAuD;IAEvD,IAAIA,6BAA6BY,cAAc2C,QAAQ,CAACvD,4BAA4B;QAClF,OAAOA;IACT;IACA,IAAIY,cAAc2C,QAAQ,CAACnE,kBAAkB,OAAOA;IACpD,OAAOwB,aAAa,CAAC,EAAE,IAAIxB;AAC7B;AAEA,SAAS0D,UAAU9B,KAAa;IAC9B,OAAO1B,gBAAgBkE,IAAI,CAAC,CAACC,aAAeA,WAAWlD,EAAE,KAAKS;AAChE;AAEA,SAASoC,sBAAsB,EAACnC,KAAK,EAAEV,EAAE,EAA8B;IACrE,MAAM,CAACmD,WAAWC,aAAa,GAAGxE,SAAoB;IACtD,MAAMyE,aAAa1E,OAAkD8C;IACrE,MAAM,EAAC6B,IAAI,EAAC,GAAGzF,mBAAmB;QAChC0F,MAAMvD;QACNwD,QAAQ;YACNC,sBAAsB;QACxB;IACF;IAEAhF,UAAU;QACR,OAAO;YACL,IAAI4E,WAAWK,OAAO,EAAEC,aAAaN,WAAWK,OAAO;QACzD;IACF,GAAG,EAAE;IAEL,SAASD,sBAAsBG,KAAiC;QAC9DR,aAAaQ;QACb,IAAIP,WAAWK,OAAO,EAAEC,aAAaN,WAAWK,OAAO;QACvDL,WAAWK,OAAO,GAAGpC,WAAW,IAAM8B,aAAa,SAAS;IAC9D;IAEA,eAAeS;QACb,IAAI;YACF,MAAMP;QACR,EAAE,OAAM;YACNG,sBAAsB;QACxB;IACF;IAEA,qBACE,MAACK;QAAGpC,WAAU;;0BACZ,MAACtD;gBAAQkB,MAAM6D,cAAc;;kCAC3B,KAAC7E;wBAAeyF,OAAO;kCACrB,cAAA,MAACC;4BACClB,MAAK;4BACLb,cAAY,CAAC,KAAK,EAAEvB,MAAM,UAAU,EAAEV,IAAI;4BAC1C0B,WAAU;4BACVqB,SAAS;gCACP,KAAKc;4BACP;;8CAEA,KAACrF;oCAAKuD,IAAG;oCAAOH,MAAK;oCAAKe,IAAI;oCAACjB,WAAU;8CACtChB;;8CAEH,KAACnC;oCACCwD,IAAG;oCACHC,SAAQ;oCACRN,WAAU;8CAET1B;;;;;kCAIP,KAAC3B;wBAAe4F,MAAK;wBAAMC,OAAM;kCAAS;;;;YAI3Cf,cAAc,yBACb,MAACgB;gBAAKzC,WAAU;gBAAU0C,MAAK;;oBAAS;oBAC9B1D;oBAAM;;iBAEd;YACHyC,cAAc,yBACb,MAACgB;gBAAKzC,WAAU;gBAAU0C,MAAK;;oBAAS;oBACtB1D;oBAAM;;iBAEtB;;;AAGV"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/model-provider-usage-modal.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {\n CodeBlock,\n CodeBlockBody,\n CodeBlockContent,\n CodeBlockCopyButton,\n CodeBlockFilename,\n CodeBlockFiles,\n CodeBlockHeader,\n CodeBlockItem,\n} from '@shipfox/react-ui/code-block';\nimport {Combobox} from '@shipfox/react-ui/combobox';\nimport {useCopyToClipboard} from '@shipfox/react-ui/hooks';\nimport {\n Modal,\n ModalBody,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalTitle,\n} from '@shipfox/react-ui/modal';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {useEffect, useMemo, useRef, useState} from 'react';\nimport {DEFAULT_HARNESS, getHarness, listHarnesses} from '#core/harness-policy.js';\nimport type {HarnessId} from '#core/models.js';\nimport {buildAgentWorkflowExample} from './agent-workflow-example.js';\nimport {compatibleHarnessIds} from './harness-availability.js';\nimport type {ModelProviderUsageTarget} from './model-provider-usage-target.js';\n\ntype CopyState = 'idle' | 'copied' | 'failed';\n\nconst WORKFLOW_EXAMPLE_FILENAME = '.shipfox/workflows/agent.yml';\n\nexport function ModelProviderUsageModal({\n target,\n initialModel,\n open,\n onOpenChange,\n closeFocusTarget,\n workspaceDefaultHarnessId,\n}: {\n target: ModelProviderUsageTarget | null;\n initialModel?: string | null | undefined;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n closeFocusTarget?: HTMLElement | null | undefined;\n workspaceDefaultHarnessId?: HarnessId | null | undefined;\n}) {\n const [selectedModel, setSelectedModel] = useState('');\n const [selectedHarness, setSelectedHarness] = useState<HarnessId>(DEFAULT_HARNESS);\n\n useEffect(() => {\n if (!target) return;\n setSelectedModel(initialModel ?? target.defaultModel ?? target.models[0]?.id ?? '');\n }, [target, initialModel]);\n\n useEffect(() => {\n if (!target) return;\n const nextCompatibleHarnessIds = compatibleHarnessIds({\n isCustom: target.isCustom,\n providerId: target.id,\n });\n setSelectedHarness(selectInitialHarness(nextCompatibleHarnessIds, workspaceDefaultHarnessId));\n }, [target, workspaceDefaultHarnessId]);\n\n const compatibleIds = useMemo(\n () =>\n target\n ? compatibleHarnessIds({isCustom: target.isCustom, providerId: target.id})\n : ([] as HarnessId[]),\n [target],\n );\n const harnessOptions = useMemo(\n () =>\n compatibleIds.map((harnessId) => ({\n value: harnessId,\n label: getHarness(harnessId).label,\n })),\n [compatibleIds],\n );\n const modelOptions = useMemo(\n () => target?.models.map((model) => ({value: model.id, label: model.label})) ?? [],\n [target],\n );\n const example = target\n ? buildAgentWorkflowExample({\n harness: selectedHarness,\n providerId: target.id,\n model: selectedModel,\n })\n : null;\n const data =\n example === null\n ? []\n : [{language: 'yaml', filename: WORKFLOW_EXAMPLE_FILENAME, code: example.code}];\n\n function handleOpenChange(nextOpen: boolean) {\n onOpenChange(nextOpen);\n if (!nextOpen && closeFocusTarget) {\n window.setTimeout(() => closeFocusTarget.focus(), 0);\n }\n }\n\n return (\n <Modal open={open && target !== null} onOpenChange={handleOpenChange}>\n <ModalContent aria-describedby={undefined} className=\"max-h-[calc(100vh-32px)] max-w-[640px]\">\n <ModalTitle className=\"sr-only\">\n {target ? `Use ${target.label} in a workflow` : 'Use provider in a workflow'}\n </ModalTitle>\n <ModalHeader>\n <div className=\"flex min-w-0 flex-col gap-tight\">\n <Text size=\"lg\" aria-hidden=\"true\" className=\"truncate\">\n {target ? `Use ${target.label} in a workflow` : 'Use provider in a workflow'}\n </Text>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Reference this provider from a workflow agent step.\n </Text>\n </div>\n </ModalHeader>\n {target && example ? (\n <>\n <ModalBody className=\"min-h-0 flex-1 gap-0 overflow-y-auto overflow-x-clip scrollbar\">\n <div className=\"flex w-full flex-col gap-section\">\n {compatibleIds.length === 1 ? (\n <div className=\"flex flex-col gap-cluster\">\n <div className=\"flex min-h-40 items-center justify-between gap-cluster rounded-8 border border-border-neutral-base px-row py-row\">\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Harness\n </Text>\n <Code as=\"span\" variant=\"label\">\n {compatibleIds[0]}\n </Code>\n </div>\n <Combobox\n aria-label=\"Model\"\n options={modelOptions}\n value={selectedModel}\n onValueChange={(value) => {\n if (value) setSelectedModel(value);\n }}\n placeholder=\"Select model...\"\n searchPlaceholder=\"Search models...\"\n emptyState=\"No model found.\"\n className=\"w-full\"\n />\n </div>\n ) : (\n <div className=\"flex flex-col gap-cluster sm:flex-row sm:gap-cluster\">\n <Combobox\n aria-label=\"Harness\"\n options={harnessOptions}\n value={selectedHarness}\n onValueChange={(value) => {\n if (isHarness(value)) setSelectedHarness(value);\n }}\n placeholder=\"Select harness...\"\n searchPlaceholder=\"Search harnesses...\"\n emptyState=\"No harness found.\"\n className=\"w-full sm:flex-1\"\n />\n <Combobox\n aria-label=\"Model\"\n options={modelOptions}\n value={selectedModel}\n onValueChange={(value) => {\n if (value) setSelectedModel(value);\n }}\n placeholder=\"Select model...\"\n searchPlaceholder=\"Search models...\"\n emptyState=\"No model found.\"\n className=\"w-full sm:flex-1\"\n />\n </div>\n )}\n\n <div className=\"flex flex-col gap-inline\">\n <CodeBlock data={data} className=\"h-auto min-h-0 rounded-8\">\n <CodeBlockHeader>\n <CodeBlockFiles>\n {(item) => (\n <CodeBlockFilename value={item.filename}>\n {item.filename}\n </CodeBlockFilename>\n )}\n </CodeBlockFiles>\n <CodeBlockCopyButton />\n </CodeBlockHeader>\n <CodeBlockBody>\n {(item) => (\n <CodeBlockItem value={item.filename}>\n <CodeBlockContent\n language=\"yaml\"\n syntaxHighlighting\n highlightedLineRange={example.highlightedLineRange}\n >\n {item.code}\n </CodeBlockContent>\n </CodeBlockItem>\n )}\n </CodeBlockBody>\n </CodeBlock>\n\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Add this to a workflow file under{' '}\n <Code as=\"span\" variant=\"label\">\n .shipfox/workflows/\n </Code>{' '}\n in your repository to run it.\n </Text>\n </div>\n\n <div className=\"flex flex-col gap-inline\">\n <Text size=\"sm\" bold>\n Available models ({target.models.length})\n </Text>\n <ul className=\"rounded-8 border border-border-neutral-base\">\n {target.models.map((model) => (\n <ModelProviderModelRow key={model.id} label={model.label} id={model.id} />\n ))}\n </ul>\n </div>\n </div>\n </ModalBody>\n <ModalFooter>\n <Button type=\"button\" onClick={() => handleOpenChange(false)}>\n Done\n </Button>\n </ModalFooter>\n </>\n ) : null}\n </ModalContent>\n </Modal>\n );\n}\n\nfunction selectInitialHarness(\n compatibleIds: HarnessId[],\n workspaceDefaultHarnessId: HarnessId | null | undefined,\n): HarnessId {\n if (workspaceDefaultHarnessId && compatibleIds.includes(workspaceDefaultHarnessId)) {\n return workspaceDefaultHarnessId;\n }\n if (compatibleIds.includes(DEFAULT_HARNESS)) return DEFAULT_HARNESS;\n return compatibleIds[0] ?? DEFAULT_HARNESS;\n}\n\nfunction isHarness(value: string): value is HarnessId {\n return listHarnesses().some((descriptor) => descriptor.id === value);\n}\n\nfunction ModelProviderModelRow({label, id}: {label: string; id: string}) {\n const [copyState, setCopyState] = useState<CopyState>('idle');\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const {copy} = useCopyToClipboard({\n text: id,\n onCopy: () => {\n setTemporaryCopyState('copied');\n },\n });\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n };\n }, []);\n\n function setTemporaryCopyState(state: Exclude<CopyState, 'idle'>) {\n setCopyState(state);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n timeoutRef.current = setTimeout(() => setCopyState('idle'), 2500);\n }\n\n async function handleCopy() {\n try {\n await copy();\n } catch {\n setTemporaryCopyState('failed');\n }\n }\n\n return (\n <li className=\"border-b border-border-neutral-base last:border-b-0\">\n <Tooltip open={copyState === 'copied'}>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={`Copy ${label} model id ${id}`}\n className=\"flex min-h-40 w-full min-w-0 flex-col items-start gap-tight px-row py-row text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none sm:flex-row sm:items-center sm:gap-inline\"\n onClick={() => {\n void handleCopy();\n }}\n >\n <Text as=\"span\" size=\"sm\" bold className=\"max-w-full shrink-0 truncate sm:max-w-[48%]\">\n {label}\n </Text>\n <Code\n as=\"span\"\n variant=\"label\"\n className=\"min-w-0 max-w-full truncate text-left text-foreground-neutral-muted sm:flex-1 sm:text-right\"\n >\n {id}\n </Code>\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"top\" align=\"center\">\n Copied\n </TooltipContent>\n </Tooltip>\n {copyState === 'copied' ? (\n <span className=\"sr-only\" role=\"status\">\n Copied {label} id\n </span>\n ) : null}\n {copyState === 'failed' ? (\n <span className=\"sr-only\" role=\"status\">\n Could not copy {label} id\n </span>\n ) : null}\n </li>\n );\n}\n"],"names":["Button","CodeBlock","CodeBlockBody","CodeBlockContent","CodeBlockCopyButton","CodeBlockFilename","CodeBlockFiles","CodeBlockHeader","CodeBlockItem","Combobox","useCopyToClipboard","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","Tooltip","TooltipContent","TooltipTrigger","Code","Text","useEffect","useMemo","useRef","useState","DEFAULT_HARNESS","getHarness","listHarnesses","buildAgentWorkflowExample","compatibleHarnessIds","WORKFLOW_EXAMPLE_FILENAME","ModelProviderUsageModal","target","initialModel","open","onOpenChange","closeFocusTarget","workspaceDefaultHarnessId","selectedModel","setSelectedModel","selectedHarness","setSelectedHarness","defaultModel","models","id","nextCompatibleHarnessIds","isCustom","providerId","selectInitialHarness","compatibleIds","harnessOptions","map","harnessId","value","label","modelOptions","model","example","harness","data","language","filename","code","handleOpenChange","nextOpen","window","setTimeout","focus","aria-describedby","undefined","className","div","size","aria-hidden","length","as","variant","aria-label","options","onValueChange","placeholder","searchPlaceholder","emptyState","isHarness","item","syntaxHighlighting","highlightedLineRange","bold","ul","ModelProviderModelRow","type","onClick","includes","some","descriptor","copyState","setCopyState","timeoutRef","copy","text","onCopy","setTemporaryCopyState","current","clearTimeout","state","handleCopy","li","asChild","button","side","align","span","role"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-provider-usage-modal.stories.d.ts","sourceRoot":"","sources":["../../src/components/model-provider-usage-modal.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AASrD,UAAU,iCAAiC;IACzC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;CAC/C;AAED,iBAAS,4BAA4B,CAAC,EAAC,OAAO,EAAC,EAAE,iCAAiC,+BAmBjF;AAED,QAAA,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"model-provider-usage-modal.stories.d.ts","sourceRoot":"","sources":["../../src/components/model-provider-usage-modal.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AASrD,UAAU,iCAAiC;IACzC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;CAC/C;AAED,iBAAS,4BAA4B,CAAC,EAAC,OAAO,EAAC,EAAE,iCAAiC,+BAmBjF;AAED,QAAA,MAAM,IAAI;;;;QAGK,MAAM;;;;;CAEgC,CAAC;eAEvC,IAAI;AACnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,KAE5B,CAAC"}
|