@flowdular/sdk 0.1.0 → 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 (174) hide show
  1. package/.ai/skills/test-hardening/SKILL.md +1 -1
  2. package/README.md +69 -12
  3. package/assets/flowdular-banner.png +0 -0
  4. package/modules/agents/package.json +1 -1
  5. package/modules/auth/package.json +1 -1
  6. package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
  7. package/modules/auth/src/server/endpoints.ts +3 -1
  8. package/modules/auth/src/server/runtime.ts +4 -0
  9. package/modules/automations/package.json +1 -1
  10. package/modules/automations-workflows-integration/package.json +1 -1
  11. package/modules/profile/package.json +1 -1
  12. package/modules/sandbox/package.json +1 -1
  13. package/modules/system/package.json +1 -1
  14. package/modules/users/package.json +1 -1
  15. package/modules/workflows/package.json +1 -1
  16. package/package.json +5 -13
  17. package/packages/ai-provider/package.json +1 -1
  18. package/packages/cli-protocol/package.json +1 -1
  19. package/packages/client/package.json +1 -1
  20. package/packages/client/src/routing.ts +39 -2
  21. package/packages/client/src/shell/BrandMark.tsrx +5 -1
  22. package/packages/client/src/shell/navigation.ts +3 -1
  23. package/packages/client/src/state.ts +6 -2
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/contracts/schemas/project.schema.json +16 -42
  26. package/packages/database/package.json +1 -1
  27. package/packages/database-pglite/package.json +1 -1
  28. package/packages/database-testing/package.json +1 -1
  29. package/packages/dev-console/package.json +1 -1
  30. package/packages/harness/package.json +1 -1
  31. package/packages/kernel/package.json +1 -1
  32. package/packages/server/package.json +1 -1
  33. package/packages/server/src/application-routes.ts +80 -8
  34. package/packages/server/src/index.ts +4 -1
  35. package/packages/server/src/web-html.ts +9 -5
  36. package/packages/server/src/web.ts +59 -7
  37. package/packages/ui/package.json +1 -1
  38. package/packages/coding-agent/package.json +0 -44
  39. package/packages/coding-agent/src/drivers/byok.ts +0 -322
  40. package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
  41. package/packages/coding-agent/src/drivers/codex.ts +0 -281
  42. package/packages/coding-agent/src/index.ts +0 -56
  43. package/packages/coding-agent/src/registry.ts +0 -119
  44. package/packages/coding-agent/src/roles/contract.ts +0 -60
  45. package/packages/coding-agent/src/roles/defaults.ts +0 -134
  46. package/packages/coding-agent/src/roles/handoff.ts +0 -41
  47. package/packages/coding-agent/src/roles/registry.ts +0 -156
  48. package/packages/coding-agent/src/roles/skills.ts +0 -72
  49. package/packages/coding-agent/src/roles/sync.ts +0 -88
  50. package/packages/coding-agent/src/types.ts +0 -102
  51. package/packages/coding-agent/src/workspace.ts +0 -275
  52. package/packages/sandbox/README.md +0 -691
  53. package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
  54. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
  55. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
  56. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
  57. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  58. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  59. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  60. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  61. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  62. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  63. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  64. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  65. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  66. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  67. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  68. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  69. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  70. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  71. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  72. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  73. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  74. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  75. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  76. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  77. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  78. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  79. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  80. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  81. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  82. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  83. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
  84. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
  85. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
  86. package/packages/sandbox/dist/client/favicon.svg +0 -1
  87. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
  88. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
  89. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
  90. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
  91. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
  92. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
  93. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
  94. package/packages/sandbox/dist/server/entry.js +0 -91671
  95. package/packages/sandbox/dist/server/favicon.svg +0 -1
  96. package/packages/sandbox/dist/server/index.html +0 -161
  97. package/packages/sandbox/index.html +0 -159
  98. package/packages/sandbox/octane.config.ts +0 -36
  99. package/packages/sandbox/package.json +0 -83
  100. package/packages/sandbox/public/favicon.svg +0 -1
  101. package/packages/sandbox/public/og.png +0 -0
  102. package/packages/sandbox/src/App.tsrx +0 -924
  103. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
  104. package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
  105. package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
  106. package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
  107. package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
  108. package/packages/sandbox/src/client/GateResults.tsrx +0 -60
  109. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
  110. package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
  111. package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
  112. package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
  113. package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
  114. package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
  115. package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
  116. package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
  117. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
  118. package/packages/sandbox/src/client/api.ts +0 -797
  119. package/packages/sandbox/src/client/attachments.ts +0 -73
  120. package/packages/sandbox/src/client/dashboard.ts +0 -46
  121. package/packages/sandbox/src/client/github-settings.ts +0 -48
  122. package/packages/sandbox/src/client/highlight.ts +0 -105
  123. package/packages/sandbox/src/client/i18n.ts +0 -32
  124. package/packages/sandbox/src/client/locales/en.json +0 -466
  125. package/packages/sandbox/src/client/locales/pl.json +0 -466
  126. package/packages/sandbox/src/client/session-labels.ts +0 -77
  127. package/packages/sandbox/src/client/state.ts +0 -108
  128. package/packages/sandbox/src/index.ts +0 -10
  129. package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
  130. package/packages/sandbox/src/preview/load-module.ts +0 -16
  131. package/packages/sandbox/src/server/attachments.ts +0 -308
  132. package/packages/sandbox/src/server/auto-review.ts +0 -181
  133. package/packages/sandbox/src/server/checkpoints.ts +0 -90
  134. package/packages/sandbox/src/server/config.ts +0 -460
  135. package/packages/sandbox/src/server/dashboard.ts +0 -245
  136. package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
  137. package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
  138. package/packages/sandbox/src/server/delivery/index.ts +0 -83
  139. package/packages/sandbox/src/server/delivery/local.ts +0 -151
  140. package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
  141. package/packages/sandbox/src/server/delivery/plan.ts +0 -218
  142. package/packages/sandbox/src/server/delivery/policies.ts +0 -239
  143. package/packages/sandbox/src/server/delivery/record.ts +0 -41
  144. package/packages/sandbox/src/server/delivery/steps.ts +0 -464
  145. package/packages/sandbox/src/server/delivery/types.ts +0 -158
  146. package/packages/sandbox/src/server/dependencies.ts +0 -88
  147. package/packages/sandbox/src/server/diff.ts +0 -230
  148. package/packages/sandbox/src/server/gates.ts +0 -388
  149. package/packages/sandbox/src/server/index.ts +0 -139
  150. package/packages/sandbox/src/server/path-guard.ts +0 -282
  151. package/packages/sandbox/src/server/planning.ts +0 -749
  152. package/packages/sandbox/src/server/platform-client.ts +0 -189
  153. package/packages/sandbox/src/server/preview-database-host.ts +0 -340
  154. package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
  155. package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
  156. package/packages/sandbox/src/server/preview-database.ts +0 -109
  157. package/packages/sandbox/src/server/preview-ipc.ts +0 -15
  158. package/packages/sandbox/src/server/preview-modules.ts +0 -83
  159. package/packages/sandbox/src/server/preview-revision.ts +0 -44
  160. package/packages/sandbox/src/server/preview-runtime.ts +0 -467
  161. package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
  162. package/packages/sandbox/src/server/preview-worker.ts +0 -168
  163. package/packages/sandbox/src/server/reference.ts +0 -251
  164. package/packages/sandbox/src/server/routes.ts +0 -2258
  165. package/packages/sandbox/src/server/runtime.ts +0 -188
  166. package/packages/sandbox/src/server/session-owner.ts +0 -23
  167. package/packages/sandbox/src/server/sessions.ts +0 -849
  168. package/packages/sandbox/src/server/spec.ts +0 -471
  169. package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
  170. package/packages/sandbox/src/server/turns.ts +0 -993
  171. package/packages/sandbox/src/server/workspace-install.ts +0 -266
  172. package/packages/sandbox/src/server/workspace-root.ts +0 -73
  173. package/packages/sandbox/src/styles.css +0 -1573
  174. package/packages/sandbox/vite.config.ts +0 -133
@@ -1,219 +0,0 @@
1
- import { useState } from 'octane';
2
- import { Alert, Button, Drawer, FormField } from '@flowdular/sdk/ui';
3
- import { githubSettingsInput } from './github-settings.ts';
4
- import type { SafeSandboxConfiguration } from '../server/config.ts';
5
- import { useTranslation } from './i18n.ts';
6
-
7
- export interface GitHubSettingsInput {
8
- readonly githubEnabled: boolean;
9
- readonly githubOverridesProject: boolean;
10
- readonly githubRemote: string;
11
- readonly githubRepository: string;
12
- readonly githubBaseBranch: string;
13
- readonly githubBranchPrefix: string;
14
- readonly githubMode: 'auto' | 'direct' | 'fork';
15
- readonly githubForkOwner: string;
16
- readonly githubReviewers: readonly string[];
17
- readonly githubToken?: string;
18
- readonly githubClearToken?: boolean;
19
- }
20
-
21
- export interface GitHubSettingsModalProps {
22
- readonly configuration: SafeSandboxConfiguration;
23
- readonly busy: boolean;
24
- readonly error: string;
25
- readonly onSave: (input: GitHubSettingsInput) => void;
26
- readonly onClose: () => void;
27
- }
28
-
29
- export function GitHubSettingsModal(props: GitHubSettingsModalProps) @{
30
- const { t } = useTranslation();
31
- const github = props.configuration.github;
32
- const [enabled, setEnabled] = useState(github.enabled);
33
- const [custom, setCustom] = useState(github.overridesProject);
34
- const submit = (event: SubmitEvent) => {
35
- event.preventDefault();
36
- props.onSave(
37
- githubSettingsInput(
38
- new FormData(event.currentTarget as HTMLFormElement),
39
- github,
40
- ),
41
- );
42
- };
43
-
44
- <Drawer
45
- open={true}
46
- title={t('sandbox.github.title')}
47
- subtitle={t('sandbox.github.description')}
48
- width="lg"
49
- onClose={() => {
50
- if (!props.busy) props.onClose();
51
- }}
52
- >
53
- <form class="ui-drawer__form" onSubmit={submit}>
54
- <div class="ui-drawer__body">
55
- <div class="ui-form">
56
- <label class="ui-checkbox">
57
- <input
58
- type="checkbox"
59
- name="githubEnabled"
60
- checked={enabled}
61
- onChange={(event) => setEnabled(event.currentTarget.checked)}
62
- />
63
- <span>{t('sandbox.github.offer')}</span>
64
- </label>
65
- <Alert tone="info">{t('sandbox.github.safety')}</Alert>
66
- <label class="ui-checkbox">
67
- <input
68
- type="checkbox"
69
- name="githubOverridesProject"
70
- checked={custom}
71
- onChange={(event) => setCustom(event.currentTarget.checked)}
72
- />
73
- <span>{t('sandbox.github.overrideProject')}</span>
74
- </label>
75
- @if (!custom) {
76
- <p class="ui-help">{t('sandbox.github.projectHelp')}</p>
77
- } @else {
78
- <div class="ui-form__section">
79
- <FormField
80
- label={t('sandbox.github.repository.label')}
81
- help={t('sandbox.github.repository.help')}
82
- >
83
- <input
84
- class="ui-input ui-mono"
85
- name="githubRepository"
86
- defaultValue={github.repository ?? ''}
87
- placeholder={t('sandbox.github.repository.placeholder')}
88
- maxlength={200}
89
- />
90
- </FormField>
91
- <FormField
92
- label={t('sandbox.github.mode.label')}
93
- help={t('sandbox.github.mode.help')}
94
- >
95
- <select
96
- class="ui-select"
97
- name="githubMode"
98
- defaultValue={github.mode}
99
- >
100
- <option value="auto">{t('sandbox.github.mode.auto')}</option>
101
- <option value="direct">{t(
102
- 'sandbox.github.mode.direct',
103
- )}</option>
104
- <option value="fork">{t('sandbox.github.mode.fork')}</option>
105
- </select>
106
- </FormField>
107
- <FormField
108
- label={t('sandbox.github.reviewers.label')}
109
- help={t('sandbox.github.reviewers.help')}
110
- >
111
- <input
112
- class="ui-input ui-mono"
113
- name="githubReviewers"
114
- defaultValue={github.reviewers.join(', ')}
115
- maxlength={1000}
116
- />
117
- </FormField>
118
- <details>
119
- <summary class="ui-label">{t(
120
- 'sandbox.github.advanced',
121
- )}</summary>
122
- <div class="ui-form">
123
- <div class="ui-form__row">
124
- <FormField
125
- label={t('sandbox.github.remote.label')}
126
- help={t('sandbox.github.remote.help')}
127
- >
128
- <input
129
- class="ui-input ui-mono"
130
- name="githubRemote"
131
- defaultValue={github.remote}
132
- maxlength={120}
133
- />
134
- </FormField>
135
- <FormField
136
- label={t('sandbox.github.base.label')}
137
- help={t('sandbox.github.base.help')}
138
- >
139
- <input
140
- class="ui-input ui-mono"
141
- name="githubBaseBranch"
142
- defaultValue={github.baseBranch}
143
- maxlength={120}
144
- />
145
- </FormField>
146
- </div>
147
- <div class="ui-form__row">
148
- <FormField label={t('sandbox.github.prefix.label')}>
149
- <input
150
- class="ui-input ui-mono"
151
- name="githubBranchPrefix"
152
- defaultValue={github.branchPrefix}
153
- maxlength={120}
154
- />
155
- </FormField>
156
- <FormField
157
- label={t('sandbox.github.forkOwner.label')}
158
- help={t('sandbox.github.forkOwner.help')}
159
- >
160
- <input
161
- class="ui-input ui-mono"
162
- name="githubForkOwner"
163
- defaultValue={github.forkOwner ?? ''}
164
- maxlength={120}
165
- />
166
- </FormField>
167
- </div>
168
- </div>
169
- </details>
170
- </div>
171
- }
172
- <div class="ui-form__section">
173
- <div class="ui-form__section-head">
174
- <b>{t('sandbox.github.access')}</b>
175
- <small>{t('sandbox.github.accessHelp')}</small>
176
- </div>
177
- <FormField
178
- label={t('sandbox.github.token.label')}
179
- help={github.tokenFingerprint
180
- ? t('sandbox.github.token.stored', {
181
- fingerprint: github.tokenFingerprint,
182
- })
183
- : t('sandbox.github.token.help')}
184
- >
185
- <input
186
- class="ui-input ui-mono"
187
- name="githubToken"
188
- type="password"
189
- autocomplete="new-password"
190
- placeholder={t('sandbox.github.token.placeholder')}
191
- maxlength={16384}
192
- />
193
- </FormField>
194
- @if (github.tokenFingerprint) {
195
- <label class="ui-checkbox">
196
- <input type="checkbox" name="githubClearToken" />
197
- <span>{t('sandbox.github.token.remove')}</span>
198
- </label>
199
- }
200
- </div>
201
- @if (props.error) {
202
- <Alert>{props.error}</Alert>
203
- }
204
- </div>
205
- </div>
206
- <div class="ui-drawer__foot">
207
- <small>{t('sandbox.github.saveNote')}</small>
208
- <div class="ui-form__actions">
209
- <Button onClick={props.onClose} disabled={props.busy}>{t(
210
- 'sandbox.common.cancel',
211
- )}</Button>
212
- <Button type="submit" variant="primary" disabled={props.busy}>
213
- {props.busy ? t('sandbox.common.saving') : t('sandbox.github.save')}
214
- </Button>
215
- </div>
216
- </div>
217
- </form>
218
- </Drawer>
219
- }
@@ -1,214 +0,0 @@
1
- import { useEffect, useRef, useState } from 'octane';
2
- import { Button, Icon } from '@flowdular/sdk/ui';
3
- import type { DriverSummary } from './api.ts';
4
- import {
5
- ATTACH_ACCEPT,
6
- attachmentKind,
7
- attachmentRefusal,
8
- formatBytes,
9
- } from './attachments.ts';
10
- import { useTranslation } from './i18n.ts';
11
-
12
- export interface NewSessionFormProps {
13
- readonly drivers: readonly DriverSummary[];
14
- readonly driver: string;
15
- readonly busy: boolean;
16
- readonly onDriver: (driver: string) => void;
17
- readonly onCreate: (brief: string, driver: string, attachments: readonly File[]) => void;
18
- }
19
- /* A file the operator attached before the session exists. It is uploaded to
20
- the new session id right after the session is created. */
21
- interface PickedFile {
22
- readonly key: string;
23
- readonly file: File;
24
- readonly kind: 'image' | 'file';
25
- /* Object URL for an image thumbnail, empty otherwise. Revoked on removal
26
- and when the screen goes away. */
27
- readonly url: string;
28
- }
29
-
30
- export function NewSessionForm(props: NewSessionFormProps) @{
31
- const { t } = useTranslation();
32
- const offered = props.drivers.filter((driver) => driver.offered);
33
- const selected =
34
- offered.find((driver) => driver.id === props.driver) ?? offered[0] ?? null;
35
- const attachInput = useRef<HTMLInputElement | null>(null);
36
- const opened = useRef<string[]>([]);
37
- const [picked, setPicked] = useState<readonly PickedFile[]>([]);
38
- const [attachError, setAttachError] = useState('');
39
-
40
- /* Thumbnails hold object URLs; nothing else releases them, so the screen
41
- does it when it goes away. */
42
- useEffect(
43
- () => () => {
44
- for (const url of opened.current) URL.revokeObjectURL(url);
45
- },
46
- [],
47
- );
48
-
49
- const addFiles = (chosen: readonly File[]) => {
50
- const next = [...picked];
51
- let refusal = '';
52
- for (const file of chosen) {
53
- const reason = attachmentRefusal(file, next.length);
54
- if (reason) {
55
- refusal = reason;
56
- continue;
57
- }
58
- const kind = attachmentKind(file);
59
- const url =
60
- kind === 'image' ? URL.createObjectURL(file) : '';
61
- if (url) opened.current.push(url);
62
- next.push({
63
- key: `${file.name}-${Date.now()}-${Math.random().toString(36).slice(
64
- 2,
65
- 8,
66
- )}`,
67
- file,
68
- kind,
69
- url,
70
- });
71
- }
72
- setPicked(next);
73
- setAttachError(refusal);
74
- };
75
-
76
- const removeFile = (key: string) => {
77
- const found = picked.find((item) => item.key === key);
78
- if (found?.url) URL.revokeObjectURL(found.url);
79
- setPicked(picked.filter((item) => item.key !== key));
80
- };
81
-
82
- const onPaste = (event: ClipboardEvent) => {
83
- const files = event.clipboardData?.files;
84
- if (!files || files.length === 0) return;
85
- const images = Array.from(files).filter(
86
- (file) => file.type.startsWith('image/'),
87
- );
88
- if (images.length === 0) return;
89
- event.preventDefault();
90
- addFiles(images);
91
- };
92
-
93
- const onPick = (event: Event & { currentTarget: HTMLInputElement }) => {
94
- const chosen = event.currentTarget.files;
95
- if (chosen && chosen.length > 0) addFiles(Array.from(chosen));
96
- event.currentTarget.value = '';
97
- };
98
-
99
- const submit = (event: SubmitEvent) => {
100
- event.preventDefault();
101
- const form = new FormData(event.currentTarget as HTMLFormElement);
102
- const brief = form.get('brief');
103
- if (typeof brief === 'string' && brief.trim().length > 7 && selected) {
104
- props.onCreate(
105
- brief.trim(),
106
- selected.id,
107
- picked.map((item) => item.file),
108
- );
109
- }
110
- };
111
-
112
- <form class="ui-drawer__form" onSubmit={submit}>
113
- <div class="ui-drawer__body">
114
- <div class="ui-form">
115
- @if (picked.length > 0 || attachError) {
116
- <div class="chat__attachments">
117
- @for (const item of picked; key item.key) {
118
- <div class="chat__attachment">
119
- @if (item.kind === 'image') {
120
- <img
121
- class="chat__attachment-thumb"
122
- src={item.url}
123
- alt={item.file.name}
124
- />
125
- } @else {
126
- <span
127
- class="chat__attachment-thumb chat__attachment-thumb--file"
128
- >
129
- <Icon name="file-text" size={18} />
130
- </span>
131
- }
132
- <span class="chat__attachment-info">
133
- <span class="chat__attachment-name">{item.file.name}</span>
134
- <span class="chat__attachment-size">{formatBytes(
135
- item.file.size,
136
- )}</span>
137
- </span>
138
- <button
139
- class="chat__attachment-remove"
140
- type="button"
141
- aria-label={t('sandbox.attachments.remove', {
142
- name: item.file.name,
143
- })}
144
- onClick={() => removeFile(item.key)}
145
- >
146
- <Icon name="x" size={12} />
147
- </button>
148
- </div>
149
- }
150
- @if (attachError) {
151
- <span class="chat__attachment-error">{attachError}</span>
152
- }
153
- </div>
154
- }
155
- <textarea
156
- class="ui-textarea"
157
- aria-label={t('sandbox.sessions.brief.placeholder')}
158
- minlength={8}
159
- maxlength={20000}
160
- name="brief"
161
- rows={4}
162
- placeholder={t('sandbox.sessions.brief.placeholder')}
163
- required
164
- onPaste={onPaste}
165
- ></textarea>
166
- </div>
167
- </div>
168
- <div class="ui-drawer__foot">
169
- <button
170
- class="chat__attach"
171
- type="button"
172
- aria-label={t('sandbox.attachments.action')}
173
- title={t('sandbox.attachments.help')}
174
- onClick={() => attachInput.current?.click()}
175
- >
176
- <Icon name="plus" size={16} />
177
- </button>
178
- <input
179
- ref={attachInput}
180
- class="chat__attach-input"
181
- type="file"
182
- multiple
183
- accept={ATTACH_ACCEPT}
184
- onChange={onPick}
185
- />
186
- <select
187
- class="chat__picker"
188
- aria-label={t('sandbox.sessions.driver.label')}
189
- value={selected?.id ?? ''}
190
- disabled={offered.length === 0}
191
- onChange={(event) => props.onDriver(event.currentTarget.value)}
192
- >
193
- @for (const driver of offered; key driver.id) {
194
- <option value={driver.id}>{driver.label}</option>
195
- }
196
- </select>
197
- <span class="sandbox__spacer"></span>
198
- <Button
199
- size="sm"
200
- variant="primary"
201
- type="submit"
202
- disabled={props.busy || offered.length === 0}
203
- >
204
- <Icon name="play" size={14} />
205
- {props.busy
206
- ? t('sandbox.sessions.planning')
207
- : t('sandbox.sessions.start')}
208
- </Button>
209
- </div>
210
- <p class="ui-note">
211
- {selected ? selected.description : t('sandbox.sessions.driver.none')}
212
- </p>
213
- </form>
214
- }
@@ -1,151 +0,0 @@
1
- import { useState } from 'octane';
2
- import { Button, Icon, Tag } from '@flowdular/sdk/ui';
3
- import type { SandboxSession } from '../server/sessions.ts';
4
- import type { WorkspaceModuleSummary } from './api.ts';
5
- import { useTranslation } from './i18n.ts';
6
-
7
- export interface SessionBarProps {
8
- readonly session: SandboxSession;
9
- readonly changes: number;
10
- readonly busy: boolean;
11
- /* The module the panes show; empty means every module of the session. */
12
- readonly activeModule: string;
13
- /* Every module of the workspace, so the session can take another one in. */
14
- readonly workspaceModules: readonly WorkspaceModuleSummary[];
15
- readonly onModule: (directory: string) => void;
16
- readonly onAddModule: (moduleId: string) => void;
17
- readonly onBack: () => void;
18
- readonly onGates: () => void;
19
- readonly onChanges: () => void;
20
- readonly onPreview: () => void;
21
- readonly onEject: () => void;
22
- }
23
-
24
- function stateTone(
25
- state: SandboxSession['state'],
26
- ): 'success' | 'warning' | 'danger' | 'info' | 'neutral' {
27
- if (state === 'accepted') return 'success';
28
- if (state === 'failed' || state === 'blocked') return 'danger';
29
- if (state === 'awaiting-approval' || state === 'validating') return 'warning';
30
- if (state === 'draft') return 'neutral';
31
- return 'info';
32
- }
33
-
34
- /* Everything here belongs to one module session. The application bar above
35
- stays global, so a session action can never look like a workspace action. */
36
- export function SessionBar(props: SessionBarProps) @{
37
- const { t } = useTranslation();
38
- const delivered = Boolean(props.session.ejectedAt);
39
- const modules = props.session.modules;
40
- const held = new Set(modules.map((module) => module.id));
41
- const candidates = props.workspaceModules.filter(
42
- (module) => !held.has(module.id),
43
- );
44
- const [adding, setAdding] = useState(false);
45
- const [chosen, setChosen] = useState('');
46
-
47
- <div class="session-bar">
48
- <button class="session-bar__back" type="button" onClick={props.onBack}>
49
- <Icon name="chevron-down" size={14} />
50
- {t('sandbox.session.back')}
51
- </button>
52
- <span class="session-bar__title">
53
- <b>{props.session.title}</b>
54
- <small class="ui-mono">
55
- {modules.length === 1
56
- ? props.session.moduleId
57
- : t('sandbox.session.modules', { count: modules.length })}
58
- </small>
59
- </span>
60
- <Tag tone={stateTone(props.session.state)} dot>
61
- {delivered
62
- ? t('sandbox.session.state.delivered')
63
- : t('sandbox.session.state.' + props.session.state)}
64
- </Tag>
65
- @if (modules.length > 1) {
66
- <select
67
- class="chat__picker"
68
- aria-label={t('sandbox.session.module.visible')}
69
- value={props.activeModule}
70
- onChange={(event) => props.onModule(event.currentTarget.value)}
71
- >
72
- <option value="">{t('sandbox.session.module.all')}</option>
73
- @for (const module of modules; key module.id) {
74
- <option value={module.directory}>{module.id}</option>
75
- }
76
- </select>
77
- }
78
- @if (adding) {
79
- <>
80
- <select
81
- class="chat__picker"
82
- aria-label={t('sandbox.session.module.addLabel')}
83
- value={chosen}
84
- onChange={(event) => setChosen(event.currentTarget.value)}
85
- >
86
- <option value="">{t('sandbox.session.module.choose')}</option>
87
- @for (const module of candidates; key module.id) {
88
- <option value={module.id}>{module.id}</option>
89
- }
90
- </select>
91
- <Button
92
- size="sm"
93
- variant="primary"
94
- disabled={props.busy || !chosen}
95
- onClick={() => {
96
- props.onAddModule(chosen);
97
- setChosen('');
98
- setAdding(false);
99
- }}
100
- >{t('sandbox.common.add')}</Button>
101
- <Button size="sm" variant="ghost" onClick={() => setAdding(false)}>{t(
102
- 'sandbox.common.cancel',
103
- )}</Button>
104
- </>
105
- } @else if (!delivered && candidates.length > 0) {
106
- <button
107
- class="session-bar__add"
108
- type="button"
109
- disabled={props.busy}
110
- onClick={() => setAdding(true)}
111
- >
112
- <Icon name="plus" size={12} />
113
- {t('sandbox.session.module.add')}
114
- </button>
115
- }
116
- <span class="sandbox__spacer"></span>
117
- <Button size="sm" variant="ghost" onClick={props.onPreview}>
118
- <Icon name="play" size={14} />
119
- {t('sandbox.work.preview')}
120
- </Button>
121
- <Button
122
- size="sm"
123
- variant="ghost"
124
- onClick={props.onGates}
125
- disabled={props.busy}
126
- >
127
- <Icon name="check" size={14} />
128
- {t('sandbox.session.gates')}
129
- </Button>
130
- <Button
131
- size="sm"
132
- variant="ghost"
133
- onClick={props.onChanges}
134
- disabled={props.changes === 0}
135
- >
136
- <Icon name="code" size={14} />
137
- {t('sandbox.session.changes', { count: props.changes })}
138
- </Button>
139
- <Button
140
- size="sm"
141
- variant={delivered ? 'ghost' : 'secondary'}
142
- onClick={props.onEject}
143
- disabled={props.busy || props.changes === 0}
144
- >
145
- <Icon name={delivered ? 'check' : 'download'} size={14} />
146
- {delivered
147
- ? t('sandbox.session.deliverAgain')
148
- : t('sandbox.session.eject')}
149
- </Button>
150
- </div>
151
- }