@electerm/electerm-react 1.70.2 → 1.72.6
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/client/common/cache.js +56 -0
- package/client/common/constants.js +2 -0
- package/client/common/download.jsx +5 -7
- package/client/common/setting-list.js +27 -0
- package/client/components/ai/ai-cache.jsx +36 -0
- package/client/components/ai/ai-chat-history-item.jsx +1 -1
- package/client/components/ai/ai-chat.jsx +5 -40
- package/client/components/ai/ai-config-props.js +7 -0
- package/client/components/ai/ai-config.jsx +5 -14
- package/client/components/ai/ai.styl +0 -4
- package/client/components/ai/providers.js +2 -2
- package/client/components/batch-op/batch-op-entry.jsx +1 -1
- package/client/components/batch-op/batch-op.jsx +2 -2
- package/client/components/bookmark-form/form-ssh-common.jsx +2 -3
- package/client/components/bookmark-form/form-tabs.jsx +2 -2
- package/client/components/bookmark-form/render-connection-hopping.jsx +2 -2
- package/client/components/bookmark-form/render-delayed-scripts.jsx +4 -4
- package/client/components/bookmark-form/render-ssh-tunnel.jsx +2 -2
- package/client/components/bookmark-form/use-quick-commands.jsx +2 -2
- package/client/components/common/input-auto-focus.jsx +3 -11
- package/client/components/main/main.jsx +5 -0
- package/client/components/profile/profile-form-elem.jsx +1 -3
- package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -3
- package/client/components/quick-commands/quick-commands-list-form.jsx +2 -2
- package/client/components/session/session.jsx +50 -14
- package/client/components/session/session.styl +10 -3
- package/client/components/session/sessions.jsx +1 -1
- package/client/components/setting-panel/keywords-form.jsx +36 -38
- package/client/components/setting-panel/setting-modal.jsx +2 -2
- package/client/components/setting-panel/tab-settings.jsx +26 -0
- package/client/components/setting-sync/setting-sync-form.jsx +8 -0
- package/client/components/sftp/address-bar.jsx +9 -2
- package/client/components/sftp/address-bookmark.jsx +4 -6
- package/client/components/sftp/keyword-filter.jsx +63 -0
- package/client/components/sftp/list-table-ui.jsx +7 -9
- package/client/components/sftp/sftp-entry.jsx +45 -8
- package/client/components/sftp/sftp.styl +6 -1
- package/client/components/shortcuts/shortcut-control.jsx +20 -0
- package/client/components/shortcuts/shortcut-editor.jsx +2 -2
- package/client/components/shortcuts/shortcuts.jsx +2 -2
- package/client/components/sidebar/info-modal.jsx +2 -2
- package/client/components/sidebar/transfer-list-control.jsx +18 -20
- package/client/components/ssh-config/ssh-config-item.jsx +2 -4
- package/client/components/ssh-config/ssh-config-load-notify.jsx +2 -2
- package/client/components/sys-menu/zoom.jsx +2 -2
- package/client/components/tabs/index.jsx +1 -1
- package/client/components/tabs/tab.jsx +3 -3
- package/client/components/terminal/cmd-item.jsx +32 -0
- package/client/components/terminal/command-tracker-addon.js +3 -1
- package/client/components/terminal/term-search.jsx +5 -6
- package/client/components/terminal/terminal-command-dropdown.jsx +303 -0
- package/client/components/terminal/terminal.jsx +84 -3
- package/client/components/terminal/terminal.styl +58 -0
- package/client/components/terminal-info/terminal-info.jsx +2 -2
- package/client/components/tree-list/tree-list.jsx +1 -1
- package/client/components/web/address-bar.jsx +2 -2
- package/client/store/common.js +27 -2
- package/client/store/init-state.js +3 -3
- package/client/store/item.js +2 -1
- package/client/store/setting.js +3 -2
- package/client/store/store.js +23 -24
- package/client/store/watch.js +7 -1
- package/package.json +1 -1
|
@@ -35,6 +35,7 @@ class ShortcutControl extends React.PureComponent {
|
|
|
35
35
|
this.handleSftpKeyboardEvent(e)
|
|
36
36
|
// Then handle extended shortcuts
|
|
37
37
|
this.handleKeyboardEvent(e)
|
|
38
|
+
this.handleAiChat(e)
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
getActiveSftp = () => {
|
|
@@ -45,6 +46,25 @@ class ShortcutControl extends React.PureComponent {
|
|
|
45
46
|
return ref
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
handleAiChat = (e) => {
|
|
50
|
+
const { rightPanelTab } = window.store
|
|
51
|
+
if (rightPanelTab !== 'ai') {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
const elem = document.activeElement
|
|
55
|
+
if (
|
|
56
|
+
e.ctrlKey &&
|
|
57
|
+
e.key === 'Enter' &&
|
|
58
|
+
!e.shiftKey &&
|
|
59
|
+
!e.altKey &&
|
|
60
|
+
!e.metaKey &&
|
|
61
|
+
elem?.tagName === 'TEXTAREA' &&
|
|
62
|
+
elem?.classList.contains('ai-chat-textarea')
|
|
63
|
+
) {
|
|
64
|
+
refsStatic.get('AIChat')?.handleSubmit()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
// SFTP shortcuts handler
|
|
49
69
|
handleSftpKeyboardEvent = (e) => {
|
|
50
70
|
const activeSftp = this.getActiveSftp()
|
|
@@ -175,7 +175,7 @@ export default class ShortcutEdit extends PureComponent {
|
|
|
175
175
|
return null
|
|
176
176
|
}
|
|
177
177
|
return (
|
|
178
|
-
|
|
178
|
+
<>
|
|
179
179
|
<CheckOutlined
|
|
180
180
|
onClick={this.handleConfirm}
|
|
181
181
|
className='pointer'
|
|
@@ -184,7 +184,7 @@ export default class ShortcutEdit extends PureComponent {
|
|
|
184
184
|
onClick={this.handleCancel}
|
|
185
185
|
className='pointer mg1l'
|
|
186
186
|
/>
|
|
187
|
-
|
|
187
|
+
</>
|
|
188
188
|
)
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -140,7 +140,7 @@ export default class Shortcuts extends PureComponent {
|
|
|
140
140
|
rowKey: 'id'
|
|
141
141
|
}
|
|
142
142
|
return (
|
|
143
|
-
|
|
143
|
+
<>
|
|
144
144
|
<Table
|
|
145
145
|
{...props}
|
|
146
146
|
/>
|
|
@@ -151,7 +151,7 @@ export default class Shortcuts extends PureComponent {
|
|
|
151
151
|
{e('resetAllToDefault')}
|
|
152
152
|
</Button>
|
|
153
153
|
</div>
|
|
154
|
-
|
|
154
|
+
</>
|
|
155
155
|
)
|
|
156
156
|
}
|
|
157
157
|
}
|
|
@@ -119,7 +119,7 @@ export default auto(function InfoModal (props) {
|
|
|
119
119
|
key: infoTabs.info,
|
|
120
120
|
label: e('about'),
|
|
121
121
|
children: (
|
|
122
|
-
|
|
122
|
+
<>
|
|
123
123
|
<LogoElem />
|
|
124
124
|
<p className='mg2b'>{e('desc')}</p>
|
|
125
125
|
<RunningTime />
|
|
@@ -187,7 +187,7 @@ export default auto(function InfoModal (props) {
|
|
|
187
187
|
</Link>
|
|
188
188
|
</p>
|
|
189
189
|
{renderCheckUpdate()}
|
|
190
|
-
|
|
190
|
+
</>
|
|
191
191
|
)
|
|
192
192
|
},
|
|
193
193
|
{
|
|
@@ -137,26 +137,24 @@ export default class TransferModalUI extends Component {
|
|
|
137
137
|
...groups
|
|
138
138
|
]
|
|
139
139
|
return (
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
</Select>
|
|
159
|
-
</div>
|
|
140
|
+
<Select
|
|
141
|
+
value={this.state.filter}
|
|
142
|
+
onChange={this.handleFilter}
|
|
143
|
+
popupMatchSelectWidth={false}
|
|
144
|
+
>
|
|
145
|
+
{
|
|
146
|
+
all.map(item => {
|
|
147
|
+
return (
|
|
148
|
+
<Option
|
|
149
|
+
key={item.id}
|
|
150
|
+
value={item.id}
|
|
151
|
+
>
|
|
152
|
+
{item.title}
|
|
153
|
+
</Option>
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
</Select>
|
|
160
158
|
)
|
|
161
159
|
}
|
|
162
160
|
|
|
@@ -16,10 +16,8 @@ export default function SshConfigItem (props) {
|
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<Tooltip title={generateTooltipContent(item)}>
|
|
19
|
-
<div>
|
|
20
|
-
|
|
21
|
-
ssh {item.title}
|
|
22
|
-
</div>
|
|
19
|
+
<div className='elli pd1y pd2x'>
|
|
20
|
+
ssh {item.title}
|
|
23
21
|
</div>
|
|
24
22
|
</Tooltip>
|
|
25
23
|
)
|
|
@@ -25,7 +25,7 @@ function showNotification () {
|
|
|
25
25
|
placement: 'bottom',
|
|
26
26
|
key: 'sshConfigNotify',
|
|
27
27
|
description: (
|
|
28
|
-
|
|
28
|
+
<>
|
|
29
29
|
<p>{e('sshConfigNotice')}</p>
|
|
30
30
|
<Button type='primary' onClick={handleLoad} className='mg1r mg1b'>
|
|
31
31
|
{e('import')}
|
|
@@ -33,7 +33,7 @@ function showNotification () {
|
|
|
33
33
|
<Button onClick={handleIgnore} className='mg1r mg1b'>
|
|
34
34
|
{e('ignore')}
|
|
35
35
|
</Button>
|
|
36
|
-
|
|
36
|
+
</>
|
|
37
37
|
)
|
|
38
38
|
})
|
|
39
39
|
}
|
|
@@ -20,7 +20,7 @@ export default function ZoomMenu (props) {
|
|
|
20
20
|
max={500}
|
|
21
21
|
addonAfter='%'
|
|
22
22
|
addonBefore={
|
|
23
|
-
|
|
23
|
+
<>
|
|
24
24
|
<PlusCircleOutlined
|
|
25
25
|
onClick={() => store.zoom(0.25, true)}
|
|
26
26
|
className='mg1r pointer font16'
|
|
@@ -29,7 +29,7 @@ export default function ZoomMenu (props) {
|
|
|
29
29
|
onClick={() => store.zoom(-0.25, true)}
|
|
30
30
|
className='pointer font16'
|
|
31
31
|
/>
|
|
32
|
-
|
|
32
|
+
</>
|
|
33
33
|
}
|
|
34
34
|
/>
|
|
35
35
|
)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { createRef } from 'react'
|
|
6
|
-
import { Component } from '
|
|
6
|
+
import { Component } from 'manate/react/class-components'
|
|
7
7
|
import { refs } from '../common/ref'
|
|
8
8
|
import {
|
|
9
9
|
CloseOutlined,
|
|
@@ -373,10 +373,10 @@ class Tab extends Component {
|
|
|
373
373
|
return <div key={tunnel}>{tunnel}</div>
|
|
374
374
|
})
|
|
375
375
|
return (
|
|
376
|
-
|
|
376
|
+
<>
|
|
377
377
|
<div>{title}</div>
|
|
378
378
|
{list}
|
|
379
|
-
|
|
379
|
+
</>
|
|
380
380
|
)
|
|
381
381
|
}
|
|
382
382
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { CloseCircleOutlined } from '@ant-design/icons'
|
|
3
|
+
|
|
4
|
+
const SuggestionItem = ({ item, onSelect, onDelete }) => {
|
|
5
|
+
const handleClick = () => {
|
|
6
|
+
onSelect(item)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const handleDelete = (e) => {
|
|
10
|
+
e.stopPropagation()
|
|
11
|
+
onDelete(item)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className='suggestion-item'>
|
|
16
|
+
<span className='suggestion-command' onClick={handleClick}>
|
|
17
|
+
{item.command}
|
|
18
|
+
</span>
|
|
19
|
+
<span className='suggestion-type'>
|
|
20
|
+
{item.type}
|
|
21
|
+
</span>
|
|
22
|
+
{item.type === 'H' && (
|
|
23
|
+
<CloseCircleOutlined
|
|
24
|
+
className='suggestion-delete'
|
|
25
|
+
onClick={handleDelete}
|
|
26
|
+
/>
|
|
27
|
+
)}
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default SuggestionItem
|
|
@@ -45,7 +45,9 @@ export class CommandTrackerAddon {
|
|
|
45
45
|
// This is now our internal handler
|
|
46
46
|
_handleKey = (e) => {
|
|
47
47
|
const { key } = e
|
|
48
|
-
if (key === '
|
|
48
|
+
if (e.ctrlKey && key.toLowerCase() === 'c') {
|
|
49
|
+
this.clearCommand()
|
|
50
|
+
} else if (key === 'Enter') {
|
|
49
51
|
// Command executed, reset
|
|
50
52
|
this.currentCommand = this.activeCommand
|
|
51
53
|
this.activeCommand = ''
|
|
@@ -127,14 +127,14 @@ export default class TermSearch extends PureComponent {
|
|
|
127
127
|
|
|
128
128
|
renderAfter = () => {
|
|
129
129
|
return (
|
|
130
|
-
|
|
130
|
+
<>
|
|
131
131
|
{
|
|
132
132
|
this.renderMatchData()
|
|
133
133
|
}
|
|
134
134
|
{
|
|
135
135
|
this.searchActions.map(this.renderSearchAction)
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
</>
|
|
138
138
|
)
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -174,11 +174,11 @@ export default class TermSearch extends PureComponent {
|
|
|
174
174
|
|
|
175
175
|
renderSuffix = () => {
|
|
176
176
|
return (
|
|
177
|
-
|
|
177
|
+
<>
|
|
178
178
|
{
|
|
179
179
|
this.searchControls.map(this.renderSearchControl)
|
|
180
180
|
}
|
|
181
|
-
|
|
181
|
+
</>
|
|
182
182
|
)
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -191,8 +191,7 @@ export default class TermSearch extends PureComponent {
|
|
|
191
191
|
if (
|
|
192
192
|
!termSearchOpen ||
|
|
193
193
|
!currentTab ||
|
|
194
|
-
currentTab.pane === paneMap.fileManager
|
|
195
|
-
currentTab.pane === paneMap.sftp
|
|
194
|
+
currentTab.pane === paneMap.fileManager
|
|
196
195
|
) {
|
|
197
196
|
return null
|
|
198
197
|
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Component } from 'manate/react/class-components'
|
|
2
|
+
import { refsStatic, refs } from '../common/ref'
|
|
3
|
+
import SuggestionItem from './cmd-item'
|
|
4
|
+
import { aiSuggestionsCache } from '../../common/cache'
|
|
5
|
+
import uid from '../../common/uid'
|
|
6
|
+
import classnames from 'classnames'
|
|
7
|
+
import {
|
|
8
|
+
LoadingOutlined
|
|
9
|
+
} from '@ant-design/icons'
|
|
10
|
+
|
|
11
|
+
export default class TerminalCmdSuggestions extends Component {
|
|
12
|
+
state = {
|
|
13
|
+
cursorPosition: {},
|
|
14
|
+
showSuggestions: false,
|
|
15
|
+
loadingAiSuggestions: false,
|
|
16
|
+
aiSuggestions: [],
|
|
17
|
+
cmdIsDescription: false,
|
|
18
|
+
reverse: false,
|
|
19
|
+
cmd: ''
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
componentDidMount () {
|
|
23
|
+
refsStatic.add('terminal-suggestions', this)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
componentWillUnmount () {
|
|
27
|
+
refsStatic.remove('terminal-suggestions')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parseAiSuggestions = (aiResponse) => {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(aiResponse.response).map(d => {
|
|
33
|
+
return {
|
|
34
|
+
command: d,
|
|
35
|
+
type: 'AI'
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.log('parseAiSuggestions error:', e)
|
|
40
|
+
return []
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getAiSuggestions = async (event) => {
|
|
45
|
+
event.stopPropagation()
|
|
46
|
+
const { cmd } = this.state
|
|
47
|
+
if (window.store.aiConfigMissing()) {
|
|
48
|
+
window.store.toggleAIConfig()
|
|
49
|
+
}
|
|
50
|
+
this.setState({
|
|
51
|
+
loadingAiSuggestions: true
|
|
52
|
+
})
|
|
53
|
+
const {
|
|
54
|
+
config
|
|
55
|
+
} = window.store
|
|
56
|
+
const prompt = `give me max 5 command suggestions for user input: "${cmd}", return pure json format result only, no extra words, no markdown format, follow this format: ["command1","command2"...]`
|
|
57
|
+
const cached = aiSuggestionsCache.get(cmd)
|
|
58
|
+
if (cached) {
|
|
59
|
+
this.setState({
|
|
60
|
+
loadingAiSuggestions: false,
|
|
61
|
+
aiSuggestions: cached
|
|
62
|
+
})
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const aiResponse = aiSuggestionsCache.get(prompt) || await window.pre.runGlobalAsync(
|
|
67
|
+
'AIchat',
|
|
68
|
+
prompt,
|
|
69
|
+
config.modelAI,
|
|
70
|
+
config.roleAI,
|
|
71
|
+
config.baseURLAI,
|
|
72
|
+
config.apiPathAI,
|
|
73
|
+
config.apiKeyAI
|
|
74
|
+
).catch(
|
|
75
|
+
window.store.onError
|
|
76
|
+
)
|
|
77
|
+
if (cmd !== this.state.cmd) {
|
|
78
|
+
this.setState({
|
|
79
|
+
loadingAiSuggestions: false
|
|
80
|
+
})
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
if (aiResponse && aiResponse.error) {
|
|
84
|
+
this.setState({
|
|
85
|
+
loadingAiSuggestions: false
|
|
86
|
+
})
|
|
87
|
+
return window.store.onError(
|
|
88
|
+
new Error(aiResponse.error)
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
this.setState({
|
|
92
|
+
loadingAiSuggestions: false,
|
|
93
|
+
aiSuggestions: this.parseAiSuggestions(aiResponse, cmd)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
openSuggestions = (cursorPosition, cmd) => {
|
|
98
|
+
if (!this.state.showSuggestions) {
|
|
99
|
+
document.addEventListener('click', this.handleClickOutside)
|
|
100
|
+
document.addEventListener('keydown', this.handleKeyDown)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const {
|
|
104
|
+
left,
|
|
105
|
+
top,
|
|
106
|
+
cellHeight
|
|
107
|
+
} = cursorPosition
|
|
108
|
+
const w = window.innerWidth
|
|
109
|
+
const h = window.innerHeight
|
|
110
|
+
|
|
111
|
+
const position = {}
|
|
112
|
+
const reverse = top > h / 2
|
|
113
|
+
|
|
114
|
+
// Use right position if close to right edge
|
|
115
|
+
if (left > w / 2) {
|
|
116
|
+
position.right = w - left
|
|
117
|
+
} else {
|
|
118
|
+
position.left = left
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Use bottom position if close to bottom edge
|
|
122
|
+
if (reverse) {
|
|
123
|
+
position.bottom = h - top + cellHeight
|
|
124
|
+
} else {
|
|
125
|
+
position.top = top
|
|
126
|
+
}
|
|
127
|
+
this.setState({
|
|
128
|
+
showSuggestions: true,
|
|
129
|
+
cursorPosition: position,
|
|
130
|
+
cmd,
|
|
131
|
+
reverse
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
closeSuggestions = () => {
|
|
136
|
+
document.removeEventListener('click', this.handleClickOutside)
|
|
137
|
+
document.removeEventListener('keydown', this.handleKeyDown)
|
|
138
|
+
const {
|
|
139
|
+
aiSuggestions
|
|
140
|
+
} = this.state
|
|
141
|
+
if (aiSuggestions.length) {
|
|
142
|
+
aiSuggestionsCache.set(this.state.cmd, aiSuggestions)
|
|
143
|
+
aiSuggestions.forEach(item => {
|
|
144
|
+
window.store.addCmdHistory(item.command, 'aiCmdHistory')
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
this.setState({
|
|
148
|
+
showSuggestions: false,
|
|
149
|
+
aiSuggestions: []
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
handleClickOutside = (event) => {
|
|
154
|
+
const suggestionElement = document.querySelector('.terminal-suggestions-wrap')
|
|
155
|
+
if (suggestionElement && !suggestionElement.contains(event.target)) {
|
|
156
|
+
this.closeSuggestions()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handleKeyDown = (event) => {
|
|
161
|
+
if (event.key === 'Escape') {
|
|
162
|
+
this.closeSuggestions()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
handleDelete = (item) => {
|
|
167
|
+
window.store.terminalCommandHistory.delete(item.command)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
handleSelect = (item) => {
|
|
171
|
+
const { activeTabId } = window.store
|
|
172
|
+
const terminal = refs.get('term-' + activeTabId)
|
|
173
|
+
if (!terminal) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// const titleElement = domEvent.target.closest('.ant-menu-title-content')
|
|
178
|
+
// const command = titleElement?.firstChild?.textContent
|
|
179
|
+
const { command } = item
|
|
180
|
+
const { cmd } = this.state
|
|
181
|
+
let txt = ''
|
|
182
|
+
if (cmd && command.startsWith(cmd)) {
|
|
183
|
+
txt = command.slice(cmd.length)
|
|
184
|
+
} else {
|
|
185
|
+
const pre = '\b'.repeat(cmd.length)
|
|
186
|
+
txt = pre + command
|
|
187
|
+
}
|
|
188
|
+
terminal.attachAddon._sendData(txt)
|
|
189
|
+
terminal.term.focus()
|
|
190
|
+
this.closeSuggestions()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
processCommands = (commands, type, uniqueCommands, res) => {
|
|
194
|
+
const { cmd } = this.state
|
|
195
|
+
commands
|
|
196
|
+
.filter(command => command.startsWith(cmd))
|
|
197
|
+
.forEach(command => {
|
|
198
|
+
if (!uniqueCommands.has(command)) {
|
|
199
|
+
uniqueCommands.add(command)
|
|
200
|
+
res.push({
|
|
201
|
+
id: uid(),
|
|
202
|
+
command,
|
|
203
|
+
type
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getSuggestions = () => {
|
|
210
|
+
const uniqueCommands = new Set()
|
|
211
|
+
const {
|
|
212
|
+
history = [],
|
|
213
|
+
batch = [],
|
|
214
|
+
quick = []
|
|
215
|
+
} = this.props.suggestions
|
|
216
|
+
const res = []
|
|
217
|
+
this.state.aiSuggestions
|
|
218
|
+
.forEach(item => {
|
|
219
|
+
if (!uniqueCommands.has(item.command)) {
|
|
220
|
+
uniqueCommands.add(item.command)
|
|
221
|
+
}
|
|
222
|
+
res.push({
|
|
223
|
+
id: uid(),
|
|
224
|
+
...item
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
this.processCommands(history, 'H', uniqueCommands, res)
|
|
228
|
+
this.processCommands(batch, 'B', uniqueCommands, res)
|
|
229
|
+
this.processCommands(quick, 'Q', uniqueCommands, res)
|
|
230
|
+
return this.state.reverse ? res.reverse() : res
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
renderAIIcon () {
|
|
234
|
+
const e = window.translate
|
|
235
|
+
const {
|
|
236
|
+
loadingAiSuggestions
|
|
237
|
+
} = this.state
|
|
238
|
+
if (loadingAiSuggestions) {
|
|
239
|
+
return (
|
|
240
|
+
<>
|
|
241
|
+
<LoadingOutlined /> {e('getAiSuggestions')}
|
|
242
|
+
</>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
const aiProps = {
|
|
246
|
+
onClick: this.getAiSuggestions,
|
|
247
|
+
className: 'pointer'
|
|
248
|
+
}
|
|
249
|
+
return (
|
|
250
|
+
<div {...aiProps}>
|
|
251
|
+
{e('getAiSuggestions')}
|
|
252
|
+
</div>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
renderSticky (pos) {
|
|
257
|
+
const {
|
|
258
|
+
reverse
|
|
259
|
+
} = this.state
|
|
260
|
+
if (
|
|
261
|
+
(pos === 'top' && !reverse) ||
|
|
262
|
+
(pos === 'bottom' && reverse)
|
|
263
|
+
) {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
return (
|
|
267
|
+
<div className='terminal-suggestions-sticky'>
|
|
268
|
+
{this.renderAIIcon()}
|
|
269
|
+
</div>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
render () {
|
|
274
|
+
const { showSuggestions, cursorPosition, reverse } = this.state
|
|
275
|
+
if (!showSuggestions) {
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
const suggestions = this.getSuggestions()
|
|
279
|
+
const cls = classnames('terminal-suggestions-wrap', {
|
|
280
|
+
reverse
|
|
281
|
+
})
|
|
282
|
+
return (
|
|
283
|
+
<div className={cls} style={cursorPosition}>
|
|
284
|
+
{this.renderSticky('top')}
|
|
285
|
+
<div className='terminal-suggestions-list'>
|
|
286
|
+
{
|
|
287
|
+
suggestions.map(item => {
|
|
288
|
+
return (
|
|
289
|
+
<SuggestionItem
|
|
290
|
+
key={item.id}
|
|
291
|
+
item={item}
|
|
292
|
+
onSelect={this.handleSelect}
|
|
293
|
+
onDelete={this.handleDelete}
|
|
294
|
+
/>
|
|
295
|
+
)
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
</div>
|
|
299
|
+
{this.renderSticky('bottom')}
|
|
300
|
+
</div>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
}
|