@bamptee/aia-code 1.0.4 → 2.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/README.md +16 -1
- package/package.json +1 -1
- package/src/cli.js +9 -1
- package/src/commands/iterate.js +26 -0
- package/src/prompt-builder.js +7 -1
- package/src/services/runner.js +3 -3
- package/src/ui/api/constants.js +11 -0
- package/src/ui/api/features.js +29 -0
- package/src/ui/public/components/feature-detail.js +104 -53
package/README.md
CHANGED
|
@@ -35,10 +35,12 @@ Each CLI manages its own authentication. Run `claude`, `codex`, or `gemini` once
|
|
|
35
35
|
| `aia next <feature> [description]` | Run the next pending step automatically |
|
|
36
36
|
| `aia status <feature>` | Show the current status of a feature |
|
|
37
37
|
| `aia reset <step> <feature>` | Reset a step to pending so it can be re-run |
|
|
38
|
+
| `aia iterate <step> <feature> <instructions>` | Re-run a step with additional instructions to refine the output |
|
|
38
39
|
| `aia quick <name> [description]` | Quick story/ticket: dev-plan → implement → review only |
|
|
39
40
|
| `aia repo scan` | Scan codebase and generate `repo-map.json` |
|
|
41
|
+
| `aia ui` | Launch the local web UI to manage features and config |
|
|
40
42
|
|
|
41
|
-
### Options for `run`, `next`, and `
|
|
43
|
+
### Options for `run`, `next`, `quick`, and `iterate`
|
|
42
44
|
|
|
43
45
|
| Flag | Description |
|
|
44
46
|
|------|-------------|
|
|
@@ -281,6 +283,18 @@ aia reset tech-spec session-replay
|
|
|
281
283
|
aia run tech-spec session-replay "Add WebSocket support and rate limiting"
|
|
282
284
|
```
|
|
283
285
|
|
|
286
|
+
#### Iterating on a step
|
|
287
|
+
|
|
288
|
+
Use `aia iterate` to refine a completed step with specific instructions. It resets the step, feeds back the previous output, and applies your instructions in a single command:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
aia iterate tech-spec session-replay "Add error handling for WebSocket disconnections"
|
|
292
|
+
aia iterate brief session-replay "Focus more on mobile use cases"
|
|
293
|
+
aia iterate dev-plan session-replay "Split the implementation into smaller PRs" -v
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
You can iterate multiple times — each run builds on the previous output.
|
|
297
|
+
|
|
284
298
|
#### Quick mode (stories & tickets)
|
|
285
299
|
|
|
286
300
|
For small stories or tickets that don't need the full 8-step pipeline, use `aia quick`. It skips brief, ba-spec, questions, tech-spec, and challenge, and runs only **dev-plan → implement → review**:
|
|
@@ -419,6 +433,7 @@ src/
|
|
|
419
433
|
feature.js # aia feature <name>
|
|
420
434
|
run.js # aia run <step> <feature>
|
|
421
435
|
next.js # aia next <feature>
|
|
436
|
+
iterate.js # aia iterate <step> <feature> <instructions>
|
|
422
437
|
quick.js # aia quick <name> [description]
|
|
423
438
|
status.js # aia status <feature>
|
|
424
439
|
reset.js # aia reset <step> <feature>
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
2
8
|
import { registerInitCommand } from './commands/init.js';
|
|
3
9
|
import { registerFeatureCommand } from './commands/feature.js';
|
|
4
10
|
import { registerRunCommand } from './commands/run.js';
|
|
@@ -7,6 +13,7 @@ import { registerStatusCommand } from './commands/status.js';
|
|
|
7
13
|
import { registerResetCommand } from './commands/reset.js';
|
|
8
14
|
import { registerNextCommand } from './commands/next.js';
|
|
9
15
|
import { registerQuickCommand } from './commands/quick.js';
|
|
16
|
+
import { registerIterateCommand } from './commands/iterate.js';
|
|
10
17
|
import { registerUiCommand } from './commands/ui.js';
|
|
11
18
|
|
|
12
19
|
export function createCli() {
|
|
@@ -15,7 +22,7 @@ export function createCli() {
|
|
|
15
22
|
program
|
|
16
23
|
.name('aia')
|
|
17
24
|
.description('AI Architecture Assistant')
|
|
18
|
-
.version(
|
|
25
|
+
.version(pkg.version);
|
|
19
26
|
|
|
20
27
|
registerInitCommand(program);
|
|
21
28
|
registerFeatureCommand(program);
|
|
@@ -25,6 +32,7 @@ export function createCli() {
|
|
|
25
32
|
registerRepoCommand(program);
|
|
26
33
|
registerStatusCommand(program);
|
|
27
34
|
registerResetCommand(program);
|
|
35
|
+
registerIterateCommand(program);
|
|
28
36
|
registerUiCommand(program);
|
|
29
37
|
|
|
30
38
|
return program;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { resetStep } from '../services/status.js';
|
|
3
|
+
import { runStep } from '../services/runner.js';
|
|
4
|
+
|
|
5
|
+
export function registerIterateCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('iterate <step> <feature> <instructions>')
|
|
8
|
+
.description('Re-run a step with additional instructions to refine the output')
|
|
9
|
+
.option('-v, --verbose', 'Show AI thinking/tool usage')
|
|
10
|
+
.option('-a, --apply', 'Force agent mode (file editing)')
|
|
11
|
+
.action(async (step, feature, instructions, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
await resetStep(feature, step);
|
|
14
|
+
console.log(chalk.gray(`Reset "${step}" — iterating with new instructions...`));
|
|
15
|
+
|
|
16
|
+
await runStep(step, feature, {
|
|
17
|
+
instructions,
|
|
18
|
+
verbose: opts.verbose,
|
|
19
|
+
apply: opts.apply,
|
|
20
|
+
});
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(chalk.red(err.message));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
package/src/prompt-builder.js
CHANGED
|
@@ -77,7 +77,7 @@ async function loadPromptTemplate(step, root) {
|
|
|
77
77
|
return content;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export async function buildPrompt(feature, step, { description, root = process.cwd() } = {}) {
|
|
80
|
+
export async function buildPrompt(feature, step, { description, instructions, root = process.cwd() } = {}) {
|
|
81
81
|
const config = await loadConfig(root);
|
|
82
82
|
|
|
83
83
|
const [context, knowledgeCategories, initSpecs, featureContent, previousOutput, task] = await Promise.all([
|
|
@@ -127,6 +127,12 @@ export async function buildPrompt(feature, step, { description, root = process.c
|
|
|
127
127
|
parts.push('\n\nThe above is a previous version of this step. Rewrite it incorporating any new information, answers to questions, and improvements.');
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
if (instructions) {
|
|
131
|
+
parts.push('\n\n=== ITERATION INSTRUCTIONS ===\n');
|
|
132
|
+
parts.push('Apply the following changes/feedback to the previous output:\n');
|
|
133
|
+
parts.push(instructions);
|
|
134
|
+
}
|
|
135
|
+
|
|
130
136
|
parts.push('\n\n=== TASK ===\n');
|
|
131
137
|
parts.push(task);
|
|
132
138
|
|
package/src/services/runner.js
CHANGED
|
@@ -8,7 +8,7 @@ import { callModel } from './model-call.js';
|
|
|
8
8
|
import { loadStatus, updateStepStatus } from './status.js';
|
|
9
9
|
import { logExecution } from '../logger.js';
|
|
10
10
|
|
|
11
|
-
export async function runStep(step, feature, { description, verbose = false, apply = false, root = process.cwd(), onData } = {}) {
|
|
11
|
+
export async function runStep(step, feature, { description, instructions, model: modelOverride, verbose = false, apply = false, root = process.cwd(), onData } = {}) {
|
|
12
12
|
if (!FEATURE_STEPS.includes(step)) {
|
|
13
13
|
throw new Error(`Unknown step "${step}". Valid steps: ${FEATURE_STEPS.join(', ')}`);
|
|
14
14
|
}
|
|
@@ -26,8 +26,8 @@ export async function runStep(step, feature, { description, verbose = false, app
|
|
|
26
26
|
const shouldApply = apply || APPLY_STEPS.has(step);
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
const model = await resolveModel(step, root);
|
|
30
|
-
const prompt = await buildPrompt(feature, step, { description, root });
|
|
29
|
+
const model = modelOverride || await resolveModel(step, root);
|
|
30
|
+
const prompt = await buildPrompt(feature, step, { description, instructions, root });
|
|
31
31
|
|
|
32
32
|
const start = performance.now();
|
|
33
33
|
const output = await callModel(model, prompt, { verbose, apply: shouldApply, onData });
|
package/src/ui/api/constants.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { FEATURE_STEPS, STEP_STATUS, QUICK_STEPS } from '../../constants.js';
|
|
2
|
+
import { loadConfig } from '../../models.js';
|
|
2
3
|
import { json } from '../router.js';
|
|
3
4
|
|
|
4
5
|
export function registerConstantsRoutes(router) {
|
|
5
6
|
router.get('/api/constants', (req, res) => {
|
|
6
7
|
json(res, { FEATURE_STEPS, STEP_STATUS, QUICK_STEPS });
|
|
7
8
|
});
|
|
9
|
+
|
|
10
|
+
// List available models per step from config
|
|
11
|
+
router.get('/api/models', async (req, res, { root }) => {
|
|
12
|
+
try {
|
|
13
|
+
const config = await loadConfig(root);
|
|
14
|
+
json(res, config.models || {});
|
|
15
|
+
} catch (err) {
|
|
16
|
+
json(res, {});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
8
19
|
}
|
package/src/ui/api/features.js
CHANGED
|
@@ -97,6 +97,7 @@ export function registerFeatureRoutes(router) {
|
|
|
97
97
|
try {
|
|
98
98
|
const output = await runStep(params.step, params.name, {
|
|
99
99
|
description: body.description,
|
|
100
|
+
model: body.model || undefined,
|
|
100
101
|
verbose: true,
|
|
101
102
|
apply: body.apply || false,
|
|
102
103
|
root,
|
|
@@ -157,6 +158,34 @@ export function registerFeatureRoutes(router) {
|
|
|
157
158
|
res.end();
|
|
158
159
|
});
|
|
159
160
|
|
|
161
|
+
// Iterate a step with SSE streaming (reset + re-run with instructions)
|
|
162
|
+
router.post('/api/features/:name/iterate/:step', async (req, res, { params, root, parseBody }) => {
|
|
163
|
+
const body = await parseBody();
|
|
164
|
+
sseHeaders(res);
|
|
165
|
+
sseSend(res, 'status', { step: params.step, status: 'iterating' });
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await resetStep(params.name, params.step, root);
|
|
169
|
+
|
|
170
|
+
const onData = ({ type, text }) => {
|
|
171
|
+
try { sseSend(res, 'log', { type, text }); } catch {}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const output = await runStep(params.step, params.name, {
|
|
175
|
+
instructions: body.instructions,
|
|
176
|
+
model: body.model || undefined,
|
|
177
|
+
verbose: true,
|
|
178
|
+
apply: body.apply || false,
|
|
179
|
+
root,
|
|
180
|
+
onData,
|
|
181
|
+
});
|
|
182
|
+
sseSend(res, 'done', { step: params.step });
|
|
183
|
+
} catch (err) {
|
|
184
|
+
sseSend(res, 'error', { message: err.message });
|
|
185
|
+
}
|
|
186
|
+
res.end();
|
|
187
|
+
});
|
|
188
|
+
|
|
160
189
|
// Reset a step
|
|
161
190
|
router.post('/api/features/:name/reset/:step', async (req, res, { params, root }) => {
|
|
162
191
|
try {
|
|
@@ -138,75 +138,126 @@ function LogViewer({ logs }) {
|
|
|
138
138
|
}, logs.join(''));
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
function
|
|
141
|
+
function ModelSelect({ step, model, onChange, disabled }) {
|
|
142
|
+
const [models, setModels] = React.useState([]);
|
|
143
|
+
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
api.get('/models').then(data => {
|
|
146
|
+
const stepModels = data[step] || [];
|
|
147
|
+
setModels(stepModels.map(m => m.model));
|
|
148
|
+
}).catch(() => {});
|
|
149
|
+
}, [step]);
|
|
150
|
+
|
|
151
|
+
if (models.length <= 1) return null;
|
|
152
|
+
|
|
153
|
+
return React.createElement('select', {
|
|
154
|
+
value: model,
|
|
155
|
+
onChange: e => onChange(e.target.value),
|
|
156
|
+
disabled,
|
|
157
|
+
className: 'bg-aia-card border border-aia-border rounded px-2 py-1 text-xs text-slate-300 focus:border-aia-accent focus:outline-none',
|
|
158
|
+
},
|
|
159
|
+
React.createElement('option', { value: '' }, 'Model: auto (weighted)'),
|
|
160
|
+
...models.map(m => React.createElement('option', { key: m, value: m }, m)),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function RunPanel({ name, step, stepStatus, onDone }) {
|
|
165
|
+
const isDone = stepStatus === 'done';
|
|
142
166
|
const [description, setDescription] = React.useState('');
|
|
167
|
+
const [instructions, setInstructions] = React.useState('');
|
|
168
|
+
const [model, setModel] = React.useState('');
|
|
143
169
|
const [apply, setApply] = React.useState(false);
|
|
144
170
|
const [running, setRunning] = React.useState(false);
|
|
145
171
|
const [result, setResult] = React.useState(null);
|
|
146
172
|
const [err, setErr] = React.useState(null);
|
|
147
173
|
const [logs, setLogs] = React.useState([]);
|
|
148
174
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
setLogs([]);
|
|
175
|
+
const sseCallbacks = {
|
|
176
|
+
onLog: (text) => setLogs(prev => [...prev, text]),
|
|
177
|
+
onStatus: (data) => setLogs(prev => [...prev, `[${data.status}] ${data.step || ''}\n`]),
|
|
178
|
+
};
|
|
154
179
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
async function run() {
|
|
181
|
+
setRunning(true); setResult(null); setErr(null); setLogs([]);
|
|
182
|
+
const res = await streamPost(`/features/${name}/run/${step}`, { description, apply, model: model || undefined }, sseCallbacks);
|
|
183
|
+
if (res.ok) { setResult('Step completed.'); if (onDone) onDone(); }
|
|
184
|
+
else setErr(res.error);
|
|
185
|
+
setRunning(false);
|
|
186
|
+
}
|
|
159
187
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
188
|
+
async function iterate() {
|
|
189
|
+
setRunning(true); setResult(null); setErr(null); setLogs([]);
|
|
190
|
+
const res = await streamPost(`/features/${name}/iterate/${step}`, { instructions, apply, model: model || undefined }, sseCallbacks);
|
|
191
|
+
if (res.ok) { setResult('Iteration completed.'); setInstructions(''); if (onDone) onDone(); }
|
|
192
|
+
else setErr(res.error);
|
|
166
193
|
setRunning(false);
|
|
167
194
|
}
|
|
168
195
|
|
|
169
196
|
async function reset() {
|
|
170
|
-
try {
|
|
171
|
-
|
|
172
|
-
if (onDone) onDone();
|
|
173
|
-
} catch (e) {
|
|
174
|
-
setErr(e.message);
|
|
175
|
-
}
|
|
197
|
+
try { await api.post(`/features/${name}/reset/${step}`); if (onDone) onDone(); }
|
|
198
|
+
catch (e) { setErr(e.message); }
|
|
176
199
|
}
|
|
177
200
|
|
|
178
|
-
return React.createElement('div', { className: '
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
React.createElement('div', { className: 'flex items-center gap-4' },
|
|
189
|
-
React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
|
|
190
|
-
React.createElement('input', {
|
|
191
|
-
type: 'checkbox',
|
|
192
|
-
checked: apply,
|
|
193
|
-
onChange: e => setApply(e.target.checked),
|
|
194
|
-
disabled: running,
|
|
195
|
-
className: 'rounded',
|
|
196
|
-
}),
|
|
197
|
-
'Agent mode (--apply)'
|
|
198
|
-
),
|
|
199
|
-
React.createElement('button', {
|
|
200
|
-
onClick: run,
|
|
201
|
+
return React.createElement('div', { className: 'space-y-3' },
|
|
202
|
+
|
|
203
|
+
// --- Run block (when step is not done) ---
|
|
204
|
+
!isDone && React.createElement('div', { className: 'bg-slate-900 border border-aia-border rounded p-4 space-y-3' },
|
|
205
|
+
React.createElement('h4', { className: 'text-sm font-semibold text-emerald-400' }, `Run: ${step}`),
|
|
206
|
+
React.createElement('input', {
|
|
207
|
+
type: 'text',
|
|
208
|
+
value: description,
|
|
209
|
+
onChange: e => setDescription(e.target.value),
|
|
210
|
+
placeholder: 'Optional description...',
|
|
201
211
|
disabled: running,
|
|
202
|
-
className: '
|
|
203
|
-
}
|
|
204
|
-
React.createElement('
|
|
205
|
-
|
|
212
|
+
className: 'w-full bg-aia-card border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-emerald-400 focus:outline-none',
|
|
213
|
+
}),
|
|
214
|
+
React.createElement('div', { className: 'flex items-center gap-4 flex-wrap' },
|
|
215
|
+
React.createElement(ModelSelect, { step, model, onChange: setModel, disabled: running }),
|
|
216
|
+
React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
|
|
217
|
+
React.createElement('input', { type: 'checkbox', checked: apply, onChange: e => setApply(e.target.checked), disabled: running, className: 'rounded' }),
|
|
218
|
+
'Agent mode (--apply)'
|
|
219
|
+
),
|
|
220
|
+
React.createElement('button', {
|
|
221
|
+
onClick: run, disabled: running,
|
|
222
|
+
className: 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 rounded px-4 py-1.5 text-sm hover:bg-emerald-500/30 disabled:opacity-40',
|
|
223
|
+
}, running ? 'Running...' : 'Run Step'),
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
// --- Iterate block (when step is done) ---
|
|
228
|
+
isDone && React.createElement('div', { className: 'bg-slate-900 border border-violet-500/30 rounded p-4 space-y-3' },
|
|
229
|
+
React.createElement('div', { className: 'flex items-center justify-between' },
|
|
230
|
+
React.createElement('h4', { className: 'text-sm font-semibold text-violet-400' }, `Iterate: ${step}`),
|
|
231
|
+
React.createElement('span', { className: 'text-xs bg-emerald-500/20 text-emerald-400 px-2 py-0.5 rounded' }, 'done'),
|
|
232
|
+
),
|
|
233
|
+
React.createElement('p', { className: 'text-xs text-slate-500' }, 'The previous output will be used as base. Describe what to change below.'),
|
|
234
|
+
React.createElement('label', { className: 'text-xs font-medium text-slate-400 block' }, 'Iteration instructions'),
|
|
235
|
+
React.createElement('textarea', {
|
|
236
|
+
value: instructions,
|
|
237
|
+
onChange: e => setInstructions(e.target.value),
|
|
238
|
+
placeholder: 'e.g. "Add error handling for edge cases", "Focus more on mobile", "Split into smaller functions"...',
|
|
206
239
|
disabled: running,
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
rows: 3,
|
|
241
|
+
className: 'w-full bg-aia-card border border-aia-border rounded px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:border-violet-400 focus:outline-none resize-y',
|
|
242
|
+
}),
|
|
243
|
+
React.createElement('div', { className: 'flex items-center gap-4 flex-wrap' },
|
|
244
|
+
React.createElement(ModelSelect, { step, model, onChange: setModel, disabled: running }),
|
|
245
|
+
React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
|
|
246
|
+
React.createElement('input', { type: 'checkbox', checked: apply, onChange: e => setApply(e.target.checked), disabled: running, className: 'rounded' }),
|
|
247
|
+
'Agent mode (--apply)'
|
|
248
|
+
),
|
|
249
|
+
React.createElement('button', {
|
|
250
|
+
onClick: iterate, disabled: running || !instructions.trim(),
|
|
251
|
+
className: 'bg-violet-500/20 text-violet-400 border border-violet-500/30 rounded px-4 py-1.5 text-sm hover:bg-violet-500/30 disabled:opacity-40',
|
|
252
|
+
}, running ? 'Iterating...' : 'Iterate'),
|
|
253
|
+
React.createElement('button', {
|
|
254
|
+
onClick: reset, disabled: running,
|
|
255
|
+
className: 'text-slate-500 hover:text-slate-300 text-xs',
|
|
256
|
+
}, 'Reset to pending'),
|
|
257
|
+
),
|
|
209
258
|
),
|
|
259
|
+
|
|
260
|
+
// --- Shared log viewer + results ---
|
|
210
261
|
React.createElement(LogViewer, { logs }),
|
|
211
262
|
result && React.createElement('p', { className: 'text-emerald-400 text-xs' }, result),
|
|
212
263
|
err && React.createElement('p', { className: 'text-red-400 text-xs' }, err),
|
|
@@ -258,8 +309,8 @@ export function FeatureDetail({ name }) {
|
|
|
258
309
|
)
|
|
259
310
|
),
|
|
260
311
|
|
|
261
|
-
// Run panel
|
|
262
|
-
activeStep && React.createElement(RunPanel, { name, step: activeStep, onDone: load }),
|
|
312
|
+
// Run / Iterate panel
|
|
313
|
+
activeStep && React.createElement(RunPanel, { name, step: activeStep, stepStatus: steps[activeStep], onDone: load }),
|
|
263
314
|
|
|
264
315
|
// File tabs
|
|
265
316
|
React.createElement('div', { className: 'flex gap-1 border-b border-aia-border' },
|