@electerm/electerm-react 5.5.9 → 5.5.25
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 +12 -2
- package/client/common/constants.js +14 -0
- package/client/common/default-setting.js +2 -2
- package/client/common/download.jsx +12 -0
- package/client/common/parse-vv.js +313 -0
- package/client/common/resolve.js +20 -14
- package/client/common/theme-defaults.js +5 -2
- package/client/common/update-check.js +5 -4
- package/client/components/ai/ai-history-item.jsx +1 -1
- package/client/components/ai/ai-history.jsx +1 -0
- package/client/components/ai/ai-history.styl +23 -0
- package/client/components/ai/ai-presets.js +13 -2
- package/client/components/bg/css-overwrite.jsx +16 -1
- package/client/components/bookmark-form/common/color-picker.styl +3 -0
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/common/vv-file-field.jsx +137 -0
- package/client/components/bookmark-form/config/spice.js +1 -0
- package/client/components/common/remote-pointer.js +49 -0
- package/client/components/common/remote-session-shell.jsx +137 -0
- package/client/components/common/remote-session-shell.styl +80 -0
- package/client/components/common/responsive-tabs.jsx +89 -0
- package/client/components/common/responsive-tabs.styl +34 -0
- package/client/components/footer/footer-entry.jsx +39 -14
- package/client/components/icons/ai-icon.styl +4 -1
- package/client/components/layout/layout.jsx +7 -1
- package/client/components/main/main.jsx +9 -2
- package/client/components/main/term-fullscreen.styl +7 -0
- package/client/components/quick-commands/quick-command-ai-editor.jsx +255 -0
- package/client/components/quick-commands/quick-command-ai-editor.styl +13 -0
- package/client/components/quick-commands/quick-commands-box.jsx +23 -5
- package/client/components/quick-commands/quick-commands-form-elem.jsx +129 -65
- package/client/components/rdp/rdp-session.jsx +94 -124
- package/client/components/rdp/rdp.styl +13 -36
- package/client/components/session/session.styl +0 -16
- package/client/components/setting-panel/list.jsx +2 -2
- package/client/components/setting-panel/setting-modal.jsx +8 -3
- package/client/components/sftp/sftp-entry.jsx +26 -0
- package/client/components/side-panel-r/right-side-panel.styl +11 -1
- package/client/components/side-panel-r/side-panel-r.jsx +45 -9
- package/client/components/sidebar/bookmark-select.jsx +3 -1
- package/client/components/sidebar/index.jsx +13 -4
- package/client/components/sidebar/side-panel.jsx +8 -3
- package/client/components/sidebar/sidebar-panel.jsx +20 -7
- package/client/components/spice/spice-session.jsx +69 -72
- package/client/components/spice/spice.styl +10 -19
- package/client/components/terminal/shortcut-bar.styl +14 -0
- package/client/components/terminal/unix-timestamp-tooltip.jsx +27 -1
- package/client/components/terminal-info/terminal-info.styl +3 -0
- package/client/components/theme/terminal-theme-list.styl +3 -0
- package/client/components/theme/theme-ai-editor.jsx +2 -2
- package/client/components/theme/theme-form.jsx +20 -23
- package/client/components/theme/theme-form.styl +8 -10
- package/client/components/theme/theme-list-item.jsx +59 -25
- package/client/components/vnc/vnc-session.jsx +72 -104
- package/client/components/vnc/vnc.styl +25 -16
- package/client/css/mobile.styl +28 -0
- package/client/store/common.js +44 -0
- package/client/store/init-state.js +2 -1
- package/client/store/setting.js +4 -0
- package/client/store/sidebar.js +5 -1
- package/client/store/store.js +9 -2
- package/package.json +1 -1
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
right 10px !important
|
|
38
38
|
bottom 10px !important
|
|
39
39
|
|
|
40
|
+
// RDP/VNC/Spice fullscreen has no footer (it is in the hide list above), so
|
|
41
|
+
// the right panel has nothing to clear and may use the full height. When the
|
|
42
|
+
// footer IS kept, see .fullscreen.fs-with-footer below.
|
|
43
|
+
.fullscreen:not(.fs-with-footer)
|
|
44
|
+
.right-side-panel
|
|
45
|
+
bottom 0
|
|
46
|
+
|
|
40
47
|
// Terminal fullscreen keeps the footer (and the quick-command popup above it)
|
|
41
48
|
// visible; the session shrinks to sit on top of the footer. RDP/VNC/Spice
|
|
42
49
|
// fullscreen stays footer-less — inActiveTerminal is false for those tabs, so
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI-powered quick command generator.
|
|
3
|
+
*
|
|
4
|
+
* This is the "create with AI" tab of the quick command form: describe the
|
|
5
|
+
* quick command in natural language, the configured AI provider returns the
|
|
6
|
+
* quick command (name + sub commands in JSON), and the result is written back
|
|
7
|
+
* into the very same form through `onChange`, so the user reviews/edits it in
|
|
8
|
+
* the manual tab before saving.
|
|
9
|
+
*/
|
|
10
|
+
import { useState, useEffect } from 'react'
|
|
11
|
+
import { Button, Input, Space, Alert } from 'antd'
|
|
12
|
+
import {
|
|
13
|
+
RobotOutlined,
|
|
14
|
+
LoadingOutlined
|
|
15
|
+
} from '@ant-design/icons'
|
|
16
|
+
import message from '../common/message'
|
|
17
|
+
import { appendMandatoryGuardrails } from '../ai/ai-guardrails'
|
|
18
|
+
import AiHistory, { addHistoryItem } from '../ai/ai-history.jsx'
|
|
19
|
+
import HelpIcon from '../common/help-icon'
|
|
20
|
+
import { getItem, setItem } from '../../common/safe-local-storage'
|
|
21
|
+
import templates from './templates'
|
|
22
|
+
import './quick-command-ai-editor.styl'
|
|
23
|
+
|
|
24
|
+
const STORAGE_KEY_DESC = 'ai_quick_command_description'
|
|
25
|
+
const STORAGE_KEY_HISTORY = 'ai_quick_command_history'
|
|
26
|
+
const EVENT_NAME_HISTORY = 'ai-quick-command-history-update'
|
|
27
|
+
const defaultDelay = 100
|
|
28
|
+
const maxDelay = 65535
|
|
29
|
+
const maxNameLength = 60
|
|
30
|
+
const { TextArea } = Input
|
|
31
|
+
const e = window.translate
|
|
32
|
+
|
|
33
|
+
const wiki = 'https://github.com/electerm/electerm/wiki/Create-quick-command-by-AI'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* build the prompt that asks the AI for one quick command definition
|
|
37
|
+
* @param {string} description
|
|
38
|
+
* @return {string}
|
|
39
|
+
*/
|
|
40
|
+
export function buildPrompt (description) {
|
|
41
|
+
const lang = window.store.config.languageAI || window.store.getLangName()
|
|
42
|
+
const templateList = templates.map(t => `{{${t}}}`).join(', ')
|
|
43
|
+
return `You are an electerm quick command generator. Based on the user's natural language description, generate ONE quick command definition in JSON format.
|
|
44
|
+
|
|
45
|
+
The quick command JSON shape:
|
|
46
|
+
{
|
|
47
|
+
"name": "string (required) - short name of the quick command",
|
|
48
|
+
"commands": [
|
|
49
|
+
{
|
|
50
|
+
"name": "string - short name of this step",
|
|
51
|
+
"command": "string (required) - the exact shell command to send to the terminal",
|
|
52
|
+
"delay": "number - milliseconds to wait before sending this step, 1-${maxDelay}, default ${defaultDelay}"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"inputOnly": "boolean - true means only type the command into the terminal without pressing enter, default false",
|
|
56
|
+
"labels": "array of strings - optional tags used to group quick commands"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Important rules:
|
|
60
|
+
1. Respond with ONE quick command object, never an array of quick commands
|
|
61
|
+
2. Split a multi step operation into several "commands" items, one shell command per item, and give a later step a longer "delay" when the step before it takes time
|
|
62
|
+
3. "command" must be a plain shell command, no markdown, no code fence, no trailing newline
|
|
63
|
+
4. Keep "name" short (max ${maxNameLength} chars) and write it in ${lang} language, labels included
|
|
64
|
+
5. You may use the built in templates ${templateList} inside a command when the user asks for clipboard content, current time or current date
|
|
65
|
+
6. Never invent hosts, paths, ports or credentials the user did not mention
|
|
66
|
+
7. Respond ONLY with valid JSON, no markdown formatting and no extra explanation
|
|
67
|
+
|
|
68
|
+
User description: ${description}
|
|
69
|
+
|
|
70
|
+
Generate the quick command JSON:`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stripFence (str) {
|
|
74
|
+
let s = str.trim()
|
|
75
|
+
if (s.startsWith('```json')) {
|
|
76
|
+
s = s.slice(7)
|
|
77
|
+
} else if (s.startsWith('```')) {
|
|
78
|
+
s = s.slice(3)
|
|
79
|
+
}
|
|
80
|
+
if (s.endsWith('```')) {
|
|
81
|
+
s = s.slice(0, -3)
|
|
82
|
+
}
|
|
83
|
+
return s.trim()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Normalize whatever the model returned into a quick command definition the
|
|
88
|
+
* quick command form can consume. Tolerates markdown fences, prose around the
|
|
89
|
+
* JSON, an array payload and string commands.
|
|
90
|
+
* @param {string|object} raw
|
|
91
|
+
* @return {object|null} {name, commands, inputOnly, labels}
|
|
92
|
+
*/
|
|
93
|
+
export function normalizeQuickCommand (raw) {
|
|
94
|
+
if (!raw) {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
let data = raw
|
|
98
|
+
if (typeof data === 'string') {
|
|
99
|
+
const txt = stripFence(data)
|
|
100
|
+
try {
|
|
101
|
+
data = JSON.parse(txt)
|
|
102
|
+
} catch (err) {
|
|
103
|
+
// the model sometimes wraps the JSON in explanation text
|
|
104
|
+
const match = txt.match(/[{[][\s\S]*[}\]]/)
|
|
105
|
+
if (!match) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
data = JSON.parse(match[0])
|
|
110
|
+
} catch (err2) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(data)) {
|
|
116
|
+
data = data[0]
|
|
117
|
+
}
|
|
118
|
+
if (!data || typeof data !== 'object') {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
const rawCommands = Array.isArray(data.commands)
|
|
122
|
+
? data.commands
|
|
123
|
+
: (data.command ? [data] : [])
|
|
124
|
+
const commands = rawCommands.map(c => {
|
|
125
|
+
if (typeof c === 'string') {
|
|
126
|
+
return {
|
|
127
|
+
name: '',
|
|
128
|
+
command: c,
|
|
129
|
+
delay: defaultDelay
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const delay = Number(c.delay)
|
|
133
|
+
return {
|
|
134
|
+
name: (c.name || '').toString(),
|
|
135
|
+
command: (c.command || c.cmd || '').toString(),
|
|
136
|
+
delay: delay >= 1 ? Math.min(Math.round(delay), maxDelay) : defaultDelay
|
|
137
|
+
}
|
|
138
|
+
}).filter(c => c.command.trim())
|
|
139
|
+
if (!commands.length) {
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
const name = (data.name || data.title || '').toString().trim()
|
|
143
|
+
return {
|
|
144
|
+
name: name || commands[0].command.slice(0, maxNameLength),
|
|
145
|
+
commands,
|
|
146
|
+
inputOnly: !!data.inputOnly,
|
|
147
|
+
labels: Array.isArray(data.labels)
|
|
148
|
+
? data.labels.map(l => l.toString()).filter(Boolean)
|
|
149
|
+
: []
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default function QuickCommandAiEditor (props) {
|
|
154
|
+
const { onChange } = props
|
|
155
|
+
const [description, setDescription] = useState(() => getItem(STORAGE_KEY_DESC) || '')
|
|
156
|
+
const [loading, setLoading] = useState(false)
|
|
157
|
+
const [preview, setPreview] = useState('')
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
setItem(STORAGE_KEY_DESC, description)
|
|
161
|
+
}, [description])
|
|
162
|
+
|
|
163
|
+
async function handleGenerate () {
|
|
164
|
+
if (window.store.aiConfigMissing()) {
|
|
165
|
+
window.store.toggleAIConfig()
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
if (!description.trim()) {
|
|
169
|
+
return message.warning(e('description') + ' ' + e('required'))
|
|
170
|
+
}
|
|
171
|
+
setLoading(true)
|
|
172
|
+
try {
|
|
173
|
+
const config = window.store.config
|
|
174
|
+
const aiResponse = await window.pre.runGlobalAsync(
|
|
175
|
+
'AIchat',
|
|
176
|
+
buildPrompt(description),
|
|
177
|
+
config.modelAI,
|
|
178
|
+
appendMandatoryGuardrails('You are a helpful assistant that generates electerm quick commands.'),
|
|
179
|
+
config.baseURLAI,
|
|
180
|
+
config.apiPathAI,
|
|
181
|
+
config.apiKeyAI,
|
|
182
|
+
config.proxyAI,
|
|
183
|
+
false, // disable streaming, we need the whole JSON at once
|
|
184
|
+
config.authHeaderNameAI
|
|
185
|
+
)
|
|
186
|
+
if (aiResponse && aiResponse.error) {
|
|
187
|
+
throw new Error(aiResponse.error)
|
|
188
|
+
}
|
|
189
|
+
const data = normalizeQuickCommand(aiResponse && aiResponse.response)
|
|
190
|
+
if (!data) {
|
|
191
|
+
throw new Error('no quick command found in AI response')
|
|
192
|
+
}
|
|
193
|
+
setPreview(JSON.stringify(data, null, 2))
|
|
194
|
+
addHistoryItem(STORAGE_KEY_HISTORY, description, EVENT_NAME_HISTORY)
|
|
195
|
+
onChange(data)
|
|
196
|
+
message.success(e('Done'))
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error('AI quick command generation error:', err)
|
|
199
|
+
message.error('Can not generate quick command from AI response: ' + err.message)
|
|
200
|
+
} finally {
|
|
201
|
+
setLoading(false)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const textAreaProps = {
|
|
206
|
+
value: description,
|
|
207
|
+
onChange: ev => setDescription(ev.target.value),
|
|
208
|
+
placeholder: e('description'),
|
|
209
|
+
autoSize: { minRows: 4, maxRows: 8 },
|
|
210
|
+
disabled: loading
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const generateBtnProps = {
|
|
214
|
+
type: 'primary',
|
|
215
|
+
onClick: handleGenerate,
|
|
216
|
+
disabled: !description.trim(),
|
|
217
|
+
icon: loading ? <LoadingOutlined /> : <RobotOutlined />,
|
|
218
|
+
loading
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div className='qm-ai-editor'>
|
|
223
|
+
<div className='pd1b'>
|
|
224
|
+
<Alert
|
|
225
|
+
type='info'
|
|
226
|
+
showIcon
|
|
227
|
+
title={e('aiSecurityNotice')}
|
|
228
|
+
/>
|
|
229
|
+
</div>
|
|
230
|
+
<div className='pd1b'>
|
|
231
|
+
<TextArea {...textAreaProps} />
|
|
232
|
+
</div>
|
|
233
|
+
<AiHistory
|
|
234
|
+
storageKey={STORAGE_KEY_HISTORY}
|
|
235
|
+
eventName={EVENT_NAME_HISTORY}
|
|
236
|
+
onSelect={setDescription}
|
|
237
|
+
/>
|
|
238
|
+
<div className='pd1t'>
|
|
239
|
+
<Space>
|
|
240
|
+
<Button {...generateBtnProps}>
|
|
241
|
+
{e('submit')}
|
|
242
|
+
</Button>
|
|
243
|
+
<HelpIcon link={wiki} />
|
|
244
|
+
</Space>
|
|
245
|
+
</div>
|
|
246
|
+
{
|
|
247
|
+
!!preview && (
|
|
248
|
+
<div className='pd1t'>
|
|
249
|
+
<pre className='qm-ai-preview'>{preview}</pre>
|
|
250
|
+
</div>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
</div>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// "create with AI" tab of the quick command form
|
|
2
|
+
.qm-ai-editor
|
|
3
|
+
// the generated quick command, kept visible for reference after it has been
|
|
4
|
+
// written back into the form
|
|
5
|
+
.qm-ai-preview
|
|
6
|
+
padding 12px
|
|
7
|
+
margin 0
|
|
8
|
+
max-height 300px
|
|
9
|
+
overflow auto
|
|
10
|
+
font-size 12px
|
|
11
|
+
font-family monospace
|
|
12
|
+
white-space pre-wrap
|
|
13
|
+
word-break break-word
|
|
@@ -163,7 +163,11 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
163
163
|
inActiveTerminal,
|
|
164
164
|
leftSidePanelWidth,
|
|
165
165
|
leftSideBarWidth,
|
|
166
|
-
openedSideBar
|
|
166
|
+
openedSideBar,
|
|
167
|
+
rightPanelVisible,
|
|
168
|
+
rightPanelPinned,
|
|
169
|
+
rightPanelWidth,
|
|
170
|
+
isMobile
|
|
167
171
|
} = props
|
|
168
172
|
if ((!openQuickCommandBar && !pinnedQuickCommandBar) || !inActiveTerminal) {
|
|
169
173
|
return null
|
|
@@ -189,12 +193,26 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
189
193
|
: 'text'
|
|
190
194
|
const cls = classNames('qm-list-wrap')
|
|
191
195
|
const type = qmSortByFrequency ? 'primary' : 'default'
|
|
192
|
-
|
|
196
|
+
// Mirrors the footer's left offset. Mobile reserves nothing for the side
|
|
197
|
+
// panel — it is a full-screen drawer there (store.leftSidePanelWidth returns
|
|
198
|
+
// the viewport width), so adding it would push the popup off-screen.
|
|
199
|
+
const w = openedSideBar && !isMobile
|
|
200
|
+
? leftSideBarWidth + leftSidePanelWidth
|
|
201
|
+
: leftSideBarWidth
|
|
202
|
+
// Keep the popup's horizontal extent in step with the footer (same `w`
|
|
203
|
+
// expression above). A pinned right panel is a dock that reserves its width
|
|
204
|
+
// in layout.jsx, so give that width up here too — otherwise the popup's right
|
|
205
|
+
// end slides under the dock. Unpinned is an overlay and reserves nothing, and
|
|
206
|
+
// mobile is excluded to match layout.jsx.
|
|
207
|
+
const qmStyle = {
|
|
208
|
+
left: w
|
|
209
|
+
}
|
|
210
|
+
if (rightPanelVisible && rightPanelPinned && !isMobile) {
|
|
211
|
+
qmStyle.right = rightPanelWidth
|
|
212
|
+
}
|
|
193
213
|
const qmProps = {
|
|
194
214
|
className: 'qm-wrap-tooltip',
|
|
195
|
-
style:
|
|
196
|
-
left: w
|
|
197
|
-
}
|
|
215
|
+
style: qmStyle
|
|
198
216
|
}
|
|
199
217
|
return (
|
|
200
218
|
<div
|
|
@@ -4,12 +4,16 @@ import {
|
|
|
4
4
|
Select,
|
|
5
5
|
Input
|
|
6
6
|
} from 'antd'
|
|
7
|
+
import { RobotOutlined } from '@ant-design/icons'
|
|
7
8
|
import message from '../common/message'
|
|
8
9
|
import SwitchLabel from '../common/switch'
|
|
9
10
|
import { useState } from 'react'
|
|
10
11
|
import generate from '../../common/uid'
|
|
11
12
|
import InputAutoFocus from '../common/input-auto-focus'
|
|
12
13
|
import renderQm from './quick-commands-list-form'
|
|
14
|
+
import QuickCommandAiEditor from './quick-command-ai-editor'
|
|
15
|
+
import ResponsiveTabs from '../common/responsive-tabs'
|
|
16
|
+
import { isAIDisabled } from '../../common/ai-feature'
|
|
13
17
|
import ShortcutEdit from '../shortcuts/shortcut-editor'
|
|
14
18
|
import { getKeysTakenData } from '../shortcuts/shortcut-utils'
|
|
15
19
|
import deepCopy from 'json-deep-copy'
|
|
@@ -18,6 +22,8 @@ import HelpIcon from '../common/help-icon'
|
|
|
18
22
|
|
|
19
23
|
const FormItem = Form.Item
|
|
20
24
|
const { Option } = Select
|
|
25
|
+
const manualTab = 'qm-form-manual'
|
|
26
|
+
const aiTab = 'qm-form-ai'
|
|
21
27
|
const e = window.translate
|
|
22
28
|
|
|
23
29
|
export default function QuickCommandForm (props) {
|
|
@@ -25,6 +31,7 @@ export default function QuickCommandForm (props) {
|
|
|
25
31
|
const { store, formData } = props
|
|
26
32
|
const { quickCommandTags = [] } = store
|
|
27
33
|
const [shortcut, setShortcut] = useState(formData.shortcut || '')
|
|
34
|
+
const [tab, setTab] = useState(manualTab)
|
|
28
35
|
const uid = formData.id || generate()
|
|
29
36
|
const updateConfig = (name, value) => {
|
|
30
37
|
form.setFieldsValue({
|
|
@@ -86,6 +93,22 @@ export default function QuickCommandForm (props) {
|
|
|
86
93
|
message.success(e('saved'))
|
|
87
94
|
props.onSaved?.()
|
|
88
95
|
}
|
|
96
|
+
|
|
97
|
+
// AI generated quick command: write it into the same form (so the user can
|
|
98
|
+
// review/edit it) and jump back to the manual tab, where the save button is
|
|
99
|
+
function handleAiGenerated (data) {
|
|
100
|
+
const update = {
|
|
101
|
+
name: data.name,
|
|
102
|
+
commands: data.commands,
|
|
103
|
+
inputOnly: data.inputOnly
|
|
104
|
+
}
|
|
105
|
+
if (data.labels.length) {
|
|
106
|
+
update.labels = data.labels
|
|
107
|
+
}
|
|
108
|
+
form.setFieldsValue(update)
|
|
109
|
+
setTab(manualTab)
|
|
110
|
+
}
|
|
111
|
+
|
|
89
112
|
const initialValues = formData
|
|
90
113
|
if (!initialValues.labels) {
|
|
91
114
|
initialValues.labels = []
|
|
@@ -112,6 +135,111 @@ export default function QuickCommandForm (props) {
|
|
|
112
135
|
return `{{${t}}}`
|
|
113
136
|
}).join(', ')
|
|
114
137
|
const wiki = 'https://github.com/electerm/electerm/wiki/quick-command-templates'
|
|
138
|
+
const aiDisabled = isAIDisabled()
|
|
139
|
+
// renderQm() uses hooks internally, so it must run on every render
|
|
140
|
+
const qmItem = renderQm(form)
|
|
141
|
+
const manualPane = (
|
|
142
|
+
<>
|
|
143
|
+
<FormItem
|
|
144
|
+
label={e('quickCommandName')}
|
|
145
|
+
rules={[{
|
|
146
|
+
max: 60, message: '60 chars max'
|
|
147
|
+
}, {
|
|
148
|
+
required: true, message: 'Name required'
|
|
149
|
+
}]}
|
|
150
|
+
hasFeedback
|
|
151
|
+
name='name'
|
|
152
|
+
>
|
|
153
|
+
<InputAutoFocus />
|
|
154
|
+
</FormItem>
|
|
155
|
+
{qmItem}
|
|
156
|
+
<FormItem
|
|
157
|
+
name='labels'
|
|
158
|
+
label={e('label')}
|
|
159
|
+
>
|
|
160
|
+
<Select
|
|
161
|
+
mode='tags'
|
|
162
|
+
>
|
|
163
|
+
{
|
|
164
|
+
quickCommandTags.map(q => {
|
|
165
|
+
return (
|
|
166
|
+
<Option value={q} key={'qmt-' + q}>
|
|
167
|
+
{q}
|
|
168
|
+
</Option>
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
</Select>
|
|
173
|
+
</FormItem>
|
|
174
|
+
<FormItem
|
|
175
|
+
label={e('settingShortcuts')}
|
|
176
|
+
name='shortcut'
|
|
177
|
+
>
|
|
178
|
+
<div>
|
|
179
|
+
<Input className='hide' />
|
|
180
|
+
<ShortcutEdit
|
|
181
|
+
{...editorProps}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
</FormItem>
|
|
185
|
+
<FormItem
|
|
186
|
+
label={e('inputOnly')}
|
|
187
|
+
name='inputOnly'
|
|
188
|
+
valuePropName='checked'
|
|
189
|
+
>
|
|
190
|
+
<SwitchLabel />
|
|
191
|
+
</FormItem>
|
|
192
|
+
<FormItem>
|
|
193
|
+
<p>
|
|
194
|
+
<Button
|
|
195
|
+
type='primary'
|
|
196
|
+
htmlType='submit'
|
|
197
|
+
>{e('save')}
|
|
198
|
+
</Button>
|
|
199
|
+
</p>
|
|
200
|
+
</FormItem>
|
|
201
|
+
<p>
|
|
202
|
+
<b className='mg1r'>{e('templates')}:</b>
|
|
203
|
+
<span className='mg1r'>{templatesStr}</span>
|
|
204
|
+
<HelpIcon
|
|
205
|
+
link={wiki}
|
|
206
|
+
/>
|
|
207
|
+
</p>
|
|
208
|
+
</>
|
|
209
|
+
)
|
|
210
|
+
const tabsProps = {
|
|
211
|
+
activeKey: tab,
|
|
212
|
+
onChange: setTab,
|
|
213
|
+
className: 'qm-form-tabs',
|
|
214
|
+
size: 'small',
|
|
215
|
+
items: [
|
|
216
|
+
{
|
|
217
|
+
key: manualTab,
|
|
218
|
+
label: e('quickCommand')
|
|
219
|
+
},
|
|
220
|
+
...(!aiDisabled
|
|
221
|
+
? [{
|
|
222
|
+
key: aiTab,
|
|
223
|
+
label: (
|
|
224
|
+
<span className='qm-form-tab-ai'>
|
|
225
|
+
<RobotOutlined className='mg1r' />
|
|
226
|
+
AI
|
|
227
|
+
</span>
|
|
228
|
+
)
|
|
229
|
+
}]
|
|
230
|
+
: [])
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
const aiPane = (
|
|
234
|
+
<>
|
|
235
|
+
<ResponsiveTabs {...tabsProps} />
|
|
236
|
+
{
|
|
237
|
+
tab === aiTab
|
|
238
|
+
? <QuickCommandAiEditor onChange={handleAiGenerated} />
|
|
239
|
+
: manualPane
|
|
240
|
+
}
|
|
241
|
+
</>
|
|
242
|
+
)
|
|
115
243
|
return (
|
|
116
244
|
<>
|
|
117
245
|
<Form
|
|
@@ -121,71 +249,7 @@ export default function QuickCommandForm (props) {
|
|
|
121
249
|
layout='vertical'
|
|
122
250
|
initialValues={initialValues}
|
|
123
251
|
>
|
|
124
|
-
|
|
125
|
-
label={e('quickCommandName')}
|
|
126
|
-
rules={[{
|
|
127
|
-
max: 60, message: '60 chars max'
|
|
128
|
-
}, {
|
|
129
|
-
required: true, message: 'Name required'
|
|
130
|
-
}]}
|
|
131
|
-
hasFeedback
|
|
132
|
-
name='name'
|
|
133
|
-
>
|
|
134
|
-
<InputAutoFocus />
|
|
135
|
-
</FormItem>
|
|
136
|
-
{renderQm(form)}
|
|
137
|
-
<FormItem
|
|
138
|
-
name='labels'
|
|
139
|
-
label={e('label')}
|
|
140
|
-
>
|
|
141
|
-
<Select
|
|
142
|
-
mode='tags'
|
|
143
|
-
>
|
|
144
|
-
{
|
|
145
|
-
quickCommandTags.map(q => {
|
|
146
|
-
return (
|
|
147
|
-
<Option value={q} key={'qmt-' + q}>
|
|
148
|
-
{q}
|
|
149
|
-
</Option>
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
</Select>
|
|
154
|
-
</FormItem>
|
|
155
|
-
<FormItem
|
|
156
|
-
label={e('settingShortcuts')}
|
|
157
|
-
name='shortcut'
|
|
158
|
-
>
|
|
159
|
-
<div>
|
|
160
|
-
<Input className='hide' />
|
|
161
|
-
<ShortcutEdit
|
|
162
|
-
{...editorProps}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
</FormItem>
|
|
166
|
-
<FormItem
|
|
167
|
-
label={e('inputOnly')}
|
|
168
|
-
name='inputOnly'
|
|
169
|
-
valuePropName='checked'
|
|
170
|
-
>
|
|
171
|
-
<SwitchLabel />
|
|
172
|
-
</FormItem>
|
|
173
|
-
<FormItem>
|
|
174
|
-
<p>
|
|
175
|
-
<Button
|
|
176
|
-
type='primary'
|
|
177
|
-
htmlType='submit'
|
|
178
|
-
>{e('save')}
|
|
179
|
-
</Button>
|
|
180
|
-
</p>
|
|
181
|
-
</FormItem>
|
|
182
|
-
<p>
|
|
183
|
-
<b className='mg1r'>{e('templates')}:</b>
|
|
184
|
-
<span className='mg1r'>{templatesStr}</span>
|
|
185
|
-
<HelpIcon
|
|
186
|
-
link={wiki}
|
|
187
|
-
/>
|
|
188
|
-
</p>
|
|
252
|
+
{aiDisabled ? manualPane : aiPane}
|
|
189
253
|
</Form>
|
|
190
254
|
</>
|
|
191
255
|
)
|