@zhongqian97-code/ecode 0.1.1 → 0.2.1
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 +167 -65
- 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, useEffect as useEffect3 } from "react";
|
|
99
|
-
import { Box as
|
|
98
|
+
import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
99
|
+
import { Box as Box5, useInput as useInput2 } from "ink";
|
|
100
100
|
|
|
101
101
|
// src/llm.ts
|
|
102
102
|
import OpenAI from "openai";
|
|
@@ -631,14 +631,27 @@ function ConversationHistory({
|
|
|
631
631
|
}
|
|
632
632
|
|
|
633
633
|
// src/ui/Input.tsx
|
|
634
|
-
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
634
|
+
import { useState as useState2, useEffect as useEffect2, useRef, forwardRef, useImperativeHandle } from "react";
|
|
635
635
|
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
636
636
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
637
637
|
var CURSOR_CHAR = "\u258C";
|
|
638
638
|
var BLINK_INTERVAL_MS = 530;
|
|
639
|
-
|
|
639
|
+
var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placeholder }, ref) {
|
|
640
640
|
const [lines, setLines] = useState2([""]);
|
|
641
641
|
const [cursorVisible, setCursorVisible] = useState2(true);
|
|
642
|
+
const linesRef = useRef(lines);
|
|
643
|
+
linesRef.current = lines;
|
|
644
|
+
const onChangeRef = useRef(onChange);
|
|
645
|
+
onChangeRef.current = onChange;
|
|
646
|
+
const onSubmitRef = useRef(onSubmit);
|
|
647
|
+
onSubmitRef.current = onSubmit;
|
|
648
|
+
useImperativeHandle(ref, () => ({
|
|
649
|
+
fill(text) {
|
|
650
|
+
const newLines = text ? text.split("\n") : [""];
|
|
651
|
+
setLines(newLines);
|
|
652
|
+
onChangeRef.current?.(text);
|
|
653
|
+
}
|
|
654
|
+
}));
|
|
642
655
|
useEffect2(() => {
|
|
643
656
|
if (!isActive) {
|
|
644
657
|
setCursorVisible(true);
|
|
@@ -653,44 +666,44 @@ function Input({ isActive, onSubmit, placeholder }) {
|
|
|
653
666
|
}, [isActive]);
|
|
654
667
|
useInput(
|
|
655
668
|
(input, key) => {
|
|
669
|
+
const currentLines = linesRef.current;
|
|
656
670
|
if (key.return && key.shift) {
|
|
657
|
-
|
|
671
|
+
const newLines = [...currentLines, ""];
|
|
672
|
+
setLines(newLines);
|
|
673
|
+
onChangeRef.current?.(newLines.join("\n"));
|
|
658
674
|
return;
|
|
659
675
|
}
|
|
660
676
|
if (key.return) {
|
|
661
|
-
const text =
|
|
662
|
-
|
|
677
|
+
const text = currentLines.join("\n");
|
|
678
|
+
onSubmitRef.current(text);
|
|
663
679
|
setLines([""]);
|
|
680
|
+
onChangeRef.current?.("");
|
|
664
681
|
return;
|
|
665
682
|
}
|
|
666
683
|
if (key.backspace || key.delete) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
});
|
|
684
|
+
const lastIdx = currentLines.length - 1;
|
|
685
|
+
const lastLine = currentLines[lastIdx];
|
|
686
|
+
let newLines;
|
|
687
|
+
if (lastLine.length > 0) {
|
|
688
|
+
newLines = [...currentLines.slice(0, lastIdx), lastLine.slice(0, -1)];
|
|
689
|
+
} else if (currentLines.length > 1) {
|
|
690
|
+
newLines = currentLines.slice(0, -1);
|
|
691
|
+
} else {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
setLines(newLines);
|
|
695
|
+
onChangeRef.current?.(newLines.join("\n"));
|
|
680
696
|
return;
|
|
681
697
|
}
|
|
682
698
|
if (key.ctrl || key.escape || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.tab || key.pageUp || key.pageDown) {
|
|
683
699
|
return;
|
|
684
700
|
}
|
|
685
701
|
if (input.length > 0) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
return next;
|
|
690
|
-
});
|
|
702
|
+
const newLines = [...currentLines.slice(0, -1), currentLines[currentLines.length - 1] + input];
|
|
703
|
+
setLines(newLines);
|
|
704
|
+
onChangeRef.current?.(newLines.join("\n"));
|
|
691
705
|
}
|
|
692
706
|
},
|
|
693
|
-
// 第二个参数:仅在 isActive=true 时注册键盘监听
|
|
694
707
|
{ isActive }
|
|
695
708
|
);
|
|
696
709
|
const isEmpty = lines.every((line) => line === "");
|
|
@@ -712,20 +725,69 @@ function Input({ isActive, onSubmit, placeholder }) {
|
|
|
712
725
|
] }, idx);
|
|
713
726
|
}) });
|
|
714
727
|
};
|
|
715
|
-
return /* @__PURE__ */ jsx3(Box3, { children: isActive ? (
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
728
|
+
return /* @__PURE__ */ jsx3(Box3, { children: isActive ? renderLines() : /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
729
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "> " }),
|
|
730
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isEmpty ? placeholder ?? "" : lines.join(" ") })
|
|
731
|
+
] }) });
|
|
732
|
+
});
|
|
733
|
+
var Input_default = Input;
|
|
734
|
+
|
|
735
|
+
// src/ui/SkillAutocomplete.tsx
|
|
736
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
737
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
738
|
+
function SkillAutocomplete({
|
|
739
|
+
suggestions,
|
|
740
|
+
selectedIndex,
|
|
741
|
+
isOpen: isOpen2
|
|
742
|
+
}) {
|
|
743
|
+
if (!isOpen2 || suggestions.length === 0) {
|
|
744
|
+
return /* @__PURE__ */ jsx4(Fragment2, {});
|
|
745
|
+
}
|
|
746
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", children: suggestions.map((skill, idx) => {
|
|
747
|
+
const selected = idx === selectedIndex;
|
|
748
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
749
|
+
/* @__PURE__ */ jsx4(Text4, { color: selected ? "cyan" : void 0, bold: selected, children: selected ? "> " : " " }),
|
|
750
|
+
/* @__PURE__ */ jsxs4(Text4, { color: selected ? "cyan" : void 0, bold: selected, children: [
|
|
751
|
+
"/",
|
|
752
|
+
skill.name
|
|
753
|
+
] }),
|
|
754
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
755
|
+
" \u2014 ",
|
|
756
|
+
skill.description
|
|
757
|
+
] })
|
|
758
|
+
] }, skill.name);
|
|
759
|
+
}) });
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/ui/autocompleteLogic.ts
|
|
763
|
+
function getInitialState() {
|
|
764
|
+
return { query: "", selectedIndex: 0, dismissed: false };
|
|
765
|
+
}
|
|
766
|
+
function computeSuggestions(skills, state, maxSuggestions = 5) {
|
|
767
|
+
const { query } = state;
|
|
768
|
+
if (!query.startsWith("/")) return [];
|
|
769
|
+
const term = query.slice(1).toLowerCase();
|
|
770
|
+
if (term.includes(" ")) return [];
|
|
771
|
+
return skills.filter((s) => s.name.toLowerCase().startsWith(term)).slice(0, maxSuggestions);
|
|
772
|
+
}
|
|
773
|
+
function isOpen(state, suggestions) {
|
|
774
|
+
return state.query.startsWith("/") && suggestions.length > 0 && !state.dismissed;
|
|
775
|
+
}
|
|
776
|
+
function handleInputChange(_state, newQuery) {
|
|
777
|
+
return { query: newQuery, selectedIndex: 0, dismissed: false };
|
|
778
|
+
}
|
|
779
|
+
function moveUp(state, count) {
|
|
780
|
+
return { ...state, selectedIndex: (state.selectedIndex - 1 + count) % count };
|
|
781
|
+
}
|
|
782
|
+
function moveDown(state, count) {
|
|
783
|
+
return { ...state, selectedIndex: (state.selectedIndex + 1) % count };
|
|
784
|
+
}
|
|
785
|
+
function dismiss(state) {
|
|
786
|
+
return { ...state, dismissed: true };
|
|
725
787
|
}
|
|
726
788
|
|
|
727
789
|
// src/ui/App.tsx
|
|
728
|
-
import { jsx as
|
|
790
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
729
791
|
function App({ config: config2, version: version2, autoMode: autoMode2 = false, registry: registry2 }) {
|
|
730
792
|
const [messages, setMessages] = useState3([]);
|
|
731
793
|
const [status, setStatus] = useState3("idle");
|
|
@@ -738,10 +800,12 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
738
800
|
const [toolName, setToolName] = useState3(void 0);
|
|
739
801
|
const [confirmPrompt, setConfirmPrompt] = useState3(void 0);
|
|
740
802
|
const [expandTools, setExpandTools] = useState3(false);
|
|
741
|
-
const pendingConfirmRef =
|
|
742
|
-
const llmRef =
|
|
743
|
-
const
|
|
744
|
-
const
|
|
803
|
+
const pendingConfirmRef = useRef2(null);
|
|
804
|
+
const llmRef = useRef2(createLLMClient(config2));
|
|
805
|
+
const inputRef = useRef2(null);
|
|
806
|
+
const [acState, setAcState] = useState3(getInitialState());
|
|
807
|
+
const loggerRef = useRef2(null);
|
|
808
|
+
const loggedCountRef = useRef2(0);
|
|
745
809
|
useEffect3(() => {
|
|
746
810
|
if (config2.logDir) {
|
|
747
811
|
loggerRef.current = createLogger(config2.logDir, /* @__PURE__ */ new Date());
|
|
@@ -765,6 +829,30 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
765
829
|
loggedCountRef.current = messages.length;
|
|
766
830
|
}, [messages]);
|
|
767
831
|
useInput2((_input, key) => {
|
|
832
|
+
const skillList = registry2?.list() ?? [];
|
|
833
|
+
const suggestions = computeSuggestions(skillList, acState);
|
|
834
|
+
const open = isOpen(acState, suggestions);
|
|
835
|
+
if (open) {
|
|
836
|
+
if (key.upArrow) {
|
|
837
|
+
setAcState((prev) => moveUp(prev, suggestions.length));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (key.downArrow) {
|
|
841
|
+
setAcState((prev) => moveDown(prev, suggestions.length));
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (key.escape) {
|
|
845
|
+
setAcState((prev) => dismiss(prev));
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (key.tab) {
|
|
849
|
+
const selected = suggestions[acState.selectedIndex];
|
|
850
|
+
if (selected) {
|
|
851
|
+
inputRef.current?.fill(`/${selected.name} `);
|
|
852
|
+
}
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
768
856
|
if (key.tab) {
|
|
769
857
|
setExpandTools((prev) => !prev);
|
|
770
858
|
}
|
|
@@ -950,30 +1038,44 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
950
1038
|
[status, messages, runLlmLoop]
|
|
951
1039
|
);
|
|
952
1040
|
const isInputActive = status === "idle" || status === "awaiting_confirm";
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1041
|
+
const handleInputTextChange = useCallback((text) => {
|
|
1042
|
+
if (status !== "awaiting_confirm") {
|
|
1043
|
+
setAcState((prev) => handleInputChange(prev, text));
|
|
1044
|
+
}
|
|
1045
|
+
}, [status]);
|
|
1046
|
+
const skillSuggestions = registry2 ? computeSuggestions(registry2.list(), acState) : [];
|
|
1047
|
+
const acOpen = isOpen(acState, skillSuggestions);
|
|
1048
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: "100%", children: [
|
|
1049
|
+
/* @__PURE__ */ jsx5(ConversationHistory, { messages, expandTools }),
|
|
1050
|
+
/* @__PURE__ */ jsx5(
|
|
1051
|
+
StatusBar,
|
|
1052
|
+
{
|
|
1053
|
+
status,
|
|
1054
|
+
toolName,
|
|
1055
|
+
confirmPrompt,
|
|
1056
|
+
version: version2,
|
|
1057
|
+
tokenUsage
|
|
1058
|
+
}
|
|
1059
|
+
),
|
|
1060
|
+
/* @__PURE__ */ jsx5(
|
|
1061
|
+
SkillAutocomplete,
|
|
1062
|
+
{
|
|
1063
|
+
suggestions: skillSuggestions,
|
|
1064
|
+
selectedIndex: acState.selectedIndex,
|
|
1065
|
+
isOpen: acOpen
|
|
1066
|
+
}
|
|
1067
|
+
),
|
|
1068
|
+
/* @__PURE__ */ jsx5(
|
|
1069
|
+
Input_default,
|
|
1070
|
+
{
|
|
1071
|
+
ref: inputRef,
|
|
1072
|
+
isActive: isInputActive,
|
|
1073
|
+
onSubmit: handleSubmit,
|
|
1074
|
+
onChange: handleInputTextChange,
|
|
1075
|
+
placeholder: status === "awaiting_confirm" ? "y / n" : void 0
|
|
1076
|
+
}
|
|
1077
|
+
)
|
|
1078
|
+
] });
|
|
977
1079
|
}
|
|
978
1080
|
|
|
979
1081
|
// src/skills/registry.ts
|