@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.
Files changed (63) hide show
  1. package/client/common/cache.js +56 -0
  2. package/client/common/constants.js +2 -0
  3. package/client/common/download.jsx +5 -7
  4. package/client/common/setting-list.js +27 -0
  5. package/client/components/ai/ai-cache.jsx +36 -0
  6. package/client/components/ai/ai-chat-history-item.jsx +1 -1
  7. package/client/components/ai/ai-chat.jsx +5 -40
  8. package/client/components/ai/ai-config-props.js +7 -0
  9. package/client/components/ai/ai-config.jsx +5 -14
  10. package/client/components/ai/ai.styl +0 -4
  11. package/client/components/ai/providers.js +2 -2
  12. package/client/components/batch-op/batch-op-entry.jsx +1 -1
  13. package/client/components/batch-op/batch-op.jsx +2 -2
  14. package/client/components/bookmark-form/form-ssh-common.jsx +2 -3
  15. package/client/components/bookmark-form/form-tabs.jsx +2 -2
  16. package/client/components/bookmark-form/render-connection-hopping.jsx +2 -2
  17. package/client/components/bookmark-form/render-delayed-scripts.jsx +4 -4
  18. package/client/components/bookmark-form/render-ssh-tunnel.jsx +2 -2
  19. package/client/components/bookmark-form/use-quick-commands.jsx +2 -2
  20. package/client/components/common/input-auto-focus.jsx +3 -11
  21. package/client/components/main/main.jsx +5 -0
  22. package/client/components/profile/profile-form-elem.jsx +1 -3
  23. package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -3
  24. package/client/components/quick-commands/quick-commands-list-form.jsx +2 -2
  25. package/client/components/session/session.jsx +50 -14
  26. package/client/components/session/session.styl +10 -3
  27. package/client/components/session/sessions.jsx +1 -1
  28. package/client/components/setting-panel/keywords-form.jsx +36 -38
  29. package/client/components/setting-panel/setting-modal.jsx +2 -2
  30. package/client/components/setting-panel/tab-settings.jsx +26 -0
  31. package/client/components/setting-sync/setting-sync-form.jsx +8 -0
  32. package/client/components/sftp/address-bar.jsx +9 -2
  33. package/client/components/sftp/address-bookmark.jsx +4 -6
  34. package/client/components/sftp/keyword-filter.jsx +63 -0
  35. package/client/components/sftp/list-table-ui.jsx +7 -9
  36. package/client/components/sftp/sftp-entry.jsx +45 -8
  37. package/client/components/sftp/sftp.styl +6 -1
  38. package/client/components/shortcuts/shortcut-control.jsx +20 -0
  39. package/client/components/shortcuts/shortcut-editor.jsx +2 -2
  40. package/client/components/shortcuts/shortcuts.jsx +2 -2
  41. package/client/components/sidebar/info-modal.jsx +2 -2
  42. package/client/components/sidebar/transfer-list-control.jsx +18 -20
  43. package/client/components/ssh-config/ssh-config-item.jsx +2 -4
  44. package/client/components/ssh-config/ssh-config-load-notify.jsx +2 -2
  45. package/client/components/sys-menu/zoom.jsx +2 -2
  46. package/client/components/tabs/index.jsx +1 -1
  47. package/client/components/tabs/tab.jsx +3 -3
  48. package/client/components/terminal/cmd-item.jsx +32 -0
  49. package/client/components/terminal/command-tracker-addon.js +3 -1
  50. package/client/components/terminal/term-search.jsx +5 -6
  51. package/client/components/terminal/terminal-command-dropdown.jsx +303 -0
  52. package/client/components/terminal/terminal.jsx +84 -3
  53. package/client/components/terminal/terminal.styl +58 -0
  54. package/client/components/terminal-info/terminal-info.jsx +2 -2
  55. package/client/components/tree-list/tree-list.jsx +1 -1
  56. package/client/components/web/address-bar.jsx +2 -2
  57. package/client/store/common.js +27 -2
  58. package/client/store/init-state.js +3 -3
  59. package/client/store/item.js +2 -1
  60. package/client/store/setting.js +3 -2
  61. package/client/store/store.js +23 -24
  62. package/client/store/watch.js +7 -1
  63. package/package.json +1 -1
@@ -0,0 +1,56 @@
1
+ // cache class that can set(key, value), get(key), init with limit, so we only
2
+ // keep limit items in cache
3
+ // we persist cache to local storage, so we can keep cache after restart
4
+
5
+ class MapCache {
6
+ constructor (limit, key) {
7
+ this.limit = limit
8
+ this.key = key
9
+ this.cache = new Map()
10
+ this.load()
11
+ }
12
+
13
+ load () {
14
+ const data = window.localStorage.getItem(this.key)
15
+ if (data) {
16
+ const arr = JSON.parse(data)
17
+ for (const item of arr) {
18
+ this.cache.set(item.key, item.value)
19
+ }
20
+ }
21
+ }
22
+
23
+ save () {
24
+ const arr = []
25
+ for (const [key, value] of this.cache) {
26
+ arr.push({
27
+ key,
28
+ value
29
+ })
30
+ }
31
+ window.localStorage.setItem(this.key, JSON.stringify(arr))
32
+ }
33
+
34
+ set (key, value) {
35
+ this.cache.set(key, value)
36
+ if (this.cache.size > this.limit) {
37
+ // Delete oldest 20 items when cache exceeds limit
38
+ const values = Array.from(this.cache.values())
39
+ for (let i = 0; i < 20 && i < values.length; i++) {
40
+ this.cache.delete(values[i])
41
+ }
42
+ }
43
+ this.save()
44
+ }
45
+
46
+ get (key) {
47
+ return this.cache.get(key)
48
+ }
49
+
50
+ clear () {
51
+ this.cache.clear()
52
+ this.save()
53
+ }
54
+ }
55
+
56
+ export const aiSuggestionsCache = new MapCache(100, 'ai-cmd-suggestion-cache')
@@ -218,6 +218,7 @@ export const syncTokenCreateUrls = {
218
218
  export const settingSyncId = 'setting-sync'
219
219
  export const settingTerminalId = 'setting-terminal'
220
220
  export const settingShortcutsId = 'setting-shortcuts'
221
+ export const settingAiId = 'setting-ai'
221
222
  export const settingCommonId = 'setting-common'
222
223
  export const defaultEnvLang = 'en_US.UTF-8'
223
224
  const defaultThemeLightConf = _get(
@@ -373,3 +374,4 @@ export const sshConfigKey = 'ignore-ssh-config'
373
374
  export const connectionHoppingWarnKey = 'connectionHoppingWarnned'
374
375
  export const aiChatHistoryKey = 'ai-chat-history'
375
376
  export const syncServerDataKey = 'sync-server-data'
377
+ export const cmdHistoryKey = 'cmd-history'
@@ -19,13 +19,11 @@ export default async function download (filename, text) {
19
19
  notification.success({
20
20
  message: '',
21
21
  description: (
22
- <div>
23
- <ShowItem
24
- to={filePath}
25
- >
26
- {filePath}
27
- </ShowItem>
28
- </div>
22
+ <ShowItem
23
+ to={filePath}
24
+ >
25
+ {filePath}
26
+ </ShowItem>
29
27
  )
30
28
  })
31
29
  }
@@ -0,0 +1,27 @@
1
+ import {
2
+ settingSyncId,
3
+ settingShortcutsId,
4
+ settingTerminalId,
5
+ settingAiId
6
+ } from '../common/constants'
7
+
8
+ const e = window.translate
9
+
10
+ export default () => ([
11
+ {
12
+ id: settingTerminalId,
13
+ title: e('terminal')
14
+ },
15
+ {
16
+ id: settingShortcutsId,
17
+ title: e('settingShortcuts')
18
+ },
19
+ {
20
+ id: settingSyncId,
21
+ title: e('settingSync')
22
+ },
23
+ {
24
+ id: settingAiId,
25
+ title: 'AI'
26
+ }
27
+ ])
@@ -0,0 +1,36 @@
1
+ import { aiSuggestionsCache } from '../../common/cache'
2
+ import { useEffect, useState } from 'react'
3
+ import {
4
+ Button,
5
+ Alert
6
+ } from 'antd'
7
+
8
+ const e = window.translate
9
+
10
+ export default function AiCache () {
11
+ const [count, setCount] = useState(0)
12
+
13
+ function handleClick () {
14
+ aiSuggestionsCache.clear()
15
+ setCount(0)
16
+ }
17
+
18
+ useEffect(() => {
19
+ setCount(aiSuggestionsCache.cache.size)
20
+ }, [])
21
+
22
+ const msg = (
23
+ <>
24
+ <span className='mg3r'>{e('aiSuggestionsCache')}: <b>{count}</b></span>
25
+ <Button
26
+ onClick={handleClick}
27
+ size='small'
28
+ >
29
+ {e('clear')}
30
+ </Button>
31
+ </>
32
+ )
33
+ return (
34
+ <Alert message={msg} type='info' className='mg2y' />
35
+ )
36
+ }
@@ -17,7 +17,7 @@ export default function AIChatHistoryItem ({ item }) {
17
17
  } = item
18
18
  const alertProps = {
19
19
  message: (
20
- <div><UserOutlined />: {prompt}</div>
20
+ <><UserOutlined />: {prompt}</>
21
21
  ),
22
22
  type: 'info'
23
23
  }
@@ -1,6 +1,5 @@
1
1
  import { useState, useCallback, useEffect } from 'react'
2
- import { Flex, Input, message } from 'antd'
3
- import AIConfigForm from './ai-config'
2
+ import { Flex, Input } from 'antd'
4
3
  import TabSelect from '../footer/tab-select'
5
4
  import AiChatHistory from './ai-chat-history'
6
5
  import uid from '../../common/uid'
@@ -20,13 +19,6 @@ import './ai.styl'
20
19
 
21
20
  const { TextArea } = Input
22
21
  const MAX_HISTORY = 100
23
- const aiConfigsArr = [
24
- 'baseURLAI',
25
- 'modelAI',
26
- 'roleAI',
27
- 'apiKeyAI',
28
- 'apiPathAI'
29
- ]
30
22
 
31
23
  export default function AIChat (props) {
32
24
  const [prompt, setPrompt] = useState('')
@@ -42,7 +34,7 @@ export default function AIChat (props) {
42
34
  }
43
35
 
44
36
  const handleSubmit = useCallback(async function () {
45
- if (aiConfigMissing()) {
37
+ if (window.store.aiConfigMissing()) {
46
38
  window.store.toggleAIConfig()
47
39
  }
48
40
  if (!prompt.trim() || isLoading) return
@@ -82,30 +74,6 @@ export default function AIChat (props) {
82
74
  setIsLoading(false)
83
75
  }, [prompt, isLoading, props.config])
84
76
 
85
- function handleConfigSubmit (values) {
86
- window.store.updateConfig(values)
87
- message.success('Saved')
88
- }
89
-
90
- function getInitialValues () {
91
- const res = pick(props.config, aiConfigsArr)
92
- if (!res.languageAI) {
93
- res.languageAI = window.store.getLangName()
94
- }
95
- return res
96
- }
97
-
98
- const renderConfig = useCallback(() => {
99
- if (!props.showAIConfig) return null
100
- return (
101
- <AIConfigForm
102
- initialValues={getInitialValues()}
103
- onSubmit={handleConfigSubmit}
104
- showAIConfig={props.showAIConfig}
105
- />
106
- )
107
- }, [props.showAIConfig, props.config])
108
-
109
77
  function renderHistory () {
110
78
  return (
111
79
  <AiChatHistory
@@ -122,10 +90,6 @@ export default function AIChat (props) {
122
90
  window.store.aiChatHistory = []
123
91
  }
124
92
 
125
- function aiConfigMissing () {
126
- return aiConfigsArr.some(k => !props.config[k])
127
- }
128
-
129
93
  function renderSendIcon () {
130
94
  if (isLoading) {
131
95
  return <LoadingOutlined />
@@ -134,6 +98,7 @@ export default function AIChat (props) {
134
98
  <SendOutlined
135
99
  onClick={handleSubmit}
136
100
  className='mg1l pointer icon-hover'
101
+ title='Ctrl+Enter'
137
102
  />
138
103
  )
139
104
  }
@@ -143,7 +108,7 @@ export default function AIChat (props) {
143
108
  setPrompt,
144
109
  handleSubmit
145
110
  })
146
- if (aiConfigMissing()) {
111
+ if (window.store.aiConfigMissing()) {
147
112
  window.store.toggleAIConfig()
148
113
  }
149
114
  return () => {
@@ -168,6 +133,7 @@ export default function AIChat (props) {
168
133
  placeholder='Enter your prompt here'
169
134
  autoSize={{ minRows: 3, maxRows: 10 }}
170
135
  disabled={isLoading}
136
+ className='ai-chat-textarea'
171
137
  />
172
138
  <Flex className='ai-chat-terminals' justify='space-between' align='center'>
173
139
  <Flex align='center'>
@@ -188,7 +154,6 @@ export default function AIChat (props) {
188
154
  <HelpIcon
189
155
  link={aiConfigWikiLink}
190
156
  />
191
- {renderConfig()}
192
157
  </Flex>
193
158
  {renderSendIcon()}
194
159
  </Flex>
@@ -0,0 +1,7 @@
1
+ export const aiConfigsArr = [
2
+ 'baseURLAI',
3
+ 'modelAI',
4
+ 'roleAI',
5
+ 'apiKeyAI',
6
+ 'apiPathAI'
7
+ ]
@@ -3,12 +3,12 @@ import {
3
3
  Input,
4
4
  Button,
5
5
  AutoComplete,
6
- Modal,
7
6
  Alert,
8
7
  Space
9
8
  } from 'antd'
10
9
  import { useEffect, useState } from 'react'
11
10
  import Link from '../common/external-link'
11
+ import AiCache from './ai-cache'
12
12
  import {
13
13
  aiConfigWikiLink
14
14
  } from '../../common/constants'
@@ -61,10 +61,6 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
61
61
  onSubmit(values)
62
62
  }
63
63
 
64
- function handleCancel () {
65
- window.store.toggleAIConfig()
66
- }
67
-
68
64
  function handleChange (v) {
69
65
  const options = getModelOptions(v)
70
66
  setModelOptions(options)
@@ -76,16 +72,9 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
76
72
  if (!showAIConfig) {
77
73
  return null
78
74
  }
79
- const title = 'AI ' + e('setting')
80
75
  const defaultLangs = window.store.getLangNames().map(l => ({ value: l }))
81
76
  return (
82
- <Modal
83
- title={title}
84
- open
85
- onCancel={handleCancel}
86
- footer={null}
87
- width='90%'
88
- >
77
+ <>
89
78
  <Alert
90
79
  message={
91
80
  <Link to={aiConfigWikiLink}>WIKI: {aiConfigWikiLink}</Link>
@@ -98,6 +87,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
98
87
  onFinish={handleSubmit}
99
88
  initialValues={initialValues}
100
89
  layout='vertical'
90
+ className='ai-config-form'
101
91
  >
102
92
  <Form.Item label='API URL' required>
103
93
  <Space.Compact block>
@@ -181,6 +171,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
181
171
  </Button>
182
172
  </Form.Item>
183
173
  </Form>
184
- </Modal>
174
+ <AiCache />
175
+ </>
185
176
  )
186
177
  }
@@ -15,10 +15,6 @@
15
15
  overflow-y auto
16
16
  overflow-x hidden
17
17
 
18
- .ai-config-form
19
- height 200px
20
- overflow-y auto
21
-
22
18
  .chat-history-item
23
19
  .code-block
24
20
  border 1px dashed text
@@ -3,12 +3,12 @@ export default [
3
3
  label: 'OpenAI',
4
4
  baseURL: 'https://api.openai.com/v1',
5
5
  homepage: 'https://openai.com',
6
- models: ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k']
6
+ models: ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4.5']
7
7
  },
8
8
  {
9
9
  label: 'DeepSeek',
10
10
  baseURL: 'https://api.deepseek.com/v1',
11
11
  homepage: 'https://deepseek.com',
12
- models: ['deepseek-chat', 'deepseek-coder']
12
+ models: ['deepseek-chat', 'deepseek-coder', 'deepseek-reasoner']
13
13
  }
14
14
  ]
@@ -6,7 +6,7 @@ export const BatchOp = lazy(() => import('./batch-op'))
6
6
  // Wrap BatchOp with Suspense
7
7
  export default function BatchOpEntry (props) {
8
8
  return (
9
- <Suspense fallback={<div>Loading...</div>}>
9
+ <Suspense fallback={<>Loading...</>}>
10
10
  <BatchOp {...props} />
11
11
  </Suspense>
12
12
  )
@@ -479,7 +479,7 @@ export default class BatchOp extends PureComponent {
479
479
  }
480
480
 
481
481
  return (
482
- <div>
482
+ <>
483
483
  <div className='pd1y'>
484
484
  <h2>
485
485
  {e('batchOperation')}
@@ -555,7 +555,7 @@ export default class BatchOp extends PureComponent {
555
555
  <div className='pd1y'>
556
556
  {this.renderTables()}
557
557
  </div>
558
- </div>
558
+ </>
559
559
  )
560
560
  }
561
561
 
@@ -61,7 +61,7 @@ export default function renderCommon (props) {
61
61
  })
62
62
  }
63
63
  return (
64
- <div>
64
+ <>
65
65
  <FormItem
66
66
  {...formItemLayout}
67
67
  label={e('host')}
@@ -84,7 +84,6 @@ export default function renderCommon (props) {
84
84
  }
85
85
  <FormItem noStyle name='host'>
86
86
  <InputAutoFocus
87
- selectall='yes'
88
87
  name='host'
89
88
  onBlur={props.onBlur}
90
89
  onPaste={e => props.onPaste(e, form)}
@@ -223,6 +222,6 @@ export default function renderCommon (props) {
223
222
  }
224
223
  </Select>
225
224
  </FormItem>
226
- </div>
225
+ </>
227
226
  )
228
227
  }
@@ -30,12 +30,12 @@ export default function renderTabs (props) {
30
30
  label: e('settings'),
31
31
  forceRender: true,
32
32
  children: (
33
- <div>
33
+ <>
34
34
  {props.renderEnableSftp()}
35
35
  {props.uis}
36
36
  {props.renderProxy(props)}
37
37
  {props.renderX11()}
38
- </div>
38
+ </>
39
39
  )
40
40
  },
41
41
  {
@@ -169,7 +169,7 @@ export default function renderConnectionHopping (props) {
169
169
  onSelect: handleFinish
170
170
  }
171
171
  return (
172
- <div>
172
+ <>
173
173
  <FormItem
174
174
  name='connectionHoppings'
175
175
  className='hide'
@@ -269,6 +269,6 @@ export default function renderConnectionHopping (props) {
269
269
  </Button>
270
270
  </FormItem>
271
271
  </Form>
272
- </div>
272
+ </>
273
273
  )
274
274
  }
@@ -15,7 +15,7 @@ const e = window.translate
15
15
  export default function renderRunScripts () {
16
16
  function renderItem (field, i, add, remove) {
17
17
  return (
18
- <div>
18
+ <>
19
19
  <Space
20
20
  align='center'
21
21
  key={field.key}
@@ -51,7 +51,7 @@ export default function renderRunScripts () {
51
51
  className='mg24b'
52
52
  />
53
53
  </Space>
54
- </div>
54
+ </>
55
55
  )
56
56
  }
57
57
 
@@ -63,7 +63,7 @@ export default function renderRunScripts () {
63
63
  {
64
64
  (fields, { add, remove }, { errors }) => {
65
65
  return (
66
- <div>
66
+ <>
67
67
  {
68
68
  fields.map((field, i) => {
69
69
  return renderItem(field, i, add, remove)
@@ -79,7 +79,7 @@ export default function renderRunScripts () {
79
79
  {e('loginScript')}
80
80
  </Button>
81
81
  </FormItem>
82
- </div>
82
+ </>
83
83
  )
84
84
  }
85
85
  }
@@ -197,7 +197,7 @@ export default function renderSshTunnels (props) {
197
197
  }
198
198
 
199
199
  return (
200
- <div>
200
+ <>
201
201
  <FormItem
202
202
  name='sshTunnels'
203
203
  className='hide'
@@ -291,6 +291,6 @@ export default function renderSshTunnels (props) {
291
291
  </Button>
292
292
  </FormItem>
293
293
  </Form>
294
- </div>
294
+ </>
295
295
  )
296
296
  }
@@ -57,7 +57,7 @@ export default function useQuickCmds (form, formData) {
57
57
  {
58
58
  (fields, { add, remove }, { errors }) => {
59
59
  return (
60
- <div>
60
+ <>
61
61
  {
62
62
  fields.map((field, i) => {
63
63
  return renderItem(field, i, add, remove)
@@ -73,7 +73,7 @@ export default function useQuickCmds (form, formData) {
73
73
  {e('newQuickCommand')}
74
74
  </Button>
75
75
  </FormItem>
76
- </div>
76
+ </>
77
77
  )
78
78
  }
79
79
  }
@@ -4,22 +4,14 @@ import {
4
4
  } from 'antd'
5
5
 
6
6
  export default function InputAutoFocus (props) {
7
- const { type, selectall = false, ...rest } = props
7
+ const { type, ...rest } = props
8
8
  const inputRef = useRef(null)
9
- const isFirstRender = useRef(true)
10
9
 
11
10
  useEffect(() => {
12
11
  if (inputRef.current) {
13
- const { value } = props
14
- if (value && selectall && isFirstRender.current) {
15
- inputRef.current.focus()
16
- inputRef.current.setSelectionRange(0, value.length)
17
- isFirstRender.current = false
18
- } else {
19
- inputRef.current.focus()
20
- }
12
+ inputRef.current.focus()
21
13
  }
22
- }, [props.value, props.selectall])
14
+ }, [props.value])
23
15
 
24
16
  let InputComponent
25
17
  switch (type) {
@@ -14,6 +14,7 @@ import Resolutions from '../rdp/resolution-edit'
14
14
  import TerminalInteractive from '../terminal/terminal-interactive'
15
15
  import ConfirmModalStore from '../sftp/confirm-modal-store.jsx'
16
16
  import TransferConflictStore from '../sftp/transfer-conflict-store.jsx'
17
+ import TerminalCmdSuggestions from '../terminal/terminal-command-dropdown'
17
18
  import TransportsActionStore from '../sftp/transports-action-store.jsx'
18
19
  import classnames from 'classnames'
19
20
  import ShortcutControl from '../shortcuts/shortcut-control.jsx'
@@ -221,6 +222,9 @@ export default auto(function Index (props) {
221
222
  showAIConfig: store.showAIConfig,
222
223
  rightPanelTab
223
224
  }
225
+ const cmdSuggestionsProps = {
226
+ suggestions: store.terminalCommandSuggestions
227
+ }
224
228
  return (
225
229
  <ConfigProvider
226
230
  theme={uiThemeConfig}
@@ -281,6 +285,7 @@ export default auto(function Index (props) {
281
285
  sshConfigs={store.sshConfigs}
282
286
  />
283
287
  <ConnectionHoppingWarning {...warningProps} />
288
+ <TerminalCmdSuggestions {...cmdSuggestionsProps} />
284
289
  </div>
285
290
  </ConfigProvider>
286
291
  )
@@ -71,9 +71,7 @@ export default function ProfileFormElem (props) {
71
71
  hasFeedback
72
72
  name='name'
73
73
  >
74
- <InputAutoFocus
75
- selectall='yes'
76
- />
74
+ <InputAutoFocus />
77
75
  </FormItem>
78
76
  <ProfileTabs {...tabsProps} />
79
77
  <FormItem>
@@ -148,9 +148,7 @@ export default function QuickCommandForm (props) {
148
148
  hasFeedback
149
149
  name='name'
150
150
  >
151
- <InputAutoFocus
152
- selectall='yes'
153
- />
151
+ <InputAutoFocus />
154
152
  </FormItem>
155
153
  {renderQm()}
156
154
  <FormItem
@@ -113,7 +113,7 @@ export default function renderQm () {
113
113
  {
114
114
  (fields, { add, remove }, { errors }) => {
115
115
  return (
116
- <div>
116
+ <>
117
117
  {
118
118
  fields.map((field, i) => {
119
119
  return renderItem(field, i, add, remove)
@@ -129,7 +129,7 @@ export default function renderQm () {
129
129
  {e('quickCommand')}
130
130
  </Button>
131
131
  </FormItem>
132
- </div>
132
+ </>
133
133
  )
134
134
  }
135
135
  }