@shipfox/client-agent 0.2.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.
Files changed (182) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +4 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.turbo/turbo-build.log +2 -0
  5. package/.turbo/turbo-type$colon$emit.log +1 -0
  6. package/.turbo/turbo-type.log +1 -0
  7. package/CHANGELOG.md +93 -0
  8. package/LICENSE +21 -0
  9. package/dist/components/add-custom-provider-card.d.ts +4 -0
  10. package/dist/components/add-custom-provider-card.d.ts.map +1 -0
  11. package/dist/components/add-custom-provider-card.js +41 -0
  12. package/dist/components/add-custom-provider-card.js.map +1 -0
  13. package/dist/components/agent-workflow-example.d.ts +11 -0
  14. package/dist/components/agent-workflow-example.d.ts.map +1 -0
  15. package/dist/components/agent-workflow-example.js +38 -0
  16. package/dist/components/agent-workflow-example.js.map +1 -0
  17. package/dist/components/available-provider-card.d.ts +6 -0
  18. package/dist/components/available-provider-card.d.ts.map +1 -0
  19. package/dist/components/available-provider-card.js +41 -0
  20. package/dist/components/available-provider-card.js.map +1 -0
  21. package/dist/components/available-providers-grid.d.ts +10 -0
  22. package/dist/components/available-providers-grid.d.ts.map +1 -0
  23. package/dist/components/available-providers-grid.js +89 -0
  24. package/dist/components/available-providers-grid.js.map +1 -0
  25. package/dist/components/change-default-model-form.d.ts +9 -0
  26. package/dist/components/change-default-model-form.d.ts.map +1 -0
  27. package/dist/components/change-default-model-form.js +98 -0
  28. package/dist/components/change-default-model-form.js.map +1 -0
  29. package/dist/components/custom-model-provider-api-options.d.ts +7 -0
  30. package/dist/components/custom-model-provider-api-options.d.ts.map +1 -0
  31. package/dist/components/custom-model-provider-api-options.js +23 -0
  32. package/dist/components/custom-model-provider-api-options.js.map +1 -0
  33. package/dist/components/custom-model-provider-form.d.ts +8 -0
  34. package/dist/components/custom-model-provider-form.d.ts.map +1 -0
  35. package/dist/components/custom-model-provider-form.js +667 -0
  36. package/dist/components/custom-model-provider-form.js.map +1 -0
  37. package/dist/components/custom-model-provider-form.stories.d.ts +20 -0
  38. package/dist/components/custom-model-provider-form.stories.d.ts.map +1 -0
  39. package/dist/components/custom-model-provider-form.stories.js +104 -0
  40. package/dist/components/custom-model-provider-form.stories.js.map +1 -0
  41. package/dist/components/custom-model-provider-payload.d.ts +39 -0
  42. package/dist/components/custom-model-provider-payload.d.ts.map +1 -0
  43. package/dist/components/custom-model-provider-payload.js +203 -0
  44. package/dist/components/custom-model-provider-payload.js.map +1 -0
  45. package/dist/components/custom-model-provider-slug.d.ts +3 -0
  46. package/dist/components/custom-model-provider-slug.d.ts.map +1 -0
  47. package/dist/components/custom-model-provider-slug.js +14 -0
  48. package/dist/components/custom-model-provider-slug.js.map +1 -0
  49. package/dist/components/default-model-field.d.ts +16 -0
  50. package/dist/components/default-model-field.d.ts.map +1 -0
  51. package/dist/components/default-model-field.js +60 -0
  52. package/dist/components/default-model-field.js.map +1 -0
  53. package/dist/components/form-errors.d.ts +11 -0
  54. package/dist/components/form-errors.d.ts.map +1 -0
  55. package/dist/components/form-errors.js +101 -0
  56. package/dist/components/form-errors.js.map +1 -0
  57. package/dist/components/harness-availability.d.ts +8 -0
  58. package/dist/components/harness-availability.d.ts.map +1 -0
  59. package/dist/components/harness-availability.js +16 -0
  60. package/dist/components/harness-availability.js.map +1 -0
  61. package/dist/components/harnesses-section.d.ts +4 -0
  62. package/dist/components/harnesses-section.d.ts.map +1 -0
  63. package/dist/components/harnesses-section.js +249 -0
  64. package/dist/components/harnesses-section.js.map +1 -0
  65. package/dist/components/harnesses-section.stories.d.ts +25 -0
  66. package/dist/components/harnesses-section.stories.d.ts.map +1 -0
  67. package/dist/components/harnesses-section.stories.js +138 -0
  68. package/dist/components/harnesses-section.stories.js.map +1 -0
  69. package/dist/components/model-provider-usage-modal.d.ts +11 -0
  70. package/dist/components/model-provider-usage-modal.d.ts.map +1 -0
  71. package/dist/components/model-provider-usage-modal.js +349 -0
  72. package/dist/components/model-provider-usage-modal.js.map +1 -0
  73. package/dist/components/model-provider-usage-modal.stories.d.ts +21 -0
  74. package/dist/components/model-provider-usage-modal.stories.d.ts.map +1 -0
  75. package/dist/components/model-provider-usage-modal.stories.js +115 -0
  76. package/dist/components/model-provider-usage-modal.stories.js.map +1 -0
  77. package/dist/components/model-provider-usage-target.d.ts +14 -0
  78. package/dist/components/model-provider-usage-target.d.ts.map +1 -0
  79. package/dist/components/model-provider-usage-target.js +20 -0
  80. package/dist/components/model-provider-usage-target.js.map +1 -0
  81. package/dist/components/model-providers-section.d.ts +4 -0
  82. package/dist/components/model-providers-section.d.ts.map +1 -0
  83. package/dist/components/model-providers-section.js +668 -0
  84. package/dist/components/model-providers-section.js.map +1 -0
  85. package/dist/components/model-providers-section.stories.d.ts +32 -0
  86. package/dist/components/model-providers-section.stories.d.ts.map +1 -0
  87. package/dist/components/model-providers-section.stories.js +442 -0
  88. package/dist/components/model-providers-section.stories.js.map +1 -0
  89. package/dist/components/provider-search.d.ts +4 -0
  90. package/dist/components/provider-search.d.ts.map +1 -0
  91. package/dist/components/provider-search.js +13 -0
  92. package/dist/components/provider-search.js.map +1 -0
  93. package/dist/components/supported-model-provider-catalog-entry.d.ts +8 -0
  94. package/dist/components/supported-model-provider-catalog-entry.d.ts.map +1 -0
  95. package/dist/components/supported-model-provider-catalog-entry.js +9 -0
  96. package/dist/components/supported-model-provider-catalog-entry.js.map +1 -0
  97. package/dist/components/test-and-save-form.d.ts +10 -0
  98. package/dist/components/test-and-save-form.d.ts.map +1 -0
  99. package/dist/components/test-and-save-form.js +138 -0
  100. package/dist/components/test-and-save-form.js.map +1 -0
  101. package/dist/feature.d.ts +9 -0
  102. package/dist/feature.d.ts.map +1 -0
  103. package/dist/feature.js +13 -0
  104. package/dist/feature.js.map +1 -0
  105. package/dist/hooks/api/model-providers.d.ts +334 -0
  106. package/dist/hooks/api/model-providers.d.ts.map +1 -0
  107. package/dist/hooks/api/model-providers.js +190 -0
  108. package/dist/hooks/api/model-providers.js.map +1 -0
  109. package/dist/index.d.ts +9 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +10 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/pages/model-provider-onboarding-page.d.ts +6 -0
  114. package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -0
  115. package/dist/pages/model-provider-onboarding-page.js +295 -0
  116. package/dist/pages/model-provider-onboarding-page.js.map +1 -0
  117. package/dist/pages/model-provider-onboarding-page.stories.d.ts +27 -0
  118. package/dist/pages/model-provider-onboarding-page.stories.d.ts.map +1 -0
  119. package/dist/pages/model-provider-onboarding-page.stories.js +368 -0
  120. package/dist/pages/model-provider-onboarding-page.stories.js.map +1 -0
  121. package/dist/routes/model-provider.d.ts +5 -0
  122. package/dist/routes/model-provider.d.ts.map +1 -0
  123. package/dist/routes/model-provider.js +28 -0
  124. package/dist/routes/model-provider.js.map +1 -0
  125. package/dist/state/model-provider-onboarding.d.ts +3 -0
  126. package/dist/state/model-provider-onboarding.d.ts.map +1 -0
  127. package/dist/state/model-provider-onboarding.js +28 -0
  128. package/dist/state/model-provider-onboarding.js.map +1 -0
  129. package/dist/tsconfig.test.tsbuildinfo +1 -0
  130. package/package.json +114 -0
  131. package/src/components/add-custom-provider-card.tsx +32 -0
  132. package/src/components/agent-workflow-example.test.ts +74 -0
  133. package/src/components/agent-workflow-example.ts +55 -0
  134. package/src/components/available-provider-card.tsx +39 -0
  135. package/src/components/available-providers-grid.test.tsx +55 -0
  136. package/src/components/available-providers-grid.tsx +106 -0
  137. package/src/components/change-default-model-form.tsx +104 -0
  138. package/src/components/custom-model-provider-api-options.test.ts +18 -0
  139. package/src/components/custom-model-provider-api-options.ts +12 -0
  140. package/src/components/custom-model-provider-form.stories.tsx +91 -0
  141. package/src/components/custom-model-provider-form.tsx +710 -0
  142. package/src/components/custom-model-provider-payload.test.ts +170 -0
  143. package/src/components/custom-model-provider-payload.ts +264 -0
  144. package/src/components/custom-model-provider-slug.test.ts +40 -0
  145. package/src/components/custom-model-provider-slug.ts +23 -0
  146. package/src/components/default-model-field.tsx +85 -0
  147. package/src/components/form-errors.test.ts +159 -0
  148. package/src/components/form-errors.ts +102 -0
  149. package/src/components/harness-availability.test.ts +45 -0
  150. package/src/components/harness-availability.ts +35 -0
  151. package/src/components/harnesses-section.stories.tsx +129 -0
  152. package/src/components/harnesses-section.test.tsx +188 -0
  153. package/src/components/harnesses-section.tsx +245 -0
  154. package/src/components/model-provider-usage-modal.stories.tsx +106 -0
  155. package/src/components/model-provider-usage-modal.test.tsx +210 -0
  156. package/src/components/model-provider-usage-modal.tsx +326 -0
  157. package/src/components/model-provider-usage-target.ts +36 -0
  158. package/src/components/model-providers-section.stories.tsx +345 -0
  159. package/src/components/model-providers-section.test.tsx +831 -0
  160. package/src/components/model-providers-section.tsx +645 -0
  161. package/src/components/provider-search.test.ts +45 -0
  162. package/src/components/provider-search.ts +18 -0
  163. package/src/components/supported-model-provider-catalog-entry.ts +19 -0
  164. package/src/components/test-and-save-form.tsx +166 -0
  165. package/src/feature.ts +12 -0
  166. package/src/hooks/api/model-providers.test.ts +167 -0
  167. package/src/hooks/api/model-providers.ts +281 -0
  168. package/src/index.ts +8 -0
  169. package/src/pages/model-provider-onboarding-page.stories.tsx +284 -0
  170. package/src/pages/model-provider-onboarding-page.test.tsx +368 -0
  171. package/src/pages/model-provider-onboarding-page.tsx +290 -0
  172. package/src/routes/model-provider.tsx +20 -0
  173. package/src/state/model-provider-onboarding.test.ts +55 -0
  174. package/src/state/model-provider-onboarding.ts +34 -0
  175. package/test/fixtures/model-providers.ts +111 -0
  176. package/test/setup.ts +3 -0
  177. package/tsconfig.build.json +9 -0
  178. package/tsconfig.build.tsbuildinfo +1 -0
  179. package/tsconfig.json +3 -0
  180. package/tsconfig.test.json +8 -0
  181. package/vercel.json +8 -0
  182. package/vitest.config.ts +72 -0
@@ -0,0 +1,138 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { configureApiClient } from '@shipfox/client-api';
3
+ import { Toaster } from '@shipfox/react-ui/toast';
4
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
+ import { useMemo } from 'react';
6
+ import { WorkspaceHarnessesSection } from './harnesses-section.js';
7
+ const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
8
+ function HarnessesStory({ scenario }) {
9
+ configureApiClient({
10
+ baseUrl: 'https://api.example.test',
11
+ fetchImpl: fetchForScenario(scenario)
12
+ });
13
+ const queryClient = useMemo(()=>new QueryClient({
14
+ defaultOptions: {
15
+ queries: {
16
+ retry: false
17
+ }
18
+ }
19
+ }), []);
20
+ return /*#__PURE__*/ _jsxs(QueryClientProvider, {
21
+ client: queryClient,
22
+ children: [
23
+ /*#__PURE__*/ _jsx("div", {
24
+ className: "mx-auto w-full max-w-[760px] bg-background-neutral-background p-24",
25
+ children: /*#__PURE__*/ _jsx(WorkspaceHarnessesSection, {
26
+ workspaceId: WORKSPACE_ID
27
+ })
28
+ }),
29
+ /*#__PURE__*/ _jsx(Toaster, {})
30
+ ]
31
+ });
32
+ }
33
+ const meta = {
34
+ title: 'Agent/HarnessSettings',
35
+ component: HarnessesStory,
36
+ parameters: {
37
+ layout: 'fullscreen'
38
+ },
39
+ args: {
40
+ scenario: 'default-pi'
41
+ }
42
+ };
43
+ export default meta;
44
+ export const Playground = {};
45
+ export const DefaultClaude = {
46
+ args: {
47
+ scenario: 'default-claude'
48
+ }
49
+ };
50
+ export const ClaudeUnavailable = {
51
+ args: {
52
+ scenario: 'claude-unavailable'
53
+ }
54
+ };
55
+ export const DefaultClaudeUnavailable = {
56
+ args: {
57
+ scenario: 'default-claude-unavailable'
58
+ }
59
+ };
60
+ export const Loading = {
61
+ args: {
62
+ scenario: 'loading'
63
+ }
64
+ };
65
+ export const ErrorState = {
66
+ args: {
67
+ scenario: 'error'
68
+ }
69
+ };
70
+ function fetchForScenario(scenario) {
71
+ return ()=>{
72
+ if (scenario === 'loading') return new Promise(()=>undefined);
73
+ if (scenario === 'error') return Promise.resolve(errorResponse());
74
+ return Promise.resolve(jsonResponse(configsForScenario(scenario)));
75
+ };
76
+ }
77
+ function configsForScenario(scenario) {
78
+ if (scenario === 'claude-unavailable') {
79
+ return {
80
+ configs: [
81
+ providerConfig({
82
+ provider_id: 'openai'
83
+ })
84
+ ],
85
+ default_provider_id: 'openai',
86
+ default_harness_id: null
87
+ };
88
+ }
89
+ if (scenario === 'default-claude-unavailable') {
90
+ return {
91
+ configs: [
92
+ providerConfig({
93
+ provider_id: 'openai'
94
+ })
95
+ ],
96
+ default_provider_id: 'openai',
97
+ default_harness_id: 'claude'
98
+ };
99
+ }
100
+ return {
101
+ configs: [
102
+ providerConfig({
103
+ provider_id: 'anthropic'
104
+ })
105
+ ],
106
+ default_provider_id: 'anthropic',
107
+ default_harness_id: scenario === 'default-claude' ? 'claude' : null
108
+ };
109
+ }
110
+ function providerConfig(overrides = {}) {
111
+ return {
112
+ kind: 'builtin',
113
+ provider_id: 'anthropic',
114
+ default_model: null,
115
+ created_at: '2026-05-08T00:00:00.000Z',
116
+ updated_at: '2026-05-08T00:00:00.000Z',
117
+ ...overrides
118
+ };
119
+ }
120
+ function jsonResponse(body, init = {}) {
121
+ return new Response(JSON.stringify(body), {
122
+ status: 200,
123
+ headers: {
124
+ 'content-type': 'application/json'
125
+ },
126
+ ...init
127
+ });
128
+ }
129
+ function errorResponse() {
130
+ return jsonResponse({
131
+ code: 'server-error'
132
+ }, {
133
+ status: 500,
134
+ statusText: 'Server error'
135
+ });
136
+ }
137
+
138
+ //# sourceMappingURL=harnesses-section.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/harnesses-section.stories.tsx"],"sourcesContent":["import type {ModelProviderConfigDto} from '@shipfox/api-agent-dto';\nimport {configureApiClient} from '@shipfox/client-api';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport type {Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {useMemo} from 'react';\nimport {WorkspaceHarnessesSection} from './harnesses-section.js';\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\n\ntype Scenario =\n | 'default-pi'\n | 'default-claude'\n | 'claude-unavailable'\n | 'default-claude-unavailable'\n | 'loading'\n | 'error';\n\ninterface HarnessesStoryProps {\n scenario: Scenario;\n}\n\nfunction HarnessesStory({scenario}: HarnessesStoryProps) {\n configureApiClient({\n baseUrl: 'https://api.example.test',\n fetchImpl: fetchForScenario(scenario),\n });\n\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}}}),\n [],\n );\n\n return (\n <QueryClientProvider client={queryClient}>\n <div className=\"mx-auto w-full max-w-[760px] bg-background-neutral-background p-24\">\n <WorkspaceHarnessesSection workspaceId={WORKSPACE_ID} />\n </div>\n <Toaster />\n </QueryClientProvider>\n );\n}\n\nconst meta = {\n title: 'Agent/HarnessSettings',\n component: HarnessesStory,\n parameters: {layout: 'fullscreen'},\n args: {scenario: 'default-pi'},\n} satisfies Meta<typeof HarnessesStory>;\n\nexport default meta;\ntype Story = StoryObj<typeof meta>;\n\nexport const Playground: Story = {};\n\nexport const DefaultClaude: Story = {\n args: {scenario: 'default-claude'},\n};\n\nexport const ClaudeUnavailable: Story = {\n args: {scenario: 'claude-unavailable'},\n};\n\nexport const DefaultClaudeUnavailable: Story = {\n args: {scenario: 'default-claude-unavailable'},\n};\n\nexport const Loading: Story = {\n args: {scenario: 'loading'},\n};\n\nexport const ErrorState: Story = {\n args: {scenario: 'error'},\n};\n\nfunction fetchForScenario(scenario: Scenario): typeof fetch {\n return () => {\n if (scenario === 'loading') return new Promise<Response>(() => undefined);\n if (scenario === 'error') return Promise.resolve(errorResponse());\n return Promise.resolve(jsonResponse(configsForScenario(scenario)));\n };\n}\n\nfunction configsForScenario(scenario: Scenario) {\n if (scenario === 'claude-unavailable') {\n return {\n configs: [providerConfig({provider_id: 'openai'})],\n default_provider_id: 'openai',\n default_harness_id: null,\n };\n }\n if (scenario === 'default-claude-unavailable') {\n return {\n configs: [providerConfig({provider_id: 'openai'})],\n default_provider_id: 'openai',\n default_harness_id: 'claude',\n };\n }\n return {\n configs: [providerConfig({provider_id: 'anthropic'})],\n default_provider_id: 'anthropic',\n default_harness_id: scenario === 'default-claude' ? 'claude' : null,\n };\n}\n\nfunction providerConfig(\n overrides: Partial<Omit<ModelProviderConfigDto, 'kind'>> = {},\n): ModelProviderConfigDto {\n return {\n kind: 'builtin',\n provider_id: 'anthropic',\n default_model: null,\n created_at: '2026-05-08T00:00:00.000Z',\n updated_at: '2026-05-08T00:00:00.000Z',\n ...overrides,\n };\n}\n\nfunction jsonResponse(body: unknown, init: ResponseInit = {}) {\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: {'content-type': 'application/json'},\n ...init,\n });\n}\n\nfunction errorResponse() {\n return jsonResponse({code: 'server-error'}, {status: 500, statusText: 'Server error'});\n}\n"],"names":["configureApiClient","Toaster","QueryClient","QueryClientProvider","useMemo","WorkspaceHarnessesSection","WORKSPACE_ID","HarnessesStory","scenario","baseUrl","fetchImpl","fetchForScenario","queryClient","defaultOptions","queries","retry","client","div","className","workspaceId","meta","title","component","parameters","layout","args","Playground","DefaultClaude","ClaudeUnavailable","DefaultClaudeUnavailable","Loading","ErrorState","Promise","undefined","resolve","errorResponse","jsonResponse","configsForScenario","configs","providerConfig","provider_id","default_provider_id","default_harness_id","overrides","kind","default_model","created_at","updated_at","body","init","Response","JSON","stringify","status","headers","code","statusText"],"mappings":";AACA,SAAQA,kBAAkB,QAAO,sBAAsB;AACvD,SAAQC,OAAO,QAAO,0BAA0B;AAEhD,SAAQC,WAAW,EAAEC,mBAAmB,QAAO,wBAAwB;AACvE,SAAQC,OAAO,QAAO,QAAQ;AAC9B,SAAQC,yBAAyB,QAAO,yBAAyB;AAEjE,MAAMC,eAAe;AAcrB,SAASC,eAAe,EAACC,QAAQ,EAAsB;IACrDR,mBAAmB;QACjBS,SAAS;QACTC,WAAWC,iBAAiBH;IAC9B;IAEA,MAAMI,cAAcR,QAClB,IAAM,IAAIF,YAAY;YAACW,gBAAgB;gBAACC,SAAS;oBAACC,OAAO;gBAAK;YAAC;QAAC,IAChE,EAAE;IAGJ,qBACE,MAACZ;QAAoBa,QAAQJ;;0BAC3B,KAACK;gBAAIC,WAAU;0BACb,cAAA,KAACb;oBAA0Bc,aAAab;;;0BAE1C,KAACL;;;AAGP;AAEA,MAAMmB,OAAO;IACXC,OAAO;IACPC,WAAWf;IACXgB,YAAY;QAACC,QAAQ;IAAY;IACjCC,MAAM;QAACjB,UAAU;IAAY;AAC/B;AAEA,eAAeY,KAAK;AAGpB,OAAO,MAAMM,aAAoB,CAAC,EAAE;AAEpC,OAAO,MAAMC,gBAAuB;IAClCF,MAAM;QAACjB,UAAU;IAAgB;AACnC,EAAE;AAEF,OAAO,MAAMoB,oBAA2B;IACtCH,MAAM;QAACjB,UAAU;IAAoB;AACvC,EAAE;AAEF,OAAO,MAAMqB,2BAAkC;IAC7CJ,MAAM;QAACjB,UAAU;IAA4B;AAC/C,EAAE;AAEF,OAAO,MAAMsB,UAAiB;IAC5BL,MAAM;QAACjB,UAAU;IAAS;AAC5B,EAAE;AAEF,OAAO,MAAMuB,aAAoB;IAC/BN,MAAM;QAACjB,UAAU;IAAO;AAC1B,EAAE;AAEF,SAASG,iBAAiBH,QAAkB;IAC1C,OAAO;QACL,IAAIA,aAAa,WAAW,OAAO,IAAIwB,QAAkB,IAAMC;QAC/D,IAAIzB,aAAa,SAAS,OAAOwB,QAAQE,OAAO,CAACC;QACjD,OAAOH,QAAQE,OAAO,CAACE,aAAaC,mBAAmB7B;IACzD;AACF;AAEA,SAAS6B,mBAAmB7B,QAAkB;IAC5C,IAAIA,aAAa,sBAAsB;QACrC,OAAO;YACL8B,SAAS;gBAACC,eAAe;oBAACC,aAAa;gBAAQ;aAAG;YAClDC,qBAAqB;YACrBC,oBAAoB;QACtB;IACF;IACA,IAAIlC,aAAa,8BAA8B;QAC7C,OAAO;YACL8B,SAAS;gBAACC,eAAe;oBAACC,aAAa;gBAAQ;aAAG;YAClDC,qBAAqB;YACrBC,oBAAoB;QACtB;IACF;IACA,OAAO;QACLJ,SAAS;YAACC,eAAe;gBAACC,aAAa;YAAW;SAAG;QACrDC,qBAAqB;QACrBC,oBAAoBlC,aAAa,mBAAmB,WAAW;IACjE;AACF;AAEA,SAAS+B,eACPI,YAA2D,CAAC,CAAC;IAE7D,OAAO;QACLC,MAAM;QACNJ,aAAa;QACbK,eAAe;QACfC,YAAY;QACZC,YAAY;QACZ,GAAGJ,SAAS;IACd;AACF;AAEA,SAASP,aAAaY,IAAa,EAAEC,OAAqB,CAAC,CAAC;IAC1D,OAAO,IAAIC,SAASC,KAAKC,SAAS,CAACJ,OAAO;QACxCK,QAAQ;QACRC,SAAS;YAAC,gBAAgB;QAAkB;QAC5C,GAAGL,IAAI;IACT;AACF;AAEA,SAASd;IACP,OAAOC,aAAa;QAACmB,MAAM;IAAc,GAAG;QAACF,QAAQ;QAAKG,YAAY;IAAc;AACtF"}
@@ -0,0 +1,11 @@
1
+ import { type Harness } from '@shipfox/api-agent-dto';
2
+ import type { ModelProviderUsageTarget } from './model-provider-usage-target.js';
3
+ export declare function ModelProviderUsageModal({ target, initialModel, open, onOpenChange, closeFocusTarget, workspaceDefaultHarnessId, }: {
4
+ target: ModelProviderUsageTarget | null;
5
+ initialModel?: string | null | undefined;
6
+ open: boolean;
7
+ onOpenChange: (open: boolean) => void;
8
+ closeFocusTarget?: HTMLElement | null | undefined;
9
+ workspaceDefaultHarnessId?: Harness | null | undefined;
10
+ }): import("react").JSX.Element;
11
+ //# sourceMappingURL=model-provider-usage-modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-provider-usage-modal.d.ts","sourceRoot":"","sources":["../../src/components/model-provider-usage-modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EAEb,MAAM,wBAAwB,CAAC;AA2BhC,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,kCAAkC,CAAC;AAM/E,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,YAAY,EACZ,IAAI,EACJ,YAAY,EACZ,gBAAgB,EAChB,yBAAyB,GAC1B,EAAE;IACD,MAAM,EAAE,wBAAwB,GAAG,IAAI,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,gBAAgB,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,yBAAyB,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;CACxD,+BA0LA"}
@@ -0,0 +1,349 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { DEFAULT_HARNESS, getHarnessDescriptor, listHarnessDescriptors } from '@shipfox/api-agent-dto';
3
+ import { Button } from '@shipfox/react-ui/button';
4
+ import { CodeBlock, CodeBlockBody, CodeBlockContent, CodeBlockCopyButton, CodeBlockFilename, CodeBlockFiles, CodeBlockHeader, CodeBlockItem } from '@shipfox/react-ui/code-block';
5
+ import { Combobox } from '@shipfox/react-ui/combobox';
6
+ import { useCopyToClipboard } from '@shipfox/react-ui/hooks';
7
+ import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ModalTitle } from '@shipfox/react-ui/modal';
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
9
+ import { Code, Text } from '@shipfox/react-ui/typography';
10
+ import { useEffect, useMemo, useRef, useState } from 'react';
11
+ import { buildAgentWorkflowExample } from './agent-workflow-example.js';
12
+ import { compatibleHarnessIds } from './harness-availability.js';
13
+ const WORKFLOW_EXAMPLE_FILENAME = '.shipfox/workflows/agent.yml';
14
+ export function ModelProviderUsageModal({ target, initialModel, open, onOpenChange, closeFocusTarget, workspaceDefaultHarnessId }) {
15
+ const [selectedModel, setSelectedModel] = useState('');
16
+ const [selectedHarness, setSelectedHarness] = useState(DEFAULT_HARNESS);
17
+ useEffect(()=>{
18
+ if (!target) return;
19
+ setSelectedModel(initialModel ?? target.default_model ?? target.models[0]?.id ?? '');
20
+ }, [
21
+ target,
22
+ initialModel
23
+ ]);
24
+ useEffect(()=>{
25
+ if (!target) return;
26
+ const nextCompatibleHarnessIds = compatibleHarnessIds({
27
+ isCustom: target.isCustom,
28
+ providerId: target.id
29
+ });
30
+ setSelectedHarness(selectInitialHarness(nextCompatibleHarnessIds, workspaceDefaultHarnessId));
31
+ }, [
32
+ target,
33
+ workspaceDefaultHarnessId
34
+ ]);
35
+ const compatibleIds = useMemo(()=>target ? compatibleHarnessIds({
36
+ isCustom: target.isCustom,
37
+ providerId: target.id
38
+ }) : [], [
39
+ target
40
+ ]);
41
+ const harnessOptions = useMemo(()=>compatibleIds.map((harnessId)=>({
42
+ value: harnessId,
43
+ label: getHarnessDescriptor(harnessId).label
44
+ })), [
45
+ compatibleIds
46
+ ]);
47
+ const modelOptions = useMemo(()=>target?.models.map((model)=>({
48
+ value: model.id,
49
+ label: model.label
50
+ })) ?? [], [
51
+ target
52
+ ]);
53
+ const example = target ? buildAgentWorkflowExample({
54
+ harness: selectedHarness,
55
+ providerId: target.id,
56
+ model: selectedModel
57
+ }) : null;
58
+ const data = example === null ? [] : [
59
+ {
60
+ language: 'yaml',
61
+ filename: WORKFLOW_EXAMPLE_FILENAME,
62
+ code: example.code
63
+ }
64
+ ];
65
+ function handleOpenChange(nextOpen) {
66
+ onOpenChange(nextOpen);
67
+ if (!nextOpen && closeFocusTarget) {
68
+ window.setTimeout(()=>closeFocusTarget.focus(), 0);
69
+ }
70
+ }
71
+ return /*#__PURE__*/ _jsx(Modal, {
72
+ open: open && target !== null,
73
+ onOpenChange: handleOpenChange,
74
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
75
+ "aria-describedby": undefined,
76
+ className: "max-h-[calc(100vh-32px)] max-w-[640px]",
77
+ children: [
78
+ /*#__PURE__*/ _jsx(ModalTitle, {
79
+ className: "sr-only",
80
+ children: target ? `Use ${target.label} in a workflow` : 'Use provider in a workflow'
81
+ }),
82
+ /*#__PURE__*/ _jsx(ModalHeader, {
83
+ children: /*#__PURE__*/ _jsxs("div", {
84
+ className: "flex min-w-0 flex-col gap-2",
85
+ children: [
86
+ /*#__PURE__*/ _jsx(Text, {
87
+ size: "lg",
88
+ "aria-hidden": "true",
89
+ className: "truncate",
90
+ children: target ? `Use ${target.label} in a workflow` : 'Use provider in a workflow'
91
+ }),
92
+ /*#__PURE__*/ _jsx(Text, {
93
+ size: "sm",
94
+ className: "text-foreground-neutral-muted",
95
+ children: "Reference this provider from a workflow agent step."
96
+ })
97
+ ]
98
+ })
99
+ }),
100
+ target && example ? /*#__PURE__*/ _jsxs(_Fragment, {
101
+ children: [
102
+ /*#__PURE__*/ _jsx(ModalBody, {
103
+ className: "min-h-0 flex-1 gap-0 overflow-y-auto overflow-x-clip scrollbar",
104
+ children: /*#__PURE__*/ _jsxs("div", {
105
+ className: "flex w-full flex-col gap-20",
106
+ children: [
107
+ compatibleIds.length === 1 ? /*#__PURE__*/ _jsxs("div", {
108
+ className: "flex flex-col gap-12",
109
+ children: [
110
+ /*#__PURE__*/ _jsxs("div", {
111
+ className: "flex min-h-40 items-center justify-between gap-12 rounded-8 border border-border-neutral-base px-12 py-8",
112
+ children: [
113
+ /*#__PURE__*/ _jsx(Text, {
114
+ size: "sm",
115
+ className: "text-foreground-neutral-muted",
116
+ children: "Harness"
117
+ }),
118
+ /*#__PURE__*/ _jsx(Code, {
119
+ as: "span",
120
+ variant: "label",
121
+ children: compatibleIds[0]
122
+ })
123
+ ]
124
+ }),
125
+ /*#__PURE__*/ _jsx(Combobox, {
126
+ "aria-label": "Model",
127
+ options: modelOptions,
128
+ value: selectedModel,
129
+ onValueChange: (value)=>{
130
+ if (value) setSelectedModel(value);
131
+ },
132
+ placeholder: "Select model...",
133
+ searchPlaceholder: "Search models...",
134
+ emptyState: "No model found.",
135
+ className: "w-full"
136
+ })
137
+ ]
138
+ }) : /*#__PURE__*/ _jsxs("div", {
139
+ className: "flex flex-col gap-12 sm:flex-row sm:gap-12",
140
+ children: [
141
+ /*#__PURE__*/ _jsx(Combobox, {
142
+ "aria-label": "Harness",
143
+ options: harnessOptions,
144
+ value: selectedHarness,
145
+ onValueChange: (value)=>{
146
+ if (isHarness(value)) setSelectedHarness(value);
147
+ },
148
+ placeholder: "Select harness...",
149
+ searchPlaceholder: "Search harnesses...",
150
+ emptyState: "No harness found.",
151
+ className: "w-full sm:flex-1"
152
+ }),
153
+ /*#__PURE__*/ _jsx(Combobox, {
154
+ "aria-label": "Model",
155
+ options: modelOptions,
156
+ value: selectedModel,
157
+ onValueChange: (value)=>{
158
+ if (value) setSelectedModel(value);
159
+ },
160
+ placeholder: "Select model...",
161
+ searchPlaceholder: "Search models...",
162
+ emptyState: "No model found.",
163
+ className: "w-full sm:flex-1"
164
+ })
165
+ ]
166
+ }),
167
+ /*#__PURE__*/ _jsxs("div", {
168
+ className: "flex flex-col gap-8",
169
+ children: [
170
+ /*#__PURE__*/ _jsxs(CodeBlock, {
171
+ data: data,
172
+ className: "h-auto min-h-0 rounded-8",
173
+ children: [
174
+ /*#__PURE__*/ _jsxs(CodeBlockHeader, {
175
+ children: [
176
+ /*#__PURE__*/ _jsx(CodeBlockFiles, {
177
+ children: (item)=>/*#__PURE__*/ _jsx(CodeBlockFilename, {
178
+ value: item.filename,
179
+ children: item.filename
180
+ })
181
+ }),
182
+ /*#__PURE__*/ _jsx(CodeBlockCopyButton, {})
183
+ ]
184
+ }),
185
+ /*#__PURE__*/ _jsx(CodeBlockBody, {
186
+ children: (item)=>/*#__PURE__*/ _jsx(CodeBlockItem, {
187
+ value: item.filename,
188
+ children: /*#__PURE__*/ _jsx(CodeBlockContent, {
189
+ language: "yaml",
190
+ syntaxHighlighting: true,
191
+ highlightedLineRange: example.highlightedLineRange,
192
+ children: item.code
193
+ })
194
+ })
195
+ })
196
+ ]
197
+ }),
198
+ /*#__PURE__*/ _jsxs(Text, {
199
+ size: "sm",
200
+ className: "text-foreground-neutral-muted",
201
+ children: [
202
+ "Add this to a workflow file under",
203
+ ' ',
204
+ /*#__PURE__*/ _jsx(Code, {
205
+ as: "span",
206
+ variant: "label",
207
+ children: ".shipfox/workflows/"
208
+ }),
209
+ ' ',
210
+ "in your repository to run it."
211
+ ]
212
+ })
213
+ ]
214
+ }),
215
+ /*#__PURE__*/ _jsxs("div", {
216
+ className: "flex flex-col gap-8",
217
+ children: [
218
+ /*#__PURE__*/ _jsxs(Text, {
219
+ size: "sm",
220
+ bold: true,
221
+ children: [
222
+ "Available models (",
223
+ target.models.length,
224
+ ")"
225
+ ]
226
+ }),
227
+ /*#__PURE__*/ _jsx("ul", {
228
+ className: "rounded-8 border border-border-neutral-base",
229
+ children: target.models.map((model)=>/*#__PURE__*/ _jsx(ModelProviderModelRow, {
230
+ label: model.label,
231
+ id: model.id
232
+ }, model.id))
233
+ })
234
+ ]
235
+ })
236
+ ]
237
+ })
238
+ }),
239
+ /*#__PURE__*/ _jsx(ModalFooter, {
240
+ children: /*#__PURE__*/ _jsx(Button, {
241
+ type: "button",
242
+ onClick: ()=>handleOpenChange(false),
243
+ children: "Done"
244
+ })
245
+ })
246
+ ]
247
+ }) : null
248
+ ]
249
+ })
250
+ });
251
+ }
252
+ function selectInitialHarness(compatibleIds, workspaceDefaultHarnessId) {
253
+ if (workspaceDefaultHarnessId && compatibleIds.includes(workspaceDefaultHarnessId)) {
254
+ return workspaceDefaultHarnessId;
255
+ }
256
+ if (compatibleIds.includes(DEFAULT_HARNESS)) return DEFAULT_HARNESS;
257
+ return compatibleIds[0] ?? DEFAULT_HARNESS;
258
+ }
259
+ function isHarness(value) {
260
+ return listHarnessDescriptors().some((descriptor)=>descriptor.id === value);
261
+ }
262
+ function ModelProviderModelRow({ label, id }) {
263
+ const [copyState, setCopyState] = useState('idle');
264
+ const timeoutRef = useRef(undefined);
265
+ const { copy } = useCopyToClipboard({
266
+ text: id,
267
+ onCopy: ()=>{
268
+ setTemporaryCopyState('copied');
269
+ }
270
+ });
271
+ useEffect(()=>{
272
+ return ()=>{
273
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
274
+ };
275
+ }, []);
276
+ function setTemporaryCopyState(state) {
277
+ setCopyState(state);
278
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
279
+ timeoutRef.current = setTimeout(()=>setCopyState('idle'), 2500);
280
+ }
281
+ async function handleCopy() {
282
+ try {
283
+ await copy();
284
+ } catch {
285
+ setTemporaryCopyState('failed');
286
+ }
287
+ }
288
+ return /*#__PURE__*/ _jsxs("li", {
289
+ className: "border-b border-border-neutral-base last:border-b-0",
290
+ children: [
291
+ /*#__PURE__*/ _jsxs(Tooltip, {
292
+ open: copyState === 'copied',
293
+ children: [
294
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
295
+ asChild: true,
296
+ children: /*#__PURE__*/ _jsxs("button", {
297
+ type: "button",
298
+ "aria-label": `Copy ${label} model id ${id}`,
299
+ 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",
300
+ onClick: ()=>{
301
+ void handleCopy();
302
+ },
303
+ children: [
304
+ /*#__PURE__*/ _jsx(Text, {
305
+ as: "span",
306
+ size: "sm",
307
+ bold: true,
308
+ className: "max-w-full shrink-0 truncate sm:max-w-[48%]",
309
+ children: label
310
+ }),
311
+ /*#__PURE__*/ _jsx(Code, {
312
+ as: "span",
313
+ variant: "label",
314
+ className: "min-w-0 max-w-full truncate text-left text-foreground-neutral-muted sm:flex-1 sm:text-right",
315
+ children: id
316
+ })
317
+ ]
318
+ })
319
+ }),
320
+ /*#__PURE__*/ _jsx(TooltipContent, {
321
+ side: "top",
322
+ align: "center",
323
+ children: "Copied"
324
+ })
325
+ ]
326
+ }),
327
+ copyState === 'copied' ? /*#__PURE__*/ _jsxs("span", {
328
+ className: "sr-only",
329
+ role: "status",
330
+ children: [
331
+ "Copied ",
332
+ label,
333
+ " id"
334
+ ]
335
+ }) : null,
336
+ copyState === 'failed' ? /*#__PURE__*/ _jsxs("span", {
337
+ className: "sr-only",
338
+ role: "status",
339
+ children: [
340
+ "Could not copy ",
341
+ label,
342
+ " id"
343
+ ]
344
+ }) : null
345
+ ]
346
+ });
347
+ }
348
+
349
+ //# sourceMappingURL=model-provider-usage-modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/model-provider-usage-modal.tsx"],"sourcesContent":["import {\n DEFAULT_HARNESS,\n getHarnessDescriptor,\n type Harness,\n listHarnessDescriptors,\n} from '@shipfox/api-agent-dto';\nimport {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 {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?: Harness | null | undefined;\n}) {\n const [selectedModel, setSelectedModel] = useState('');\n const [selectedHarness, setSelectedHarness] = useState<Harness>(DEFAULT_HARNESS);\n\n useEffect(() => {\n if (!target) return;\n setSelectedModel(initialModel ?? target.default_model ?? 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 Harness[]),\n [target],\n );\n const harnessOptions = useMemo(\n () =>\n compatibleIds.map((harnessId) => ({\n value: harnessId,\n label: getHarnessDescriptor(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: Harness[],\n workspaceDefaultHarnessId: Harness | null | undefined,\n): Harness {\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 Harness {\n return listHarnessDescriptors().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":["DEFAULT_HARNESS","getHarnessDescriptor","listHarnessDescriptors","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","buildAgentWorkflowExample","compatibleHarnessIds","WORKFLOW_EXAMPLE_FILENAME","ModelProviderUsageModal","target","initialModel","open","onOpenChange","closeFocusTarget","workspaceDefaultHarnessId","selectedModel","setSelectedModel","selectedHarness","setSelectedHarness","default_model","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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"}
@@ -0,0 +1,21 @@
1
+ import type { StoryObj } from '@storybook/react';
2
+ interface ModelProviderUsageModalStoryProps {
3
+ variant: 'anthropic' | 'custom' | 'long-list';
4
+ }
5
+ declare function ModelProviderUsageModalStory({ variant }: ModelProviderUsageModalStoryProps): import("react").JSX.Element;
6
+ declare const meta: {
7
+ title: string;
8
+ component: typeof ModelProviderUsageModalStory;
9
+ parameters: {
10
+ layout: string;
11
+ };
12
+ args: {
13
+ variant: "anthropic";
14
+ };
15
+ };
16
+ export default meta;
17
+ type Story = StoryObj<typeof meta>;
18
+ export declare const Playground: Story;
19
+ export declare const LongModelList: Story;
20
+ export declare const CustomProvider: Story;
21
+ //# sourceMappingURL=model-provider-usage-modal.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-provider-usage-modal.stories.d.ts","sourceRoot":"","sources":["../../src/components/model-provider-usage-modal.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAQrD,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;;;;;;;;;CAK2C,CAAC;AAEtD,eAAe,IAAI,CAAC;AACpB,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"}