fix: MultilineInput (#8382)
This commit is contained in:
Generated
+8
-23
@@ -387,9 +387,9 @@ importers:
|
|||||||
ink:
|
ink:
|
||||||
specifier: ^6.8.0
|
specifier: ^6.8.0
|
||||||
version: 6.8.0(@types/react@19.2.14)(react@19.2.4)
|
version: 6.8.0(@types/react@19.2.14)(react@19.2.4)
|
||||||
ink-text-input:
|
ink-multiline-input:
|
||||||
specifier: ^6.0.0
|
specifier: ^0.1.0
|
||||||
version: 6.0.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
version: 0.1.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||||
marked:
|
marked:
|
||||||
specifier: ^15.0.12
|
specifier: ^15.0.12
|
||||||
version: 15.0.12
|
version: 15.0.12
|
||||||
@@ -536,10 +536,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@babel/core': ^7.0.0-0
|
||||||
|
|
||||||
'@babel/runtime@7.28.6':
|
|
||||||
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/runtime@7.29.2':
|
'@babel/runtime@7.29.2':
|
||||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -4667,11 +4663,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
|
resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
ink-text-input@6.0.0:
|
ink-multiline-input@0.1.0:
|
||||||
resolution: {integrity: sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==}
|
resolution: {integrity: sha512-GmIF5YdaxmT5pHwI67yTDe5UDId20WeGJx39zwaYJ6SwMrlioRRMn+BxklsAUYsvA6BJfh2kpHNR1Kan69UTxw==}
|
||||||
engines: {node: '>=18'}
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
ink: '>=5'
|
ink: '>=6'
|
||||||
react: ^19.2.4
|
react: ^19.2.4
|
||||||
|
|
||||||
ink@6.8.0:
|
ink@6.8.0:
|
||||||
@@ -6816,10 +6811,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
|
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
type-fest@4.41.0:
|
|
||||||
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
|
|
||||||
type-fest@5.5.0:
|
type-fest@5.5.0:
|
||||||
resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==}
|
resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -7397,8 +7388,6 @@ snapshots:
|
|||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/helper-plugin-utils': 7.28.6
|
'@babel/helper-plugin-utils': 7.28.6
|
||||||
|
|
||||||
'@babel/runtime@7.28.6': {}
|
|
||||||
|
|
||||||
'@babel/runtime@7.29.2': {}
|
'@babel/runtime@7.29.2': {}
|
||||||
|
|
||||||
'@babel/template@7.28.6':
|
'@babel/template@7.28.6':
|
||||||
@@ -9851,7 +9840,7 @@ snapshots:
|
|||||||
'@testing-library/dom@10.4.1':
|
'@testing-library/dom@10.4.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.0
|
'@babel/code-frame': 7.29.0
|
||||||
'@babel/runtime': 7.28.6
|
'@babel/runtime': 7.29.2
|
||||||
'@types/aria-query': 5.0.4
|
'@types/aria-query': 5.0.4
|
||||||
aria-query: 5.3.0
|
aria-query: 5.3.0
|
||||||
dom-accessibility-api: 0.5.16
|
dom-accessibility-api: 0.5.16
|
||||||
@@ -12139,12 +12128,10 @@ snapshots:
|
|||||||
|
|
||||||
ini@2.0.0: {}
|
ini@2.0.0: {}
|
||||||
|
|
||||||
ink-text-input@6.0.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.4))(react@19.2.4):
|
ink-multiline-input@0.1.0(ink@6.8.0(@types/react@19.2.14)(react@19.2.4))(react@19.2.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk: 5.6.2
|
|
||||||
ink: 6.8.0(@types/react@19.2.14)(react@19.2.4)
|
ink: 6.8.0(@types/react@19.2.14)(react@19.2.4)
|
||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
type-fest: 4.41.0
|
|
||||||
|
|
||||||
ink@6.8.0(@types/react@19.2.14)(react@19.2.4):
|
ink@6.8.0(@types/react@19.2.14)(react@19.2.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14691,8 +14678,6 @@ snapshots:
|
|||||||
|
|
||||||
type-fest@1.4.0: {}
|
type-fest@1.4.0: {}
|
||||||
|
|
||||||
type-fest@4.41.0: {}
|
|
||||||
|
|
||||||
type-fest@5.5.0:
|
type-fest@5.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tagged-tag: 1.0.0
|
tagged-tag: 1.0.0
|
||||||
|
|||||||
@@ -30,10 +30,10 @@
|
|||||||
"lint": "tsc --noEmit"
|
"lint": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentclientprotocol/sdk": "^0.14.1",
|
|
||||||
"@aaif/goose-acp": "workspace:*",
|
"@aaif/goose-acp": "workspace:*",
|
||||||
|
"@agentclientprotocol/sdk": "^0.14.1",
|
||||||
"ink": "^6.8.0",
|
"ink": "^6.8.0",
|
||||||
"ink-text-input": "^6.0.0",
|
"ink-multiline-input": "^0.1.0",
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.12",
|
||||||
"marked-terminal": "^7.3.0",
|
"marked-terminal": "^7.3.0",
|
||||||
"meow": "^13.2.0",
|
"meow": "^13.2.0",
|
||||||
|
|||||||
+163
-19
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import { Box, Text, render, useApp, useInput, useStdout } from "ink";
|
import { Box, Text, render, useApp, useInput, useStdout } from "ink";
|
||||||
import TextInput from "ink-text-input";
|
import { MultilineInput } from "ink-multiline-input";
|
||||||
import meow from "meow";
|
import meow from "meow";
|
||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { Readable, Writable } from "node:stream";
|
import { Readable, Writable } from "node:stream";
|
||||||
@@ -178,6 +178,35 @@ function Header({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PASTE_THRESHOLD = 80;
|
||||||
|
const PASTE_PREVIEW_LEN = 40;
|
||||||
|
const INPUT_MAX_ROWS = 8;
|
||||||
|
const SENT_PREVIEW_LEN = 60;
|
||||||
|
|
||||||
|
function collapseForDisplay(text: string, availableWidth = PASTE_PREVIEW_LEN): string {
|
||||||
|
const flat = text.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
|
||||||
|
if (flat.length <= availableWidth) return flat;
|
||||||
|
const suffix = ` (${flat.length.toLocaleString()} chars)`;
|
||||||
|
const previewLen = Math.max(availableWidth - suffix.length - 1, 10);
|
||||||
|
return flat.slice(0, previewLen) + "…" + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapsedUserPrompt(text: string, width: number): React.ReactElement {
|
||||||
|
const flat = text.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
|
||||||
|
const maxPreview = Math.max(width - 30, SENT_PREVIEW_LEN);
|
||||||
|
if (flat.length <= maxPreview + 10) {
|
||||||
|
return <Text color={TEXT_PRIMARY} bold>{flat}</Text>;
|
||||||
|
}
|
||||||
|
const preview = flat.slice(0, maxPreview) + "…";
|
||||||
|
const remaining = flat.length - maxPreview;
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
<Text color={TEXT_PRIMARY} bold>{preview}</Text>
|
||||||
|
<Text color={TEXT_DIM}> ({remaining.toLocaleString()} more chars)</Text>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function InputBar({
|
function InputBar({
|
||||||
width,
|
width,
|
||||||
input,
|
input,
|
||||||
@@ -186,6 +215,9 @@ function InputBar({
|
|||||||
queued,
|
queued,
|
||||||
scrollHint,
|
scrollHint,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
focused,
|
||||||
|
pastedFull,
|
||||||
|
onPastedFullChange,
|
||||||
}: {
|
}: {
|
||||||
width: number;
|
width: number;
|
||||||
input: string;
|
input: string;
|
||||||
@@ -194,7 +226,65 @@ function InputBar({
|
|||||||
queued: boolean;
|
queued: boolean;
|
||||||
scrollHint: boolean;
|
scrollHint: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
focused: boolean;
|
||||||
|
pastedFull: string | null;
|
||||||
|
onPastedFullChange: (v: string | null) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const prevLenRef = useRef(input.length);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(newValue: string) => {
|
||||||
|
const delta = newValue.length - prevLenRef.current;
|
||||||
|
prevLenRef.current = newValue.length;
|
||||||
|
if (delta >= PASTE_THRESHOLD) {
|
||||||
|
onPastedFullChange(newValue);
|
||||||
|
onChange(newValue);
|
||||||
|
} else {
|
||||||
|
if (pastedFull !== null) onPastedFullChange(null);
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChange, pastedFull, onPastedFullChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
prevLenRef.current = 0;
|
||||||
|
onPastedFullChange(null);
|
||||||
|
onSubmit(value);
|
||||||
|
},
|
||||||
|
[onSubmit, onPastedFullChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useInput(
|
||||||
|
(ch, key) => {
|
||||||
|
if (key.return) {
|
||||||
|
handleSubmit(input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.backspace || key.delete) {
|
||||||
|
prevLenRef.current = 0;
|
||||||
|
onPastedFullChange(null);
|
||||||
|
onChange("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.escape) {
|
||||||
|
prevLenRef.current = 0;
|
||||||
|
onPastedFullChange(null);
|
||||||
|
onChange("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ch && !key.ctrl && !key.meta) {
|
||||||
|
prevLenRef.current = ch.length;
|
||||||
|
onPastedFullChange(null);
|
||||||
|
onChange(ch);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ isActive: focused && pastedFull !== null },
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPasteMode = pastedFull !== null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
@@ -204,18 +294,47 @@ function InputBar({
|
|||||||
width={width}
|
width={width}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
<Box justifyContent="space-between">
|
<Box>
|
||||||
<Box flexGrow={1}>
|
<Text color={CRANBERRY} bold>{"❯ "}</Text>
|
||||||
<Text color={CRANBERRY} bold>{"❯ "}</Text>
|
{isPasteMode ? (
|
||||||
<TextInput
|
<Box width={width - 4 - 2} justifyContent="space-between">
|
||||||
value={input}
|
<Text color={TEXT_PRIMARY} wrap="truncate-end">
|
||||||
onChange={onChange}
|
{collapseForDisplay(pastedFull, width - 4 - 2)}
|
||||||
onSubmit={onSubmit}
|
</Text>
|
||||||
placeholder={placeholder}
|
{scrollHint && <Text color={TEXT_DIM}>shift+↑↓ history</Text>}
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
) : (
|
||||||
{scrollHint && <Text color={TEXT_DIM}>shift+↑↓ history</Text>}
|
<Box flexGrow={1} justifyContent="space-between">
|
||||||
|
<MultilineInput
|
||||||
|
value={input}
|
||||||
|
onChange={handleChange}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
rows={1}
|
||||||
|
maxRows={INPUT_MAX_ROWS}
|
||||||
|
placeholder={placeholder}
|
||||||
|
focus={focused}
|
||||||
|
keyBindings={{
|
||||||
|
submit: (key) => key.return && !key.ctrl,
|
||||||
|
newline: (key) => key.return && key.ctrl,
|
||||||
|
}}
|
||||||
|
useCustomInput={(handler, isActive) => {
|
||||||
|
useInput((ch, key) => {
|
||||||
|
if (key.shift && (key.upArrow || key.downArrow)) return;
|
||||||
|
handler(ch, key);
|
||||||
|
}, { isActive });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{scrollHint && <Text color={TEXT_DIM}>shift+↑↓ history</Text>}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
{isPasteMode && (
|
||||||
|
<Box>
|
||||||
|
<Text color={TEXT_DIM} italic>
|
||||||
|
enter to send · esc to clear
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
{queued && (
|
{queued && (
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={GOLD} dimColor italic>
|
<Text color={GOLD} dimColor italic>
|
||||||
@@ -315,12 +434,11 @@ function buildContentLines({
|
|||||||
const lines: React.ReactElement[] = [];
|
const lines: React.ReactElement[] = [];
|
||||||
if (!turn) return lines;
|
if (!turn) return lines;
|
||||||
|
|
||||||
// User prompt
|
|
||||||
lines.push(emptyLine("u-gap", width));
|
lines.push(emptyLine("u-gap", width));
|
||||||
lines.push(
|
lines.push(
|
||||||
<Box key="u-prompt" width={width} height={1}>
|
<Box key="u-prompt" width={width} height={1}>
|
||||||
<Text color={CRANBERRY} bold>{"❯ "}</Text>
|
<Text color={CRANBERRY} bold>{"❯ "}</Text>
|
||||||
<Text wrap="truncate-end" color={TEXT_PRIMARY} bold>{turn.userText}</Text>
|
{collapsedUserPrompt(turn.userText, width - 4)}
|
||||||
</Box>,
|
</Box>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -458,12 +576,14 @@ function Viewport({
|
|||||||
function SplashScreen({
|
function SplashScreen({
|
||||||
animFrame,
|
animFrame,
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
status,
|
status,
|
||||||
loading,
|
loading,
|
||||||
spinIdx,
|
spinIdx,
|
||||||
}: {
|
}: {
|
||||||
animFrame: number;
|
animFrame: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
height: number;
|
||||||
status: string;
|
status: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
spinIdx: number;
|
spinIdx: number;
|
||||||
@@ -472,14 +592,19 @@ function SplashScreen({
|
|||||||
const statusColor =
|
const statusColor =
|
||||||
status === "ready" ? TEAL : isErrorStatus(status) ? CRANBERRY : TEXT_DIM;
|
status === "ready" ? TEAL : isErrorStatus(status) ? CRANBERRY : TEXT_DIM;
|
||||||
|
|
||||||
|
const contentHeight = frame.length + 1 + 1 + 1 + 2 + 1;
|
||||||
|
|
||||||
|
const topPad = Math.max(0, Math.floor((height - contentHeight) / 2));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
|
||||||
flexGrow={1}
|
|
||||||
width={width}
|
width={width}
|
||||||
|
height={height}
|
||||||
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
|
{topPad > 0 && <Box height={topPad} />}
|
||||||
<Box flexDirection="column" alignItems="center">
|
<Box flexDirection="column" alignItems="center">
|
||||||
{frame.map((line, i) => (
|
{frame.map((line, i) => (
|
||||||
<Text key={i} color={TEXT_PRIMARY}>{line}</Text>
|
<Text key={i} color={TEXT_PRIMARY}>{line}</Text>
|
||||||
@@ -524,6 +649,7 @@ function App({
|
|||||||
const [viewTurnIdx, setViewTurnIdx] = useState(-1);
|
const [viewTurnIdx, setViewTurnIdx] = useState(-1);
|
||||||
const [toolCallsExpanded, setToolCallsExpanded] = useState(false);
|
const [toolCallsExpanded, setToolCallsExpanded] = useState(false);
|
||||||
const [scrollOffset, setScrollOffset] = useState(0);
|
const [scrollOffset, setScrollOffset] = useState(0);
|
||||||
|
const [pastedFull, setPastedFull] = useState<string | null>(null);
|
||||||
|
|
||||||
const clientRef = useRef<GooseClient | null>(null);
|
const clientRef = useRef<GooseClient | null>(null);
|
||||||
const sessionIdRef = useRef<string | null>(null);
|
const sessionIdRef = useRef<string | null>(null);
|
||||||
@@ -778,6 +904,7 @@ function App({
|
|||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
setInput("");
|
setInput("");
|
||||||
|
setPastedFull(null);
|
||||||
setViewTurnIdx(-1);
|
setViewTurnIdx(-1);
|
||||||
setToolCallsExpanded(false);
|
setToolCallsExpanded(false);
|
||||||
setScrollOffset(0);
|
setScrollOffset(0);
|
||||||
@@ -795,6 +922,7 @@ function App({
|
|||||||
useInput((ch, key) => {
|
useInput((ch, key) => {
|
||||||
if (key.escape || (ch === "c" && key.ctrl)) {
|
if (key.escape || (ch === "c" && key.ctrl)) {
|
||||||
if (pendingPermission) { resolvePermission("cancelled"); return; }
|
if (pendingPermission) { resolvePermission("cancelled"); return; }
|
||||||
|
if (key.escape && pastedFull !== null) return;
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,6 +946,10 @@ function App({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewingHistory = viewTurnIdx !== -1 && viewTurnIdx < turns.length - 1;
|
||||||
|
const multilineOwnsArrows =
|
||||||
|
!pendingPermission && !initialPrompt && !viewingHistory && pastedFull === null;
|
||||||
|
|
||||||
if (key.tab) {
|
if (key.tab) {
|
||||||
const idx = viewTurnIdx === -1 ? turns.length - 1 : viewTurnIdx;
|
const idx = viewTurnIdx === -1 ? turns.length - 1 : viewTurnIdx;
|
||||||
const t = turns[idx];
|
const t = turns[idx];
|
||||||
@@ -828,11 +960,11 @@ function App({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key.upArrow && !key.shift) {
|
if (key.upArrow && !key.shift) {
|
||||||
setScrollOffset((prev) => prev + 3);
|
if (!multilineOwnsArrows) setScrollOffset((prev) => prev + 3);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key.downArrow && !key.shift) {
|
if (key.downArrow && !key.shift) {
|
||||||
setScrollOffset((prev) => Math.max(prev - 3, 0));
|
if (!multilineOwnsArrows) setScrollOffset((prev) => Math.max(prev - 3, 0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,7 +1004,15 @@ function App({
|
|||||||
const showInputBar = !pendingPermission && !initialPrompt && !isViewingHistory;
|
const showInputBar = !pendingPermission && !initialPrompt && !isViewingHistory;
|
||||||
|
|
||||||
const headerH = 2;
|
const headerH = 2;
|
||||||
const inputBarH = showInputBar ? (queuedMessages.length > 0 ? 4 : 3) : 0;
|
const isPasteMode = pastedFull !== null;
|
||||||
|
const inputContentRows = showInputBar
|
||||||
|
? isPasteMode
|
||||||
|
? 1
|
||||||
|
: Math.min(Math.max(input.split("\n").length, 1), INPUT_MAX_ROWS)
|
||||||
|
: 0;
|
||||||
|
const inputExtraLines =
|
||||||
|
(isPasteMode ? 1 : 0) + (queuedMessages.length > 0 ? 1 : 0);
|
||||||
|
const inputBarH = showInputBar ? 2 + inputContentRows + inputExtraLines : 0;
|
||||||
const historyBarH = isViewingHistory ? 2 : 0;
|
const historyBarH = isViewingHistory ? 2 : 0;
|
||||||
const viewportHeight = Math.max(
|
const viewportHeight = Math.max(
|
||||||
termHeight - PAD_Y * 2 - headerH - inputBarH - historyBarH,
|
termHeight - PAD_Y * 2 - headerH - inputBarH - historyBarH,
|
||||||
@@ -903,6 +1043,7 @@ function App({
|
|||||||
<SplashScreen
|
<SplashScreen
|
||||||
animFrame={gooseFrame}
|
animFrame={gooseFrame}
|
||||||
width={contentWidth}
|
width={contentWidth}
|
||||||
|
height={Math.max(termHeight - PAD_Y * 2 - inputBarH, 0)}
|
||||||
status={status}
|
status={status}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
spinIdx={spinIdx}
|
spinIdx={spinIdx}
|
||||||
@@ -951,6 +1092,9 @@ function App({
|
|||||||
queued={queuedMessages.length > 0}
|
queued={queuedMessages.length > 0}
|
||||||
scrollHint={!bannerVisible && turns.length > 1}
|
scrollHint={!bannerVisible && turns.length > 1}
|
||||||
placeholder={bannerVisible ? INITIAL_GREETING : undefined}
|
placeholder={bannerVisible ? INITIAL_GREETING : undefined}
|
||||||
|
focused={showInputBar}
|
||||||
|
pastedFull={pastedFull}
|
||||||
|
onPastedFullChange={setPastedFull}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user