1
- import React , { memo , useCallback , useMemo , useState } from 'react' ;
1
+ import React , { memo , useCallback , useEffect , useMemo , useState } from 'react' ;
2
2
3
3
import { PreferenceService , useInjectable } from '@opensumi/ide-core-browser' ;
4
4
import { Button , Icon } from '@opensumi/ide-core-browser/lib/components' ;
5
+ import { EnhancePopover } from '@opensumi/ide-core-browser/lib/components/ai-native/popover' ;
6
+ import { Loading } from '@opensumi/ide-core-browser/lib/components/loading' ;
5
7
import { CommandService , localize } from '@opensumi/ide-core-common' ;
6
8
import { AINativeSettingSectionsId } from '@opensumi/ide-core-common/lib/settings' ;
7
9
@@ -37,16 +39,27 @@ function getResult(raw: string) {
37
39
}
38
40
}
39
41
42
+ const autoExecutionPolicyLabels : { [ k in ETerminalAutoExecutionPolicy ] : string } = {
43
+ [ ETerminalAutoExecutionPolicy . always ] : 'ai.native.terminal.autorun.always' ,
44
+ [ ETerminalAutoExecutionPolicy . auto ] : 'ai.native.terminal.autorun.auto' ,
45
+ [ ETerminalAutoExecutionPolicy . off ] : 'ai.native.terminal.autorun.off' ,
46
+ } ;
47
+
48
+ function getAutoExecutionPolicyLabels ( k : ETerminalAutoExecutionPolicy ) {
49
+ return localize ( autoExecutionPolicyLabels [ k ] ) ;
50
+ }
51
+
40
52
export const TerminalToolComponent = memo ( ( props : IMCPServerToolComponentProps ) => {
41
53
const { args, toolCallId } = props ;
42
54
const handler = useInjectable < RunCommandHandler > ( RunCommandHandler ) ;
43
55
const preferenceService : PreferenceService = useInjectable ( PreferenceService ) ;
44
56
const commandService = useInjectable < CommandService > ( CommandService ) ;
45
57
46
- const [ disabled , toggleDisabled ] = useState ( false ) ;
47
- const [ showPolicy , toggleShowPolicy ] = useState ( false ) ;
58
+ const [ running , toggleRunning ] = useState ( false ) ;
48
59
49
- const terminalAutoExecution = preferenceService . get ( AINativeSettingSectionsId . TerminalAutoRun ) ;
60
+ const terminalAutoExecution = preferenceService . get < ETerminalAutoExecutionPolicy > (
61
+ AINativeSettingSectionsId . TerminalAutoRun ,
62
+ ) ;
50
63
51
64
const needApproval = useMemo ( ( ) => {
52
65
// 值为 off 或 auto 且 args.require_user_approval
@@ -60,6 +73,14 @@ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps)
60
73
return false ;
61
74
} , [ props . args ] ) ;
62
75
76
+ useEffect ( ( ) => {
77
+ if ( props . state === 'result' ) {
78
+ toggleRunning ( false ) ;
79
+ } else if ( ! needApproval ) {
80
+ toggleRunning ( true ) ;
81
+ }
82
+ } , [ props ] ) ;
83
+
63
84
const openCommandAutoExecutionConfig = useCallback ( ( ) => {
64
85
commandService . executeCommand ( 'workbench.action.openSettings' , 'ai.native.terminal.autorun' ) ;
65
86
} , [ ] ) ;
@@ -69,7 +90,7 @@ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps)
69
90
return ;
70
91
}
71
92
handler . handleApproval ( toolCallId , approval ) ;
72
- toggleDisabled ( true ) ;
93
+ toggleRunning ( true ) ;
73
94
} , [ ] ) ;
74
95
75
96
const output = useMemo ( ( ) => {
@@ -81,68 +102,51 @@ export const TerminalToolComponent = memo((props: IMCPServerToolComponentProps)
81
102
82
103
return (
83
104
< div className = { styles . run_cmd_tool } >
84
- { props . state === 'result' && (
85
- < div >
86
- { args && (
87
- < >
88
- < div className = { styles . command_title } >
89
- < Icon icon = 'terminal' />
90
- < span > { localize ( 'ai.native.mcp.terminal.command' ) } :</ span >
91
- </ div >
92
- < p className = { styles . command_content } >
93
- < code > $ { args . command } </ code >
94
- </ p >
95
- </ >
96
- ) }
97
- { output ? (
98
- < div className = { styles . command_content } >
99
- < Icon icon = 'output' />
100
- < code dangerouslySetInnerHTML = { { __html : computeAnsiLogString ( output . text || '' ) } } />
101
- { needApproval && (
102
- < div className = { styles . auto_execution_policy } >
103
- < span className = { styles . auto_execution_policy_title } onClick = { ( ) => toggleShowPolicy ( ! showPolicy ) } >
104
- { showPolicy ? (
105
- < Icon iconClass = 'codicon codicon-chevron-down' />
106
- ) : (
107
- < Icon iconClass = 'codicon codicon-chevron-right' />
108
- ) }
109
- { localize ( 'ai.native.terminal.autorun.denied' ) }
110
- </ span >
111
- { showPolicy && (
112
- < >
113
- < span > { localize ( 'ai.native.terminal.autorun.question' ) } </ span >
114
- < span className = { styles . auto_execution_policy_conf } onClick = { openCommandAutoExecutionConfig } >
115
- { localize ( 'ai.native.terminal.autorun.command' ) }
116
- </ span >
117
- </ >
118
- ) }
119
- </ div >
120
- ) }
121
- </ div >
122
- ) : (
123
- ''
124
- ) }
105
+ < div >
106
+ < div className = { styles . command_title } >
107
+ < Icon icon = 'terminal' />
108
+ < span >
109
+ { needApproval
110
+ ? localize ( 'ai.native.mcp.terminal.allow-question' )
111
+ : localize ( 'ai.native.mcp.terminal.command' ) }
112
+ </ span >
125
113
</ div >
126
- ) }
127
-
128
- { props . state === 'complete' && needApproval && args && (
129
- < div >
130
- < div className = { styles . command_title } >
131
- < Icon icon = 'terminal' />
132
- < span > { localize ( 'ai.native.mcp.terminal.allow-question' ) } </ span >
133
- </ div >
134
- < p className = { styles . command_content } >
135
- < code > $ { args . command } </ code >
136
- </ p >
137
- < p className = { styles . comand_description } > { args . explanation } </ p >
114
+ < p className = { styles . command_content } >
115
+ < code > $ { args ?. command } </ code >
116
+ </ p >
117
+ < p className = { styles . comand_description } > { args ?. explanation } </ p >
118
+ { props . state === 'complete' && needApproval && args && ! running && (
138
119
< div className = { styles . cmmand_footer } >
139
- < Button type = 'link' size = 'small' disabled = { disabled } onClick = { ( ) => handleClick ( true ) } >
120
+ < Button type = 'link' size = 'small' onClick = { ( ) => handleClick ( true ) } >
140
121
{ localize ( 'ai.native.mcp.terminal.allow' ) }
141
122
</ Button >
142
- < Button type = 'link' size = 'small' disabled = { disabled } onClick = { ( ) => handleClick ( false ) } >
123
+ < Button type = 'link' size = 'small' onClick = { ( ) => handleClick ( false ) } >
143
124
{ localize ( 'ai.native.mcp.terminal.deny' ) }
144
125
</ Button >
145
126
</ div >
127
+ ) }
128
+ { props . state === 'result' && output && (
129
+ < >
130
+ < div className = { styles . command_content } >
131
+ < Icon icon = 'output' />
132
+ < code dangerouslySetInnerHTML = { { __html : computeAnsiLogString ( output . text || '' ) } } />
133
+ </ div >
134
+
135
+ < div className = { styles . auto_execution_policy } >
136
+ < span className = { styles . auto_execution_policy_label } >
137
+ { getAutoExecutionPolicyLabels ( terminalAutoExecution || ETerminalAutoExecutionPolicy . auto ) }
138
+ </ span >
139
+ < EnhancePopover id = 'policy-config-popover' title = { localize ( 'ai.native.terminal.autorun.command' ) } >
140
+ < Icon size = 'small' iconClass = 'codicon codicon-settings-gear' onClick = { openCommandAutoExecutionConfig } />
141
+ </ EnhancePopover >
142
+ </ div >
143
+ </ >
144
+ ) }
145
+ </ div >
146
+ { running && (
147
+ < div className = { styles . running } >
148
+ < Loading />
149
+ < span > { localize ( 'ai.native.terminal.autorun.running' ) } </ span >
146
150
</ div >
147
151
) }
148
152
</ div >
0 commit comments