asdf
Squash the current repository state back into one baseline commit while preserving the README reframing and repository contents. Constraint: User explicitly requested a single squashed commit with subject "asdf" Confidence: high Scope-risk: broad Reversibility: clean Directive: This commit intentionally rewrites published history; coordinate before future force-pushes Tested: git status clean; local history rewritten to one commit; force-pushed main to origin and instructkr Not-tested: Fresh clone verification after push
This commit is contained in:
commit
d2542c9a62
51
src/components/PromptInput/HistorySearchInput.tsx
Normal file
51
src/components/PromptInput/HistorySearchInput.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { stringWidth } from '../../ink/stringWidth.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import TextInput from '../TextInput.js';
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
historyFailedMatch: boolean;
|
||||
};
|
||||
function HistorySearchInput(t0) {
|
||||
const $ = _c(9);
|
||||
const {
|
||||
value,
|
||||
onChange,
|
||||
historyFailedMatch
|
||||
} = t0;
|
||||
const t1 = historyFailedMatch ? "no matching prompt:" : "search prompts:";
|
||||
let t2;
|
||||
if ($[0] !== t1) {
|
||||
t2 = <Text dimColor={true}>{t1}</Text>;
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
const t3 = stringWidth(value) + 1;
|
||||
let t4;
|
||||
if ($[2] !== onChange || $[3] !== t3 || $[4] !== value) {
|
||||
t4 = <TextInput value={value} onChange={onChange} cursorOffset={value.length} onChangeCursorOffset={_temp} columns={t3} focus={true} showCursor={true} multiline={false} dimColor={true} />;
|
||||
$[2] = onChange;
|
||||
$[3] = t3;
|
||||
$[4] = value;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
let t5;
|
||||
if ($[6] !== t2 || $[7] !== t4) {
|
||||
t5 = <Box gap={1}>{t2}{t4}</Box>;
|
||||
$[6] = t2;
|
||||
$[7] = t4;
|
||||
$[8] = t5;
|
||||
} else {
|
||||
t5 = $[8];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
function _temp() {}
|
||||
export default HistorySearchInput;
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInN0cmluZ1dpZHRoIiwiQm94IiwiVGV4dCIsIlRleHRJbnB1dCIsIlByb3BzIiwidmFsdWUiLCJvbkNoYW5nZSIsImhpc3RvcnlGYWlsZWRNYXRjaCIsIkhpc3RvcnlTZWFyY2hJbnB1dCIsInQwIiwiJCIsIl9jIiwidDEiLCJ0MiIsInQzIiwidDQiLCJsZW5ndGgiLCJfdGVtcCIsInQ1Il0sInNvdXJjZXMiOlsiSGlzdG9yeVNlYXJjaElucHV0LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHN0cmluZ1dpZHRoIH0gZnJvbSAnLi4vLi4vaW5rL3N0cmluZ1dpZHRoLmpzJ1xuaW1wb3J0IHsgQm94LCBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IFRleHRJbnB1dCBmcm9tICcuLi9UZXh0SW5wdXQuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIHZhbHVlOiBzdHJpbmdcbiAgb25DaGFuZ2U6ICh2YWx1ZTogc3RyaW5nKSA9PiB2b2lkXG4gIGhpc3RvcnlGYWlsZWRNYXRjaDogYm9vbGVhblxufVxuXG5mdW5jdGlvbiBIaXN0b3J5U2VhcmNoSW5wdXQoe1xuICB2YWx1ZSxcbiAgb25DaGFuZ2UsXG4gIGhpc3RvcnlGYWlsZWRNYXRjaCxcbn06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgcmV0dXJuIChcbiAgICA8Qm94IGdhcD17MX0+XG4gICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAge2hpc3RvcnlGYWlsZWRNYXRjaCA/ICdubyBtYXRjaGluZyBwcm9tcHQ6JyA6ICdzZWFyY2ggcHJvbXB0czonfVxuICAgICAgPC9UZXh0PlxuICAgICAgPFRleHRJbnB1dFxuICAgICAgICB2YWx1ZT17dmFsdWV9XG4gICAgICAgIG9uQ2hhbmdlPXtvbkNoYW5nZX1cbiAgICAgICAgLy8gRm9yY2UgY3Vyc29yIHRvIGVuZCBvZiBzZWFyY2ggaW5wdXQgc2luY2UgbmF2aWdhdGlvbiBzaG91bGQgY2FuY2VsIHNlYXJjaFxuICAgICAgICBjdXJzb3JPZmZzZXQ9e3ZhbHVlLmxlbmd0aH1cbiAgICAgICAgb25DaGFuZ2VDdXJzb3JPZmZzZXQ9eygpID0+IHt9fVxuICAgICAgICBjb2x1bW5zPXtzdHJpbmdXaWR0aCh2YWx1ZSkgKyAxfVxuICAgICAgICBmb2N1cz17dHJ1ZX1cbiAgICAgICAgc2hvd0N1cnNvcj17dHJ1ZX1cbiAgICAgICAgbXVsdGlsaW5lPXtmYWxzZX1cbiAgICAgICAgZGltQ29sb3I9e3RydWV9XG4gICAgICAvPlxuICAgIDwvQm94PlxuICApXG59XG5cbmV4cG9ydCBkZWZhdWx0IEhpc3RvcnlTZWFyY2hJbnB1dFxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxXQUFXLFFBQVEsMEJBQTBCO0FBQ3RELFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxRQUFRLGNBQWM7QUFDeEMsT0FBT0MsU0FBUyxNQUFNLGlCQUFpQjtBQUV2QyxLQUFLQyxLQUFLLEdBQUc7RUFDWEMsS0FBSyxFQUFFLE1BQU07RUFDYkMsUUFBUSxFQUFFLENBQUNELEtBQUssRUFBRSxNQUFNLEVBQUUsR0FBRyxJQUFJO0VBQ2pDRSxrQkFBa0IsRUFBRSxPQUFPO0FBQzdCLENBQUM7QUFFRCxTQUFBQyxtQkFBQUMsRUFBQTtFQUFBLE1BQUFDLENBQUEsR0FBQUMsRUFBQTtFQUE0QjtJQUFBTixLQUFBO0lBQUFDLFFBQUE7SUFBQUM7RUFBQSxJQUFBRSxFQUlwQjtFQUlDLE1BQUFHLEVBQUEsR0FBQUwsa0JBQWtCLEdBQWxCLHFCQUE4RCxHQUE5RCxpQkFBOEQ7RUFBQSxJQUFBTSxFQUFBO0VBQUEsSUFBQUgsQ0FBQSxRQUFBRSxFQUFBO0lBRGpFQyxFQUFBLElBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBUixLQUFPLENBQUMsQ0FDWCxDQUFBRCxFQUE2RCxDQUNoRSxFQUZDLElBQUksQ0FFRTtJQUFBRixDQUFBLE1BQUFFLEVBQUE7SUFBQUYsQ0FBQSxNQUFBRyxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBSCxDQUFBO0VBQUE7RUFPSSxNQUFBSSxFQUFBLEdBQUFkLFdBQVcsQ0FBQ0ssS0FBSyxDQUFDLEdBQUcsQ0FBQztFQUFBLElBQUFVLEVBQUE7RUFBQSxJQUFBTCxDQUFBLFFBQUFKLFFBQUEsSUFBQUksQ0FBQSxRQUFBSSxFQUFBLElBQUFKLENBQUEsUUFBQUwsS0FBQTtJQU5qQ1UsRUFBQSxJQUFDLFNBQVMsQ0FDRFYsS0FBSyxDQUFMQSxNQUFJLENBQUMsQ0FDRkMsUUFBUSxDQUFSQSxTQUFPLENBQUMsQ0FFSixZQUFZLENBQVosQ0FBQUQsS0FBSyxDQUFBVyxNQUFNLENBQUMsQ0FDSixvQkFBUSxDQUFSLENBQUFDLEtBQU8sQ0FBQyxDQUNyQixPQUFzQixDQUF0QixDQUFBSCxFQUFxQixDQUFDLENBQ3hCLEtBQUksQ0FBSixLQUFHLENBQUMsQ0FDQyxVQUFJLENBQUosS0FBRyxDQUFDLENBQ0wsU0FBSyxDQUFMLE1BQUksQ0FBQyxDQUNOLFFBQUksQ0FBSixLQUFHLENBQUMsR0FDZDtJQUFBSixDQUFBLE1BQUFKLFFBQUE7SUFBQUksQ0FBQSxNQUFBSSxFQUFBO0lBQUFKLENBQUEsTUFBQUwsS0FBQTtJQUFBSyxDQUFBLE1BQUFLLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFMLENBQUE7RUFBQTtFQUFBLElBQUFRLEVBQUE7RUFBQSxJQUFBUixDQUFBLFFBQUFHLEVBQUEsSUFBQUgsQ0FBQSxRQUFBSyxFQUFBO0lBZkpHLEVBQUEsSUFBQyxHQUFHLENBQU0sR0FBQyxDQUFELEdBQUMsQ0FDVCxDQUFBTCxFQUVNLENBQ04sQ0FBQUUsRUFXQyxDQUNILEVBaEJDLEdBQUcsQ0FnQkU7SUFBQUwsQ0FBQSxNQUFBRyxFQUFBO0lBQUFILENBQUEsTUFBQUssRUFBQTtJQUFBTCxDQUFBLE1BQUFRLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFSLENBQUE7RUFBQTtFQUFBLE9BaEJOUSxFQWdCTTtBQUFBO0FBdEJWLFNBQUFELE1BQUE7QUEwQkEsZUFBZVQsa0JBQWtCIiwiaWdub3JlTGlzdCI6W119
|
||||
12
src/components/PromptInput/IssueFlagBanner.tsx
Normal file
12
src/components/PromptInput/IssueFlagBanner.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { FLAG_ICON } from '../../constants/figures.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
|
||||
/**
|
||||
* ANT-ONLY: Banner shown in the transcript that prompts users to report
|
||||
* issues via /issue. Appears when friction is detected in the conversation.
|
||||
*/
|
||||
export function IssueFlagBanner() {
|
||||
return null;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkZMQUdfSUNPTiIsIkJveCIsIlRleHQiLCJJc3N1ZUZsYWdCYW5uZXIiXSwic291cmNlcyI6WyJJc3N1ZUZsYWdCYW5uZXIudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgRkxBR19JQ09OIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL2ZpZ3VyZXMuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5cbi8qKlxuICogQU5ULU9OTFk6IEJhbm5lciBzaG93biBpbiB0aGUgdHJhbnNjcmlwdCB0aGF0IHByb21wdHMgdXNlcnMgdG8gcmVwb3J0XG4gKiBpc3N1ZXMgdmlhIC9pc3N1ZS4gQXBwZWFycyB3aGVuIGZyaWN0aW9uIGlzIGRldGVjdGVkIGluIHRoZSBjb252ZXJzYXRpb24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJc3N1ZUZsYWdCYW5uZXIoKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgaWYgKFwiZXh0ZXJuYWxcIiAhPT0gJ2FudCcpIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJyb3dcIiBtYXJnaW5Ub3A9ezF9IHdpZHRoPVwiMTAwJVwiPlxuICAgICAgPEJveCBtaW5XaWR0aD17Mn0+XG4gICAgICAgIDxUZXh0IGNvbG9yPVwid2FybmluZ1wiPntGTEFHX0lDT059PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgICA8VGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I+W0FOVC1PTkxZXSA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGNvbG9yPVwid2FybmluZ1wiIGJvbGQ+XG4gICAgICAgICAgU29tZXRoaW5nIG9mZiB3aXRoIENsYXVkZT9cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj4gL2lzc3VlIHRvIHJlcG9ydCBpdDwvVGV4dD5cbiAgICAgIDwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUtBLEtBQUssTUFBTSxPQUFPO0FBQzlCLFNBQVNDLFNBQVMsUUFBUSw0QkFBNEI7QUFDdEQsU0FBU0MsR0FBRyxFQUFFQyxJQUFJLFFBQVEsY0FBYzs7QUFFeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPLFNBQUFDLGdCQUFBO0VBQUEsT0FFSSxJQUFJO0FBQUEiLCJpZ25vcmVMaXN0IjpbXX0=
|
||||
332
src/components/PromptInput/Notifications.tsx
Normal file
332
src/components/PromptInput/Notifications.tsx
Normal file
File diff suppressed because one or more lines are too long
2339
src/components/PromptInput/PromptInput.tsx
Normal file
2339
src/components/PromptInput/PromptInput.tsx
Normal file
File diff suppressed because one or more lines are too long
191
src/components/PromptInput/PromptInputFooter.tsx
Normal file
191
src/components/PromptInput/PromptInputFooter.tsx
Normal file
File diff suppressed because one or more lines are too long
517
src/components/PromptInput/PromptInputFooterLeftSide.tsx
Normal file
517
src/components/PromptInput/PromptInputFooterLeftSide.tsx
Normal file
File diff suppressed because one or more lines are too long
293
src/components/PromptInput/PromptInputFooterSuggestions.tsx
Normal file
293
src/components/PromptInput/PromptInputFooterSuggestions.tsx
Normal file
File diff suppressed because one or more lines are too long
358
src/components/PromptInput/PromptInputHelpMenu.tsx
Normal file
358
src/components/PromptInput/PromptInputHelpMenu.tsx
Normal file
File diff suppressed because one or more lines are too long
93
src/components/PromptInput/PromptInputModeIndicator.tsx
Normal file
93
src/components/PromptInput/PromptInputModeIndicator.tsx
Normal file
File diff suppressed because one or more lines are too long
117
src/components/PromptInput/PromptInputQueuedCommands.tsx
Normal file
117
src/components/PromptInput/PromptInputQueuedCommands.tsx
Normal file
File diff suppressed because one or more lines are too long
25
src/components/PromptInput/PromptInputStashNotice.tsx
Normal file
25
src/components/PromptInput/PromptInputStashNotice.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import figures from 'figures';
|
||||
import * as React from 'react';
|
||||
import { Box, Text } from 'src/ink.js';
|
||||
type Props = {
|
||||
hasStash: boolean;
|
||||
};
|
||||
export function PromptInputStashNotice(t0) {
|
||||
const $ = _c(1);
|
||||
const {
|
||||
hasStash
|
||||
} = t0;
|
||||
if (!hasStash) {
|
||||
return null;
|
||||
}
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <Box paddingLeft={2}><Text dimColor={true}>{figures.pointerSmall} Stashed (auto-restores after submit)</Text></Box>;
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJCb3giLCJUZXh0IiwiUHJvcHMiLCJoYXNTdGFzaCIsIlByb21wdElucHV0U3Rhc2hOb3RpY2UiLCJ0MCIsIiQiLCJfYyIsInQxIiwiU3ltYm9sIiwiZm9yIiwicG9pbnRlclNtYWxsIl0sInNvdXJjZXMiOlsiUHJvbXB0SW5wdXRTdGFzaE5vdGljZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZpZ3VyZXMgZnJvbSAnZmlndXJlcydcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgQm94LCBUZXh0IH0gZnJvbSAnc3JjL2luay5qcydcblxudHlwZSBQcm9wcyA9IHtcbiAgaGFzU3Rhc2g6IGJvb2xlYW5cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFByb21wdElucHV0U3Rhc2hOb3RpY2UoeyBoYXNTdGFzaCB9OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGlmICghaGFzU3Rhc2gpIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IHBhZGRpbmdMZWZ0PXsyfT5cbiAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICB7ZmlndXJlcy5wb2ludGVyU21hbGx9IFN0YXNoZWQgKGF1dG8tcmVzdG9yZXMgYWZ0ZXIgc3VibWl0KVxuICAgICAgPC9UZXh0PlxuICAgIDwvQm94PlxuICApXG59XG4iXSwibWFwcGluZ3MiOiI7QUFBQSxPQUFPQSxPQUFPLE1BQU0sU0FBUztBQUM3QixPQUFPLEtBQUtDLEtBQUssTUFBTSxPQUFPO0FBQzlCLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxRQUFRLFlBQVk7QUFFdEMsS0FBS0MsS0FBSyxHQUFHO0VBQ1hDLFFBQVEsRUFBRSxPQUFPO0FBQ25CLENBQUM7QUFFRCxPQUFPLFNBQUFDLHVCQUFBQyxFQUFBO0VBQUEsTUFBQUMsQ0FBQSxHQUFBQyxFQUFBO0VBQWdDO0lBQUFKO0VBQUEsSUFBQUUsRUFBbUI7RUFDeEQsSUFBSSxDQUFDRixRQUFRO0lBQUEsT0FDSixJQUFJO0VBQUE7RUFDWixJQUFBSyxFQUFBO0VBQUEsSUFBQUYsQ0FBQSxRQUFBRyxNQUFBLENBQUFDLEdBQUE7SUFHQ0YsRUFBQSxJQUFDLEdBQUcsQ0FBYyxXQUFDLENBQUQsR0FBQyxDQUNqQixDQUFDLElBQUksQ0FBQyxRQUFRLENBQVIsS0FBTyxDQUFDLENBQ1gsQ0FBQVYsT0FBTyxDQUFBYSxZQUFZLENBQUUscUNBQ3hCLEVBRkMsSUFBSSxDQUdQLEVBSkMsR0FBRyxDQUlFO0lBQUFMLENBQUEsTUFBQUUsRUFBQTtFQUFBO0lBQUFBLEVBQUEsR0FBQUYsQ0FBQTtFQUFBO0VBQUEsT0FKTkUsRUFJTTtBQUFBIiwiaWdub3JlTGlzdCI6W119
|
||||
64
src/components/PromptInput/SandboxPromptFooterHint.tsx
Normal file
64
src/components/PromptInput/SandboxPromptFooterHint.tsx
Normal file
File diff suppressed because one or more lines are too long
143
src/components/PromptInput/ShimmeredInput.tsx
Normal file
143
src/components/PromptInput/ShimmeredInput.tsx
Normal file
File diff suppressed because one or more lines are too long
137
src/components/PromptInput/VoiceIndicator.tsx
Normal file
137
src/components/PromptInput/VoiceIndicator.tsx
Normal file
File diff suppressed because one or more lines are too long
33
src/components/PromptInput/inputModes.ts
Normal file
33
src/components/PromptInput/inputModes.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { HistoryMode } from 'src/hooks/useArrowKeyHistory.js'
|
||||
import type { PromptInputMode } from 'src/types/textInputTypes.js'
|
||||
|
||||
export function prependModeCharacterToInput(
|
||||
input: string,
|
||||
mode: PromptInputMode,
|
||||
): string {
|
||||
switch (mode) {
|
||||
case 'bash':
|
||||
return `!${input}`
|
||||
default:
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
export function getModeFromInput(input: string): HistoryMode {
|
||||
if (input.startsWith('!')) {
|
||||
return 'bash'
|
||||
}
|
||||
return 'prompt'
|
||||
}
|
||||
|
||||
export function getValueFromInput(input: string): string {
|
||||
const mode = getModeFromInput(input)
|
||||
if (mode === 'prompt') {
|
||||
return input
|
||||
}
|
||||
return input.slice(1)
|
||||
}
|
||||
|
||||
export function isInputModeCharacter(input: string): boolean {
|
||||
return input === '!'
|
||||
}
|
||||
90
src/components/PromptInput/inputPaste.ts
Normal file
90
src/components/PromptInput/inputPaste.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { getPastedTextRefNumLines } from 'src/history.js'
|
||||
import type { PastedContent } from 'src/utils/config.js'
|
||||
|
||||
const TRUNCATION_THRESHOLD = 10000 // Characters before we truncate
|
||||
const PREVIEW_LENGTH = 1000 // Characters to show at start and end
|
||||
|
||||
type TruncatedMessage = {
|
||||
truncatedText: string
|
||||
placeholderContent: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the input text should be truncated. If so, it adds a
|
||||
* truncated text placeholder and neturns
|
||||
*
|
||||
* @param text The input text
|
||||
* @param nextPasteId The reference id to use
|
||||
* @returns The new text to display and separate placeholder content if applicable.
|
||||
*/
|
||||
export function maybeTruncateMessageForInput(
|
||||
text: string,
|
||||
nextPasteId: number,
|
||||
): TruncatedMessage {
|
||||
// If the text is short enough, return it as-is
|
||||
if (text.length <= TRUNCATION_THRESHOLD) {
|
||||
return {
|
||||
truncatedText: text,
|
||||
placeholderContent: '',
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how much text to keep from start and end
|
||||
const startLength = Math.floor(PREVIEW_LENGTH / 2)
|
||||
const endLength = Math.floor(PREVIEW_LENGTH / 2)
|
||||
|
||||
// Extract the portions we'll keep
|
||||
const startText = text.slice(0, startLength)
|
||||
const endText = text.slice(-endLength)
|
||||
|
||||
// Calculate the number of lines that will be truncated
|
||||
const placeholderContent = text.slice(startLength, -endLength)
|
||||
const truncatedLines = getPastedTextRefNumLines(placeholderContent)
|
||||
|
||||
// Create a placeholder reference similar to pasted text
|
||||
const placeholderId = nextPasteId
|
||||
const placeholderRef = formatTruncatedTextRef(placeholderId, truncatedLines)
|
||||
|
||||
// Combine the parts with the placeholder
|
||||
const truncatedText = startText + placeholderRef + endText
|
||||
|
||||
return {
|
||||
truncatedText,
|
||||
placeholderContent,
|
||||
}
|
||||
}
|
||||
|
||||
function formatTruncatedTextRef(id: number, numLines: number): string {
|
||||
return `[...Truncated text #${id} +${numLines} lines...]`
|
||||
}
|
||||
|
||||
export function maybeTruncateInput(
|
||||
input: string,
|
||||
pastedContents: Record<number, PastedContent>,
|
||||
): { newInput: string; newPastedContents: Record<number, PastedContent> } {
|
||||
// Get the next available ID for the truncated content
|
||||
const existingIds = Object.keys(pastedContents).map(Number)
|
||||
const nextPasteId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1
|
||||
|
||||
// Apply truncation
|
||||
const { truncatedText, placeholderContent } = maybeTruncateMessageForInput(
|
||||
input,
|
||||
nextPasteId,
|
||||
)
|
||||
|
||||
if (!placeholderContent) {
|
||||
return { newInput: input, newPastedContents: pastedContents }
|
||||
}
|
||||
|
||||
return {
|
||||
newInput: truncatedText,
|
||||
newPastedContents: {
|
||||
...pastedContents,
|
||||
[nextPasteId]: {
|
||||
id: nextPasteId,
|
||||
type: 'text',
|
||||
content: placeholderContent,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
58
src/components/PromptInput/useMaybeTruncateInput.ts
Normal file
58
src/components/PromptInput/useMaybeTruncateInput.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { PastedContent } from 'src/utils/config.js'
|
||||
import { maybeTruncateInput } from './inputPaste.js'
|
||||
|
||||
type Props = {
|
||||
input: string
|
||||
pastedContents: Record<number, PastedContent>
|
||||
onInputChange: (input: string) => void
|
||||
setCursorOffset: (offset: number) => void
|
||||
setPastedContents: (contents: Record<number, PastedContent>) => void
|
||||
}
|
||||
|
||||
export function useMaybeTruncateInput({
|
||||
input,
|
||||
pastedContents,
|
||||
onInputChange,
|
||||
setCursorOffset,
|
||||
setPastedContents,
|
||||
}: Props) {
|
||||
// Track if we've initialized this specific input value
|
||||
const [hasAppliedTruncationToInput, setHasAppliedTruncationToInput] =
|
||||
useState(false)
|
||||
|
||||
// Process input for truncation and pasted images from MessageSelector.
|
||||
useEffect(() => {
|
||||
if (hasAppliedTruncationToInput) {
|
||||
return
|
||||
}
|
||||
|
||||
if (input.length <= 10_000) {
|
||||
return
|
||||
}
|
||||
|
||||
const { newInput, newPastedContents } = maybeTruncateInput(
|
||||
input,
|
||||
pastedContents,
|
||||
)
|
||||
|
||||
onInputChange(newInput)
|
||||
setCursorOffset(newInput.length)
|
||||
setPastedContents(newPastedContents)
|
||||
setHasAppliedTruncationToInput(true)
|
||||
}, [
|
||||
input,
|
||||
hasAppliedTruncationToInput,
|
||||
pastedContents,
|
||||
onInputChange,
|
||||
setPastedContents,
|
||||
setCursorOffset,
|
||||
])
|
||||
|
||||
// Reset hasInitializedInput when input is cleared (e.g., after submission)
|
||||
useEffect(() => {
|
||||
if (input === '') {
|
||||
setHasAppliedTruncationToInput(false)
|
||||
}
|
||||
}, [input])
|
||||
}
|
||||
76
src/components/PromptInput/usePromptInputPlaceholder.ts
Normal file
76
src/components/PromptInput/usePromptInputPlaceholder.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { feature } from 'bun:bundle'
|
||||
import { useMemo } from 'react'
|
||||
import { useCommandQueue } from 'src/hooks/useCommandQueue.js'
|
||||
import { useAppState } from 'src/state/AppState.js'
|
||||
import { getGlobalConfig } from 'src/utils/config.js'
|
||||
import { getExampleCommandFromCache } from 'src/utils/exampleCommands.js'
|
||||
import { isQueuedCommandEditable } from 'src/utils/messageQueueManager.js'
|
||||
|
||||
// Dead code elimination: conditional import for proactive mode
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const proactiveModule =
|
||||
feature('PROACTIVE') || feature('KAIROS')
|
||||
? require('../../proactive/index.js')
|
||||
: null
|
||||
|
||||
type Props = {
|
||||
input: string
|
||||
submitCount: number
|
||||
viewingAgentName?: string
|
||||
}
|
||||
|
||||
const NUM_TIMES_QUEUE_HINT_SHOWN = 3
|
||||
const MAX_TEAMMATE_NAME_LENGTH = 20
|
||||
|
||||
export function usePromptInputPlaceholder({
|
||||
input,
|
||||
submitCount,
|
||||
viewingAgentName,
|
||||
}: Props): string | undefined {
|
||||
const queuedCommands = useCommandQueue()
|
||||
const promptSuggestionEnabled = useAppState(s => s.promptSuggestionEnabled)
|
||||
const placeholder = useMemo(() => {
|
||||
if (input !== '') {
|
||||
return
|
||||
}
|
||||
|
||||
// Show teammate hint when viewing teammate
|
||||
if (viewingAgentName) {
|
||||
const displayName =
|
||||
viewingAgentName.length > MAX_TEAMMATE_NAME_LENGTH
|
||||
? viewingAgentName.slice(0, MAX_TEAMMATE_NAME_LENGTH - 3) + '...'
|
||||
: viewingAgentName
|
||||
return `Message @${displayName}…`
|
||||
}
|
||||
|
||||
// Show queue hint if user has not seen it yet.
|
||||
// Only count user-editable commands — task-notification and isMeta
|
||||
// are hidden from the prompt area (see PromptInputQueuedCommands).
|
||||
if (
|
||||
queuedCommands.some(isQueuedCommandEditable) &&
|
||||
(getGlobalConfig().queuedCommandUpHintCount || 0) <
|
||||
NUM_TIMES_QUEUE_HINT_SHOWN
|
||||
) {
|
||||
return 'Press up to edit queued messages'
|
||||
}
|
||||
|
||||
// Show example command if user has not submitted yet and suggestions are enabled.
|
||||
// Skip in proactive mode — the model drives the conversation so onboarding
|
||||
// examples are irrelevant and block prompt suggestions from showing.
|
||||
if (
|
||||
submitCount < 1 &&
|
||||
promptSuggestionEnabled &&
|
||||
!proactiveModule?.isProactiveActive()
|
||||
) {
|
||||
return getExampleCommandFromCache()
|
||||
}
|
||||
}, [
|
||||
input,
|
||||
queuedCommands,
|
||||
submitCount,
|
||||
promptSuggestionEnabled,
|
||||
viewingAgentName,
|
||||
])
|
||||
|
||||
return placeholder
|
||||
}
|
||||
31
src/components/PromptInput/useShowFastIconHint.ts
Normal file
31
src/components/PromptInput/useShowFastIconHint.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const HINT_DISPLAY_DURATION_MS = 5000
|
||||
|
||||
let hasShownThisSession = false
|
||||
|
||||
/**
|
||||
* Hook to manage the /fast hint display next to the fast icon.
|
||||
* Shows the hint for 5 seconds once per session.
|
||||
*/
|
||||
export function useShowFastIconHint(showFastIcon: boolean): boolean {
|
||||
const [showHint, setShowHint] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (hasShownThisSession || !showFastIcon) {
|
||||
return
|
||||
}
|
||||
|
||||
hasShownThisSession = true
|
||||
setShowHint(true)
|
||||
|
||||
const timer = setTimeout(setShowHint, HINT_DISPLAY_DURATION_MS, false)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
setShowHint(false)
|
||||
}
|
||||
}, [showFastIcon])
|
||||
|
||||
return showHint
|
||||
}
|
||||
155
src/components/PromptInput/useSwarmBanner.ts
Normal file
155
src/components/PromptInput/useSwarmBanner.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import * as React from 'react'
|
||||
import { useAppState, useAppStateStore } from '../../state/AppState.js'
|
||||
import {
|
||||
getActiveAgentForInput,
|
||||
getViewedTeammateTask,
|
||||
} from '../../state/selectors.js'
|
||||
import {
|
||||
AGENT_COLOR_TO_THEME_COLOR,
|
||||
AGENT_COLORS,
|
||||
type AgentColorName,
|
||||
getAgentColor,
|
||||
} from '../../tools/AgentTool/agentColorManager.js'
|
||||
import { getStandaloneAgentName } from '../../utils/standaloneAgent.js'
|
||||
import { isInsideTmux } from '../../utils/swarm/backends/detection.js'
|
||||
import {
|
||||
getCachedDetectionResult,
|
||||
isInProcessEnabled,
|
||||
} from '../../utils/swarm/backends/registry.js'
|
||||
import { getSwarmSocketName } from '../../utils/swarm/constants.js'
|
||||
import {
|
||||
getAgentName,
|
||||
getTeammateColor,
|
||||
getTeamName,
|
||||
isTeammate,
|
||||
} from '../../utils/teammate.js'
|
||||
import { isInProcessTeammate } from '../../utils/teammateContext.js'
|
||||
import type { Theme } from '../../utils/theme.js'
|
||||
|
||||
type SwarmBannerInfo = {
|
||||
text: string
|
||||
bgColor: keyof Theme
|
||||
} | null
|
||||
|
||||
/**
|
||||
* Hook that returns banner information for swarm, standalone agent, or --agent CLI context.
|
||||
* - Leader (not in tmux): Returns "tmux -L ... attach" command with cyan background
|
||||
* - Leader (in tmux / in-process): Falls through to standalone-agent check — shows
|
||||
* /rename name + /color background if set, else null
|
||||
* - Teammate: Returns "teammate@team" format with their assigned color background
|
||||
* - Viewing a background agent (CoordinatorTaskPanel): Returns agent name with its color
|
||||
* - Standalone agent: Returns agent name with their color background (no @team)
|
||||
* - --agent CLI flag: Returns "@agentName" with cyan background
|
||||
*/
|
||||
export function useSwarmBanner(): SwarmBannerInfo {
|
||||
const teamContext = useAppState(s => s.teamContext)
|
||||
const standaloneAgentContext = useAppState(s => s.standaloneAgentContext)
|
||||
const agent = useAppState(s => s.agent)
|
||||
// Subscribe so the banner updates on enter/exit teammate view even though
|
||||
// getActiveAgentForInput reads it from store.getState().
|
||||
useAppState(s => s.viewingAgentTaskId)
|
||||
const store = useAppStateStore()
|
||||
const [insideTmux, setInsideTmux] = React.useState<boolean | null>(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
void isInsideTmux().then(setInsideTmux)
|
||||
}, [])
|
||||
|
||||
const state = store.getState()
|
||||
|
||||
// Teammate process: show @agentName with assigned color.
|
||||
// In-process teammates run headless — their banner shows in the leader UI instead.
|
||||
if (isTeammate() && !isInProcessTeammate()) {
|
||||
const agentName = getAgentName()
|
||||
if (agentName && getTeamName()) {
|
||||
return {
|
||||
text: `@${agentName}`,
|
||||
bgColor: toThemeColor(
|
||||
teamContext?.selfAgentColor ?? getTeammateColor(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Leader with spawned teammates: tmux-attach hint when external, else show
|
||||
// the viewed teammate's name when inside tmux / native panes / in-process.
|
||||
const hasTeammates =
|
||||
teamContext?.teamName &&
|
||||
teamContext.teammates &&
|
||||
Object.keys(teamContext.teammates).length > 0
|
||||
if (hasTeammates) {
|
||||
const viewedTeammate = getViewedTeammateTask(state)
|
||||
const viewedColor = toThemeColor(viewedTeammate?.identity.color)
|
||||
const inProcessMode = isInProcessEnabled()
|
||||
const nativePanes = getCachedDetectionResult()?.isNative ?? false
|
||||
|
||||
if (insideTmux === false && !inProcessMode && !nativePanes) {
|
||||
return {
|
||||
text: `View teammates: \`tmux -L ${getSwarmSocketName()} a\``,
|
||||
bgColor: viewedColor,
|
||||
}
|
||||
}
|
||||
if (
|
||||
(insideTmux === true || inProcessMode || nativePanes) &&
|
||||
viewedTeammate
|
||||
) {
|
||||
return {
|
||||
text: `@${viewedTeammate.identity.agentName}`,
|
||||
bgColor: viewedColor,
|
||||
}
|
||||
}
|
||||
// insideTmux === null: still loading — fall through.
|
||||
// Not viewing a teammate: fall through so /rename and /color are honored.
|
||||
}
|
||||
|
||||
// Viewing a background agent (CoordinatorTaskPanel): local_agent tasks aren't
|
||||
// InProcessTeammates, so getViewedTeammateTask misses them. Reverse-lookup the
|
||||
// name from agentNameRegistry the same way CoordinatorAgentStatus does.
|
||||
const active = getActiveAgentForInput(state)
|
||||
if (active.type === 'named_agent') {
|
||||
const task = active.task
|
||||
let name: string | undefined
|
||||
for (const [n, id] of state.agentNameRegistry) {
|
||||
if (id === task.id) {
|
||||
name = n
|
||||
break
|
||||
}
|
||||
}
|
||||
return {
|
||||
text: name ? `@${name}` : task.description,
|
||||
bgColor: getAgentColor(task.agentType) ?? 'cyan_FOR_SUBAGENTS_ONLY',
|
||||
}
|
||||
}
|
||||
|
||||
// Standalone agent (/rename, /color): name and/or custom color, no @team.
|
||||
const standaloneName = getStandaloneAgentName(state)
|
||||
const standaloneColor = standaloneAgentContext?.color
|
||||
if (standaloneName || standaloneColor) {
|
||||
return {
|
||||
text: standaloneName ?? '',
|
||||
bgColor: toThemeColor(standaloneColor),
|
||||
}
|
||||
}
|
||||
|
||||
// --agent CLI flag (when not handled above).
|
||||
if (agent) {
|
||||
const agentDef = state.agentDefinitions.activeAgents.find(
|
||||
a => a.agentType === agent,
|
||||
)
|
||||
return {
|
||||
text: agent,
|
||||
bgColor: toThemeColor(agentDef?.color, 'promptBorder'),
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function toThemeColor(
|
||||
colorName: string | undefined,
|
||||
fallback: keyof Theme = 'cyan_FOR_SUBAGENTS_ONLY',
|
||||
): keyof Theme {
|
||||
return colorName && AGENT_COLORS.includes(colorName as AgentColorName)
|
||||
? AGENT_COLOR_TO_THEME_COLOR[colorName as AgentColorName]
|
||||
: fallback
|
||||
}
|
||||
60
src/components/PromptInput/utils.ts
Normal file
60
src/components/PromptInput/utils.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
hasUsedBackslashReturn,
|
||||
isShiftEnterKeyBindingInstalled,
|
||||
} from '../../commands/terminalSetup/terminalSetup.js'
|
||||
import type { Key } from '../../ink.js'
|
||||
import { getGlobalConfig } from '../../utils/config.js'
|
||||
import { env } from '../../utils/env.js'
|
||||
/**
|
||||
* Helper function to check if vim mode is currently enabled
|
||||
* @returns boolean indicating if vim mode is active
|
||||
*/
|
||||
export function isVimModeEnabled(): boolean {
|
||||
const config = getGlobalConfig()
|
||||
return config.editorMode === 'vim'
|
||||
}
|
||||
|
||||
export function getNewlineInstructions(): string {
|
||||
// Apple Terminal on macOS uses native modifier key detection for Shift+Enter
|
||||
if (env.terminal === 'Apple_Terminal' && process.platform === 'darwin') {
|
||||
return 'shift + ⏎ for newline'
|
||||
}
|
||||
|
||||
// For iTerm2 and VSCode, show Shift+Enter instructions if installed
|
||||
if (isShiftEnterKeyBindingInstalled()) {
|
||||
return 'shift + ⏎ for newline'
|
||||
}
|
||||
|
||||
// Otherwise show backslash+return instructions
|
||||
return hasUsedBackslashReturn()
|
||||
? '\\⏎ for newline'
|
||||
: 'backslash (\\) + return (⏎) for newline'
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the keystroke is a printable character that does not begin
|
||||
* with whitespace — i.e., a normal letter/digit/symbol the user typed.
|
||||
* Used to gate the lazy space inserted after an image pill.
|
||||
*/
|
||||
export function isNonSpacePrintable(input: string, key: Key): boolean {
|
||||
if (
|
||||
key.ctrl ||
|
||||
key.meta ||
|
||||
key.escape ||
|
||||
key.return ||
|
||||
key.tab ||
|
||||
key.backspace ||
|
||||
key.delete ||
|
||||
key.upArrow ||
|
||||
key.downArrow ||
|
||||
key.leftArrow ||
|
||||
key.rightArrow ||
|
||||
key.pageUp ||
|
||||
key.pageDown ||
|
||||
key.home ||
|
||||
key.end
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return input.length > 0 && !/^\s/.test(input) && !input.startsWith('\x1b')
|
||||
}
|
||||
Reference in New Issue
Block a user