@outputai/cli 0.4.1-next.ae3ab85.0 → 0.4.1-next.d085dde.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/dist/api/generated/api.d.ts +21 -23
- package/dist/api/generated/api.js +0 -4
- package/dist/assets/docker/docker-compose-dev.yml +1 -4
- package/dist/generated/framework_version.json +1 -1
- package/dist/utils/format_workflow_result.spec.js +4 -0
- package/dist/views/dev/chrome/footer.d.ts +5 -4
- package/dist/views/dev/chrome/footer.js +12 -2
- package/dist/views/dev/chrome/header.d.ts +2 -1
- package/dist/views/dev/chrome/header.js +18 -47
- package/dist/views/dev/chrome/header.spec.js +6 -46
- package/dist/views/dev/chrome/layout_heights.spec.d.ts +1 -0
- package/dist/views/dev/chrome/layout_heights.spec.js +19 -0
- package/dist/views/dev/chrome/search_bar.d.ts +1 -1
- package/dist/views/dev/chrome/search_bar.js +10 -11
- package/dist/views/dev/chrome/tab_bar.d.ts +8 -1
- package/dist/views/dev/chrome/tab_bar.js +16 -2
- package/dist/views/dev/chrome/toasts.d.ts +1 -0
- package/dist/views/dev/chrome/toasts.js +8 -4
- package/dist/views/dev/components/content_title.d.ts +6 -0
- package/dist/views/dev/components/content_title.js +7 -0
- package/dist/views/dev/components/docker_service_status.d.ts +12 -0
- package/dist/views/dev/components/docker_service_status.js +19 -0
- package/dist/views/dev/components/inline_snippet.d.ts +4 -0
- package/dist/views/dev/components/inline_snippet.js +3 -0
- package/dist/views/dev/components/master_detail_panel.d.ts +9 -8
- package/dist/views/dev/components/master_detail_panel.js +8 -5
- package/dist/views/dev/components/run_info_sidebar.d.ts +7 -0
- package/dist/views/dev/components/run_info_sidebar.js +19 -0
- package/dist/views/dev/components/workflow_status.d.ts +12 -0
- package/dist/views/dev/components/workflow_status.js +19 -0
- package/dist/views/dev/dev_app.js +107 -31
- package/dist/views/dev/hooks/use_run_detail.js +6 -9
- package/dist/views/dev/hooks/use_run_detail.spec.js +7 -0
- package/dist/views/dev/modals/expanded_json_modal.js +5 -6
- package/dist/views/dev/modals/modal_frame.d.ts +13 -0
- package/dist/views/dev/modals/modal_frame.js +13 -0
- package/dist/views/dev/modals/run_modal.js +23 -13
- package/dist/views/dev/{panels/run_detail_view.d.ts → modals/steps_modal.d.ts} +2 -1
- package/dist/views/dev/modals/steps_modal.js +102 -0
- package/dist/views/dev/panels/help_panel.d.ts +14 -0
- package/dist/views/dev/panels/help_panel.js +19 -21
- package/dist/views/dev/panels/runs_panel.d.ts +6 -2
- package/dist/views/dev/panels/runs_panel.js +82 -83
- package/dist/views/dev/panels/runs_panel.spec.js +1 -28
- package/dist/views/dev/panels/services_panel.d.ts +6 -0
- package/dist/views/dev/panels/services_panel.js +53 -62
- package/dist/views/dev/panels/workflows_panel.d.ts +6 -0
- package/dist/views/dev/panels/workflows_panel.js +21 -29
- package/dist/views/dev/panels/workflows_panel.spec.d.ts +1 -0
- package/dist/views/dev/panels/workflows_panel.spec.js +39 -0
- package/dist/views/dev/state/ui_state.d.ts +7 -3
- package/dist/views/dev/state/ui_state.js +23 -6
- package/dist/views/dev/utils/constants.d.ts +2 -2
- package/dist/views/dev/utils/constants.js +2 -2
- package/dist/views/dev/utils/json_editor.js +3 -3
- package/dist/views/dev/utils/json_render.d.ts +2 -0
- package/dist/views/dev/utils/json_render.js +48 -6
- package/dist/views/dev/utils/json_render.spec.js +9 -1
- package/dist/views/dev/utils/panel_helpers.d.ts +15 -0
- package/dist/views/dev/utils/panel_helpers.js +30 -0
- package/dist/views/dev/utils/panel_helpers.spec.js +46 -1
- package/package.json +4 -4
- package/dist/components/status_icon.d.ts +0 -11
- package/dist/components/status_icon.js +0 -25
- package/dist/views/dev/chrome/divider.d.ts +0 -8
- package/dist/views/dev/chrome/divider.js +0 -16
- package/dist/views/dev/panels/run_detail_view.js +0 -112
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { tokenizeLine, formatJsonText, countJsonLines } from './json_render.js';
|
|
2
|
+
import { tokenizeLine, formatJsonText, countJsonLines, wrapTokens } from './json_render.js';
|
|
3
3
|
describe('tokenizeLine', () => {
|
|
4
4
|
it('classifies an object key (followed by colon) as a key', () => {
|
|
5
5
|
const tokens = tokenizeLine(' "name": "ada"');
|
|
@@ -63,3 +63,11 @@ describe('countJsonLines', () => {
|
|
|
63
63
|
expect(countJsonLines({ a: 1, b: 2 })).toBe(4);
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
|
+
describe('wrapped token rendering', () => {
|
|
67
|
+
it('preserves string color across wrapped chunks', () => {
|
|
68
|
+
const tokens = tokenizeLine(' "text": "NASCAR Cup Series: racing"');
|
|
69
|
+
const wrapped = wrapTokens(tokens, 18);
|
|
70
|
+
const value = wrapped.flat().find(t => t.text.includes('Series'));
|
|
71
|
+
expect(value?.color).toBe('green');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Capitalize a short UI label.
|
|
4
|
+
*/
|
|
5
|
+
export declare const capitalize: (value: string) => string;
|
|
6
|
+
export declare const formatContentTitle: (parts: string[]) => string;
|
|
7
|
+
export declare const hasJsonValue: (value: unknown) => boolean;
|
|
1
8
|
/**
|
|
2
9
|
* Truncate a string to fit a column, appending an ellipsis when clipped.
|
|
3
10
|
*/
|
|
@@ -14,3 +21,11 @@ export declare const formatStartedShort: (iso: string | undefined) => string;
|
|
|
14
21
|
* runs off the end of the array.
|
|
15
22
|
*/
|
|
16
23
|
export declare const computeWindowStart: (selectedIndex: number, total: number, visibleRows: number) => number;
|
|
24
|
+
export declare const cycleValue: <T>(values: readonly T[], current: T, direction: 1 | -1) => T;
|
|
25
|
+
export declare const clampIndex: (index: number, count: number) => number;
|
|
26
|
+
export declare const useListSelection: (count: number, initialIndex?: number | (() => number)) => {
|
|
27
|
+
selectedIndex: number;
|
|
28
|
+
setSelectedIndex: Dispatch<SetStateAction<number>>;
|
|
29
|
+
selectPrevious: () => void;
|
|
30
|
+
selectNext: () => void;
|
|
31
|
+
};
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import { format, parseISO } from 'date-fns';
|
|
3
|
+
/**
|
|
4
|
+
* Capitalize a short UI label.
|
|
5
|
+
*/
|
|
6
|
+
export const capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
7
|
+
export const formatContentTitle = (parts) => parts.join(' › ');
|
|
8
|
+
export const hasJsonValue = (value) => {
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return value.length > 0;
|
|
14
|
+
}
|
|
15
|
+
return typeof value !== 'object' || Object.keys(value).length > 0;
|
|
16
|
+
};
|
|
2
17
|
/**
|
|
3
18
|
* Truncate a string to fit a column, appending an ellipsis when clipped.
|
|
4
19
|
*/
|
|
@@ -30,3 +45,18 @@ export const computeWindowStart = (selectedIndex, total, visibleRows) => {
|
|
|
30
45
|
const maxStart = Math.max(0, total - visibleRows);
|
|
31
46
|
return Math.min(start, maxStart);
|
|
32
47
|
};
|
|
48
|
+
export const cycleValue = (values, current, direction) => {
|
|
49
|
+
const idx = values.indexOf(current);
|
|
50
|
+
return values[(idx + direction + values.length) % values.length];
|
|
51
|
+
};
|
|
52
|
+
export const clampIndex = (index, count) => Math.max(0, Math.min(index, Math.max(0, count - 1)));
|
|
53
|
+
export const useListSelection = (count, initialIndex = 0) => {
|
|
54
|
+
const [rawIndex, setSelectedIndex] = useState(initialIndex);
|
|
55
|
+
const selectedIndex = clampIndex(rawIndex, count);
|
|
56
|
+
return {
|
|
57
|
+
selectedIndex,
|
|
58
|
+
setSelectedIndex,
|
|
59
|
+
selectPrevious: () => setSelectedIndex(i => Math.max(0, i - 1)),
|
|
60
|
+
selectNext: () => setSelectedIndex(i => Math.min(Math.max(0, count - 1), i + 1))
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { capitalize, clampIndex, computeWindowStart, cycleValue, formatContentTitle, formatStartedShort, hasJsonValue, truncate } from './panel_helpers.js';
|
|
3
|
+
describe('capitalize', () => {
|
|
4
|
+
it('uppercases the first character only', () => {
|
|
5
|
+
expect(capitalize('output')).toBe('Output');
|
|
6
|
+
expect(capitalize('oUTPUT')).toBe('OUTPUT');
|
|
7
|
+
});
|
|
8
|
+
it('returns empty strings unchanged', () => {
|
|
9
|
+
expect(capitalize('')).toBe('');
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
describe('formatContentTitle', () => {
|
|
13
|
+
it('joins title segments with the shared separator', () => {
|
|
14
|
+
expect(formatContentTitle(['Workflow "demo"', 'Steps'])).toBe('Workflow "demo" › Steps');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('hasJsonValue', () => {
|
|
18
|
+
it('rejects nullish and empty collection values', () => {
|
|
19
|
+
expect(hasJsonValue(null)).toBe(false);
|
|
20
|
+
expect(hasJsonValue(undefined)).toBe(false);
|
|
21
|
+
expect(hasJsonValue([])).toBe(false);
|
|
22
|
+
expect(hasJsonValue({})).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
it('accepts scalar and non-empty collection values', () => {
|
|
25
|
+
expect(hasJsonValue(false)).toBe(true);
|
|
26
|
+
expect(hasJsonValue(0)).toBe(true);
|
|
27
|
+
expect(hasJsonValue('')).toBe(true);
|
|
28
|
+
expect(hasJsonValue(['x'])).toBe(true);
|
|
29
|
+
expect(hasJsonValue({ x: 1 })).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
3
32
|
describe('truncate', () => {
|
|
4
33
|
it('returns the input unchanged when shorter than max', () => {
|
|
5
34
|
expect(truncate('short', 10)).toBe('short');
|
|
@@ -45,3 +74,19 @@ describe('computeWindowStart', () => {
|
|
|
45
74
|
expect(computeWindowStart(0, 0, 8)).toBe(0);
|
|
46
75
|
});
|
|
47
76
|
});
|
|
77
|
+
describe('cycleValue', () => {
|
|
78
|
+
it('cycles forward and backward through an ordered list', () => {
|
|
79
|
+
expect(cycleValue(['a', 'b', 'c'], 'a', 1)).toBe('b');
|
|
80
|
+
expect(cycleValue(['a', 'b', 'c'], 'a', -1)).toBe('c');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('clampIndex', () => {
|
|
84
|
+
it('clamps indexes to the available range', () => {
|
|
85
|
+
expect(clampIndex(-1, 3)).toBe(0);
|
|
86
|
+
expect(clampIndex(0, 3)).toBe(0);
|
|
87
|
+
expect(clampIndex(5, 3)).toBe(2);
|
|
88
|
+
});
|
|
89
|
+
it('returns 0 for empty lists', () => {
|
|
90
|
+
expect(clampIndex(5, 0)).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.4.1-next.
|
|
3
|
+
"version": "0.4.1-next.d085dde.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"semver": "7.7.4",
|
|
37
37
|
"undici": "8.1.0",
|
|
38
38
|
"yaml": "^2.8.3",
|
|
39
|
-
"@outputai/credentials": "0.4.1-next.
|
|
40
|
-
"@outputai/llm": "0.4.1-next.
|
|
41
|
-
"@outputai/evals": "0.4.1-next.
|
|
39
|
+
"@outputai/credentials": "0.4.1-next.d085dde.0",
|
|
40
|
+
"@outputai/llm": "0.4.1-next.d085dde.0",
|
|
41
|
+
"@outputai/evals": "0.4.1-next.d085dde.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/cli-progress": "3.11.6",
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
interface StatusDisplay {
|
|
3
|
-
icon: string;
|
|
4
|
-
color: string;
|
|
5
|
-
}
|
|
6
|
-
export declare const resolveStatus: (status: string) => StatusDisplay;
|
|
7
|
-
export declare const statusColor: (status: string) => string;
|
|
8
|
-
export declare const StatusIcon: React.FC<{
|
|
9
|
-
status: string;
|
|
10
|
-
}>;
|
|
11
|
-
export {};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
const STATUS_MAP = {
|
|
4
|
-
// Docker service health
|
|
5
|
-
healthy: { icon: '●', color: 'green' },
|
|
6
|
-
unhealthy: { icon: '○', color: 'red' },
|
|
7
|
-
starting: { icon: '◐', color: 'yellow' },
|
|
8
|
-
none: { icon: '●', color: 'blue' },
|
|
9
|
-
exited: { icon: '✗', color: 'red' },
|
|
10
|
-
// Workflow run status
|
|
11
|
-
running: { icon: '●', color: 'green' },
|
|
12
|
-
completed: { icon: '●', color: 'green' },
|
|
13
|
-
failed: { icon: '✗', color: 'red' },
|
|
14
|
-
canceled: { icon: '○', color: 'gray' },
|
|
15
|
-
terminated: { icon: '✗', color: 'red' },
|
|
16
|
-
timed_out: { icon: '✗', color: 'red' },
|
|
17
|
-
continued: { icon: '↻', color: 'blue' }
|
|
18
|
-
};
|
|
19
|
-
const DEFAULT_DISPLAY = { icon: '?', color: 'white' };
|
|
20
|
-
export const resolveStatus = (status) => STATUS_MAP[status] ?? DEFAULT_DISPLAY;
|
|
21
|
-
export const statusColor = (status) => resolveStatus(status).color;
|
|
22
|
-
export const StatusIcon = ({ status }) => {
|
|
23
|
-
const { icon, color } = resolveStatus(status);
|
|
24
|
-
return _jsx(Text, { color: color, children: icon });
|
|
25
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text, useStdout } from 'ink';
|
|
3
|
-
import { RULE_PURPLE } from '#views/dev/chrome/palette.js';
|
|
4
|
-
// `RULE_PURPLE` is part of the OUTPUT brand chrome — kept as the default
|
|
5
|
-
// for horizontal rules. Vertical rules pass through whatever the caller
|
|
6
|
-
// supplies; default is undefined, which Ink renders in the terminal's
|
|
7
|
-
// default foreground colour (theme-agnostic).
|
|
8
|
-
const DEFAULT_RULE_COLOR = RULE_PURPLE;
|
|
9
|
-
// dev_app.tsx Shell uses paddingX={2}, so 4 cols are eaten by horizontal padding.
|
|
10
|
-
const SHELL_HORIZONTAL_PADDING = 4;
|
|
11
|
-
export const HorizontalRule = ({ color = DEFAULT_RULE_COLOR, widthOffset = SHELL_HORIZONTAL_PADDING }) => {
|
|
12
|
-
const { stdout } = useStdout();
|
|
13
|
-
const cols = stdout?.columns ?? 80;
|
|
14
|
-
return _jsx(Text, { color: color, children: '─'.repeat(Math.max(1, cols - widthOffset)) });
|
|
15
|
-
};
|
|
16
|
-
export const VerticalRule = ({ color }) => (_jsx(Box, { borderStyle: "single", borderColor: color, borderTop: false, borderBottom: false, borderRight: false, flexDirection: "column" }));
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { StatusIcon, statusColor } from '#components/status_icon.js';
|
|
5
|
-
import { formatDurationCompact, formatDate, elapsedMs } from '#utils/date_formatter.js';
|
|
6
|
-
import { Footer } from '#views/dev/chrome/footer.js';
|
|
7
|
-
import { LoadingSpinner } from '#views/dev/chrome/loading_spinner.js';
|
|
8
|
-
import { SelectionIndicator } from '#views/dev/chrome/selection_indicator.js';
|
|
9
|
-
import { useUiState } from '#views/dev/state/ui_state.js';
|
|
10
|
-
import { useRunDetail } from '#views/dev/hooks/use_run_detail.js';
|
|
11
|
-
import { JsonView } from '#views/dev/utils/json_render.js';
|
|
12
|
-
import { truncate, computeWindowStart } from '#views/dev/utils/panel_helpers.js';
|
|
13
|
-
import { RUN_DETAIL_VISIBLE_STEPS, RUN_DETAIL_PREVIEW_LINES } from '#views/dev/utils/constants.js';
|
|
14
|
-
const RIGHT_PANE_ORDER = ['input', 'output', 'meta'];
|
|
15
|
-
const cycleRightPane = (current, direction) => {
|
|
16
|
-
const idx = RIGHT_PANE_ORDER.indexOf(current);
|
|
17
|
-
const next = (idx + direction + RIGHT_PANE_ORDER.length) % RIGHT_PANE_ORDER.length;
|
|
18
|
-
return RIGHT_PANE_ORDER[next];
|
|
19
|
-
};
|
|
20
|
-
const COL = {
|
|
21
|
-
num: 6,
|
|
22
|
-
icon: 3,
|
|
23
|
-
name: 50,
|
|
24
|
-
duration: 8
|
|
25
|
-
};
|
|
26
|
-
const StepRow = ({ step, selected }) => (_jsxs(Box, { children: [_jsxs(Box, { width: COL.num, children: [_jsx(SelectionIndicator, { selected: selected }), _jsx(Text, { bold: selected, children: ` ${step.index}` })] }), _jsx(Box, { width: COL.icon, children: _jsx(StatusIcon, { status: step.status }) }), _jsx(Box, { width: COL.name, children: _jsx(Text, { bold: selected, children: truncate(step.name, COL.name - 1) }) }), _jsx(Box, { width: COL.duration, justifyContent: "flex-end", children: _jsx(Text, { dimColor: !selected, children: formatDurationCompact(step.durationMs) }) })] }));
|
|
27
|
-
const SidebarKV = ({ label, value, color }) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: label }), _jsx(Text, { color: color, wrap: "truncate-end", children: value })] }));
|
|
28
|
-
const Sidebar = ({ run, resultStatus }) => {
|
|
29
|
-
const status = resultStatus ?? run.status ?? 'unknown';
|
|
30
|
-
const duration = run.startedAt ? formatDurationCompact(elapsedMs(run.startedAt, run.completedAt)) : '-';
|
|
31
|
-
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { status: status }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: statusColor(status), children: status.toUpperCase() })] }), _jsx(SidebarKV, { label: "RUN ID", value: run.runId ?? '-' }), _jsx(SidebarKV, { label: "WORKFLOW ID", value: run.workflowId ?? '-' }), _jsx(SidebarKV, { label: "TYPE", value: run.workflowType ?? '-' }), _jsx(SidebarKV, { label: "DURATION", value: duration }), _jsx(SidebarKV, { label: "START", value: formatDate(run.startedAt) }), _jsx(SidebarKV, { label: "END", value: run.completedAt ? formatDate(run.completedAt) : '—' })] }));
|
|
32
|
-
};
|
|
33
|
-
const PaneTabs = ({ active }) => (_jsx(Box, { marginTop: 1, children: RIGHT_PANE_ORDER.map((tab, i) => (_jsx(Box, { marginRight: 2, children: tab === active ? (_jsx(Text, { inverse: true, bold: true, children: ` ${tab[0].toUpperCase()}${tab.slice(1)} ` })) : (_jsx(Text, { dimColor: true, children: `${tab[0].toUpperCase()}${tab.slice(1)}${i < RIGHT_PANE_ORDER.length - 1 ? '' : ''}` })) }, tab))) }));
|
|
34
|
-
const stepPaneValue = (step, activeTab) => {
|
|
35
|
-
if (activeTab === 'input') {
|
|
36
|
-
return step.input;
|
|
37
|
-
}
|
|
38
|
-
if (activeTab === 'output') {
|
|
39
|
-
return step.error ?? step.output;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
kind: step.kind,
|
|
43
|
-
status: step.status,
|
|
44
|
-
durationMs: step.durationMs,
|
|
45
|
-
hasError: Boolean(step.error)
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
const StepDetail = ({ step, activeTab }) => {
|
|
49
|
-
if (!step) {
|
|
50
|
-
return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Select a step to see input/output." }) }));
|
|
51
|
-
}
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(PaneTabs, { active: activeTab }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(JsonView, { value: stepPaneValue(step, activeTab), maxLines: RUN_DETAIL_PREVIEW_LINES }) })] }));
|
|
53
|
-
};
|
|
54
|
-
const HINTS = [
|
|
55
|
-
{ key: '↑/↓', label: 'navigate' },
|
|
56
|
-
{ key: '←/→', label: 'switch pane' },
|
|
57
|
-
{ key: 'e', label: 'expand' },
|
|
58
|
-
{ key: 'esc', label: 'back' },
|
|
59
|
-
{ key: 'tab', label: 'next tab' }
|
|
60
|
-
];
|
|
61
|
-
export const RunDetailView = ({ run }) => {
|
|
62
|
-
const ui = useUiState();
|
|
63
|
-
const { result, steps, loading } = useRunDetail(run.workflowId, run.runId, run.status);
|
|
64
|
-
const [stepIndex, setStepIndex] = useState(0);
|
|
65
|
-
const isActive = ui.tab === 'runs' && ui.runsView === 'detail' && !ui.search.open && !ui.runModal.open && !ui.expandedJson.open;
|
|
66
|
-
const clamped = Math.min(stepIndex, Math.max(0, steps.length - 1));
|
|
67
|
-
const selectedStep = steps[clamped];
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (clamped !== stepIndex) {
|
|
70
|
-
setStepIndex(clamped);
|
|
71
|
-
}
|
|
72
|
-
}, [clamped, stepIndex]);
|
|
73
|
-
useInput((input, key) => {
|
|
74
|
-
if (key.escape) {
|
|
75
|
-
ui.setRunsView('list');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (key.upArrow) {
|
|
79
|
-
setStepIndex(i => Math.max(0, i - 1));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (key.downArrow) {
|
|
83
|
-
setStepIndex(i => Math.min(steps.length - 1, i + 1));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (key.leftArrow) {
|
|
87
|
-
ui.setRightPaneTab(cycleRightPane(ui.rightPaneTab, -1));
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (key.rightArrow) {
|
|
91
|
-
ui.setRightPaneTab(cycleRightPane(ui.rightPaneTab, 1));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (input === 'e' && selectedStep) {
|
|
95
|
-
const content = stepPaneValue(selectedStep, ui.rightPaneTab);
|
|
96
|
-
const label = `step ${selectedStep.index}: ${selectedStep.name} → ${ui.rightPaneTab}`;
|
|
97
|
-
ui.openExpandedJson(content, label);
|
|
98
|
-
}
|
|
99
|
-
}, { isActive });
|
|
100
|
-
const windowStart = computeWindowStart(clamped, steps.length, RUN_DETAIL_VISIBLE_STEPS);
|
|
101
|
-
const visibleSteps = steps.slice(windowStart, windowStart + RUN_DETAIL_VISIBLE_STEPS);
|
|
102
|
-
const renderStepList = () => {
|
|
103
|
-
if (loading && steps.length === 0) {
|
|
104
|
-
return (_jsx(Box, { marginTop: 1, children: _jsx(LoadingSpinner, { label: "Loading steps\u2026" }) }));
|
|
105
|
-
}
|
|
106
|
-
if (steps.length === 0) {
|
|
107
|
-
return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "No steps recorded for this run." }) }));
|
|
108
|
-
}
|
|
109
|
-
return (_jsxs(_Fragment, { children: [windowStart > 0 && _jsxs(Text, { dimColor: true, children: [" \u2191 ", windowStart, " more above"] }), visibleSteps.map((step, i) => (_jsx(StepRow, { step: step, selected: windowStart + i === clamped }, `${step.index}-${i}`))), windowStart + RUN_DETAIL_VISIBLE_STEPS < steps.length && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", steps.length - windowStart - RUN_DETAIL_VISIBLE_STEPS, " more below"] }))] }));
|
|
110
|
-
};
|
|
111
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Recent Runs \u203A " }), _jsx(Text, { bold: true, children: run.workflowType }), _jsx(Text, { dimColor: true, children: " \u203A " }), _jsx(Text, { children: truncate(run.runId ?? '-', 28) })] }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, children: renderStepList() }), _jsx(Box, { flexDirection: "column", width: 40, borderStyle: "single", borderTop: false, borderBottom: false, borderRight: false, paddingLeft: 1, children: _jsx(Sidebar, { run: run, resultStatus: result?.status ?? null }) })] }), _jsx(StepDetail, { step: selectedStep, activeTab: ui.rightPaneTab }), _jsx(Footer, { hints: HINTS, itemCount: steps.length, itemLabel: "steps" })] }));
|
|
112
|
-
};
|