@adecore/agents-react 0.22.0 → 0.23.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 (113) hide show
  1. package/dist/chat/ui/EditDiff.d.ts +3 -1
  2. package/dist/chat/ui/EditDiff.js +25 -18
  3. package/dist/chat/ui/UnifiedDiff.d.ts +2 -11
  4. package/dist/chat/ui/UnifiedDiff.js +31 -58
  5. package/dist/chat/ui/diff/ApprovalReview.d.ts +5 -0
  6. package/dist/chat/ui/diff/ApprovalReview.js +29 -0
  7. package/dist/chat/ui/diff/DiffFallbackBoundary.d.ts +17 -0
  8. package/dist/chat/ui/diff/DiffFallbackBoundary.js +17 -0
  9. package/dist/chat/ui/diff/DiffOmitted.d.ts +3 -0
  10. package/dist/chat/ui/diff/DiffOmitted.js +8 -0
  11. package/dist/chat/ui/diff/DiffStack.d.ts +9 -0
  12. package/dist/chat/ui/diff/DiffStack.js +15 -0
  13. package/dist/chat/ui/diff/DiffUnavailable.d.ts +3 -0
  14. package/dist/chat/ui/diff/DiffUnavailable.js +8 -0
  15. package/dist/chat/ui/diff/EditorEditDiff.d.ts +23 -0
  16. package/dist/chat/ui/diff/EditorEditDiff.js +67 -0
  17. package/dist/chat/ui/diff/EditorPatchDiff.d.ts +16 -0
  18. package/dist/chat/ui/diff/EditorPatchDiff.js +20 -0
  19. package/dist/chat/ui/diff/LegacyDiffPool.d.ts +4 -0
  20. package/dist/chat/ui/diff/LegacyDiffPool.js +12 -0
  21. package/dist/chat/ui/diff/LegacyEditDiff.d.ts +5 -0
  22. package/dist/chat/ui/diff/LegacyEditDiff.js +23 -0
  23. package/dist/chat/ui/diff/LegacyUnifiedDiff.d.ts +2 -0
  24. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +67 -0
  25. package/dist/chat/ui/diff/RawDiff.d.ts +6 -0
  26. package/dist/chat/ui/diff/RawDiff.js +7 -0
  27. package/dist/chat/ui/diff/ReviewDiff.d.ts +5 -0
  28. package/dist/chat/ui/diff/ReviewDiff.js +10 -0
  29. package/dist/chat/ui/diff/diff-props.d.ts +12 -0
  30. package/dist/chat/ui/diff/diff-props.js +1 -0
  31. package/dist/chat/ui/diff/diff-source-budget.d.ts +10 -0
  32. package/dist/chat/ui/diff/diff-source-budget.js +47 -0
  33. package/dist/chat/ui/diff/editor-edit-model.d.ts +8 -0
  34. package/dist/chat/ui/diff/editor-edit-model.js +19 -0
  35. package/dist/chat/ui/diff/review-plan.d.ts +28 -0
  36. package/dist/chat/ui/diff/review-plan.js +48 -0
  37. package/dist/chat/ui/diff/review-settle.d.ts +5 -0
  38. package/dist/chat/ui/diff/review-settle.js +12 -0
  39. package/dist/chat/ui/diff/syntax-runtime.d.ts +18 -0
  40. package/dist/chat/ui/diff/syntax-runtime.js +184 -0
  41. package/dist/chat/ui/diff/use-review-stack.d.ts +8 -0
  42. package/dist/chat/ui/diff/use-review-stack.js +30 -0
  43. package/dist/chat/ui/diff-syntax.d.ts +19 -0
  44. package/dist/chat/ui/diff-syntax.js +63 -0
  45. package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +3 -3
  46. package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +1 -1
  47. package/dist/chat/ui/intelligent-ui/chart-data.d.ts +22 -2
  48. package/dist/chat/ui/intelligent-ui/chart-data.js +83 -19
  49. package/dist/chat/ui/intelligent-ui/renderers/chart.d.ts +1 -1
  50. package/dist/chat/ui/intelligent-ui/renderers/chart.js +99 -35
  51. package/dist/chat/ui/intelligent-ui/renderers/choices.d.ts +1 -1
  52. package/dist/chat/ui/intelligent-ui/renderers/choices.js +8 -2
  53. package/dist/chat/ui/intelligent-ui/renderers/content.js +5 -3
  54. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +9 -4
  55. package/dist/chat/ui/intelligent-ui/renderers/links.js +1 -1
  56. package/dist/chat/ui/intelligent-ui/renderers/text.js +5 -2
  57. package/dist/chat/ui/intelligent-ui/table-data.js +4 -4
  58. package/dist/chat/ui/intelligent-ui/use-truncated.d.ts +1 -0
  59. package/dist/chat/ui/intelligent-ui/use-truncated.js +28 -0
  60. package/dist/chat/ui/rows/WorkRows.js +16 -5
  61. package/dist/chat/ui/use-diff-source-tokens.d.ts +4 -0
  62. package/dist/chat/ui/use-diff-source-tokens.js +40 -0
  63. package/dist/host.d.ts +6 -1
  64. package/dist/lazy.d.ts +3 -1
  65. package/dist/lazy.js +5 -3
  66. package/dist/locales/en/agent-chat.json +12 -3
  67. package/dist/locales/en/agent-prompts.json +1 -0
  68. package/dist/locales/nl/agent-chat.json +12 -3
  69. package/dist/locales/nl/agent-prompts.json +1 -0
  70. package/dist/prompts/ui/ApprovalBody.js +7 -5
  71. package/package.json +23 -5
  72. package/src/chat/ui/EditDiff.tsx +37 -22
  73. package/src/chat/ui/UnifiedDiff.tsx +55 -88
  74. package/src/chat/ui/diff/ApprovalReview.tsx +53 -0
  75. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +32 -0
  76. package/src/chat/ui/diff/DiffOmitted.tsx +13 -0
  77. package/src/chat/ui/diff/DiffStack.tsx +45 -0
  78. package/src/chat/ui/diff/DiffUnavailable.tsx +13 -0
  79. package/src/chat/ui/diff/EditorEditDiff.tsx +145 -0
  80. package/src/chat/ui/diff/EditorPatchDiff.tsx +60 -0
  81. package/src/chat/ui/diff/LegacyDiffPool.tsx +13 -0
  82. package/src/chat/ui/diff/LegacyEditDiff.tsx +33 -0
  83. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +93 -0
  84. package/src/chat/ui/diff/RawDiff.tsx +19 -0
  85. package/src/chat/ui/diff/ReviewDiff.tsx +22 -0
  86. package/src/chat/ui/diff/diff-props.ts +16 -0
  87. package/src/chat/ui/diff/diff-source-budget.ts +56 -0
  88. package/src/chat/ui/diff/editor-edit-model.ts +21 -0
  89. package/src/chat/ui/diff/review-plan.ts +69 -0
  90. package/src/chat/ui/diff/review-settle.ts +14 -0
  91. package/src/chat/ui/diff/syntax-runtime.ts +222 -0
  92. package/src/chat/ui/diff/use-review-stack.ts +38 -0
  93. package/src/chat/ui/diff-syntax.ts +82 -0
  94. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +5 -5
  95. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +6 -1
  96. package/src/chat/ui/intelligent-ui/chart-data.ts +106 -20
  97. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +237 -72
  98. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +9 -4
  99. package/src/chat/ui/intelligent-ui/renderers/content.tsx +31 -15
  100. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +16 -5
  101. package/src/chat/ui/intelligent-ui/renderers/links.tsx +3 -3
  102. package/src/chat/ui/intelligent-ui/renderers/text.tsx +18 -8
  103. package/src/chat/ui/intelligent-ui/table-data.ts +4 -4
  104. package/src/chat/ui/intelligent-ui/use-truncated.ts +33 -0
  105. package/src/chat/ui/rows/WorkRows.tsx +42 -33
  106. package/src/chat/ui/use-diff-source-tokens.ts +43 -0
  107. package/src/host.ts +10 -1
  108. package/src/lazy.tsx +6 -3
  109. package/src/locales/en/agent-chat.json +12 -3
  110. package/src/locales/en/agent-prompts.json +1 -0
  111. package/src/locales/nl/agent-chat.json +12 -3
  112. package/src/locales/nl/agent-prompts.json +1 -0
  113. package/src/prompts/ui/ApprovalBody.tsx +8 -14
@@ -1,4 +1,6 @@
1
1
  import type { FileChange } from '../logic/tools.js';
2
- export default function EditDiff({ change }: {
2
+ export default function EditDiff({ change, allowEditor, maxHeight }: {
3
3
  change: FileChange;
4
+ allowEditor?: boolean;
5
+ maxHeight?: number;
4
6
  }): import("react").JSX.Element;
@@ -1,21 +1,28 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
3
- import { MultiFileDiff } from '@pierre/diffs/react';
2
+ import { Suspense, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { lazyNamed } from '../../lazy.js';
5
+ import { diffSourceBudget, legacyDiffBudget } from './diff/diff-source-budget.js';
6
+ import { DiffOmitted } from './diff/DiffOmitted.js';
7
+ import { DiffUnavailable } from './diff/DiffUnavailable.js';
8
+ import { DiffFallbackBoundary } from './diff/DiffFallbackBoundary.js';
4
9
  import { chatHost } from '../../host.js';
5
- import { useDiffTheme } from './diff-theme.js';
6
- /* One hunk of a file edit, highlighted in a worker. The header is ours, so the library's is off. */
7
- export default function EditDiff({ change }) {
8
- const resolved = chatHost().code.useMode();
9
- const theme = useDiffTheme();
10
- const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
11
- const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
12
- const options = useMemo(() => ({
13
- diffStyle: 'unified',
14
- theme,
15
- themeType: resolved,
16
- disableFileHeader: true,
17
- overflow: 'wrap',
18
- hunkSeparators: 'simple'
19
- }), [theme, resolved]);
20
- return (_jsx("div", { className: "chat-diff select-text", children: _jsx(MultiFileDiff, { oldFile: oldFile, newFile: newFile, options: options }) }));
10
+ const LegacyEditDiff = lazyNamed(() => import('./diff/LegacyEditDiff.js'), 'default', { prefetch: false });
11
+ const EditorEditDiff = lazyNamed(() => import('./diff/EditorEditDiff.js'), 'default');
12
+ export default function EditDiff({ change, allowEditor = true, maxHeight }) {
13
+ const { t } = useTranslation('agent-chat');
14
+ const loadEngine = chatHost().code.editor;
15
+ const sourceAllowed = useMemo(() => diffSourceBudget(change.before, change.after), [change.before, change.after]);
16
+ const legacyMode = !allowEditor || loadEngine === undefined;
17
+ const legacyAllowed = useMemo(() => legacyMode && legacyDiffBudget(change.before, change.after), [legacyMode, change.before, change.after]);
18
+ const omitted = _jsx(DiffOmitted, { path: change.path });
19
+ const fallback = legacyAllowed ? (_jsx(DiffFallbackBoundary, { fallback: omitted, resetKeys: [change.path, change.before, change.after], children: _jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: _jsx(LegacyEditDiff, { change: change, maxHeight: maxHeight }) }) })) : (omitted);
20
+ if (!sourceAllowed) {
21
+ return omitted;
22
+ }
23
+ if (legacyMode) {
24
+ return fallback;
25
+ }
26
+ const unavailable = _jsx(DiffUnavailable, { path: change.path });
27
+ return (_jsx(DiffFallbackBoundary, { fallback: unavailable, resetKeys: [change.path, change.before, change.after, loadEngine], children: _jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: _jsx(EditorEditDiff, { change: change, loadEngine: loadEngine, fallback: unavailable, maxHeight: maxHeight, sourceChecked: true }) }) }));
21
28
  }
@@ -1,11 +1,2 @@
1
- import type { ChatFileChange } from '@adecore/agent-contracts';
2
- import { type DiffContents } from './full-diff.js';
3
- interface UnifiedDiffProps {
4
- change: ChatFileChange;
5
- overflow?: 'wrap' | 'scroll';
6
- diffStyle?: 'unified' | 'split';
7
- fill?: boolean;
8
- contents?: DiffContents;
9
- }
10
- export default function UnifiedDiff({ change, overflow, diffStyle, fill, contents }: UnifiedDiffProps): import("react").JSX.Element;
11
- export {};
1
+ import type { UnifiedDiffProps } from './diff/diff-props.js';
2
+ export default function UnifiedDiff(props: UnifiedDiffProps): import("react").JSX.Element;
@@ -1,64 +1,37 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
3
- import clsx from 'clsx';
4
- import { FileDiff, PatchDiff } from '@pierre/diffs/react';
2
+ import { Suspense, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { lazyNamed } from '../../lazy.js';
5
+ import { diffSourceBudget, legacyDiffBudget, patchSourceBudget } from './diff/diff-source-budget.js';
6
+ import { DiffOmitted } from './diff/DiffOmitted.js';
7
+ import { DiffUnavailable } from './diff/DiffUnavailable.js';
8
+ import { RawDiff } from './diff/RawDiff.js';
9
+ import { DiffFallbackBoundary } from './diff/DiffFallbackBoundary.js';
5
10
  import { chatHost } from '../../host.js';
6
- import { useDiffTheme } from './diff-theme.js';
7
- import { fullFileDiff } from './full-diff.js';
8
- /*
9
- * A patch the CLI reported itself. The renderer needs a file header, which a CLI that only sends hunks
10
- * gets from the path we know; a diff without hunks is shown as its own lines, so nothing is swallowed.
11
- */
12
- function asPatch(change) {
13
- if (!/^@@/m.test(change.diff)) {
14
- return null;
11
+ const LegacyUnifiedDiff = lazyNamed(() => import('./diff/LegacyUnifiedDiff.js'), 'default', { prefetch: false });
12
+ const EditorPatchDiff = lazyNamed(() => import('./diff/EditorPatchDiff.js'), 'default');
13
+ export default function UnifiedDiff(props) {
14
+ const { t } = useTranslation('agent-chat');
15
+ const { change, contents, allowEditor = true, ignoreWhitespace = false, overflow = 'wrap', diffStyle = 'unified', fill = false, maxHeight } = props;
16
+ const loadEngine = chatHost().code.editor;
17
+ const oldText = contents?.old ?? '';
18
+ const newText = contents?.new ?? '';
19
+ const sourceAllowed = useMemo(() => diffSourceBudget(oldText, newText) && patchSourceBudget(change.diff), [oldText, newText, change.diff]);
20
+ const legacyMode = !allowEditor || loadEngine === undefined;
21
+ const hasHunks = /^@@/m.test(change.diff);
22
+ const legacyAllowed = useMemo(() => legacyMode && legacyDiffBudget(oldText, newText, change.diff), [legacyMode, oldText, newText, change.diff]);
23
+ const rawAllowed = useMemo(() => hasHunks || legacyDiffBudget('', '', change.diff), [hasHunks, change.diff]);
24
+ const omitted = _jsx(DiffOmitted, { path: change.path });
25
+ const fallback = legacyAllowed ? (_jsx(DiffFallbackBoundary, { fallback: omitted, resetKeys: [change.path, change.diff, oldText, newText, diffStyle], children: _jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: _jsx(LegacyUnifiedDiff, { ...props }) }) })) : (omitted);
26
+ if (!sourceAllowed) {
27
+ return omitted;
15
28
  }
16
- if (/^\+\+\+ /m.test(change.diff)) {
17
- return change.diff;
29
+ if (!hasHunks) {
30
+ return rawAllowed ? _jsx(RawDiff, { text: change.diff, overflow: overflow, fill: fill, maxHeight: maxHeight }) : omitted;
18
31
  }
19
- const from = change.kind === 'add' ? '/dev/null' : `a/${change.path}`;
20
- const to = change.kind === 'delete' ? '/dev/null' : `b/${change.path}`;
21
- return `--- ${from}\n+++ ${to}\n${change.diff}`;
22
- }
23
- /* Lets a diff with a view of its own run to its bottom, so a short one's scrollbar sits there and not under its last line. */
24
- const FILL_CSS = `
25
- :host { display: flex; flex-direction: column; flex-grow: 1; }
26
- pre { flex-grow: 1; align-content: start; }
27
- pre[data-diff-type="single"] { display: flex; flex-direction: column; }
28
- pre[data-diff-type="split"][data-overflow="scroll"]:not([data-dehydrated]) { align-content: stretch; }
29
- pre > [data-code] { flex-grow: 1; align-self: stretch; align-content: start; }
30
- `;
31
- function lineClass(line) {
32
- if (line.startsWith('+')) {
33
- return 'text-chat-added';
34
- }
35
- if (line.startsWith('-')) {
36
- return 'text-chat-removed';
37
- }
38
- return 'text-text-muted';
39
- }
40
- export default function UnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents }) {
41
- const resolved = chatHost().code.useMode();
42
- const theme = useDiffTheme();
43
- const patch = useMemo(() => asPatch(change), [change]);
44
- const oldText = contents?.old;
45
- const newText = contents?.new;
46
- const full = useMemo(() => (patch === null || oldText === undefined || newText === undefined ? null : fullFileDiff(patch, change.path, { old: oldText, new: newText })), [patch, change.path, oldText, newText]);
47
- const unfolds = full !== null;
48
- const options = useMemo(() => ({
49
- theme,
50
- themeType: resolved,
51
- disableFileHeader: true,
52
- diffStyle,
53
- overflow,
54
- hunkSeparators: unfolds ? 'line-info' : 'simple',
55
- unsafeCSS: fill ? FILL_CSS : undefined
56
- }), [diffStyle, overflow, theme, resolved, fill, unfolds]);
57
- if (patch === null) {
58
- return (_jsx("pre", { className: "max-h-64 overflow-auto px-3 py-2 font-mono text-code select-text", children: change.diff
59
- .replace(/\n$/, '')
60
- .split('\n')
61
- .map((line, index) => (_jsx("div", { className: clsx(lineClass(line), 'whitespace-pre-wrap'), children: line }, index))) }));
32
+ if (legacyMode) {
33
+ return fallback;
62
34
  }
63
- return (_jsx("div", { className: clsx('chat-diff select-text', fill && 'flex grow flex-col'), children: full === null ? _jsx(PatchDiff, { patch: patch, options: options }) : _jsx(FileDiff, { fileDiff: full, options: options }) }));
35
+ const unavailable = _jsx(DiffUnavailable, { path: change.path });
36
+ return (_jsx(DiffFallbackBoundary, { fallback: unavailable, resetKeys: [change.path, change.diff, oldText, newText, ignoreWhitespace, diffStyle, loadEngine], children: _jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: _jsx(EditorPatchDiff, { change: change, sourceChecked: true, contents: contents, ignoreWhitespace: ignoreWhitespace, wrap: overflow === 'wrap', fill: fill, maxHeight: maxHeight, layout: diffStyle, loadEngine: loadEngine, fallback: unavailable }) }) }));
64
37
  }
@@ -0,0 +1,5 @@
1
+ import type { ReviewEntry } from './review-plan.js';
2
+ export declare function ApprovalReview({ entries, maxHeight }: {
3
+ entries: readonly ReviewEntry[];
4
+ maxHeight: number;
5
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,29 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button } from '@adecore/ui';
5
+ import { formatNumber } from '@adecore/ui/format';
6
+ import { ReviewDiff } from './ReviewDiff.js';
7
+ export function ApprovalReview({ entries, maxHeight }) {
8
+ const { t } = useTranslation('agent-prompts');
9
+ const [selected, setSelected] = useState(null);
10
+ const active = entries.find((entry) => entry.key === selected) ?? entries[0];
11
+ const labels = useMemo(() => {
12
+ const totals = new Map();
13
+ const counts = new Map();
14
+ for (const entry of entries) {
15
+ totals.set(entry.path, (totals.get(entry.path) ?? 0) + 1);
16
+ }
17
+ return entries.map((entry) => {
18
+ const count = (counts.get(entry.path) ?? 0) + 1;
19
+ counts.set(entry.path, count);
20
+ const total = totals.get(entry.path);
21
+ const suffix = total > 1 ? ` (${formatNumber(count)}/${formatNumber(total)})` : '';
22
+ return { short: `${entry.path.split(/[\\/]/).at(-1)}${suffix}`, full: `${entry.path}${suffix}` };
23
+ });
24
+ }, [entries]);
25
+ if (active === undefined) {
26
+ return null;
27
+ }
28
+ return (_jsxs("div", { children: [entries.length > 1 && (_jsxs(_Fragment, { children: [_jsx("div", { role: "toolbar", "aria-label": t('approval.reviewChanges'), className: "flex flex-wrap gap-1 border-b border-border p-1", children: entries.map((entry, index) => (_jsx(Button, { size: "sm", variant: active.key === entry.key ? 'secondary' : 'ghost', "aria-label": labels[index].full, "aria-pressed": active.key === entry.key, onClick: () => setSelected(entry.key), children: labels[index].short }, entry.key))) }), _jsx("p", { className: "break-all px-3 py-2 font-mono text-xs text-text-muted", children: active.path })] })), _jsx(ReviewDiff, { entry: active, maxHeight: maxHeight }, active.key)] }));
29
+ }
@@ -0,0 +1,17 @@
1
+ import { Component, type ReactNode } from 'react';
2
+ interface Props {
3
+ fallback: ReactNode;
4
+ children: ReactNode;
5
+ resetKeys: readonly unknown[];
6
+ }
7
+ interface State {
8
+ failed: boolean;
9
+ resetKeys: readonly unknown[];
10
+ }
11
+ export declare class DiffFallbackBoundary extends Component<Props, State> {
12
+ state: State;
13
+ static getDerivedStateFromError(): Partial<State>;
14
+ static getDerivedStateFromProps(props: Props, state: State): State | null;
15
+ render(): ReactNode;
16
+ }
17
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Component } from 'react';
2
+ /* A failed view or lazy chunk must leave the containing review card usable. */
3
+ export class DiffFallbackBoundary extends Component {
4
+ state = { failed: false, resetKeys: this.props.resetKeys };
5
+ static getDerivedStateFromError() {
6
+ return { failed: true };
7
+ }
8
+ static getDerivedStateFromProps(props, state) {
9
+ if (state.resetKeys.length !== props.resetKeys.length || state.resetKeys.some((value, index) => !Object.is(value, props.resetKeys[index]))) {
10
+ return { failed: false, resetKeys: props.resetKeys };
11
+ }
12
+ return null;
13
+ }
14
+ render() {
15
+ return this.state.failed ? this.props.fallback : this.props.children;
16
+ }
17
+ }
@@ -0,0 +1,3 @@
1
+ export declare function DiffOmitted({ path }: {
2
+ path: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useReviewSettled } from './review-settle.js';
3
+ import { useTranslation } from 'react-i18next';
4
+ export function DiffOmitted({ path }) {
5
+ useReviewSettled();
6
+ const { t } = useTranslation('agent-chat');
7
+ return (_jsxs("div", { role: "status", "data-diff-omitted": true, className: "px-3 py-2 text-xs text-text-muted", children: [_jsx("p", { className: "truncate font-mono", children: path }), _jsx("p", { children: t('work.omitted.tooLarge') })] }));
8
+ }
@@ -0,0 +1,9 @@
1
+ import { type ReviewEntry } from './review-plan.js';
2
+ import { type ReviewStack } from './use-review-stack.js';
3
+ export declare function ReviewStackView({ entries, stack }: {
4
+ entries: readonly ReviewEntry[];
5
+ stack: ReviewStack;
6
+ }): import("react").JSX.Element;
7
+ export declare function DiffStack({ entries }: {
8
+ entries: readonly ReviewEntry[];
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button } from '@adecore/ui';
4
+ import { ReviewSettleContext } from './review-settle.js';
5
+ import { ReviewDiff } from './ReviewDiff.js';
6
+ import { reviewSize } from './review-plan.js';
7
+ import { useReviewStack } from './use-review-stack.js';
8
+ export function ReviewStackView({ entries, stack }) {
9
+ const { t } = useTranslation('agent-chat');
10
+ return (_jsx("div", { "data-review-stack": true, children: entries.map((entry) => (_jsxs("div", { className: "border-t border-border first:border-t-0", children: [_jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("p", { className: "min-w-0 grow break-all font-mono text-xs text-text-muted", children: entry.path }), stack.slots.get(entry.key) === 'deferred' && (_jsx(Button, { size: "sm", "data-review-show": entry.key, onClick: () => stack.promote(entry.key), children: t('work.showDiff') }))] }), stack.ready.has(entry.key) ? (_jsx(ReviewSettleContext, { value: { key: entry.key, settle: stack.settle }, children: _jsx(ReviewDiff, { entry: entry }) })) : stack.slots.get(entry.key) === 'editor' ? (_jsx("div", { "data-review-pending": entry.key, role: "status", className: "px-3 py-2 text-xs text-text-faint", style: { height: Math.max(28, Math.min(reviewSize(entry).rows * 20, 480)) }, children: t('work.loadingDiff') })) : null] }, entry.key))) }));
11
+ }
12
+ export function DiffStack({ entries }) {
13
+ const stack = useReviewStack(entries);
14
+ return _jsx(ReviewStackView, { entries: entries, stack: stack });
15
+ }
@@ -0,0 +1,3 @@
1
+ export declare function DiffUnavailable({ path }: {
2
+ path: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useReviewSettled } from './review-settle.js';
4
+ export function DiffUnavailable({ path }) {
5
+ useReviewSettled();
6
+ const { t } = useTranslation('agent-chat');
7
+ return (_jsxs("div", { role: "status", "data-diff-unavailable": true, className: "px-3 py-2 text-xs text-text-muted", children: [_jsx("p", { className: "truncate font-mono", children: path }), _jsx("p", { children: t('work.diffUnavailable') })] }));
8
+ }
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { EditorEngine } from '@adecore/editor';
3
+ import { type DiffDocument } from '@adecore/merge';
4
+ import type { FileChange } from '../../logic/tools.js';
5
+ type EngineLoader = () => Promise<EditorEngine>;
6
+ export declare function EditorDocumentDiff({ document, path, loadEngine, fallback, wrap, fill, layout, maxHeight }: {
7
+ document: DiffDocument;
8
+ path: string;
9
+ loadEngine: EngineLoader;
10
+ fallback: ReactNode;
11
+ wrap?: boolean;
12
+ fill?: boolean;
13
+ layout?: 'unified' | 'split';
14
+ maxHeight?: number;
15
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
16
+ export default function EditorEditDiff({ change, loadEngine, fallback, maxHeight, sourceChecked }: {
17
+ change: FileChange;
18
+ sourceChecked?: boolean;
19
+ loadEngine: EngineLoader;
20
+ fallback: ReactNode;
21
+ maxHeight?: number;
22
+ }): import("react").JSX.Element;
23
+ export {};
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useReviewSettled } from './review-settle.js';
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { getFiletypeFromFileName } from '@pierre/diffs';
6
+ import { DiffView } from '@adecore/editor-react';
7
+ import { diffDocument, patchOf } from '@adecore/merge';
8
+ import { chatHost } from '../../../host.js';
9
+ import { useDiffSourceTokens } from '../use-diff-source-tokens.js';
10
+ import { diffSourceBudget } from './diff-source-budget.js';
11
+ import { DiffOmitted } from './DiffOmitted.js';
12
+ import { canRenderEditorDiff, EDITOR_DIFF_LIMITS, EDITOR_DIFF_WORK } from './editor-edit-model.js';
13
+ // Virtualized rows remount; retain the engine so their height does not return to the loading placeholder.
14
+ const engines = new WeakMap();
15
+ function ReadyDiff({ document, path, engine, wrap, fill, layout, maxHeight }) {
16
+ useReviewSettled();
17
+ const { t } = useTranslation('agent-chat');
18
+ const mode = chatHost().code.useMode();
19
+ const theme = chatHost().code.useThemes()[mode];
20
+ const language = getFiletypeFromFileName(path);
21
+ const tokens = useDiffSourceTokens(document, language, theme);
22
+ return (_jsx(DiffView, { engine: engine, document: document, tokens: tokens, theme: theme, maxHeight: maxHeight, unfoldLimits: EDITOR_DIFF_LIMITS, label: t('work.diffLabel', { path }), layout: layout, wrap: wrap, fill: fill, className: "chat-diff select-text" }));
23
+ }
24
+ export function EditorDocumentDiff({ document, path, loadEngine, fallback, wrap = true, fill = false, layout = 'unified', maxHeight = 480 }) {
25
+ const { t } = useTranslation('agent-chat');
26
+ const withinLimits = useMemo(() => canRenderEditorDiff(document), [document]);
27
+ const [loaded, setLoaded] = useState();
28
+ useEffect(() => {
29
+ if (!withinLimits || engines.has(loadEngine)) {
30
+ return;
31
+ }
32
+ let active = true;
33
+ void Promise.resolve()
34
+ .then(loadEngine)
35
+ .then((engine) => {
36
+ engines.set(loadEngine, engine);
37
+ if (active) {
38
+ setLoaded({ load: loadEngine, engine });
39
+ }
40
+ }, () => {
41
+ engines.set(loadEngine, null);
42
+ if (active) {
43
+ setLoaded({ load: loadEngine, engine: null });
44
+ }
45
+ });
46
+ return () => {
47
+ active = false;
48
+ };
49
+ }, [loadEngine, withinLimits]);
50
+ const engine = loaded?.load === loadEngine ? loaded.engine : engines.get(loadEngine);
51
+ if (!withinLimits) {
52
+ return _jsx(DiffOmitted, { path: path });
53
+ }
54
+ if (engine === undefined) {
55
+ return _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') });
56
+ }
57
+ return engine === null ? (fallback) : (_jsx(ReadyDiff, { document: document, path: path, engine: engine, wrap: wrap, fill: fill, layout: layout, maxHeight: maxHeight }));
58
+ }
59
+ export default function EditorEditDiff({ change, loadEngine, fallback, maxHeight, sourceChecked = false }) {
60
+ const document = useMemo(() => sourceChecked || diffSourceBudget(change.before, change.after)
61
+ ? diffDocument(patchOf(change.before, change.after, 3, { maxWork: EDITOR_DIFF_WORK }), { old: change.before, new: change.after })
62
+ : undefined, [change.before, change.after, sourceChecked]);
63
+ if (document === undefined) {
64
+ return _jsx(DiffOmitted, { path: change.path });
65
+ }
66
+ return _jsx(EditorDocumentDiff, { document: document, path: change.path, loadEngine: loadEngine, fallback: fallback, maxHeight: maxHeight });
67
+ }
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ChatFileChange } from '@adecore/agent-contracts';
3
+ import type { EditorEngine } from '@adecore/editor';
4
+ import type { DiffContents } from '../full-diff.js';
5
+ export default function EditorPatchDiff({ change, sourceChecked, contents, ignoreWhitespace, wrap, fill, maxHeight, layout, loadEngine, fallback }: {
6
+ change: ChatFileChange;
7
+ sourceChecked?: boolean;
8
+ contents?: DiffContents;
9
+ ignoreWhitespace: boolean;
10
+ wrap: boolean;
11
+ fill: boolean;
12
+ layout?: 'unified' | 'split';
13
+ maxHeight?: number;
14
+ loadEngine: () => Promise<EditorEngine>;
15
+ fallback: ReactNode;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { diffDocument, parsePatch } from '@adecore/merge';
4
+ import { diffSourceBudget, patchSourceBudget } from './diff-source-budget.js';
5
+ import { DiffOmitted } from './DiffOmitted.js';
6
+ import { EditorDocumentDiff } from './EditorEditDiff.js';
7
+ export default function EditorPatchDiff({ change, sourceChecked = false, contents, ignoreWhitespace, wrap, fill, maxHeight, layout = 'unified', loadEngine, fallback }) {
8
+ const oldText = contents?.old;
9
+ const newText = contents?.new;
10
+ const document = useMemo(() => {
11
+ if (!sourceChecked && (!diffSourceBudget(oldText ?? '', newText ?? '') || !patchSourceBudget(change.diff))) {
12
+ return undefined;
13
+ }
14
+ return diffDocument(parsePatch(change.diff), oldText === undefined || newText === undefined ? undefined : { old: oldText, new: newText }, ignoreWhitespace);
15
+ }, [change.diff, oldText, newText, ignoreWhitespace, sourceChecked]);
16
+ if (document === undefined) {
17
+ return _jsx(DiffOmitted, { path: change.path });
18
+ }
19
+ return (_jsx(EditorDocumentDiff, { document: document, path: change.path, loadEngine: loadEngine, fallback: fallback, wrap: wrap, fill: fill, layout: layout, maxHeight: maxHeight }));
20
+ }
@@ -0,0 +1,4 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare function LegacyDiffPool({ children }: {
3
+ children: ReactNode;
4
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useWorkerPool } from '@pierre/diffs/react';
3
+ import { chatHost } from '../../../host.js';
4
+ import DiffPool from '../DiffPool.js';
5
+ export function LegacyDiffPool({ children }) {
6
+ const outer = useWorkerPool();
7
+ const workerFactory = chatHost().code.legacyDiffWorker;
8
+ if (outer !== undefined || workerFactory === undefined) {
9
+ return children;
10
+ }
11
+ return _jsx(DiffPool, { workerFactory: workerFactory, children: children });
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { FileChange } from '../../logic/tools.js';
2
+ export default function LegacyEditDiff({ change, maxHeight }: {
3
+ change: FileChange;
4
+ maxHeight?: number;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { LegacyDiffPool } from './LegacyDiffPool.js';
3
+ import { useReviewSettled } from './review-settle.js';
4
+ import { useMemo } from 'react';
5
+ import { MultiFileDiff } from '@pierre/diffs/react';
6
+ import { chatHost } from '../../../host.js';
7
+ import { useDiffTheme } from '../diff-theme.js';
8
+ export default function LegacyEditDiff({ change, maxHeight }) {
9
+ useReviewSettled();
10
+ const resolved = chatHost().code.useMode();
11
+ const theme = useDiffTheme();
12
+ const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
13
+ const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
14
+ const options = useMemo(() => ({
15
+ diffStyle: 'unified',
16
+ theme,
17
+ themeType: resolved,
18
+ disableFileHeader: true,
19
+ overflow: 'wrap',
20
+ hunkSeparators: 'simple'
21
+ }), [theme, resolved]);
22
+ return (_jsx("div", { className: "chat-diff select-text", style: maxHeight === undefined ? undefined : { maxHeight, overflow: 'auto' }, children: _jsx(LegacyDiffPool, { children: _jsx(MultiFileDiff, { oldFile: oldFile, newFile: newFile, options: options }) }) }));
23
+ }
@@ -0,0 +1,2 @@
1
+ import type { UnifiedDiffProps } from './diff-props.js';
2
+ export default function LegacyUnifiedDiff({ change, overflow, diffStyle, fill, contents, maxHeight }: UnifiedDiffProps): import("react").JSX.Element;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { LegacyDiffPool } from './LegacyDiffPool.js';
3
+ import { useReviewSettled } from './review-settle.js';
4
+ import { useMemo } from 'react';
5
+ import clsx from 'clsx';
6
+ import { FileDiff, PatchDiff } from '@pierre/diffs/react';
7
+ import { chatHost } from '../../../host.js';
8
+ import { useDiffTheme } from '../diff-theme.js';
9
+ import { fullFileDiff } from '../full-diff.js';
10
+ /*
11
+ * A patch the CLI reported itself. The renderer needs a file header, which a CLI that only sends hunks
12
+ * gets from the path we know; a diff without hunks is shown as its own lines, so nothing is swallowed.
13
+ */
14
+ function asPatch(change) {
15
+ if (!/^@@/m.test(change.diff)) {
16
+ return null;
17
+ }
18
+ if (/^\+\+\+ /m.test(change.diff)) {
19
+ return change.diff;
20
+ }
21
+ const from = change.kind === 'add' ? '/dev/null' : `a/${change.path}`;
22
+ const to = change.kind === 'delete' ? '/dev/null' : `b/${change.path}`;
23
+ return `--- ${from}\n+++ ${to}\n${change.diff}`;
24
+ }
25
+ /* Lets a diff with a view of its own run to its bottom, so a short one's scrollbar sits there and not under its last line. */
26
+ const FILL_CSS = `
27
+ :host { display: flex; flex-direction: column; flex-grow: 1; }
28
+ pre { flex-grow: 1; align-content: start; }
29
+ pre[data-diff-type="single"] { display: flex; flex-direction: column; }
30
+ pre[data-diff-type="split"][data-overflow="scroll"]:not([data-dehydrated]) { align-content: stretch; }
31
+ pre > [data-code] { flex-grow: 1; align-self: stretch; align-content: start; }
32
+ `;
33
+ function lineClass(line) {
34
+ if (line.startsWith('+')) {
35
+ return 'text-chat-added';
36
+ }
37
+ if (line.startsWith('-')) {
38
+ return 'text-chat-removed';
39
+ }
40
+ return 'text-text-muted';
41
+ }
42
+ export default function LegacyUnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents, maxHeight }) {
43
+ useReviewSettled();
44
+ const resolved = chatHost().code.useMode();
45
+ const theme = useDiffTheme();
46
+ const patch = useMemo(() => asPatch(change), [change]);
47
+ const oldText = contents?.old;
48
+ const newText = contents?.new;
49
+ const full = useMemo(() => (patch === null || oldText === undefined || newText === undefined ? null : fullFileDiff(patch, change.path, { old: oldText, new: newText })), [patch, change.path, oldText, newText]);
50
+ const unfolds = full !== null;
51
+ const options = useMemo(() => ({
52
+ theme,
53
+ themeType: resolved,
54
+ disableFileHeader: true,
55
+ diffStyle,
56
+ overflow,
57
+ hunkSeparators: unfolds ? 'line-info' : 'simple',
58
+ unsafeCSS: fill ? FILL_CSS : undefined
59
+ }), [diffStyle, overflow, theme, resolved, fill, unfolds]);
60
+ if (patch === null) {
61
+ return (_jsx("pre", { className: "max-h-64 overflow-auto px-3 py-2 font-mono text-code select-text", style: fill ? undefined : { maxHeight }, children: change.diff
62
+ .replace(/\n$/, '')
63
+ .split('\n')
64
+ .map((line, index) => (_jsx("div", { className: clsx(lineClass(line), 'whitespace-pre-wrap'), children: line }, index))) }));
65
+ }
66
+ return (_jsx("div", { className: clsx('chat-diff select-text', fill && 'flex grow flex-col'), style: fill || maxHeight === undefined ? undefined : { maxHeight, overflow: 'auto' }, children: _jsx(LegacyDiffPool, { children: full === null ? _jsx(PatchDiff, { patch: patch, options: options }) : _jsx(FileDiff, { fileDiff: full, options: options }) }) }));
67
+ }
@@ -0,0 +1,6 @@
1
+ export declare function RawDiff({ text, overflow, fill, maxHeight }: {
2
+ text: string;
3
+ overflow: 'wrap' | 'scroll';
4
+ fill: boolean;
5
+ maxHeight?: number;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useReviewSettled } from './review-settle.js';
4
+ export function RawDiff({ text, overflow, fill, maxHeight }) {
5
+ useReviewSettled();
6
+ return (_jsx("pre", { "data-diff-raw": true, className: clsx('overflow-auto px-3 py-2 font-mono text-code text-text-muted select-text', fill && 'h-full min-h-0', overflow === 'wrap' ? 'whitespace-pre-wrap' : 'whitespace-pre'), style: fill ? undefined : { maxHeight: maxHeight ?? 256 }, children: text.replace(/\n$/, '') }));
7
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReviewEntry } from './review-plan.js';
2
+ export declare function ReviewDiff({ entry, maxHeight }: {
3
+ entry: ReviewEntry;
4
+ maxHeight?: number;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Suspense } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { lazyNamed } from '../../../lazy.js';
5
+ const EditDiff = lazyNamed(() => import('../EditDiff.js'), 'default');
6
+ const UnifiedDiff = lazyNamed(() => import('../UnifiedDiff.js'), 'default');
7
+ export function ReviewDiff({ entry, maxHeight = 480 }) {
8
+ const { t } = useTranslation('agent-chat');
9
+ return (_jsx("div", { "data-review-entry": entry.key, children: _jsx(Suspense, { fallback: _jsx("p", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: entry.change.kind === 'edit' ? (_jsx(EditDiff, { change: entry.change.edit, maxHeight: maxHeight })) : (_jsx(UnifiedDiff, { change: entry.change.patch, contents: entry.change.contents, maxHeight: maxHeight })) }) }));
10
+ }
@@ -0,0 +1,12 @@
1
+ import type { ChatFileChange } from '@adecore/agent-contracts';
2
+ import type { DiffContents } from '../full-diff.js';
3
+ export interface UnifiedDiffProps {
4
+ allowEditor?: boolean;
5
+ maxHeight?: number;
6
+ ignoreWhitespace?: boolean;
7
+ change: ChatFileChange;
8
+ overflow?: 'wrap' | 'scroll';
9
+ diffStyle?: 'unified' | 'split';
10
+ fill?: boolean;
11
+ contents?: DiffContents;
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ interface TextBudget {
2
+ readonly rows: number;
3
+ readonly characters: number;
4
+ readonly lineLength: number;
5
+ }
6
+ export declare function textFitsBudget(text: string, budget: TextBudget): boolean;
7
+ export declare function diffSourceBudget(before: string, after: string): boolean;
8
+ export declare function patchSourceBudget(patch: string): boolean;
9
+ export declare function legacyDiffBudget(before: string, after: string, patch?: string): boolean;
10
+ export {};