@yivan-lab/pretty-please 1.0.0 → 1.2.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.
- package/README.md +381 -28
- package/bin/pls.tsx +1138 -109
- package/dist/bin/pls.d.ts +1 -1
- package/dist/bin/pls.js +994 -91
- package/dist/package.json +80 -0
- package/dist/src/ai.d.ts +1 -41
- package/dist/src/ai.js +9 -190
- package/dist/src/alias.d.ts +41 -0
- package/dist/src/alias.js +240 -0
- package/dist/src/builtin-detector.d.ts +14 -8
- package/dist/src/builtin-detector.js +36 -16
- package/dist/src/chat-history.d.ts +16 -11
- package/dist/src/chat-history.js +35 -4
- package/dist/src/components/Chat.js +5 -4
- package/dist/src/components/CodeColorizer.js +26 -20
- package/dist/src/components/CommandBox.js +3 -17
- package/dist/src/components/ConfirmationPrompt.d.ts +2 -1
- package/dist/src/components/ConfirmationPrompt.js +9 -4
- package/dist/src/components/Duration.js +2 -1
- package/dist/src/components/InlineRenderer.js +2 -1
- package/dist/src/components/MarkdownDisplay.js +2 -1
- package/dist/src/components/MultiStepCommandGenerator.d.ts +5 -1
- package/dist/src/components/MultiStepCommandGenerator.js +127 -14
- package/dist/src/components/TableRenderer.js +2 -1
- package/dist/src/config.d.ts +59 -9
- package/dist/src/config.js +147 -48
- package/dist/src/history.d.ts +19 -5
- package/dist/src/history.js +26 -11
- package/dist/src/mastra-agent.d.ts +0 -1
- package/dist/src/mastra-agent.js +3 -4
- package/dist/src/mastra-chat.d.ts +28 -0
- package/dist/src/mastra-chat.js +93 -0
- package/dist/src/multi-step.d.ts +23 -7
- package/dist/src/multi-step.js +29 -6
- package/dist/src/prompts.d.ts +11 -0
- package/dist/src/prompts.js +140 -0
- package/dist/src/remote-history.d.ts +63 -0
- package/dist/src/remote-history.js +315 -0
- package/dist/src/remote.d.ts +113 -0
- package/dist/src/remote.js +634 -0
- package/dist/src/shell-hook.d.ts +87 -12
- package/dist/src/shell-hook.js +315 -17
- package/dist/src/sysinfo.d.ts +9 -5
- package/dist/src/sysinfo.js +2 -2
- package/dist/src/ui/theme.d.ts +27 -24
- package/dist/src/ui/theme.js +71 -21
- package/dist/src/upgrade.d.ts +41 -0
- package/dist/src/upgrade.js +348 -0
- package/dist/src/utils/console.d.ts +11 -11
- package/dist/src/utils/console.js +26 -17
- package/package.json +11 -9
- package/src/alias.ts +301 -0
- package/src/builtin-detector.ts +126 -0
- package/src/chat-history.ts +140 -0
- package/src/components/Chat.tsx +6 -5
- package/src/components/CodeColorizer.tsx +27 -19
- package/src/components/CommandBox.tsx +3 -17
- package/src/components/ConfirmationPrompt.tsx +11 -3
- package/src/components/Duration.tsx +2 -1
- package/src/components/InlineRenderer.tsx +2 -1
- package/src/components/MarkdownDisplay.tsx +2 -1
- package/src/components/MultiStepCommandGenerator.tsx +167 -16
- package/src/components/TableRenderer.tsx +2 -1
- package/src/config.ts +394 -0
- package/src/history.ts +160 -0
- package/src/mastra-agent.ts +3 -4
- package/src/mastra-chat.ts +124 -0
- package/src/multi-step.ts +45 -8
- package/src/prompts.ts +154 -0
- package/src/remote-history.ts +390 -0
- package/src/remote.ts +800 -0
- package/src/shell-hook.ts +754 -0
- package/src/{sysinfo.js → sysinfo.ts} +28 -16
- package/src/ui/theme.ts +101 -24
- package/src/upgrade.ts +397 -0
- package/src/utils/{console.js → console.ts} +36 -27
- package/bin/pls.js +0 -681
- package/src/ai.js +0 -324
- package/src/builtin-detector.js +0 -98
- package/src/chat-history.js +0 -94
- package/src/components/ChatStatus.tsx +0 -53
- package/src/components/CommandGenerator.tsx +0 -184
- package/src/components/ConfigDisplay.tsx +0 -64
- package/src/components/ConfigWizard.tsx +0 -101
- package/src/components/HistoryDisplay.tsx +0 -69
- package/src/components/HookManager.tsx +0 -150
- package/src/config.js +0 -221
- package/src/history.js +0 -131
- package/src/shell-hook.js +0 -393
|
@@ -2,23 +2,27 @@ import React from 'react'
|
|
|
2
2
|
import { Text, Box } from 'ink'
|
|
3
3
|
import { common, createLowlight } from 'lowlight'
|
|
4
4
|
import type { Root, Element, Text as HastText, ElementContent, RootContent } from 'hast'
|
|
5
|
-
import {
|
|
5
|
+
import { getCurrentTheme, type Theme } from '../ui/theme.js'
|
|
6
6
|
|
|
7
7
|
// 创建 lowlight 实例
|
|
8
8
|
const lowlight = createLowlight(common)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
/**
|
|
11
|
+
* 获取语法高亮颜色映射
|
|
12
|
+
*/
|
|
13
|
+
function getSyntaxColors(theme: Theme): Record<string, string> {
|
|
14
|
+
return {
|
|
15
|
+
'hljs-keyword': theme.code.keyword,
|
|
16
|
+
'hljs-string': theme.code.string,
|
|
17
|
+
'hljs-function': theme.code.function,
|
|
18
|
+
'hljs-comment': theme.code.comment,
|
|
19
|
+
'hljs-number': theme.primary,
|
|
20
|
+
'hljs-built_in': theme.secondary,
|
|
21
|
+
'hljs-title': theme.accent,
|
|
22
|
+
'hljs-variable': theme.text.primary,
|
|
23
|
+
'hljs-type': theme.info,
|
|
24
|
+
'hljs-operator': theme.text.secondary,
|
|
25
|
+
}
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -26,7 +30,9 @@ const syntaxColors: Record<string, string> = {
|
|
|
26
30
|
*/
|
|
27
31
|
function renderHastNode(
|
|
28
32
|
node: Root | Element | HastText | RootContent,
|
|
29
|
-
inheritedColor: string | undefined
|
|
33
|
+
inheritedColor: string | undefined,
|
|
34
|
+
syntaxColors: Record<string, string>,
|
|
35
|
+
theme: Theme
|
|
30
36
|
): React.ReactNode {
|
|
31
37
|
if (node.type === 'text') {
|
|
32
38
|
const color = inheritedColor || theme.code.text
|
|
@@ -51,7 +57,7 @@ function renderHastNode(
|
|
|
51
57
|
// 递归渲染子节点
|
|
52
58
|
const children = node.children?.map((child: ElementContent, index: number) => (
|
|
53
59
|
<React.Fragment key={index}>
|
|
54
|
-
{renderHastNode(child, colorToPassDown)}
|
|
60
|
+
{renderHastNode(child, colorToPassDown, syntaxColors, theme)}
|
|
55
61
|
</React.Fragment>
|
|
56
62
|
))
|
|
57
63
|
|
|
@@ -65,7 +71,7 @@ function renderHastNode(
|
|
|
65
71
|
|
|
66
72
|
return node.children?.map((child: RootContent, index: number) => (
|
|
67
73
|
<React.Fragment key={index}>
|
|
68
|
-
{renderHastNode(child, inheritedColor)}
|
|
74
|
+
{renderHastNode(child, inheritedColor, syntaxColors, theme)}
|
|
69
75
|
</React.Fragment>
|
|
70
76
|
))
|
|
71
77
|
}
|
|
@@ -76,13 +82,13 @@ function renderHastNode(
|
|
|
76
82
|
/**
|
|
77
83
|
* 高亮并渲染一行代码
|
|
78
84
|
*/
|
|
79
|
-
function highlightLine(line: string, language: string | null): React.ReactNode {
|
|
85
|
+
function highlightLine(line: string, language: string | null, syntaxColors: Record<string, string>, theme: Theme): React.ReactNode {
|
|
80
86
|
try {
|
|
81
87
|
const highlighted = !language || !lowlight.registered(language)
|
|
82
88
|
? lowlight.highlightAuto(line)
|
|
83
89
|
: lowlight.highlight(language, line)
|
|
84
90
|
|
|
85
|
-
const rendered = renderHastNode(highlighted, undefined)
|
|
91
|
+
const rendered = renderHastNode(highlighted, undefined, syntaxColors, theme)
|
|
86
92
|
return rendered !== null ? rendered : line
|
|
87
93
|
} catch {
|
|
88
94
|
return line
|
|
@@ -99,12 +105,14 @@ interface ColorizeCodeProps {
|
|
|
99
105
|
* 代码高亮组件
|
|
100
106
|
*/
|
|
101
107
|
function ColorizeCodeInternal({ code, language = null, showLineNumbers = false }: ColorizeCodeProps) {
|
|
108
|
+
const theme = getCurrentTheme()
|
|
109
|
+
const syntaxColors = getSyntaxColors(theme)
|
|
102
110
|
const codeToHighlight = code.replace(/\n$/, '')
|
|
103
111
|
const lines = codeToHighlight.split('\n')
|
|
104
112
|
const padWidth = String(lines.length).length
|
|
105
113
|
|
|
106
114
|
const renderedLines = lines.map((line, index) => {
|
|
107
|
-
const contentToRender = highlightLine(line, language)
|
|
115
|
+
const contentToRender = highlightLine(line, language, syntaxColors, theme)
|
|
108
116
|
|
|
109
117
|
return (
|
|
110
118
|
<Box key={index} minHeight={1}>
|
|
@@ -1,32 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
4
|
+
import { getDisplayWidth } from '../utils/console.js'
|
|
4
5
|
|
|
5
6
|
interface CommandBoxProps {
|
|
6
7
|
command: string
|
|
7
8
|
title?: string
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* 计算字符串的显示宽度(中文占2个宽度)
|
|
12
|
-
*/
|
|
13
|
-
function getDisplayWidth(str: string): number {
|
|
14
|
-
let width = 0
|
|
15
|
-
for (const char of str) {
|
|
16
|
-
// 中文、日文、韩文等宽字符占 2 个宽度
|
|
17
|
-
if (char.match(/[\u4e00-\u9fff\u3400-\u4dbf\uff00-\uffef\u3000-\u303f]/)) {
|
|
18
|
-
width += 2
|
|
19
|
-
} else {
|
|
20
|
-
width += 1
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return width
|
|
24
|
-
}
|
|
25
|
-
|
|
26
11
|
/**
|
|
27
12
|
* CommandBox 组件 - 显示带边框和标题的命令框
|
|
28
13
|
*/
|
|
29
14
|
export const CommandBox: React.FC<CommandBoxProps> = ({ command, title = '生成命令' }) => {
|
|
15
|
+
const theme = getCurrentTheme()
|
|
30
16
|
const lines = command.split('\n')
|
|
31
17
|
const titleWidth = getDisplayWidth(title)
|
|
32
18
|
const maxContentWidth = Math.max(...lines.map(l => getDisplayWidth(l)))
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Text, useInput } from 'ink'
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
4
4
|
|
|
5
5
|
interface ConfirmationPromptProps {
|
|
6
6
|
prompt: string
|
|
7
7
|
onConfirm: () => void
|
|
8
8
|
onCancel: () => void
|
|
9
|
+
onEdit?: () => void // 新增:编辑回调
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* ConfirmationPrompt 组件 - 单键确认提示
|
|
13
|
-
* 回车 = 确认,Esc = 取消,Ctrl+C = 退出
|
|
14
|
+
* 回车 = 确认,E = 编辑,Esc = 取消,Ctrl+C = 退出
|
|
14
15
|
*/
|
|
15
16
|
export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = ({
|
|
16
17
|
prompt,
|
|
17
18
|
onConfirm,
|
|
18
19
|
onCancel,
|
|
20
|
+
onEdit,
|
|
19
21
|
}) => {
|
|
22
|
+
const theme = getCurrentTheme()
|
|
20
23
|
useInput((input, key) => {
|
|
21
24
|
if (key.return) {
|
|
22
25
|
// 回车键
|
|
@@ -24,6 +27,9 @@ export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = ({
|
|
|
24
27
|
} else if (key.escape) {
|
|
25
28
|
// Esc 键
|
|
26
29
|
onCancel()
|
|
30
|
+
} else if ((input === 'e' || input === 'E') && onEdit) {
|
|
31
|
+
// E 键进入编辑模式
|
|
32
|
+
onEdit()
|
|
27
33
|
} else if (key.ctrl && input === 'c') {
|
|
28
34
|
// Ctrl+C
|
|
29
35
|
process.exit(0)
|
|
@@ -35,7 +41,9 @@ export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = ({
|
|
|
35
41
|
<Text bold color={theme.warning}>
|
|
36
42
|
{prompt}
|
|
37
43
|
</Text>
|
|
38
|
-
<Text color={theme.text.secondary}>
|
|
44
|
+
<Text color={theme.text.secondary}>
|
|
45
|
+
{onEdit ? ' [回车执行 / E 编辑 / Esc 取消] ' : ' [回车执行 / Esc 取消] '}
|
|
46
|
+
</Text>
|
|
39
47
|
</Text>
|
|
40
48
|
)
|
|
41
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Text } from 'ink'
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
4
4
|
|
|
5
5
|
interface DurationProps {
|
|
6
6
|
ms: number
|
|
@@ -20,5 +20,6 @@ function formatDuration(ms: number): string {
|
|
|
20
20
|
* Duration 组件 - 显示耗时
|
|
21
21
|
*/
|
|
22
22
|
export const Duration: React.FC<DurationProps> = ({ ms }) => {
|
|
23
|
+
const theme = getCurrentTheme()
|
|
23
24
|
return <Text color={theme.text.secondary}>({formatDuration(ms)})</Text>
|
|
24
25
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Text } from 'ink'
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
4
4
|
|
|
5
5
|
interface RenderInlineProps {
|
|
6
6
|
text: string
|
|
@@ -12,6 +12,7 @@ interface RenderInlineProps {
|
|
|
12
12
|
* 处理 **粗体**、*斜体*、`代码`、~~删除线~~、<u>下划线</u>、链接
|
|
13
13
|
*/
|
|
14
14
|
function RenderInlineInternal({ text, defaultColor }: RenderInlineProps) {
|
|
15
|
+
const theme = getCurrentTheme()
|
|
15
16
|
const baseColor = defaultColor || theme.text.primary
|
|
16
17
|
|
|
17
18
|
// 快速路径:纯文本无 markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Text, Box } from 'ink'
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
4
4
|
import { ColorizeCode } from './CodeColorizer.js'
|
|
5
5
|
import { TableRenderer } from './TableRenderer.js'
|
|
6
6
|
import { RenderInline } from './InlineRenderer.js'
|
|
@@ -18,6 +18,7 @@ interface MarkdownDisplayProps {
|
|
|
18
18
|
function MarkdownDisplayInternal({ text, terminalWidth = 80 }: MarkdownDisplayProps) {
|
|
19
19
|
if (!text) return <></>
|
|
20
20
|
|
|
21
|
+
const theme = getCurrentTheme()
|
|
21
22
|
const lines = text.split(/\r?\n/)
|
|
22
23
|
|
|
23
24
|
// 正则表达式
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
2
|
+
import { Box, Text, useInput } from 'ink'
|
|
3
|
+
import TextInput from 'ink-text-input'
|
|
3
4
|
import Spinner from 'ink-spinner'
|
|
4
|
-
import { generateMultiStepCommand, type CommandStep, type ExecutedStep } from '../multi-step.js'
|
|
5
|
+
import { generateMultiStepCommand, type CommandStep, type ExecutedStep, type RemoteContext } from '../multi-step.js'
|
|
5
6
|
import { detectBuiltin, formatBuiltins } from '../builtin-detector.js'
|
|
6
7
|
import { CommandBox } from './CommandBox.js'
|
|
7
8
|
import { ConfirmationPrompt } from './ConfirmationPrompt.js'
|
|
8
9
|
import { Duration } from './Duration.js'
|
|
9
|
-
import {
|
|
10
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
11
|
+
import { getConfig } from '../config.js'
|
|
10
12
|
|
|
11
13
|
interface MultiStepCommandGeneratorProps {
|
|
12
14
|
prompt: string
|
|
13
15
|
debug?: boolean
|
|
14
16
|
onStepComplete: (step: {
|
|
15
17
|
command: string
|
|
18
|
+
aiGeneratedCommand?: string // 新增:AI 生成的原始命令
|
|
19
|
+
userModified?: boolean // 新增:用户是否修改
|
|
16
20
|
confirmed: boolean
|
|
17
21
|
cancelled?: boolean
|
|
18
22
|
hasBuiltin?: boolean
|
|
@@ -24,11 +28,14 @@ interface MultiStepCommandGeneratorProps {
|
|
|
24
28
|
}) => void
|
|
25
29
|
previousSteps?: ExecutedStep[]
|
|
26
30
|
currentStepNumber?: number
|
|
31
|
+
remoteContext?: RemoteContext // 远程执行上下文
|
|
32
|
+
isRemote?: boolean // 是否为远程执行(远程执行时不检测 builtin)
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
type State =
|
|
30
36
|
| { type: 'thinking' }
|
|
31
37
|
| { type: 'showing_command'; stepData: CommandStep }
|
|
38
|
+
| { type: 'editing'; stepData: CommandStep } // 新增:编辑状态
|
|
32
39
|
| { type: 'cancelled'; command: string }
|
|
33
40
|
| { type: 'error'; error: string }
|
|
34
41
|
|
|
@@ -41,17 +48,31 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
41
48
|
debug,
|
|
42
49
|
previousSteps = [],
|
|
43
50
|
currentStepNumber = 1,
|
|
51
|
+
remoteContext,
|
|
52
|
+
isRemote = false,
|
|
44
53
|
onStepComplete,
|
|
45
54
|
}) => {
|
|
55
|
+
const theme = getCurrentTheme()
|
|
46
56
|
const [state, setState] = useState<State>({ type: 'thinking' })
|
|
47
57
|
const [thinkDuration, setThinkDuration] = useState(0)
|
|
48
58
|
const [debugInfo, setDebugInfo] = useState<any>(null)
|
|
59
|
+
const [editedCommand, setEditedCommand] = useState('') // 新增:编辑后的命令
|
|
60
|
+
|
|
61
|
+
// 监听编辑模式下的 Esc 键
|
|
62
|
+
useInput(
|
|
63
|
+
(input, key) => {
|
|
64
|
+
if (state.type === 'editing' && key.escape) {
|
|
65
|
+
handleEditCancel()
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{ isActive: state.type === 'editing' }
|
|
69
|
+
)
|
|
49
70
|
|
|
50
71
|
// 初始化:调用 Mastra 生成命令
|
|
51
72
|
useEffect(() => {
|
|
52
73
|
const thinkStart = Date.now()
|
|
53
74
|
|
|
54
|
-
generateMultiStepCommand(prompt, previousSteps, { debug })
|
|
75
|
+
generateMultiStepCommand(prompt, previousSteps, { debug, remoteContext })
|
|
55
76
|
.then((result) => {
|
|
56
77
|
const thinkEnd = Date.now()
|
|
57
78
|
setThinkDuration(thinkEnd - thinkStart)
|
|
@@ -61,15 +82,29 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
61
82
|
setDebugInfo(result.debugInfo)
|
|
62
83
|
}
|
|
63
84
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
// 如果 AI 返回空命令且决定不继续,说明 AI 放弃了
|
|
86
|
+
// 直接结束,不显示命令框
|
|
87
|
+
if (!result.stepData.command.trim() && result.stepData.continue === false) {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
onStepComplete({
|
|
90
|
+
command: '',
|
|
91
|
+
confirmed: false,
|
|
92
|
+
reasoning: result.stepData.reasoning,
|
|
93
|
+
needsContinue: false,
|
|
94
|
+
})
|
|
95
|
+
}, 100)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
68
98
|
|
|
69
|
-
// 检测 builtin
|
|
99
|
+
// 检测 builtin(优先检测,但远程执行时跳过)
|
|
70
100
|
const { hasBuiltin, builtins } = detectBuiltin(result.stepData.command)
|
|
71
101
|
|
|
72
|
-
if (hasBuiltin) {
|
|
102
|
+
if (hasBuiltin && !isRemote) {
|
|
103
|
+
// 有 builtin 且是本地执行,不管什么模式都不编辑,直接提示
|
|
104
|
+
setState({
|
|
105
|
+
type: 'showing_command',
|
|
106
|
+
stepData: result.stepData,
|
|
107
|
+
})
|
|
73
108
|
setTimeout(() => {
|
|
74
109
|
onStepComplete({
|
|
75
110
|
command: result.stepData.command,
|
|
@@ -80,6 +115,26 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
80
115
|
needsContinue: result.stepData.continue,
|
|
81
116
|
})
|
|
82
117
|
}, 100)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 根据 editMode 决定进入哪个状态
|
|
122
|
+
const config = getConfig()
|
|
123
|
+
const autoEdit = config.editMode === 'auto'
|
|
124
|
+
|
|
125
|
+
if (autoEdit) {
|
|
126
|
+
// auto 模式:直接进入编辑状态
|
|
127
|
+
setEditedCommand(result.stepData.command)
|
|
128
|
+
setState({
|
|
129
|
+
type: 'editing',
|
|
130
|
+
stepData: result.stepData,
|
|
131
|
+
})
|
|
132
|
+
} else {
|
|
133
|
+
// manual 模式:显示命令,等待用户操作
|
|
134
|
+
setState({
|
|
135
|
+
type: 'showing_command',
|
|
136
|
+
stepData: result.stepData,
|
|
137
|
+
})
|
|
83
138
|
}
|
|
84
139
|
})
|
|
85
140
|
.catch((error: any) => {
|
|
@@ -92,13 +147,15 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
92
147
|
})
|
|
93
148
|
}, 100)
|
|
94
149
|
})
|
|
95
|
-
}, [prompt, previousSteps, debug])
|
|
150
|
+
}, [prompt, previousSteps, debug, remoteContext])
|
|
96
151
|
|
|
97
152
|
// 处理确认
|
|
98
153
|
const handleConfirm = () => {
|
|
99
154
|
if (state.type === 'showing_command') {
|
|
100
155
|
onStepComplete({
|
|
101
156
|
command: state.stepData.command,
|
|
157
|
+
aiGeneratedCommand: state.stepData.command, // 原始命令
|
|
158
|
+
userModified: false,
|
|
102
159
|
confirmed: true,
|
|
103
160
|
reasoning: state.stepData.reasoning,
|
|
104
161
|
needsContinue: state.stepData.continue,
|
|
@@ -108,6 +165,53 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
108
165
|
}
|
|
109
166
|
}
|
|
110
167
|
|
|
168
|
+
// 处理编辑
|
|
169
|
+
const handleEdit = () => {
|
|
170
|
+
if (state.type === 'showing_command') {
|
|
171
|
+
setEditedCommand(state.stepData.command) // 初始化为 AI 生成的命令
|
|
172
|
+
setState({ type: 'editing', stepData: state.stepData })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 编辑完成确认
|
|
177
|
+
const handleEditConfirm = () => {
|
|
178
|
+
if (state.type === 'editing') {
|
|
179
|
+
const modified = editedCommand !== state.stepData.command
|
|
180
|
+
onStepComplete({
|
|
181
|
+
command: editedCommand, // 使用编辑后的命令
|
|
182
|
+
aiGeneratedCommand: state.stepData.command, // 保存 AI 原始命令
|
|
183
|
+
userModified: modified,
|
|
184
|
+
confirmed: true,
|
|
185
|
+
reasoning: state.stepData.reasoning,
|
|
186
|
+
needsContinue: state.stepData.continue,
|
|
187
|
+
nextStepHint: state.stepData.nextStepHint,
|
|
188
|
+
debugInfo: debugInfo,
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 取消编辑
|
|
194
|
+
const handleEditCancel = () => {
|
|
195
|
+
if (state.type === 'editing') {
|
|
196
|
+
const config = getConfig()
|
|
197
|
+
|
|
198
|
+
if (config.editMode === 'auto') {
|
|
199
|
+
// auto 模式:Esc 直接取消整个操作
|
|
200
|
+
setState({ type: 'cancelled', command: state.stepData.command })
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
onStepComplete({
|
|
203
|
+
command: state.stepData.command,
|
|
204
|
+
confirmed: false,
|
|
205
|
+
cancelled: true,
|
|
206
|
+
})
|
|
207
|
+
}, 100)
|
|
208
|
+
} else {
|
|
209
|
+
// manual 模式:Esc 返回到 showing_command 状态
|
|
210
|
+
setState({ type: 'showing_command', stepData: state.stepData })
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
111
215
|
// 处理取消
|
|
112
216
|
const handleCancel = () => {
|
|
113
217
|
if (state.type === 'showing_command') {
|
|
@@ -129,7 +233,10 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
129
233
|
<Box>
|
|
130
234
|
<Text color={theme.info}>
|
|
131
235
|
<Spinner type="dots" />{' '}
|
|
132
|
-
{
|
|
236
|
+
{remoteContext
|
|
237
|
+
? (currentStepNumber === 1 ? `正在为 ${remoteContext.name} 思考...` : `正在规划步骤 ${currentStepNumber} (${remoteContext.name})...`)
|
|
238
|
+
: (currentStepNumber === 1 ? '正在思考...' : `正在规划步骤 ${currentStepNumber}...`)
|
|
239
|
+
}
|
|
133
240
|
</Text>
|
|
134
241
|
</Box>
|
|
135
242
|
)}
|
|
@@ -163,6 +270,12 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
163
270
|
</Box>
|
|
164
271
|
)}
|
|
165
272
|
|
|
273
|
+
{debugInfo.remoteContext && (
|
|
274
|
+
<Box marginTop={1}>
|
|
275
|
+
<Text color={theme.text.secondary}>远程服务器: {debugInfo.remoteContext.name} ({debugInfo.remoteContext.sysInfo.os})</Text>
|
|
276
|
+
</Box>
|
|
277
|
+
)}
|
|
278
|
+
|
|
166
279
|
<Box marginTop={1}>
|
|
167
280
|
<Text color={theme.text.secondary}>AI 返回的 JSON:</Text>
|
|
168
281
|
</Box>
|
|
@@ -188,8 +301,8 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
188
301
|
{/* 命令框 */}
|
|
189
302
|
<CommandBox command={state.stepData.command} />
|
|
190
303
|
|
|
191
|
-
{/* Builtin
|
|
192
|
-
{(() => {
|
|
304
|
+
{/* Builtin 警告(仅本地执行时显示) */}
|
|
305
|
+
{!isRemote && (() => {
|
|
193
306
|
const { hasBuiltin, builtins } = detectBuiltin(state.stepData.command)
|
|
194
307
|
if (hasBuiltin) {
|
|
195
308
|
return (
|
|
@@ -205,9 +318,47 @@ export const MultiStepCommandGenerator: React.FC<MultiStepCommandGeneratorProps>
|
|
|
205
318
|
})()}
|
|
206
319
|
|
|
207
320
|
{/* 确认提示 */}
|
|
208
|
-
{!detectBuiltin(state.stepData.command).hasBuiltin && (
|
|
209
|
-
<ConfirmationPrompt
|
|
321
|
+
{(isRemote || !detectBuiltin(state.stepData.command).hasBuiltin) && (
|
|
322
|
+
<ConfirmationPrompt
|
|
323
|
+
prompt="执行?"
|
|
324
|
+
onConfirm={handleConfirm}
|
|
325
|
+
onCancel={handleCancel}
|
|
326
|
+
onEdit={handleEdit} // 新增:编辑回调
|
|
327
|
+
/>
|
|
328
|
+
)}
|
|
329
|
+
</>
|
|
330
|
+
)}
|
|
331
|
+
|
|
332
|
+
{/* 编辑模式 */}
|
|
333
|
+
{state.type === 'editing' && (
|
|
334
|
+
<>
|
|
335
|
+
{/* 步骤信息(仅多步骤时显示) */}
|
|
336
|
+
{state.stepData.continue === true && (
|
|
337
|
+
<Box flexDirection="column" marginTop={1}>
|
|
338
|
+
<Text color={theme.text.secondary}>步骤 {currentStepNumber}/?</Text>
|
|
339
|
+
{state.stepData.reasoning && (
|
|
340
|
+
<Text color={theme.text.muted}>原因: {state.stepData.reasoning}</Text>
|
|
341
|
+
)}
|
|
342
|
+
</Box>
|
|
210
343
|
)}
|
|
344
|
+
|
|
345
|
+
{/* 命令框(AI 建议) */}
|
|
346
|
+
<CommandBox command={state.stepData.command} />
|
|
347
|
+
|
|
348
|
+
{/* 编辑框 */}
|
|
349
|
+
<Box flexDirection="row">
|
|
350
|
+
<Text color={theme.primary}>{'> '}</Text>
|
|
351
|
+
<TextInput
|
|
352
|
+
value={editedCommand}
|
|
353
|
+
onChange={setEditedCommand}
|
|
354
|
+
onSubmit={handleEditConfirm}
|
|
355
|
+
/>
|
|
356
|
+
</Box>
|
|
357
|
+
<Box marginTop={1}>
|
|
358
|
+
<Text color={theme.text.secondary}>
|
|
359
|
+
{getConfig().editMode === 'auto' ? '[回车执行 / Esc 取消]' : '[回车执行 / Esc 返回]'}
|
|
360
|
+
</Text>
|
|
361
|
+
</Box>
|
|
211
362
|
</>
|
|
212
363
|
)}
|
|
213
364
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
3
|
import stringWidth from 'string-width'
|
|
4
|
-
import {
|
|
4
|
+
import { getCurrentTheme } from '../ui/theme.js'
|
|
5
5
|
import { RenderInline } from './InlineRenderer.js'
|
|
6
6
|
|
|
7
7
|
interface TableRendererProps {
|
|
@@ -56,6 +56,7 @@ function calculateColumnWidths(
|
|
|
56
56
|
* 表格渲染组件
|
|
57
57
|
*/
|
|
58
58
|
function TableRendererInternal({ headers, rows, terminalWidth }: TableRendererProps) {
|
|
59
|
+
const theme = getCurrentTheme()
|
|
59
60
|
const columnWidths = calculateColumnWidths(headers, rows, terminalWidth)
|
|
60
61
|
const baseColor = theme.text.primary
|
|
61
62
|
|