@electerm/electerm-react 1.70.6 → 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 +49 -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/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
|
@@ -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
|
+
}
|
|
@@ -50,7 +50,7 @@ import AIIcon from '../icons/ai-icon.jsx'
|
|
|
50
50
|
import { formatBytes } from '../../common/byte-format.js'
|
|
51
51
|
import * as fs from './fs.js'
|
|
52
52
|
import iconsMap from '../sys-menu/icons-map.jsx'
|
|
53
|
-
import { refs } from '../common/ref.js'
|
|
53
|
+
import { refs, refsStatic } from '../common/ref.js'
|
|
54
54
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
55
55
|
|
|
56
56
|
const e = window.translate
|
|
@@ -255,7 +255,7 @@ clear\r`
|
|
|
255
255
|
|
|
256
256
|
isActiveTerminal = () => {
|
|
257
257
|
return this.props.tab.id === this.props.activeTabId &&
|
|
258
|
-
this.props.pane === paneMap.terminal
|
|
258
|
+
this.props.tab.pane === paneMap.terminal
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
clearShortcut = (e) => {
|
|
@@ -822,14 +822,56 @@ clear\r`
|
|
|
822
822
|
this.props.setCwd(cwd, this.state.id)
|
|
823
823
|
}
|
|
824
824
|
|
|
825
|
+
getCursorPosition = () => {
|
|
826
|
+
if (!this.term) return null
|
|
827
|
+
|
|
828
|
+
// Get the active buffer and cursor position
|
|
829
|
+
const buffer = this.term.buffer.active
|
|
830
|
+
const cursorRow = buffer.cursorY
|
|
831
|
+
const cursorCol = buffer.cursorX
|
|
832
|
+
|
|
833
|
+
// Get dimensions from term element
|
|
834
|
+
const termElement = this.term.element
|
|
835
|
+
if (!termElement) return null
|
|
836
|
+
|
|
837
|
+
// Get the exact position of the terminal element
|
|
838
|
+
const termRect = termElement.getBoundingClientRect()
|
|
839
|
+
|
|
840
|
+
// Calculate cell dimensions
|
|
841
|
+
const cellWidth = termRect.width / this.term.cols
|
|
842
|
+
const cellHeight = termRect.height / this.term.rows
|
|
843
|
+
|
|
844
|
+
// Calculate absolute position relative to terminal element
|
|
845
|
+
const left = Math.floor(termRect.left + (cursorCol * cellWidth))
|
|
846
|
+
const top = Math.floor(termRect.top + ((cursorRow + 1) * cellHeight))
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
cellWidth,
|
|
850
|
+
cellHeight,
|
|
851
|
+
left,
|
|
852
|
+
top
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
closeSuggestions = () => {
|
|
857
|
+
refsStatic
|
|
858
|
+
.get('terminal-suggestions')
|
|
859
|
+
?.closeSuggestions()
|
|
860
|
+
}
|
|
861
|
+
|
|
825
862
|
onData = (d) => {
|
|
826
863
|
if (this.cmdAddon) {
|
|
827
864
|
this.cmdAddon.handleData(d)
|
|
828
865
|
}
|
|
866
|
+
const data = this.getCmd().trim()
|
|
829
867
|
if (!d.includes('\r')) {
|
|
830
868
|
delete this.userTypeExit
|
|
869
|
+
const cursorPos = this.getCursorPosition()
|
|
870
|
+
refsStatic
|
|
871
|
+
.get('terminal-suggestions')
|
|
872
|
+
?.openSuggestions(cursorPos, data)
|
|
831
873
|
} else {
|
|
832
|
-
|
|
874
|
+
this.closeSuggestions()
|
|
833
875
|
if (this.term.buffer.active.type !== 'alternate') {
|
|
834
876
|
this.timers.getCwd = setTimeout(this.getCwd, 200)
|
|
835
877
|
}
|
|
@@ -837,6 +879,7 @@ clear\r`
|
|
|
837
879
|
'exit',
|
|
838
880
|
'logout'
|
|
839
881
|
]
|
|
882
|
+
window.store.addCmdHistory(data)
|
|
840
883
|
if (exitCmds.includes(data)) {
|
|
841
884
|
this.userTypeExit = true
|
|
842
885
|
this.timers.userTypeExit = setTimeout(() => {
|
|
@@ -1083,6 +1126,7 @@ clear\r`
|
|
|
1083
1126
|
socket.onclose = this.oncloseSocket
|
|
1084
1127
|
socket.onerror = this.onerrorSocket
|
|
1085
1128
|
this.socket = socket
|
|
1129
|
+
this.initSocketEvents()
|
|
1086
1130
|
this.term = term
|
|
1087
1131
|
socket.onopen = () => {
|
|
1088
1132
|
this.initAttachAddon()
|
|
@@ -1106,6 +1150,43 @@ clear\r`
|
|
|
1106
1150
|
)
|
|
1107
1151
|
}
|
|
1108
1152
|
|
|
1153
|
+
initSocketEvents = () => {
|
|
1154
|
+
const originalSend = this.socket.send
|
|
1155
|
+
this.socket.send = (data) => {
|
|
1156
|
+
// Call original send first
|
|
1157
|
+
originalSend.call(this.socket, data)
|
|
1158
|
+
|
|
1159
|
+
// Broadcast to other terminals
|
|
1160
|
+
this.broadcastSocketData(data)
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
canReceiveBroadcast = (termRef) => {
|
|
1165
|
+
const tabId = termRef.props?.tab?.id
|
|
1166
|
+
const isActiveInBatch = termRef.props.currentBatchTabId === tabId
|
|
1167
|
+
return (
|
|
1168
|
+
isActiveInBatch &&
|
|
1169
|
+
termRef.socket &&
|
|
1170
|
+
termRef.props?.tab.pane === paneMap.terminal
|
|
1171
|
+
)
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
broadcastSocketData = (data) => {
|
|
1175
|
+
if (!this.isActiveTerminal() || !this.props.broadcastInput) {
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
window.refs.forEach((termRef, refId) => {
|
|
1180
|
+
if (
|
|
1181
|
+
refId !== this.id &&
|
|
1182
|
+
refId.startsWith('term-') &&
|
|
1183
|
+
this.canReceiveBroadcast(termRef)
|
|
1184
|
+
) {
|
|
1185
|
+
termRef.socket.send(data)
|
|
1186
|
+
}
|
|
1187
|
+
})
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1109
1190
|
onResize = throttle(() => {
|
|
1110
1191
|
const cid = this.props.currentBatchTabId
|
|
1111
1192
|
const tid = this.props.tab?.id
|