@zhongqian97-code/ecode 0.2.3 → 0.2.5
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/index.js +340 -165
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -95,8 +95,8 @@ function loadConfig() {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// src/ui/App.tsx
|
|
98
|
-
import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
99
|
-
import { Box as Box5, useInput as useInput2 } from "ink";
|
|
98
|
+
import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3, useMemo } from "react";
|
|
99
|
+
import { Box as Box5, useInput as useInput2, useStdout } from "ink";
|
|
100
100
|
|
|
101
101
|
// src/llm.ts
|
|
102
102
|
import OpenAI from "openai";
|
|
@@ -472,162 +472,123 @@ function StatusBar({
|
|
|
472
472
|
|
|
473
473
|
// src/ui/ConversationHistory.tsx
|
|
474
474
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
475
|
-
|
|
475
|
+
|
|
476
|
+
// src/ui/messageLines.ts
|
|
476
477
|
var TOOL_RESULT_MAX_LINES = 3;
|
|
477
|
-
function
|
|
478
|
-
const
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
content
|
|
490
|
-
] }) })
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
function AssistantMessage({
|
|
494
|
-
content,
|
|
495
|
-
tool_calls,
|
|
496
|
-
reasoning_content,
|
|
497
|
-
expandTools
|
|
498
|
-
}) {
|
|
499
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 0, children: [
|
|
500
|
-
reasoning_content && reasoning_content.length > 0 && (expandTools ? (
|
|
501
|
-
// 展开:显示 <thinking> 标题和完整推理内容
|
|
502
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
503
|
-
/* @__PURE__ */ jsx2(Text2, { color: "gray", children: "<thinking>" }),
|
|
504
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: reasoning_content })
|
|
505
|
-
] })
|
|
506
|
-
) : (
|
|
507
|
-
// 折叠:一行简短占位符,告知用户有隐藏的思考内容
|
|
508
|
-
/* @__PURE__ */ jsx2(Text2, { color: "gray", children: "[+ thinking]" })
|
|
509
|
-
)),
|
|
510
|
-
content && content.trim().length > 0 && /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: content }),
|
|
511
|
-
tool_calls && tool_calls.length > 0 && (expandTools ? (
|
|
512
|
-
// 展开:逐条渲染每个工具调用
|
|
513
|
-
tool_calls.map((tc, idx) => {
|
|
514
|
-
let argsDisplay = tc.function.arguments;
|
|
515
|
-
try {
|
|
516
|
-
const parsed = JSON.parse(tc.function.arguments);
|
|
517
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
518
|
-
argsDisplay = Object.values(parsed).map(String).join(", ");
|
|
519
|
-
}
|
|
520
|
-
} catch {
|
|
521
|
-
}
|
|
522
|
-
return (
|
|
523
|
-
// 黄色 ⚙ 图标 + 工具名 + 格式化参数
|
|
524
|
-
/* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
|
|
525
|
-
"\u2699 \u8C03\u7528\u5DE5\u5177: ",
|
|
526
|
-
tc.function.name,
|
|
527
|
-
"(",
|
|
528
|
-
argsDisplay,
|
|
529
|
-
")"
|
|
530
|
-
] }, idx)
|
|
531
|
-
);
|
|
532
|
-
})
|
|
533
|
-
) : (
|
|
534
|
-
// 折叠:用计数占位符替代,避免占用多行
|
|
535
|
-
/* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
536
|
-
"[+ ",
|
|
537
|
-
tool_calls.length,
|
|
538
|
-
" \u4E2A\u5DE5\u5177\u8C03\u7528]"
|
|
539
|
-
] })
|
|
540
|
-
))
|
|
541
|
-
] });
|
|
542
|
-
}
|
|
543
|
-
function ToolMessage({
|
|
544
|
-
content,
|
|
545
|
-
expandTools
|
|
546
|
-
}) {
|
|
547
|
-
if (!expandTools) {
|
|
548
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "row", marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: "[+ \u5DE5\u5177\u7ED3\u679C]" }) });
|
|
478
|
+
function wrapText(text, terminalWidth) {
|
|
479
|
+
const logicalLines = text.split("\n");
|
|
480
|
+
if (terminalWidth <= 0) return logicalLines;
|
|
481
|
+
const result = [];
|
|
482
|
+
for (const line of logicalLines) {
|
|
483
|
+
if (line.length === 0) {
|
|
484
|
+
result.push("");
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
for (let i = 0; i < line.length; i += terminalWidth) {
|
|
488
|
+
result.push(line.slice(i, i + terminalWidth));
|
|
489
|
+
}
|
|
549
490
|
}
|
|
550
|
-
|
|
551
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "row", marginBottom: 0, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
552
|
-
"[\u5DE5\u5177\u7ED3\u679C] ",
|
|
553
|
-
truncated
|
|
554
|
-
] }) });
|
|
491
|
+
return result;
|
|
555
492
|
}
|
|
556
|
-
function
|
|
493
|
+
function messageToLines(msg, expandTools, terminalWidth) {
|
|
557
494
|
if (msg.role === "user") {
|
|
558
|
-
return
|
|
495
|
+
return wrapText(`> ${msg.content}`, terminalWidth).map((text) => ({
|
|
496
|
+
text,
|
|
497
|
+
color: "white"
|
|
498
|
+
}));
|
|
559
499
|
}
|
|
560
500
|
if (msg.role === "assistant") {
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
if (assistantMsg.reasoning_content && assistantMsg.reasoning_content.length > 0) {
|
|
501
|
+
const lines = [];
|
|
502
|
+
const aMsg = msg;
|
|
503
|
+
if (aMsg.reasoning_content && aMsg.reasoning_content.length > 0) {
|
|
565
504
|
if (expandTools) {
|
|
566
|
-
|
|
505
|
+
lines.push({ text: "<thinking>", color: "gray" });
|
|
506
|
+
for (const t of wrapText(aMsg.reasoning_content, terminalWidth)) {
|
|
507
|
+
lines.push({ text: t, dimColor: true });
|
|
508
|
+
}
|
|
567
509
|
} else {
|
|
568
|
-
|
|
510
|
+
lines.push({ text: "[+ thinking]", color: "gray" });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (aMsg.content && aMsg.content.trim().length > 0) {
|
|
514
|
+
for (const t of wrapText(aMsg.content, terminalWidth)) {
|
|
515
|
+
lines.push({ text: t, color: "cyan" });
|
|
569
516
|
}
|
|
570
517
|
}
|
|
571
|
-
|
|
572
|
-
if (assistantMsg.tool_calls && assistantMsg.tool_calls.length > 0) {
|
|
518
|
+
if (aMsg.tool_calls && aMsg.tool_calls.length > 0) {
|
|
573
519
|
if (expandTools) {
|
|
574
|
-
|
|
520
|
+
for (const tc of aMsg.tool_calls) {
|
|
521
|
+
let argsDisplay = tc.function.arguments;
|
|
522
|
+
try {
|
|
523
|
+
const parsed = JSON.parse(tc.function.arguments);
|
|
524
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
525
|
+
argsDisplay = Object.values(parsed).map(String).join(", ");
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
const lineText = `\u2699 \u8C03\u7528\u5DE5\u5177: ${tc.function.name}(${argsDisplay})`;
|
|
530
|
+
for (const t of wrapText(lineText, terminalWidth)) {
|
|
531
|
+
lines.push({ text: t, color: "yellow" });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
575
534
|
} else {
|
|
576
|
-
|
|
535
|
+
lines.push({
|
|
536
|
+
text: `[+ ${aMsg.tool_calls.length} \u4E2A\u5DE5\u5177\u8C03\u7528]`,
|
|
537
|
+
color: "gray"
|
|
538
|
+
});
|
|
577
539
|
}
|
|
578
540
|
}
|
|
579
|
-
return
|
|
541
|
+
return lines.length > 0 ? lines : [{ text: "" }];
|
|
580
542
|
}
|
|
581
543
|
if (msg.role === "tool") {
|
|
582
544
|
if (!expandTools) {
|
|
583
|
-
return
|
|
545
|
+
return [{ text: "[+ \u5DE5\u5177\u7ED3\u679C]", color: "gray" }];
|
|
584
546
|
}
|
|
585
|
-
|
|
547
|
+
const rawLines = msg.content.split("\n");
|
|
548
|
+
const truncated = rawLines.length > TOOL_RESULT_MAX_LINES ? rawLines.slice(0, TOOL_RESULT_MAX_LINES - 1).join("\n") + "\n\u2026" : msg.content;
|
|
549
|
+
return [
|
|
550
|
+
{ text: "[\u5DE5\u5177\u7ED3\u679C]", color: "gray" },
|
|
551
|
+
...wrapText(truncated, terminalWidth).map((text) => ({
|
|
552
|
+
text,
|
|
553
|
+
color: "gray"
|
|
554
|
+
}))
|
|
555
|
+
];
|
|
586
556
|
}
|
|
587
|
-
return
|
|
557
|
+
return [];
|
|
588
558
|
}
|
|
559
|
+
|
|
560
|
+
// src/ui/ConversationHistory.tsx
|
|
561
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
589
562
|
function ConversationHistory({
|
|
590
563
|
messages,
|
|
591
564
|
maxHeight = 20,
|
|
592
|
-
expandTools = false
|
|
565
|
+
expandTools = false,
|
|
566
|
+
terminalWidth = 0,
|
|
567
|
+
scrollOffset = 0
|
|
593
568
|
}) {
|
|
594
569
|
const visible = messages.filter(
|
|
595
570
|
(m) => m.role !== "system"
|
|
596
571
|
);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
for (let i = visible.length - 1; i >= 0; i--) {
|
|
600
|
-
const lines = estimateLines(visible[i], expandTools);
|
|
601
|
-
if (totalLines + lines > maxHeight) break;
|
|
602
|
-
totalLines += lines;
|
|
603
|
-
startIdx = i;
|
|
604
|
-
}
|
|
605
|
-
const displayMessages = visible.slice(startIdx);
|
|
606
|
-
return (
|
|
607
|
-
// 纵向堆叠所有消息
|
|
608
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: displayMessages.map((msg, idx) => {
|
|
609
|
-
if (msg.role === "user") {
|
|
610
|
-
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, idx);
|
|
611
|
-
}
|
|
612
|
-
if (msg.role === "assistant") {
|
|
613
|
-
const assistantMsg = msg;
|
|
614
|
-
return /* @__PURE__ */ jsx2(
|
|
615
|
-
AssistantMessage,
|
|
616
|
-
{
|
|
617
|
-
content: assistantMsg.content,
|
|
618
|
-
tool_calls: assistantMsg.tool_calls,
|
|
619
|
-
reasoning_content: assistantMsg.reasoning_content,
|
|
620
|
-
expandTools
|
|
621
|
-
},
|
|
622
|
-
idx
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
if (msg.role === "tool") {
|
|
626
|
-
return /* @__PURE__ */ jsx2(ToolMessage, { content: msg.content, expandTools }, idx);
|
|
627
|
-
}
|
|
628
|
-
return null;
|
|
629
|
-
}) })
|
|
572
|
+
const allLines = visible.flatMap(
|
|
573
|
+
(msg) => messageToLines(msg, expandTools, terminalWidth)
|
|
630
574
|
);
|
|
575
|
+
const totalLines = allLines.length;
|
|
576
|
+
const end = Math.max(0, Math.min(totalLines, totalLines - scrollOffset));
|
|
577
|
+
let start = Math.max(0, end - maxHeight);
|
|
578
|
+
let linesAbove = start;
|
|
579
|
+
if (linesAbove > 0 && maxHeight > 1) {
|
|
580
|
+
start = Math.max(0, end - (maxHeight - 1));
|
|
581
|
+
linesAbove = start;
|
|
582
|
+
}
|
|
583
|
+
const visibleLines = allLines.slice(start, end);
|
|
584
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
585
|
+
linesAbove > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
|
|
586
|
+
"\u2191 ",
|
|
587
|
+
linesAbove,
|
|
588
|
+
" \u884C \xB7 PageUp/Down \u6EDA\u52A8"
|
|
589
|
+
] }),
|
|
590
|
+
visibleLines.map((line, idx) => /* @__PURE__ */ jsx2(Text2, { color: line.color, dimColor: line.dimColor, children: line.text }, idx))
|
|
591
|
+
] });
|
|
631
592
|
}
|
|
632
593
|
|
|
633
594
|
// src/ui/Input.tsx
|
|
@@ -636,19 +597,45 @@ import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
|
636
597
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
637
598
|
var CURSOR_CHAR = "\u258C";
|
|
638
599
|
var BLINK_INTERVAL_MS = 530;
|
|
600
|
+
function wordBackward(s, pos) {
|
|
601
|
+
let i = pos;
|
|
602
|
+
while (i > 0 && s[i - 1] === " ") {
|
|
603
|
+
i--;
|
|
604
|
+
}
|
|
605
|
+
while (i > 0 && s[i - 1] !== " ") {
|
|
606
|
+
i--;
|
|
607
|
+
}
|
|
608
|
+
return i;
|
|
609
|
+
}
|
|
610
|
+
function wordForward(s, pos) {
|
|
611
|
+
let i = pos;
|
|
612
|
+
const len = s.length;
|
|
613
|
+
while (i < len && s[i] !== " ") {
|
|
614
|
+
i++;
|
|
615
|
+
}
|
|
616
|
+
while (i < len && s[i] === " ") {
|
|
617
|
+
i++;
|
|
618
|
+
}
|
|
619
|
+
return i;
|
|
620
|
+
}
|
|
639
621
|
var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placeholder }, ref) {
|
|
640
|
-
const [
|
|
622
|
+
const [value, setValue] = useState2("");
|
|
623
|
+
const [cursorPos, setCursorPos] = useState2(0);
|
|
641
624
|
const [cursorVisible, setCursorVisible] = useState2(true);
|
|
642
|
-
const
|
|
643
|
-
|
|
625
|
+
const valueRef = useRef(value);
|
|
626
|
+
valueRef.current = value;
|
|
627
|
+
const cursorPosRef = useRef(cursorPos);
|
|
628
|
+
cursorPosRef.current = cursorPos;
|
|
644
629
|
const onChangeRef = useRef(onChange);
|
|
645
630
|
onChangeRef.current = onChange;
|
|
646
631
|
const onSubmitRef = useRef(onSubmit);
|
|
647
632
|
onSubmitRef.current = onSubmit;
|
|
648
633
|
useImperativeHandle(ref, () => ({
|
|
649
634
|
fill(text) {
|
|
650
|
-
|
|
651
|
-
|
|
635
|
+
valueRef.current = text;
|
|
636
|
+
cursorPosRef.current = text.length;
|
|
637
|
+
setValue(text);
|
|
638
|
+
setCursorPos(text.length);
|
|
652
639
|
onChangeRef.current?.(text);
|
|
653
640
|
}
|
|
654
641
|
}));
|
|
@@ -664,49 +651,124 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
|
|
|
664
651
|
clearInterval(timer);
|
|
665
652
|
};
|
|
666
653
|
}, [isActive]);
|
|
654
|
+
function setValueSync(newValue) {
|
|
655
|
+
valueRef.current = newValue;
|
|
656
|
+
setValue(newValue);
|
|
657
|
+
}
|
|
658
|
+
function setCursorPosSync(newPos) {
|
|
659
|
+
cursorPosRef.current = newPos;
|
|
660
|
+
setCursorPos(newPos);
|
|
661
|
+
}
|
|
667
662
|
useInput(
|
|
668
663
|
(input, key) => {
|
|
669
|
-
const
|
|
664
|
+
const v = valueRef.current;
|
|
665
|
+
const pos = cursorPosRef.current;
|
|
670
666
|
if (key.return && key.shift) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
667
|
+
const newValue = v.slice(0, pos) + "\n" + v.slice(pos);
|
|
668
|
+
setValueSync(newValue);
|
|
669
|
+
setCursorPosSync(pos + 1);
|
|
670
|
+
onChangeRef.current?.(newValue);
|
|
674
671
|
return;
|
|
675
672
|
}
|
|
676
673
|
if (key.return) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
674
|
+
onSubmitRef.current(v);
|
|
675
|
+
setValueSync("");
|
|
676
|
+
setCursorPosSync(0);
|
|
680
677
|
onChangeRef.current?.("");
|
|
681
678
|
return;
|
|
682
679
|
}
|
|
683
680
|
if (key.backspace || key.delete) {
|
|
684
|
-
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
681
|
+
if (pos === 0) return;
|
|
682
|
+
const newValue = v.slice(0, pos - 1) + v.slice(pos);
|
|
683
|
+
setValueSync(newValue);
|
|
684
|
+
setCursorPosSync(pos - 1);
|
|
685
|
+
onChangeRef.current?.(newValue);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (key.ctrl) {
|
|
689
|
+
switch (input) {
|
|
690
|
+
case "a": {
|
|
691
|
+
setCursorPosSync(0);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
case "e": {
|
|
695
|
+
setCursorPosSync(v.length);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
case "b": {
|
|
699
|
+
setCursorPosSync(Math.max(0, pos - 1));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
case "f": {
|
|
703
|
+
setCursorPosSync(Math.min(v.length, pos + 1));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
case "k": {
|
|
707
|
+
const nextNl = v.indexOf("\n", pos);
|
|
708
|
+
const lineEnd = nextNl === -1 ? v.length : nextNl;
|
|
709
|
+
const newValue = v.slice(0, pos) + v.slice(lineEnd);
|
|
710
|
+
setValueSync(newValue);
|
|
711
|
+
onChangeRef.current?.(newValue);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
case "u": {
|
|
715
|
+
const newValue = v.slice(pos);
|
|
716
|
+
setValueSync(newValue);
|
|
717
|
+
setCursorPosSync(0);
|
|
718
|
+
onChangeRef.current?.(newValue);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
case "w": {
|
|
722
|
+
const newPos = wordBackward(v, pos);
|
|
723
|
+
const newValue = v.slice(0, newPos) + v.slice(pos);
|
|
724
|
+
setValueSync(newValue);
|
|
725
|
+
setCursorPosSync(newPos);
|
|
726
|
+
onChangeRef.current?.(newValue);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
case "d": {
|
|
730
|
+
if (pos >= v.length) return;
|
|
731
|
+
const newValue = v.slice(0, pos) + v.slice(pos + 1);
|
|
732
|
+
setValueSync(newValue);
|
|
733
|
+
onChangeRef.current?.(newValue);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
default:
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (key.meta) {
|
|
741
|
+
if (input === "b") {
|
|
742
|
+
setCursorPosSync(wordBackward(v, pos));
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (input === "f") {
|
|
746
|
+
setCursorPosSync(wordForward(v, pos));
|
|
692
747
|
return;
|
|
693
748
|
}
|
|
694
|
-
setLines(newLines);
|
|
695
|
-
onChangeRef.current?.(newLines.join("\n"));
|
|
696
749
|
return;
|
|
697
750
|
}
|
|
698
|
-
if (key.
|
|
751
|
+
if (key.leftArrow) {
|
|
752
|
+
setCursorPosSync(Math.max(0, pos - 1));
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (key.rightArrow) {
|
|
756
|
+
setCursorPosSync(Math.min(v.length, pos + 1));
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (key.escape || key.upArrow || key.downArrow || key.tab || key.pageUp || key.pageDown) {
|
|
699
760
|
return;
|
|
700
761
|
}
|
|
701
762
|
if (input.length > 0) {
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
763
|
+
const newValue = v.slice(0, pos) + input + v.slice(pos);
|
|
764
|
+
setValueSync(newValue);
|
|
765
|
+
setCursorPosSync(pos + input.length);
|
|
766
|
+
onChangeRef.current?.(newValue);
|
|
705
767
|
}
|
|
706
768
|
},
|
|
707
769
|
{ isActive }
|
|
708
770
|
);
|
|
709
|
-
const isEmpty =
|
|
771
|
+
const isEmpty = value === "";
|
|
710
772
|
const renderLines = () => {
|
|
711
773
|
if (isEmpty && placeholder) {
|
|
712
774
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -715,19 +777,41 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
|
|
|
715
777
|
isActive && cursorVisible && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR })
|
|
716
778
|
] });
|
|
717
779
|
}
|
|
780
|
+
const lines = value.split("\n");
|
|
781
|
+
let remaining = cursorPos;
|
|
782
|
+
let cursorLine = 0;
|
|
783
|
+
let cursorCol = 0;
|
|
784
|
+
for (let i = 0; i < lines.length; i++) {
|
|
785
|
+
const lineLen = lines[i].length;
|
|
786
|
+
if (remaining <= lineLen) {
|
|
787
|
+
cursorLine = i;
|
|
788
|
+
cursorCol = remaining;
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
remaining -= lineLen + 1;
|
|
792
|
+
}
|
|
718
793
|
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: lines.map((line, idx) => {
|
|
719
|
-
const isLastLine = idx === lines.length - 1;
|
|
720
794
|
const prefix = idx === 0 ? "> " : " ";
|
|
795
|
+
const showCursor = isActive && cursorVisible && idx === cursorLine;
|
|
796
|
+
if (!showCursor) {
|
|
797
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
798
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
|
|
799
|
+
/* @__PURE__ */ jsx3(Text3, { children: line })
|
|
800
|
+
] }, idx);
|
|
801
|
+
}
|
|
802
|
+
const before = line.slice(0, cursorCol);
|
|
803
|
+
const after = line.slice(cursorCol);
|
|
721
804
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
722
805
|
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
|
|
723
|
-
/* @__PURE__ */ jsx3(Text3, { children:
|
|
724
|
-
|
|
806
|
+
/* @__PURE__ */ jsx3(Text3, { children: before }),
|
|
807
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: CURSOR_CHAR }),
|
|
808
|
+
/* @__PURE__ */ jsx3(Text3, { children: after })
|
|
725
809
|
] }, idx);
|
|
726
810
|
}) });
|
|
727
811
|
};
|
|
728
812
|
return /* @__PURE__ */ jsx3(Box3, { children: isActive ? renderLines() : /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
729
813
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "> " }),
|
|
730
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isEmpty ? placeholder ?? "" :
|
|
814
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isEmpty ? placeholder ?? "" : value })
|
|
731
815
|
] }) });
|
|
732
816
|
});
|
|
733
817
|
var Input_default = Input;
|
|
@@ -788,7 +872,9 @@ function dismiss(state) {
|
|
|
788
872
|
|
|
789
873
|
// src/ui/App.tsx
|
|
790
874
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
791
|
-
function App({ config: config2, version: version2, autoMode: autoMode2 = false, registry: registry2 }) {
|
|
875
|
+
function App({ config: config2, version: version2, autoMode: autoMode2 = false, registry: registry2, llmClient }) {
|
|
876
|
+
const { stdout } = useStdout();
|
|
877
|
+
const historyMaxHeight = Math.max(5, (stdout?.rows ?? 24) - 4);
|
|
792
878
|
const [messages, setMessages] = useState3([]);
|
|
793
879
|
const [status, setStatus] = useState3("idle");
|
|
794
880
|
const contextLimit = getContextLimit(config2.model, config2.contextLimit);
|
|
@@ -800,8 +886,21 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
800
886
|
const [toolName, setToolName] = useState3(void 0);
|
|
801
887
|
const [confirmPrompt, setConfirmPrompt] = useState3(void 0);
|
|
802
888
|
const [expandTools, setExpandTools] = useState3(false);
|
|
889
|
+
const [scrollOffset, setScrollOffset] = useState3(0);
|
|
890
|
+
const [inputHistory, setInputHistory] = useState3([]);
|
|
891
|
+
const inputHistoryRef = useRef2([]);
|
|
892
|
+
inputHistoryRef.current = inputHistory;
|
|
893
|
+
const historyIndexRef = useRef2(-1);
|
|
894
|
+
const isNavigatingHistoryRef = useRef2(false);
|
|
895
|
+
const totalLines = useMemo(() => {
|
|
896
|
+
const visible = messages.filter((m) => m.role !== "system");
|
|
897
|
+
return visible.reduce(
|
|
898
|
+
(acc, msg) => acc + messageToLines(msg, expandTools, stdout?.columns ?? 0).length,
|
|
899
|
+
0
|
|
900
|
+
);
|
|
901
|
+
}, [messages, expandTools, stdout?.columns]);
|
|
803
902
|
const pendingConfirmRef = useRef2(null);
|
|
804
|
-
const llmRef = useRef2(createLLMClient(config2));
|
|
903
|
+
const llmRef = useRef2(llmClient ?? createLLMClient(config2));
|
|
805
904
|
const inputRef = useRef2(null);
|
|
806
905
|
const [acState, setAcState] = useState3(getInitialState());
|
|
807
906
|
const loggerRef = useRef2(null);
|
|
@@ -828,7 +927,7 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
828
927
|
}
|
|
829
928
|
loggedCountRef.current = messages.length;
|
|
830
929
|
}, [messages]);
|
|
831
|
-
useInput2((
|
|
930
|
+
useInput2((input, key) => {
|
|
832
931
|
const skillList = registry2?.list() ?? [];
|
|
833
932
|
const suggestions = computeSuggestions(skillList, acState);
|
|
834
933
|
const open = isOpen(acState, suggestions);
|
|
@@ -855,6 +954,48 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
855
954
|
}
|
|
856
955
|
if (key.tab) {
|
|
857
956
|
setExpandTools((prev) => !prev);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const scrollStep = Math.max(1, Math.floor(historyMaxHeight / 2));
|
|
960
|
+
if (key.pageUp) {
|
|
961
|
+
setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (key.pageDown) {
|
|
965
|
+
setScrollOffset((prev) => Math.max(0, prev - scrollStep));
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (key.ctrl && input === "v") {
|
|
969
|
+
setScrollOffset((prev) => Math.max(0, prev - scrollStep));
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (key.meta && input === "v") {
|
|
973
|
+
setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
if (key.ctrl && input === "p") {
|
|
977
|
+
const history = inputHistoryRef.current;
|
|
978
|
+
const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
|
|
979
|
+
if (newIndex >= 0 && newIndex < history.length) {
|
|
980
|
+
historyIndexRef.current = newIndex;
|
|
981
|
+
isNavigatingHistoryRef.current = true;
|
|
982
|
+
inputRef.current?.fill(history[newIndex]);
|
|
983
|
+
}
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
if (key.ctrl && input === "n") {
|
|
987
|
+
const history = inputHistoryRef.current;
|
|
988
|
+
if (historyIndexRef.current > 0) {
|
|
989
|
+
const newIndex = historyIndexRef.current - 1;
|
|
990
|
+
historyIndexRef.current = newIndex;
|
|
991
|
+
isNavigatingHistoryRef.current = true;
|
|
992
|
+
inputRef.current?.fill(history[newIndex]);
|
|
993
|
+
} else if (historyIndexRef.current === 0) {
|
|
994
|
+
historyIndexRef.current = -1;
|
|
995
|
+
isNavigatingHistoryRef.current = true;
|
|
996
|
+
inputRef.current?.fill("");
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
858
999
|
}
|
|
859
1000
|
});
|
|
860
1001
|
const confirm = useCallback((prompt) => {
|
|
@@ -999,6 +1140,9 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
999
1140
|
return;
|
|
1000
1141
|
}
|
|
1001
1142
|
if (!trimmed) return;
|
|
1143
|
+
setInputHistory((prev) => [trimmed, ...prev.slice(0, 99)]);
|
|
1144
|
+
historyIndexRef.current = -1;
|
|
1145
|
+
setScrollOffset(0);
|
|
1002
1146
|
if (registry2) {
|
|
1003
1147
|
const skillResult = handleSkillInput(trimmed, registry2);
|
|
1004
1148
|
if (skillResult.type === "error") {
|
|
@@ -1039,6 +1183,11 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
1039
1183
|
);
|
|
1040
1184
|
const isInputActive = status === "idle" || status === "awaiting_confirm";
|
|
1041
1185
|
const handleInputTextChange = useCallback((text) => {
|
|
1186
|
+
if (isNavigatingHistoryRef.current) {
|
|
1187
|
+
isNavigatingHistoryRef.current = false;
|
|
1188
|
+
} else {
|
|
1189
|
+
historyIndexRef.current = -1;
|
|
1190
|
+
}
|
|
1042
1191
|
if (status !== "awaiting_confirm") {
|
|
1043
1192
|
setAcState((prev) => handleInputChange(prev, text));
|
|
1044
1193
|
}
|
|
@@ -1046,7 +1195,16 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
1046
1195
|
const skillSuggestions = registry2 ? computeSuggestions(registry2.list(), acState) : [];
|
|
1047
1196
|
const acOpen = isOpen(acState, skillSuggestions);
|
|
1048
1197
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: "100%", children: [
|
|
1049
|
-
/* @__PURE__ */ jsx5(
|
|
1198
|
+
/* @__PURE__ */ jsx5(
|
|
1199
|
+
ConversationHistory,
|
|
1200
|
+
{
|
|
1201
|
+
messages,
|
|
1202
|
+
expandTools,
|
|
1203
|
+
maxHeight: historyMaxHeight,
|
|
1204
|
+
terminalWidth: stdout?.columns,
|
|
1205
|
+
scrollOffset
|
|
1206
|
+
}
|
|
1207
|
+
),
|
|
1050
1208
|
/* @__PURE__ */ jsx5(
|
|
1051
1209
|
StatusBar,
|
|
1052
1210
|
{
|
|
@@ -1205,4 +1363,21 @@ for (const dir of [builtinSkillsDir, userSkillsDir, projectSkillsDir]) {
|
|
|
1205
1363
|
const skills = await loadSkillsFromDir(dir);
|
|
1206
1364
|
for (const skill of skills) registry.register(skill);
|
|
1207
1365
|
}
|
|
1366
|
+
if (process.stdout.isTTY) {
|
|
1367
|
+
process.stdout.write("\x1B[?1049h");
|
|
1368
|
+
const exitAltScreen = () => process.stdout.write("\x1B[?1049l");
|
|
1369
|
+
process.on("exit", exitAltScreen);
|
|
1370
|
+
process.on("SIGINT", () => {
|
|
1371
|
+
exitAltScreen();
|
|
1372
|
+
process.exit(0);
|
|
1373
|
+
});
|
|
1374
|
+
process.on("SIGTERM", () => {
|
|
1375
|
+
exitAltScreen();
|
|
1376
|
+
process.exit(0);
|
|
1377
|
+
});
|
|
1378
|
+
process.on("SIGHUP", () => {
|
|
1379
|
+
exitAltScreen();
|
|
1380
|
+
process.exit(0);
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1208
1383
|
render(React4.createElement(App, { config: finalConfig, version: VERSION, autoMode, registry }));
|